装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,装饰器模式提供了比继承更有弹性的替代方案将功能附加到对象上。因此,装饰器模式的核心功能是功能扩展,使用装饰器模式可以透明且动态的扩展类的功能。装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。
应用场景:
-
用于扩展一个类的功能,或者给一个类添加附加职责
-
动态的给一个对象添加功能,这些功能可以再动态的被撤销
-
需要为一批平行的兄弟类进行改装或者加装功能
UML类图
由上图可知,装饰器模式主要包含了4个角色;
-
抽象组建(Component):可以是一个接口或者抽象类,充当被装饰类的原始对象,规定了被装饰对象的行为
-
具体组件(ConcreteComponent):实现/继承Component的一个具体对象,即被装饰对象
-
抽象装饰器(Decorator):通用的装饰ConcreteComponent的装饰器,其内部必然有一个属性指向Component,其实现一般是一个抽象类,主要为了让其子类按照其构造形式传入一个Component,这是强制的通用行为。如果系统中装饰逻辑单一,则并不需要实现许多装饰器,可以直接省略该类,而直接实现一个具体装饰器即可
-
具体装饰器(ConcreteDecorator):Decorator的具体实现类,理论上,每个ConcreteDecorator都扩展了Component对象的一种功能
装饰器模式的实现原理就是,让装饰器实现与被装饰类相同的接口,使得装饰器与被扩展类类型一致,并在构造函数中传入该接口对象,然后再实现这个接口的被包装类
属于同一类型,且构造函数的参数为其实现接口类,因此装饰器模式具备嵌套扩展功能,这样就能使用装饰器模式一层一层的对底层被包装类进行功能扩展了
通用写法
public class Client {
public static void main(String[] args) {
ConcreteComponent c1 = new ConcreteComponent();
ConcreteDecoratorA decoratorA = new ConcreteDecoratorA(c1);
decoratorA.operation();
System.out.println("---------------------------");
ConcreteDecoratorB decoratorB = new ConcreteDecoratorB(c1);
decoratorB.operation();
System.out.println("---------------------------");
ConcreteDecoratorB decoratorB1 = new ConcreteDecoratorB(decoratorA);
decoratorB1.operation();
}
static abstract class Component{
public abstract void operation();
}
static class ConcreteComponent extends Component{
@Override
public void operation() {
System.out.println("处理业务逻辑!!");
}
}
static abstract class Decorator extends Component{
protected Component component;
public Decorator(Component component){
this.component = component;
}
public void operation(){
//转发请求给组建对象,可以在转发前后执行一些附加动作
component.operation();
}
}
static class ConcreteDecoratorA extends Decorator{
public ConcreteDecoratorA(Component component) {
super(component);
}
private void operationFirst(){
System.out.println("ConcreteDecoratorA装饰operationFirst");
}
private void operationLast(){
System.out.println("ConcreteDecoratorA装饰operationLast");
}
public void operation(){
operationFirst();
super.operation();
operationLast();
}
}
static class ConcreteDecoratorB extends Decorator{
public ConcreteDecoratorB(Component component) {
super(component);
}
private void operationFirst(){
System.out.println("ConcreteDecoratorB装饰operationFirst");
}
private void operationLast(){
System.out.println("ConcreteDecoratorB装饰operationLast");
}
public void operation(){
operationFirst();
super.operation();
operationLast();
}
}
}
示例:
使用装饰器模式解决煎饼加码问题
下面用代码来模拟给煎饼加码的业务场景,先来看不用装饰器模式的情况。首先创建一个煎饼Battercake类
public class Battercake {
protected String getMsg(){
return "煎饼";
}
public int getPrice(){
return 5;
}
}
然后创建一个加鸡蛋的煎饼BattercakeWithEgg类
public class BattercakeWithEgg extends Battercake{
protected String getMsg(){
return super.getMsg() + "+ 1个鸡蛋";
}
public int getPrice(){
return super.getPrice() + 1;
}
}
在创建一个既加鸡蛋又加香肠的BattercakeWithEggAndSausage类
public class BattercakeWithEggAndSausage extends BattercakeWithEgg{
protected String getMsg(){
return super.getMsg() + "+ 1根香肠";
}
public int getPrice(){
return super.getPrice() + 2;
}
}
最后编写客户端测试代码
public class ClientTest {
public static void main(String[] args) {
Battercake battercake = new Battercake();
System.out.println(battercake.getMsg() + ",总价格" + battercake.getPrice());
BattercakeWithEgg battercakeWithEgg = new BattercakeWithEgg();
System.out.println(battercakeWithEgg.getMsg() + ",总价格" + battercakeWithEgg.getPrice());
BattercakeWithEggAndSausage battercakeWithEggAndSausage = new BattercakeWithEggAndSausage();
System.out.println(battercakeWithEggAndSausage.getMsg() + ",总价格" + battercakeWithEggAndSausage.getPrice());
}
}
运行结果如下:
煎饼,总价格5
煎饼+ 1个鸡蛋,总价格6
煎饼+ 1个鸡蛋+ 1根香肠,总价格8
运行结果没有问题。但是,如果用户需要一个加2个鸡蛋和1根香肠的煎饼,则用现在的类结构是创建不出来的,也无法自动计算出价格,除非再创建一个类做定制。如果需求在变,那么一直加定制显然是不科学的
下面用装饰器模式来解决上面的问题。首先创建一个煎饼的抽象Battercake类。
public abstract class Battercake {
protected abstract String getMsg();
protected abstract int getPrice();
}
创建一个基本的煎饼(或者叫基础套餐)BaseBattercake.
public class BaseBattercake extends Battercake{
@Override
protected String getMsg() {
return "煎饼";
}
@Override
protected int getPrice() {
return 5;
}
}
然后创建一个扩张套餐的抽象装饰器BattercakeDecotator类
public abstract class BattercakeDecotator extends Battercake{
private Battercake battercake;
public BattercakeDecotator(Battercake battercake){
this.battercake = battercake;
}
protected abstract void doSomething();
protected String getMsg(){
return this.battercake.getMsg();
}
protected int getPrice(){
return this.battercake.getPrice();
}
}
接着创建鸡蛋装饰器EggDecorator
public class EggDecorator extends BattercakeDecotator{
public EggDecorator(Battercake battercake) {
super(battercake);
}
@Override
protected void doSomething() {
}
protected String getMsg(){
return super.getMsg() + "+1个鸡蛋";
}
protected int getPrice(){
return super.getPrice() + 1;
}
}
创建香肠装饰器SausageDecorator类
public class SausageDecorator extends BattercakeDecotator{
public SausageDecorator(Battercake battercake) {
super(battercake);
}
@Override
protected void doSomething() {
}
protected String getMsg(){
return super.getMsg() + "+1根香肠";
}
protected int getPrice(){
return super.getPrice() + 2;
}
}
在编写客户端测试代码
public class Client {
public static void main(String[] args) {
//买一个煎饼
Battercake battercake;
battercake = new BaseBattercake();
//煎饼有点小,想再加1个鸡蛋
battercake = new EggDecorator(battercake);
battercake = new EggDecorator(battercake);
battercake = new SausageDecorator(battercake);
System.out.println(battercake.getMsg() + ",总价: " + battercake.getPrice());
}
}
运行结果如下图所示:
煎饼+1个鸡蛋+1个鸡蛋+1根香肠,总价: 9
装饰器模式与代理模式区别
从代理模式的UML类图和通用代码实现上看,代理模式与装饰器模式几乎一摸一样。代理模式的Subject对应装饰器模式的Component,代理模式的RealSubject对应装饰器模式的Concrete Component,代理模式的Proxy对应的装饰器模式的Decorator。确实,从代码实现上看,代理模式的确与装饰器模式是一样的,但是这两种设计模式多面向的功能扩展面是不一样的。
装饰器模式强调自身功能的扩展。Decorator所做的就是增强ConcreteComponent的功能,主体对象为ConcreteComponent,着重类功能的变化。
代理模式强调对代理过程的控制。Proxy完全掌握对RealSubject的访问控制,因此,Proxy可以决定对RealSubject进行功能扩展,功能缩减甚至功能散失,主体对象为Proxy。、
装饰器模式优缺点
优点:
-
装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态地给一个对象扩展功能,即插即用。
-
通过使用不同装饰类及这些装饰类的排列组合,可以实现不同效果
-
装饰器模式完全遵守开闭原则
缺点:
-
会出现更多的代码,更多的类,增加程序的复杂性
-
动态装饰在多层装饰时会更复杂