装饰模式
故事
程序员小明的两任女友
现在越来越多的妹子愿意找IT男当男朋友,因为不知道是哪个挨(qin)千(ge)刀(ge)的透露了IT男钱多话少死的早的秘密。
但是问题很快就来了,有女朋友之前的IT男的钱包长这样:
有了女朋友的IT男的钱包长这样:
早上,小明的女盆友如花一大早就打扮的花枝招展的拉着小明去逛街,走到金饰品店,看上了一个手链,左戴右试爱不释手,时不时还可怜兮兮的看一眼小明,看得小明心惊肉跳的,心想这月已经吃了俩礼拜的泡面了,我这可(bai)爱(jia)的女盆友怎么一点都不知道心疼我。
泡面就泡面吧,大不了就换窝头咸菜,虽然心疼,但为了博得美人一笑,小明还是帅气的掏出了信用卡。女朋友则兴高采烈的立马就戴上了手链,神采飞扬!
这个过程可以用UML类图画一下:
类图中,“女朋友”被设计成接口,是考虑到这个女朋友太可(bai)爱(jia),为了能不一直吃泡面,有可能会换另外一个实现(换女友),咳咳....
过了一段时间,小明实在没钱给女朋友买首饰了,如花很快又找到了另一个IT男,和小明分手了,好在一直暗恋小明的似玉及时向小明勇敢告白,这才让小明从失恋的阴影中走出来
我们来看一下现在的类图:
小明的新女友似玉温柔体贴,喜欢看书、吃麻辣烫,很懂事,小明心想这次终于找到了真命天女了!
/// <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();
}
}
运行结果如下:
我是如花我在和小明逛街
我是如花我在和小明吃饭
----
我是似玉我在和小明看电影,我在和小明喝摩卡
咳咳,(敲黑板)这就是我们今天要说的装饰模式/修饰模式了
先看一下定义:
修饰模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。维基百科
解读:
动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比增加子类更加灵活。在不改变接口的前提下,增强所考虑的的类的性能
使用场景:
- 需要扩展一个类的功能,或给一个类增加附加责任;
- 需要动态的给一个对象增加功能,这些功能可以再动态的撤销;
- 需要增加一些基本功能的排列组合而产生的非常大量的功能,从而使得继承变得不现实。
组成
- 抽象构件(Component)角色: 给出一个抽象接口,以规范准备接收附加责任的对象;
- 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类;
- 装饰角色(Decorator): 持有一个构件对象(Component)的实例,并定义一个与抽象构件接口一致的接口;
- 具体装饰角色(ConcreteDecorator): 负责给构件对象“贴上”附加的责任。
了解了装饰模式的基本概念,我们以网络游戏中的Buff为例进行练习。
Buff:王者荣耀和魔兽世界
玩过王者荣耀或者魔兽世界等网络游戏的同学都知道Buff是什么,王者荣耀中的红蓝buff,魔兽世界中骑士的智慧(增加魔法值上限),法师的法强(增加法术强度),战士的怒吼(增加力量或生命值上限),等等。
这些“Buff”,持续的时间短则几秒,长则几分钟甚至几小时,有的则更是持续到角色死亡。
/// <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%
总结一下装饰模式的优缺点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承关系更多的灵活性;
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
- 使用装饰模式可能会产生比继承关系更多的对象,使得查错困难。
装饰模式分透明和半透明方式:
- 其透明性要求程序不要声明一个ConcreteComponent类型的变量,而应当声明一个Component类型的变量;
- 半透明的方式则意味着需要根据实际需求声明一个ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator中才有的方法。
无论是哪种方式,出发点都是“不改变原有接口”。
所以,实际开发过程中,无需纠结其透明方式,只要能够降低系统设计的复杂度,满足设计需要即可。
据传,设计模式是药,没有病是不需要吃药的。不知道这句话该怎么理解,路过的高人可以指点一二。
好了,继续回去写Bug了,下周再聊!