装饰模式

思考并回答以下问题:

现在要求写一个可以给人搭配不同的服饰的系统。

第一版

结构图

Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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("装扮的{0}", name);
}
}

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Program
{
static void Main(string[] args)
{
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();
}
}

结果显示

1
2
3
4
5
第一种装扮:
大T恤 垮裤 破球鞋 装扮的小菜

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

如果需要增加‘超人’的装扮,就要改‘Person’类,违背了开放-封闭原则。应该把这些服饰都写成子类。

第二版

代码结构图

Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person
{
private string name;

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

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

服饰抽象类

1
2
3
4
5
// 服饰
abstract class Finery
{
public abstract void Show();
}

各种服饰子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 大T恤
class TShirts : Finery
{
public override void Show()
{
Console.Write("大T恤 ");
}
}

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

// 破球鞋
class Sneakers : Finery
{
public override void Show()
{
Console.Write("破球鞋 ");
}
}

// 西装
class Suit : Finery
{
public override void Show()
{
Console.Write("西装 ");
}
}

// 领带
class Tie : Finery
{
public override void Show()
{
Console.Write("领带 ");
}
}

// 皮鞋
class LeatherShoes : Finery
{
public override void Show()
{
Console.Write("皮鞋 ");
}
}

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Program
{
static void Main(string[] args)
{
Person xc = new Person("小菜");

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

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();
}
}

结果显示同前例,略。

如果要加超人装扮,只要增加子类就可以了。

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

1
2
3
4
dtx.Show();
kk.Show();
pqx.Show();
xc.Show();

这样写就好比:你光着身子,当着大家的面,先穿T恤,再穿裤子,再穿鞋,仿佛在跳穿衣舞。在内部组装完毕,然后再显示出来,这是建造者模式。建造者模式要求建造的过程必须是稳定的,而现在这个例子,建造过程是不稳定的,比如完全可以内穿西装,外套T恤,再加披风,打上领带,皮鞋外再穿上破球鞋;当然也完全可以只穿条裤衩就算完成。换句话就是说,通过服饰组合出一个有个性的人完全可以有无数种方案,并非是固定的。

需要把所需的功能按正确的顺序串联起来进行控制,这可以用一个非常有意思的设计模式来实现。

装饰模式

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

装饰模式(Decorator)结构图

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

基本的代码实现。

Component类

1
2
3
4
abstract class Component
{
public abstract void Operation();
}

ConcreteComponent类

1
2
3
4
5
6
7
class ConcreteComponent : Component
{
public override void Operation()
{
Console.WriteLine("具体对象的操作");
}
}

Decorator类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
abstract class Decorator : Component
{
// 设置Component
protected Component component;

public void SetComponent(Component component)
{
this.component = component;
}

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

具体装饰者类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
{
public override void Operation()
{
// 首先运行原Component的Operation(),再执行本类的功能,如AddedBehavior(),相当于对原Component进行了装饰
base.Operation();
AddedBehavior();
Console.WriteLine("具体装饰对象B的操作");
}

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

}
}

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Program
{
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就可。

第三版

代码结构图

Person类(ConcreteComponent)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person
{
public Person()
{ }

private string name;

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

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

服饰类(Decorator)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Finery : Person
{
protected Person component;

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

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

具体服饰类(ConcreteDecorator)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class TShirts : Finery
{
public override void Show()
{
Console.Write("大T恤 ");
base.Show();
}
}

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

class Sneakers : Finery
{
public override void Show()
{
Console.Write("破球鞋 ");
base.Show();
}
}

class Suit : Finery
{
public override void Show()
{
Console.Write("西装 ");
base.Show();
}
}

class Tie : Finery
{
public override void Show()
{
Console.Write("领带 ");
base.Show();
}
}

class LeatherShoes : Finery
{
public override void Show()
{
Console.Write("皮鞋 ");
base.Show();
}
}

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Program
{
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.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();

Console.Read();
}
}

结果显示

1
2
3
4
5
第一种装扮:
大T恤 垮裤 破球鞋 装扮的小菜

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

换一种装饰方式

1
2
3
4
5
6
7
8
9
10
11
12
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();

结果显示

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

装饰模式总结

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

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

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

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

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

0%