概述
什么是装饰模式
装饰模式(Decorator Pattern)是一种结构型设计模式,它允许通过将对象放入包含行为的特殊封装对象中来为原始对象添加新的行为。装饰模式在不改变原始对象接口的情况下,动态地将责任附加到对象上。就增加功能来看,装饰模式比生成子类更为灵活。
为什么使用装饰模式
使用装饰模式有以下几个优点:
灵活性:装饰模式允许在运行时动态地给对象添加功能,而不需要修改其代码或使用继承。这使得系统更加灵活,易于扩展和修改。
单一职责原则:装饰模式能够将不同的功能划分到不同的类中,使每个类只负责单一的功能。这遵循了单一职责原则,使得代码更加清晰、可读性更高。
可组合性:由于装饰模式使用了对象组合而不是继承,因此可以通过不同的方式将装饰器组合起来,以获得不同的行为组合。
装饰模式的结构
关键角色
- 抽象组件(Component):定义了原始对象和装饰器的公共接口,可以是接口或抽象类。
- 具体组件(Concrete Component):实现了抽象组件接口,是被装饰的原始对象。
- 装饰器(Decorator):实现了抽象组件接口,并持有一个抽象组件对象的引用,在装饰器中可以添加一些额外的行为。
-具体装饰器(Concrete Decorator):继承自装饰器,具体实现了在装饰器中定义的额外行为。
基本代码
应用场景
1、在不改变现有对象结构的情况下,对对象的功能进行扩展或修改。
2、需要动态地给对象添加功能,以便根据需要增加或移除功能。
3、对象的职责应该能够被多次扩展,而不会导致类的数量急剧增加。
版本迭代
需求:给人搭配不同的服饰
版本一
//Person public class Person { private String name; public Person(String name ){ this.name=name; } public void wearTShirts(){ System.out.println("大T恤"); } public void wearBigTrouser(){ System.out.println("跨裤"); } public void wearSneakers(){ System.out.println("球鞋"); } public void show(){ System.out.println("装扮的"+name); } } //客户端 public class Client { public static void main(String[] args) { Person xc=new Person("小蔡"); System.out.println("第一种装扮"); xc.wearTShirts(); xc.wearBigTrouser(); xc.wearSneakers(); xc.show(); System.out.println("--------------"); System.out.println("第二种装扮"); xc.wearTShirts(); xc.wearSneakers(); xc.show(); } }
版本一非常简单,给人穿衣服就只有Person这个类,但是需求如果需要增加“超人”的装扮,就需要修改Person类(毕竟超人不是人,不能使用实例化Person,并且超人的衣服最起码是有个内裤和斗篷的~~)
这个时候怎么办,把服饰拿出去作为单独的类是不是就可以了呢
版本二
//Person public class Person { private String name; public Person(String name){ this.name=name; } public void show(){ System.out.println("装扮的"+name); } } //抽象服饰类 public abstract class Finery { public abstract void show(); } //具体的服饰类 public class BigTrouser extends Finery{ @Override public void show() { System.out.println("跨裤"); } } public class TShirts extends Finery{ @Override public void show() { System.out.println("大t恤"); } } //客户端 public class client { public static void main(String[] args) { Person xc=new Person("小蔡"); Finery tt=new TShirts(); Finery kk=new BigTrouser(); kk.show(); tt.show(); xc.show(); } }
新的问题好像又来了,客户端看起来就是光着身子的小蔡一件件的穿上了衣服,也就是说穿衣服的过程不应该在客户端显示,而应该在内部完成,并且每个人的爱好不一样,穿衣服的顺序并不确定,这个建造者就不同了,而是需要把所有的功能按照正确顺序串联起来进行控制。装饰模式上场了
版本三—装饰模式
//服饰抽象类 public interface ICharacter { public void show(); } //被装饰对象人 public class Person implements ICharacter{ private String name; public Person(String name){ this.name=name; } @Override public void show() { System.out.println("装扮的"+name); } } //装饰类 public class Finery implements ICharacter{ protected ICharacter component; public void decorate(ICharacter component){ this.component=component; } @Override public void show() { if(component !=null){ component.show(); } } } //具体装饰类 public class Sneakers extends Finery{ public void show(){ System.out.println("球鞋"); super.show(); } } public class BigTrouser extends Finery{ public void show(){ System.out.println("裤子"); super.show(); } } public class Tshirts extends Finery{ public void show(){ System.out.println("T恤"); super.show(); } } //客户端 public class Client { public static void main(String[] args) { Person xc=new Person("小蔡"); System.out.println("第一种装扮"); Sneakers pqx=new Sneakers(); pqx.decorate(xc); BigTrouser kk=new BigTrouser(); kk.decorate(pqx); Tshirts tt=new Tshirts(); tt.decorate(kk); tt.show(); } }
装饰模式中的巧妙之处
1、被装饰对象和装饰对象共享相同的接口或父类
这样做的目的是是为了让它们可以互相替代。这种互相替代的能力是装饰模式的关键特点之一。通过共享相同的接口或父类,可以在不修改原始对象的情况下,动态地添加、删除或更改对象的行为。
互相替换的好处在于灵活性和扩展性。当需要为对象添加额外的功能时,可以直接用装饰对象替换原始对象,而不需要修改原始对象的代码。可以将装饰器对象看作是被装饰对象的 “包装”。这样可以避免引入大量的条件语句或继承关系,使得代码更加清晰、可维护和可扩展。
另外,互相替换还可以实现功能的组合和嵌套,比如上述给人装扮的例子,穿了靴子和裤子,就是把靴子和裤子的功能进行了组合。通过将装饰器对象嵌套在其他装饰器对象中,可以构建出复杂的功能组合,以满足不同的需求。
2、当调用装饰器类的装饰方法时,会先调用被装饰对象的同名方法
例如上面装扮的例子中的show方法.装饰器类(服饰类)通过持有一个被装饰对象(component)的引用来添加额外的功能。当调用装饰器类的 show() 方法时,它会先调用被装饰对象的 show()方法,然后再执行自己的装饰逻辑(如下图所示)。这样的设计可以实现动态地扩展对象的功能,而无需改变原始对象的结构。通过嵌套和组合多个装饰器类灵活地添加、移除和组合各种功能,以满足不同的需求。
3、子类方法与父类方法共享相同的引用
在具体装饰子类中都有super.show方法,这个是调取父类的show方法
那么这样写的巧妙之处在哪呢,在这里是看不到2中说到的“”当调用装饰器类的 show() 方法时,它会先用被装饰对象的 show()方法,然后再执行自己的装饰逻辑。因为这个逻辑写到了父类中
在这里有个疑问,在具体的子类中component为什么肯定是它的上一级被装扮对象呢,比如“穿了裤子的小蔡”接着要穿T恤。这就是因为每次调用装扮对象的decorate方法,传入的都是上一级被装扮对象(如下图所示),同时子类没有重写decorate方法,此时子类方法将与父类方法共享相同的引用,并且它们将具有相同的实现和行为,也就把component传给装备对象的属性,以供后面调用show方法时使用。
4、装饰模式与继承的对比
装饰模式将功能添加到对象上的方式与继承不同。使用继承时,子类会继承父类的行为,而装饰模式可以动态地将额外的行为添加到对象上,而不需要继承。这使得装饰模式更加灵活,因为可以在运行时选择不同的行为组合。
另外,装饰模式遵循了开放-关闭原则,即对扩展开放,对修改关闭。通过组合和委托的方式,可以动态地将功能添加到对象上,而不需要修改现有代码
总结
装饰模式是一种灵活且可扩展的设计模式,它可以动态地给对象添加新的行为。使用装饰模式可以避免类爆炸问题,并能够在运行时选择不同的功能组合。它与继承相比更加灵活,符合单一职责原则,并遵循开放-关闭原则。