装饰模式

故事

程序员小明的两任女友

现在越来越多的妹子愿意找IT男当男朋友,因为不知道是哪个挨(qin)千(ge)刀(ge)的透露了IT男钱多话少死的早的秘密。

但是问题很快就来了,有女朋友之前的IT男的钱包长这样:

Alt Text

有了女朋友的IT男的钱包长这样:

Alt Text

早上,小明的女盆友如花一大早就打扮的花枝招展的拉着小明去逛街,走到金饰品店,看上了一个手链,左戴右试爱不释手,时不时还可怜兮兮的看一眼小明,看得小明心惊肉跳的,心想这月已经吃了俩礼拜的泡面了,我这可(bai)爱(jia)的女盆友怎么一点都不知道心疼我。

泡面就泡面吧,大不了就换窝头咸菜,虽然心疼,但为了博得美人一笑,小明还是帅气的掏出了信用卡。女朋友则兴高采烈的立马就戴上了手链,神采飞扬!

Alt Text

这个过程可以用UML类图画一下:

Alt Text

类图中,“女朋友”被设计成接口,是考虑到这个女朋友太可(bai)爱(jia),为了能不一直吃泡面,有可能会换另外一个实现(换女友),咳咳....

Alt Text

过了一段时间,小明实在没钱给女朋友买首饰了,如花很快又找到了另一个IT男,和小明分手了,好在一直暗恋小明的似玉及时向小明勇敢告白,这才让小明从失恋的阴影中走出来

Alt Text

我们来看一下现在的类图:

Alt Text

小明的新女友似玉温柔体贴,喜欢看书、吃麻辣烫,很懂事,小明心想这次终于找到了真命天女了!

    /// <summary>
    /// 女朋友接口
    /// </summary>
    public interface IGirlFriend
    {
        string Description { get; set; }
        void Dating();
    }

    /// <summary>
    /// 如花
    /// </summary>
    public class RuHua : IGirlFriend
    {
        public string Description { get; set; } = "我是如花";

        public void Dating()
        {
        }
    }

    /// <summary>
    /// 似玉
    /// </summary>
    public class SiYu : IGirlFriend
    {
        public string Description { get; set; } = "我是似玉";

        public void Dating()
        {
        }
    }

    /// <summary>
    /// 约会装饰器
    /// </summary>
    public class Decorator : IGirlFriend
    {
        public IGirlFriend GirlFriend { get; private set; }
        public string Description { get; set; }

        public Decorator(IGirlFriend girl)
        {
            this.GirlFriend = girl;
        }

        public virtual void Dating()
        {
            Console.Write(this.GirlFriend.Description);
        }
    }

    /// <summary>
    /// 约会之逛街
    /// </summary>
    public class Shopping : Decorator
    {
        public Shopping(IGirlFriend girlFriend):base(girlFriend)
        {
        }

        public override void Dating()
        {
            base.Dating();
            this.WalkAndShopping();
        }

        public void WalkAndShopping()
        {
            Console.WriteLine("我在和小明逛街");
        }
    }

    /// <summary>
    /// 约会之吃饭
    /// </summary>
    public class Eatting : Decorator
    {
        public Eatting(IGirlFriend girlFriend) : base(girlFriend)
        {
        }

        public override void Dating()
        {
            base.Dating();
            this.EatSomething();
        }

        public void EatSomething()
        {
            Console.WriteLine("我在和小明吃饭");
        }
    }

    /// <summary>
    /// 小明真心喜欢似玉,决定带她去看电影
    /// </summary>
    public class Film : Decorator
    {
        public Film(IGirlFriend girl) : base(girl)
        {
        }

        public override void Dating()
        {
            base.Dating();
            this.WatchMovie();
            this.MoCha();
        }

        public void WatchMovie()
        {
            Console.Write("我在和小明看电影");
        }

        public void MoCha()
        {
            Console.WriteLine(",我在和小明喝摩卡");
        }
    }


    /// <summary>
    /// 场景模拟
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            //女友如花
            IGirlFriend ruHua = new RuHua(); 
            IGirlFriend ruHuaShopping = new Shopping(ruHua);
            ruHuaShopping.Dating();

            IGirlFriend ruHuaEatting = new Eatting(ruHua);
            ruHuaEatting.Dating();

            Console.WriteLine("----");

            //女友似玉
            IGirlFriend siYu = new SiYu();
            IGirlFriend siYuFilm = new Film(siYu);
            siYuFilm.Dating();

            Console.ReadKey();
        }
    }

运行结果如下:

我是如花我在和小明逛街
我是如花我在和小明吃饭
----
我是似玉我在和小明看电影,我在和小明喝摩卡

咳咳,(敲黑板)这就是我们今天要说的装饰模式/修饰模式

先看一下定义:

修饰模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。维基百科

解读:

动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比增加子类更加灵活。在不改变接口的前提下,增强所考虑的的类的性能

使用场景:
  1. 需要扩展一个类的功能,或给一个类增加附加责任;
  2. 需要动态的给一个对象增加功能,这些功能可以再动态的撤销;
  3. 需要增加一些基本功能的排列组合而产生的非常大量的功能,从而使得继承变得不现实。

Alt Text

组成
  1. 抽象构件(Component)角色: 给出一个抽象接口,以规范准备接收附加责任的对象;
  2. 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类;
  3. 装饰角色(Decorator): 持有一个构件对象(Component)的实例,并定义一个与抽象构件接口一致的接口;
  4. 具体装饰角色(ConcreteDecorator): 负责给构件对象“贴上”附加的责任。

了解了装饰模式的基本概念,我们以网络游戏中的Buff为例进行练习。

Buff:王者荣耀和魔兽世界

玩过王者荣耀或者魔兽世界等网络游戏的同学都知道Buff是什么,王者荣耀中的红蓝buff,魔兽世界中骑士的智慧(增加魔法值上限),法师的法强(增加法术强度),战士的怒吼(增加力量或生命值上限),等等。
这些“Buff”,持续的时间短则几秒,长则几分钟甚至几小时,有的则更是持续到角色死亡。

Alt Text
Alt Text

/// <summary>
/// Buff接口
/// </summary>
public interface IHeroBuff
{
    void AddBuff();
}

/// <summary>
/// 王者农药
/// </summary>
public class KingOfGlory : IHeroBuff
{
    public void AddBuff()
    {
        Console.WriteLine("王者荣耀加Buff");
    }
}

/// <summary>
/// 魔兽世界
/// </summary>
public class Wow : IHeroBuff
{
    public void AddBuff()
    {
        Console.WriteLine("魔兽世界加Buff");
    }
}

/// <summary>
/// 装饰器
/// </summary>
public abstract class GameDecorator : IHeroBuff
{
    public IHeroBuff GameBuff { get; set; }

    public GameDecorator(IHeroBuff gf)
    {
        this.GameBuff = gf;
    }

    public abstract void AddBuff();
}

/// <summary>
/// 吃红
/// </summary>
public class RedBuff : GameDecorator
{
    public RedBuff(IHeroBuff buff) : base(buff) { }

    public override void AddBuff()
    {
        this.GameBuff.AddBuff();
        this.AddRedBuff();
        this.FirstBlood();
    }

    private void AddRedBuff()
    {
        Console.WriteLine("都让开我吃红了");
    }

    public void FirstBlood()
    {
        Console.WriteLine("顺便拿个一血");
    }
}

/// <summary>
/// 吃蓝
/// </summary>
public class BlueBuff : GameDecorator
{
    public BlueBuff(IHeroBuff buff) : base(buff) { }

    public override void AddBuff()
    {
        this.GameBuff.AddBuff();
        this.BeCarefor();
        this.AddBlueBuff();
    }

    private void AddBlueBuff()
    {
        Console.WriteLine("那个,不好意思,我补个蓝");
    }

    public void BeCarefor()
    {
        Console.WriteLine("猥琐点小心被拿一血");
    }
}

/// <summary>
/// 战士团队Buff:生命咆哮
/// </summary>
public class CommandingShout : GameDecorator
{
    public CommandingShout(IHeroBuff buff):base(buff)
    {

    }

    public override void AddBuff()
    {
        this.GameBuff.AddBuff();
        this.AddBlood();
    }

    public void AddBlood()
    {
        Console.WriteLine("战士团队Buff: 生命值上限增加10%");
    }
}

/// <summary>
/// 小德Buff:野性呼唤(爪子)
/// </summary>
public class MarkOfTheWild : GameDecorator
{
    public MarkOfTheWild(IHeroBuff gf) : base(gf)
    {
    }

    public override void AddBuff()
    {
        this.GameBuff.AddBuff();
        this.AddSomeBuff();
    }

    public void AddSomeBuff()
    {
        Console.WriteLine("小德Buff: 没玩过不知道有啥增益效果的小德Buff");
    }
}

/// <summary>
/// 骑士Buff:王者祝福
/// </summary>
public class BlessingOfKings : GameDecorator
{
    public BlessingOfKings(IHeroBuff gf) : base(gf)
    {
    }

    public override void AddBuff()
    {
        this.GameBuff.AddBuff();
        this.AddBloodAndFight();
    }

    public void AddBloodAndFight()
    {
        Console.WriteLine("骑士Buff: 攻击力和生命值上限增加10%");
    }
}

/// <summary>
/// 场景模拟
/// </summary>
class Program
{
    static void Main(string[] args)
    {
        // 王者
        IHeroBuff hero = new KingOfGlory(); //定义英雄
        IHeroBuff redBuff = new RedBuff(hero); //吃红
        IHeroBuff blueBuff = new BlueBuff(redBuff); //吃蓝
        blueBuff.AddBuff();

        Console.WriteLine("...........");

        // 魔兽世界
        IHeroBuff wowHero = new Wow(); //定义英雄
        IHeroBuff commandingShout = new CommandingShout(wowHero); //战士buff
        IHeroBuff markOfTheWild = new MarkOfTheWild(commandingShout); //小德buff
        IHeroBuff blessingOfKings = new BlessingOfKings(markOfTheWild); //骑士buff
        blessingOfKings.AddBuff();

        Console.ReadKey();
    }
}
王者荣耀加Buff
都让开我吃红了
顺便拿个一血
猥琐点小心被拿一血
那个,不好意思,我补个蓝
...........
魔兽世界加Buff
战士团队Buff: 生命值上限增加10%
小德Buff: 没玩过不知道有啥增益效果的小德Buff
骑士Buff: 攻击力和生命值上限增加10%

总结一下装饰模式的优缺点:

  1. 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承关系更多的灵活性;
  2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
  3. 使用装饰模式可能会产生比继承关系更多的对象,使得查错困难。

装饰模式分透明和半透明方式:

  • 其透明性要求程序不要声明一个ConcreteComponent类型的变量,而应当声明一个Component类型的变量;
  • 半透明的方式则意味着需要根据实际需求声明一个ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator中才有的方法。

无论是哪种方式,出发点都是“不改变原有接口”。

所以,实际开发过程中,无需纠结其透明方式,只要能够降低系统设计的复杂度,满足设计需要即可。


据传,设计模式是药,没有病是不需要吃药的。不知道这句话该怎么理解,路过的高人可以指点一二。

好了,继续回去写Bug了,下周再聊!

Alt Text

posted @ 2017-10-26 15:28  DebugLife  阅读(775)  评论(3编辑  收藏  举报