概念
装饰( Decorator )模式又叫做包装模式。通过一种对客户端透明的方式来扩展对象的功能,是继承关系的一个替换方案。
装饰模式就是把要添加的附加功能分别放在单独的类中,并让这个类包含它要装饰的对象,当需要执行时,客户端就可以有选择地、按顺序地使用装饰功能包装对象。
角色及职责
- component
抽象产品类。是所有具体产品和装饰者的基类,客户端就是面向抽象产品类进行编程,他的指针和引用可以指向具体产品以及装饰者对象。–对应于案例中的Car。
- ConcreateComponent
具体产品类,继承自抽象产品,装饰者可以对具体产品对象进行装饰添加其功能!通常来说,具体产品类继承了抽象产品类,需要实现相应的纯虚函数的功能,可以不含抽象产品类的指针或者引用–对应于案例中的Runcar。
-
Decorator
抽象装饰者。也是继承自抽象产品类,装饰者本身就是一个类,是一个具有某种功能的类,该类的构造函数通过注入一个产品对象,使得该产品对象具有更多的功能!这里说的产品对象一般面向抽象类编程。–这里给出的案例没有用到抽象装饰者,全部用的具体抽象者,这样解耦合做的不好,实际生产中,建议使用抽象装饰类,再实现具体装饰类,有利于解耦合和程序扩展。 -
ConcreateDecorator
具体装饰者,里面含有更具体的装饰功能,也就是更具体的新功能。–对应于案例中的SwimCar、FlyCar。
适用于:
装饰者模式(Decorator Pattern)动态的给一个对象添加一些额外的职责。就增加功能来说,此模式比生成子类更为灵活。
demo1:
一、引言
在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类)、AccessoriesPhone(挂件手机类)等,这样就会导致 ”子类爆炸“问题,为了解决这个问题,我们可以使用装饰者模式来动态地给一个对象添加额外的职责。下面让我们看看装饰者模式。
二、装饰者模式的详细介绍
2.1 定义
装饰者模式以对客户透明的方式动态地给一个对象附加上更多的责任,装饰者模式相比生成子类可以更灵活地增加功能。
2.2 装饰者模式实现
这里以手机和手机配件的例子来演示装饰者模式的实现,具体代码如下:
/// <summary>
/// 手机抽象类,即装饰者模式中的抽象组件类
/// </summary>
public abstract class Phone
{
public abstract void Print();
}
/// <summary>
/// 苹果手机,即装饰着模式中的具体组件类
/// </summary>
public class ApplePhone:Phone
{
/// <summary>
/// 重写基类方法
/// </summary>
public override void Print()
{
Console.WriteLine("开始执行具体的对象——苹果手机");
}
}
/// <summary>
/// 装饰抽象类,要让装饰完全取代抽象组件,所以必须继承自Photo
/// </summary>
public abstract class Decorator:Phone
{
private Phone phone;
public Decorator(Phone p)
{
this.phone = p;
}
public override void Print()
{
if (phone != null)
{
phone.Print();
}
}
}
/// <summary>
/// 贴膜,即具体装饰者
/// </summary>
public class Sticker : Decorator
{
public Sticker(Phone p)
: base(p)
{
}
public override void Print()
{
base.Print();
// 添加新的行为
AddSticker();
}
/// <summary>
/// 新的行为方法
/// </summary>
public void AddSticker()
{
Console.WriteLine("现在苹果手机有贴膜了");
}
}
/// <summary>
/// 手机挂件
/// </summary>
public class Accessories : Decorator
{
public Accessories(Phone p)
: base(p)
{
}
public override void Print()
{
base.Print();
// 添加新的行为
AddAccessories();
}
/// <summary>
/// 新的行为方法
/// </summary>
public void AddAccessories()
{
Console.WriteLine("现在苹果手机有漂亮的挂件了");
}
}
客户端代码:
class Customer
{
static void Main(string[] args)
{
// 我买了个苹果手机
Phone phone = new ApplePhone();
// 现在想贴膜了
Decorator applePhoneWithSticker = new Sticker(phone);
// 扩展贴膜行为
applePhoneWithSticker.Print();
Console.WriteLine("----------------------\n");
// 现在我想有挂件了
Decorator applePhoneWithAccessories = new Accessories(phone);
// 扩展手机挂件行为
applePhoneWithAccessories.Print();
Console.WriteLine("----------------------\n");
// 现在我同时有贴膜和手机挂件了
Sticker sticker = new Sticker(phone);
Accessories applePhoneWithAccessoriesAndSticker = new Accessories(sticker);
applePhoneWithAccessoriesAndSticker.Print();
Console.ReadLine();
}
在装饰者模式中各个角色有:
- 抽象构件(Phone)角色:给出一个抽象接口,以规范准备接受附加责任的对象。
- 具体构件(AppPhone)角色:定义一个将要接收附加责任的类。
- 装饰(Dicorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
- 具体装饰(Sticker和Accessories)角色:负责给构件对象 ”贴上“附加的责任。
三、装饰者模式的优缺点
看完装饰者模式的详细介绍之后,我们继续分析下它的优缺点。
优点:
- 装饰这模式和继承的目的都是扩展对象的功能,但装饰者模式比继承更灵活
- 通过使用不同的具体装饰类以及这些类的排列组合,设计师可以创造出很多不同行为的组合
- 装饰者模式有很好地可扩展性
缺点:装饰者模式会导致设计中出现许多小对象,如果过度使用,会让程序变的更复杂。并且更多的对象会是的差错变得困难,特别是这些对象看上去都很像。
四、使用场景
下面让我们看看装饰者模式具体在哪些情况下使用,在以下情况下应当使用装饰者模式:
- 需要扩展一个类的功能或给一个类增加附加责任。
- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
- 需要增加由一些基本功能的排列组合而产生的非常大量的功能
demo2:
装饰模式为已有类动态附加额外的功能就像LOL、王者荣耀等类Dota游戏中,英雄升级一样。每次英雄升级都会附加一个额外技能点学习技能。具体的英雄就是ConcreteComponent,技能栏就是装饰器Decorator,每个技能就是ConcreteDecorator;
//Component 英雄接口
public interface Hero {
//学习技能
void learnSkills();
}
//ConcreteComponent 具体英雄盲僧
public class BlindMonk implements Hero {
private String name;
public BlindMonk(String name) {
this.name = name;
}
@Override
public void learnSkills() {
System.out.println(name + "学习了以上技能!");
}
}
//Decorator 技能栏
public class Skills implements Hero{
//持有一个英雄对象接口
private Hero hero;
public Skills(Hero hero) {
this.hero = hero;
}
@Override
public void learnSkills() {
if(hero != null)
hero.learnSkills();
}
}
//ConreteDecorator 技能:Q
public class Skill_Q extends Skills{
private String skillName;
public Skill_Q(Hero hero,String skillName) {
super(hero);
this.skillName = skillName;
}
@Override
public void learnSkills() {
System.out.println("学习了技能Q:" +skillName);
super.learnSkills();
}
}
//ConreteDecorator 技能:W
public class Skill_W extends Skills{
private String skillName;
public Skill_W(Hero hero,String skillName) {
super(hero);
this.skillName = skillName;
}
@Override
public void learnSkills() {
System.out.println("学习了技能W:" + skillName);
super.learnSkills();
}
}
//ConreteDecorator 技能:E
public class Skill_E extends Skills{
private String skillName;
public Skill_E(Hero hero,String skillName) {
super(hero);
this.skillName = skillName;
}
@Override
public void learnSkills() {
System.out.println("学习了技能E:"+skillName);
super.learnSkills();
}
}
//ConreteDecorator 技能:R
public class Skill_R extends Skills{
private String skillName;
public Skill_R(Hero hero,String skillName) {
super(hero);
this.skillName = skillName;
}
@Override
public void learnSkills() {
System.out.println("学习了技能R:" +skillName );
super.learnSkills();
}
}
//客户端:召唤师
public class Player {
public static void main(String[] args) {
//选择英雄
Hero hero = new BlindMonk("李青");
Skills skills = new Skills(hero);
Skills r = new Skill_R(skills,"猛龙摆尾");
Skills e = new Skill_E(r,"天雷破/摧筋断骨");
Skills w = new Skill_W(e,"金钟罩/铁布衫");
Skills q = new Skill_Q(w,"天音波/回音击");
//学习技能
q.learnSkills();
}
}
输出:
学习了技能Q:天音波/回音击
学习了技能W:金钟罩/铁布衫
学习了技能E:天雷破/摧筋断骨
学习了技能R:猛龙摆尾
李青学习了以上技能!