穿什么有这么重要?——装饰模式

继上一篇“会修电脑不会修收音机?–依赖倒转原则”后,本文继续讲解《大话设计模式》第六章“穿什么有这么重要?——装饰模式”。喜欢本书请到各大商城购买原书,支持正版。

1 穿什么有这么重要?

时间:3月16日20点 地点:大鸟房间 人物:小菜、大鸟

“大鸟,明天我要去见娇娇了,你说我穿什么去比较好?”小菜问大鸟道。

“这个你也来问我,干脆我代你去得了。”大鸟笑言。

“别开玩笑,我是诚心问你的。”

“哈哈,小菜呀,你别告诉我说四年大学你都没和女生约过会?”

“嗨!谁叫我念的是理工科大学呢,学校里本来女生就少,所以这些稀有宝贝们,大一时早就被那些老手们主动出击搞定了,我们这些恋爱方面的小菜哪还有什么机会,一不小心就虚度了四年。”小菜突生伤感,叹气摇头,并小声的唱了起来,“不是我不小心,只是真情难以寻觅,不是我存心故意,只因无法找到良机……”

“喂!”大鸟打断了小菜,“差不多就行了,感慨得没完没了。说正事,你要问我什么?”

“哦,你说我穿什么去见娇娇比较好?”

“那要看你想给人家什么印象?是比较年轻,还是比较干练;是比较颓废,还是要比较阳光:也有可能你想给人家一种极其难忘的印象,那穿法又大不一样了!”

“你这话怎么讲?”

“年轻,不妨走点嘻哈路线,大T恤、垮裤、破球鞋,典型的年轻人装扮。”

“啊,这不是我喜欢的风格,我从来也没这样穿过。”

“那就换一种,所谓干练,就是要有外企高级白领的样,黑西装、黑领带、黑墨镜、黑皮鞋……”

“你这叫白领?我看是黑社会。不行不行。”

“哈,来颓废的吧,颓废其实也是一种个性,可以吸引一些喜欢叛逆的女生。一般来说,其标志是:头发可养鸟、胡子能生虫、衬衣没纽扣、香烟加狐臭。”

“这根本就是‘肮脏’的代表吗,开什么玩笑。你刚才提到给人家难忘印象,是什么样的穿法?”

“哈,这当然是绝妙的招了,如果你照我说的去做,娇娇想忘都难。”

“快说快说,是什么?”

“大红色披风下一身蓝色紧身衣,胸前一个大大的‘S’,表明你其实穿的是‘小号’,还有最重要的是,一定要‘内裤外穿’……”

“喂,你拿我寻开心呀,那是‘超人’的打扮呀,‘S’代表的也不是‘Small’,是‘Super’的意思。”小菜再次打断了大鸟,还是忍不住笑道,“我如果真的穿这样的服装去见 MM,那可真是现场的人都终身难忘,而小菜我,估计这辈子也不要见人了。”

“哈,你终于明白了!我其实想表达的意思就是,你完全可以随便一些,平时穿什么,明天还是穿什么,男生嘛,只要干净一些就可以了,关键不在于你穿什么,而在于你人怎么样。对自己都这么没信心,如何追求女孩子。”

“哦,我可能是多虑了一些。”小菜点头道,“好吧,穿什么我自己再想想。没什么事了吧,那我回房了。”

“等等,”大鸟叫住了他,“今天的模式还没有开讲呢,怎么就跑了。”

“哦,我想着约会的事,把学习给忘了,今天学什么模式?”

2 小菜扮靓第一版

“先不谈模式,说说你刚才提到的穿衣问题。我现在要求你写一个可以给人搭配不同的服饰的系统,比如类似QQ、网络游戏或论坛都有的Avatar系统。你怎么开发?”

“你是说那种可以换各种各样的衣服裤子的个人形象系统?”

“是的,现在你就简单点,用控制台的程序,写可以给人搭配嘻哈服或白领装的代码。”

“哦,我试试看吧。”

半小时后,小菜的第一版代码出炉。

结构图

穿什么有这么重要?——装饰模式

“Person” 类

class Person
{
	private string name;
	public Person(string name)
	{
		this.name = name;
	}

	public void WearTShirts()
	{
		Console.Write("大T恤");
	}

	public void WearBigTrouser()
	{
		Console.Write("垮裤");
	}

	public void WearSneakers()
	{
		Console.Write("破球鞋");
	}

	public void WearSuit()
	{
		Console.Write("西装");
	}

	public void WearTie()
	{
		Console.Write("领带");
	}

	public void WearLeatherShoes()
	{
		Console.Write("皮鞋");
	}

	public void Show()
	{
		Console.WriteLine($"装扮的{name}");
	}
}

客户端代码

Person xc = new Person("小菜");

Console.WriteLine("\n第一种装扮:");

xc.WearTShirts();
xc.WearBigTrouser();
xc.WearSneakers();
xc.Show();

Console.WriteLine("\n第二种装扮:");

xc.WearSuit();
xc.WearTie();
xc.WearLeatherShoes();
xc.Show();

Console.Read();

结果显示

第一种装扮:
大T恤垮裤破球鞋装扮的小菜

第二种装扮:
西装领带皮鞋装扮的小菜

“哈,不错,功能是实现了。现在的问题就是如果我需要增加‘超人’的装扮,你得如何做?”

“那就改改‘Person’类就行了,”小菜说完就反应过来,“哦,不对,这就违背了开放-封闭原则了。哈,我知道了,应该把这些服饰都写成子类就好了。我去改。”

大鸟抬起手伸出食指对小菜点了点,“你呀,刚学的这么重要的原则,怎么还会忘?”

3 小菜扮靓第二版

过了不到十分钟,小菜的第二版代码出炉。

代码结构图

穿什么有这么重要?——装饰模式

Person类

class Person
{
  private string name;
  public Person(string name)
  {
    this.name = name;
  }

  public void Show()
  {
    Console.WriteLine($"装扮的{name}");
  }
}

服饰抽象类

abstract class Finery
{
  public abstract void Show();
}

各种服饰子类

//大T恤
class TShirts : Finery
{
  public override void Show()
  {
    Console.Write("大T恤");
  }
}

//垮裤
class BigTrouser : Finery
{
  public override void Show()
  {
    Console.Write("垮裤");
  }
}

// 其余类类似,省略
......

客户端代码

static void Main(string[] args)
{
  Person xc = new Person("小菜");

  Console.WriteLine("in第一种装扮:");
  Finery dtx = new TShirts();
  Finery kk = new BigTrouser();
  Finery pqx = new Sneakers();

  dtx.Show();
  kk.Show(); 
  pqx.Show(); 
  xc.Show();

  Console.WriteLine("\n第二种装扮:");
  Finery xz = new Suit();
  Finery ld =new Tie();
  Finery px = new LeatherShoes();

  xz.Show();
  ld.Show(); 
  px.Show(); 
  xc.Show();

  Console.Read();

}

结果显示同前例,略。

“这下你还能说我不面向对象吗?如果要加超人装扮,只要增加子类就可以了。”

“哼,用了继承,用了抽象类就算是用好了面向对象了吗?你现在的代码的确做到了‘服饰’类与‘人’类的分离,但其他问题还有存在的。”

“什么问题呢?”

“你仔细看看这段代码。”

dtx.Show();
kk.Show(); 
pqx.Show(); 
xc.Show();

“这样写意味着什么?”大鸟问道。

“就是把‘大T恤’、‘垮裤’、‘破球鞋’和‘装扮的小菜’一个词一个词的显示出来呀。”

“说得好,我要的就是你这句话,这样写就好比:你光着身子,当着大家的面,先穿T恤,再穿裤子,再穿鞋,仿佛在跳穿衣舞。难道你穿衣服都是在众目睽睽下穿的吗?”

“你的意思是,应该在内部组装完闭,然后再显示出来?这好像是建造者模式呀。”

“不是的,建造者模式要求建造的过程必须是稳定的,而现在我们这个例子,建造过程是不稳定的,比如完全可以内穿西装,外套T恤,再加披风,打上领带,皮鞋外再穿上破球鞋;当然也完全可以只穿条裤衩就算完成。换句话就是说,通过服饰组合出一个有个性的人完全可以有无数种方案,并非是固定的。”

.“啊,你说得对,其实先后顺序也是有讲究的,如你所说,先穿内裤后穿外裤,这叫凡人,内裤穿到外裤外面,那就是超人了。”

“哈,很会举一反三嘛,那你说该如何办呢?”

“我们需要把所需的功能按正确的顺序串联起来进行控制,这好像很难办哦。”

“不懂就学,其实也没什么稀罕的,这可以用一个非常有意思的设计模式来实现。”

装饰模式

装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。

“啊,装饰这词真好,无论衣服、鞋子、领带、披风其实都可以理解为对人的装饰。”

“我们来看看它的结构。”

穿什么有这么重要?——装饰模式

“Component 是定义一个对象接口,可以给这些对象动态地添加职责。ConcreteComponent 是定义了一个具体的对象,也可以给这个对象添加一些职责。Decorator,装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component 来说,是无需知道Decorator的存在的。至于ConcreteDecorator就是具体的装饰对象,起到给Component添加职责的功能。”

“来看看基本的代码实现。”

Component类

abstract class Component
{
  public abstract void Operation();
}

ConcreteComponent类

class ConcreteComponent : Component
{
  public override void Operation()
  {
    Console.WriteLine("具体对象的操作");
  }
}

Decorator类

abstract class Decorator : Component
{
  protected Component component;
  public void SetComponent(Component component)
  {
    this.component = component;
  }

  /// <summary>
  /// 重写Operation),实际执行的是Component的 Operation()
  /// </summary>
  public override void Operation()
  {
    if (component != null)
    {
      component.Operation();
    }
  }
}

ConcreteDecoratorA类

class ConcreteDecoratorA : Decorator
{
  // 本类的独有功能,以区别于ConcreteDecoratorB
  private string addedstate;
  public override void Operation()
  {
    //首先运行原Component的Operation(),再执行本类的功能,如addedState,相当于对原Component进行了装饰
    base.Operation();
    addedstate = "New State";
    Console.WriteLine("具体装饰对象A的操作");
  }
}

class ConcreteDecoratorB : Decorator
{
  // 首先运行原 Component的 Operation(), 再执行本类的功能,如AddedBehavior(),相当于对原Component进行了装饰
  public override void Operation()
  {
    base.Operation();
    AddedBehavior();
    Console.WriteLine("具体装饰对象B的操作");
  }

  // 本类独有的方法,以区别于ConcreteDecoratorB
  private void AddedBehavior()
  { }
}

客户端代码

static void Main(string[] args)
{
  ConcreteComponent c = new ConcreteComponent();
  ConcreteDecoratorA d1 = new ConcreteDecoratorA();
  ConcreteDecoratorB d2 = new ConcreteDecoratorB();

  /* 装饰的方法是: 首先用ConcreteComponent 实例
   * 化对象c,然后用ConcreteDecoratorA的实例化
   * 对象d1来包装c,再用ConcreteDecoratorB的
   * 对象d2包装d1,最终执行d2的Operation()
   */
  d1.SetComponent(c);
  d2.SetComponent(d1);
  d2.Operation();
  
  Console.Read();
}

“我明白了,原来装饰模式是利用SetComponent 来对对象进行包装的。这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中。用刚才的例子来说就是,我们完全可以先穿外裤,再穿内裤,而不一定要先内后外。”

“既然你明白了,还不快些把刚才的例子改成装饰模式的代码?”大鸟提醒道。

“我还有个问题,刚才我写的那个例子中‘人’类是Component还是ConcreteComponent 呢?”

“哈,学习模式要善于变通,如果只有一个ConcreteComponent类而没有抽象的Component类,那么Decorator类可以是ConcreteComponent的一个子类。同样道理,如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把 Decorator和ConcreteDecorator的责任合并成一个类。

“啊,原来如此。在这里我们就没有必要有 Component类了,直接让服饰类Decorator继承人类ConcreteComponent就可。”

5 小菜扮靓第三版

二十分钟后,小菜第三版代码出炉。

代码结构图

穿什么有这么重要?——装饰模式

“Person”类(ConcreteComponent)

class Person
{
  public Person()
  { }

  private string name;
  public Person(string name)
  {
    this.name = name;
  }

  public virtual void Show()
  {
    Console.WriteLine($"装扮的{name}");
  }
}

服饰类(Decorator)

class Finery : Person
{
  protected Person component;

  // 打扮
  public void Decorate(Person component)
  {
    this.component = component;
  }

  public override void Show()
  {
    if (component != null)
    {
      component.Show();
    }
  }
}

具体服饰类(ConcreteDecorator)

class TShirts : Finery
{
  public override void Show()
  {
    Console.Write("大T恤");
    base.Show();
  }
}

class BigTrouser : Finery
{
  public override void Show()
  {
    Console.Write("垮裤");
    base.Show();
  }
}

// 其余类类似,省略

客户端代码

static void Main(string[] args)
{
  Person xc = new Person("小菜");

  Console.WriteLine("\n第一种装扮:");
  Sneakers pqx = new Sneakers();
  BigTrouser kk = new BigTrouser();
  TShirts dtx = new TShirts();

  // 装饰过程
  pqx.Decorate(xc);
  kk.Decorate(pqx);
  dtx.Decorate(kk);
  dtx.Show();

  Console.WriteLine("\n第二种装扮:");

  Leathershoes px = new Leathershoes();
  Tie ld = new Tie();
  Suit xz = new Suit();

  // 装饰过程
  px.Decorate(xc);
  ld.Decorate(px);
  xz.Decorate(ld);
  xz.Show();

  Console.Read();
}

结果显示

第一种装扮:
大T恤垮裤破球鞋装扮的小菜

第二种装扮:
西装领带皮鞋装扮的小菜

“如果我换一种装饰方式,结果会怎样呢?”大鸟改动了小菜的代码。

Console.WriteLine("\n第三种装扮:");
Sneakers pqx2 = new Sneakers();
LeatherShoes px2 = new Leathershoes();
BigTrouser kk2 = new BigTrouser();
Tie ld2 = new Tie();

pqx2.Decorate(xc);
px2.Decorate(pqx);
kk2.Decorate(px2);
ld2.Decorate(kk2);

ld2.show();

结果就会显示

第三种装扮:领带 垮裤 皮鞋 破球鞋 装扮的小菜

“哈,光着膀子、打着领带、下身垮裤、左脚皮鞋、右脚破球鞋的极具个性的小菜就展现在我们面前了。”

“你这家伙,又开始拿我开涮。我要这样子,比扮超人还要丢人。”

6 装饰模式总结

“来来来,总结一下,你感觉装饰模式如何?”

“我觉得装饰模式是为已有功能动态地添加更多功能的一种方式。但到底什么时候用它呢?”

“答得很好,问的问题更加好。你起初的设计中,当系统需要新功能的时候,是向旧的类中添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为,比如用西装或嘻哈服来装饰小菜,但这种做法的问题在于,它们在主类中加入了新的字段,新的方法和新的逻辑,从而增加了主类的复杂度,就像你起初的那个‘人’类,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。而装饰模式却提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。所以就出现了上面那个例子的情况,我可以通过装饰,让你全副武装到牙齿,也可以让你只挂一丝到内裤。”

“那么装饰模式的优点我总结下来就是,把类中的装饰功能从类中搬移去除,这样可以简化原有的类。”

“是的,这样做更大的好处就是有效地把类的核心职责和装饰功能区分开了。而且可以去除相关类中重复的装饰逻辑。”

“这个模式真不错,我以后要记着常使用它。”

“你可要当心哦,装饰模式的装饰顺序很重要哦,比如加密数据和过滤词汇都可以是数据持久化前的装饰功能,但若先加密了数据再用过滤功能就会出问题了,最理想的情况,是保证装饰类之间彼此独立,这样它们就可以以任意的顺序进行组合了。”

“是呀,穿上西装再套上T恤实在不是什么好穿法。”

“明天想好了要穿什么去见MM了吗?”大鸟突然问道。

“有了装饰模式,我还用得着担心我的穿着。再说,我信奉的是《天下无贼》中刘德华说的一句经典台词:‘开好车就是好人吗?’,小菜我魅力无限,不需装饰。”

大鸟惊奇地望着小菜,无法理解地说了句:“学完模式,判若两人,你够强!”

下一篇我们接着读“第7章 为别人做嫁衣——代理模式 ”,欢迎关注微信公众号【乐趣课堂】。

原文出处:微信公众号【乐趣课堂】

原文链接:

本文观点不代表Dotnet9立场,转载请联系原作者。

发表评论

登录后才能评论