C#设计模式--装饰者模式

思考并回答以下问题:

装饰者模式介绍

装饰者顾名思义就是对一个类添加一些额外的装饰(功能)。我们想给一个对象添加一些额外的功能又不改变对象内方法的签名怎么做呢?最常用的方法就是继承了,子类继承父类,然后重写父类的方法。考虑一种情况,如我们要给父类中的Show方法添加三个新功能功能a、功能b、功能c,这三种功能组合在一起时数目和执行顺序不同,效果也是不同的,为了现实所有的功能,我们需要创建很多子类,如子类1的show方法:BaseShow+功能a,子类2的show方法:BaseShow+ab,子类3的show方法:BaseShow+abc,子类4的show方法:BaseShow+ba…..子类n的show方法:BaseShow+功能cba(BaseShow指的是父类原本的show方法),如果添加的功能组合太多使用继承会造成子类爆炸。装饰器模式可以很好地解决这个问题。

我们以大话设计模式中的人挑选服饰的例子来介绍装饰者模式的用法:小菜想通过各种服饰来打扮自己,服饰多种多样的,小菜可以自由组合,他可以穿西服+领带+皮鞋,也可以穿网鞋+T恤。

先看一下装饰者模式的四种角色:抽象组件,具体组件,抽象装饰类,具体装饰类。我们在代码中看这个几个角色的作用。

AbstractPerson抽象类,抽象组件角色,定义了我们需要扩展的Show方法:

1
2
3
4
5
6
7
8
/// <summary>
/// 抽象人类
/// </summary>
public abstract class AbstractPerson
{
//展示装饰方法,我们使用装饰器模式的目的就是为了扩展这个接口的功能
public abstract void Show();
}

Person类,具体组件角色,具体组件中的Show方法实现了原始的功能:

1
2
3
4
5
6
7
8
9
10
11
12
/// <summary>
/// 人类 具体组件角色
/// </summary>
public class Person:AbstractPerson
{
public string Name { get; set; }
//待添加功能的Show方法,具体组件中的Show方法只有原始功能
public override void Show()
{
Console.Write($"打扮的人是{this.Name}:");
}
}

抽象装饰(Finery)类,抽象装饰角色。注意:这里的服饰并不指的是衣服,而是穿了用某种衣服装饰的人,理解这一点是理解装饰者模式的前提:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//因为我们装饰后的person要直接替换装饰前的person,所以必须继承AbstractPerson
public abstract class Finery: AbstractPerson
{
protected AbstractPerson person;
//设置要打扮的人
public void SetPerson(AbstractPerson p)
{
this.person = p;
}
public override void Show()
{
if (person!=null)
{
person.Show();
}
}
}

具体服饰类,具体装饰角色,具体装饰角色通过重写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
//t恤
public class TShirt : Finery
{
public override void Show()
{
base.Show();
Console.Write("大t恤 ");
}
}
//网鞋
public class Sneaker : Finery
{
public override void Show()
{
base.Show();
Console.Write("网鞋 ");
}

}
//西装
public class Suit : Finery
{
public override void Show()
{
base.Show();
Console.Write("西装 ");
}
}
//领带
public class Tie:Finery
{
public override void Show()
{
base.Show();
Console.Write("领带 ");
}
}
//皮鞋
public class Leather : Finery
{
public override void Show()
{
base.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
class Program
{
static void Main(string[] args)
{
//首先要有打扮的人
AbstractPerson xc = new Person() { Name = "小菜" };

Console.WriteLine("第一种装饰-------------------");
Finery personWithsuit = new Suit();
Finery personWithtie = new Tie();
Finery personWithleather = new Leather();
//装饰过程
personWithsuit.SetPerson(xc);//给小菜穿上西服
personWithtie.SetPerson(personWithsuit);//给穿上西服的小菜带上领带
personWithleather.SetPerson(personWithtie);//给穿上西服带上领带的小菜穿上皮鞋
personWithleather.Show();

Console.WriteLine();
Console.WriteLine("第二种装饰-------------------");
Finery personWithTshirt = new TShirt();
Finery personWithSneaker = new Sneaker();
//装饰过程
personWithTshirt.SetPerson(xc);//给小菜穿上t恤
personWithSneaker.SetPerson(personWithTshirt);//给穿上t恤的小菜穿上网球鞋
personWithSneaker.Show();

Console.ReadKey();
}
}

运行程序结果:

小结

上边例子的类图:

装饰者模式的使用场景:

当我们需要动态添加类的功能同时不改变类的结构时可以使用装饰者模式,装饰类本质是一个现有类的包装。

装饰者模式的优点:

  • 1.一个类需要添加一些功能,而这些功能按数目、顺序组合形成的效果不一样,如果用继承会造成子类过多,装饰者模式可以很好地解决这个问题;
  • 2.使用装饰者模式我们可以动态的添加/删除类的功能,灵活性好。

装饰者模式的缺点:

多层装饰比较复杂,我们需要注意装饰顺序等因素。如先穿内裤再穿裤子,是正常人;而先穿裤子再穿内裤就是超人了。在开发中先过滤字符串再加密,和先加密字符串再过滤的效果是完全不同的。

补充:

装饰器模式和桥接模式都采用了组合大于继承的思想,不同的地方在于桥接模式中的组合用于is-a情景,如桥接模式的例子中蓝色的圆是一个几何图形,而装饰器模式中组合用于has-a情景,如人有一件衣服,人有一双鞋子。

0%