关于装饰器模式

关于装饰器模式

装饰器模式

定义:在不改变现有对象结构的情况下,动态地给该对象增加一些职责(额外功能)的模式。


装饰器模式vs继承

相比于继承来说,装饰器更加灵活。在对象的扩展功能比较多的情况下,如果使用继承,会产生大量的子类和代码重复。即使扩展功能相同但client要求扩展功能的执行顺序不同,那么也会产生不同的子类。而装饰器模式具有比较高的自由度,可以通过对具体构件使用具体装饰以递归的方式不断包装最终得到所需要的对象。

但装饰器模式仍有不足。相对于简单的继承而言装饰器模式精简了类之间的结构和所需类的数目,但装饰器模式在运行过程中会产生许多小的对象占据更多的内存,在一定程度上会影响性能。


装饰器中包含以下部分:

①Component(抽象):一个抽象接口,定义被装饰物执行的公共操作,规范准备接收附加责任的对象。

②ConcreteComponent(具体):实现Component,作为要被装饰的起始对象,此对象中包含通用的方法(无论怎样装饰都必须要最先执行的部分,基础的功能)。

③Decorator(抽象):继承Component,内部包含Component类型的一个成员变量,构造器和要被装饰的ConcreteComponent中的方法的定义。可以通过Decorator的子类扩展ConcreteComponent的功能。

④ConcreteDecorator(具体):Decorator的子类,实现Decorator定义的相关方法,并给ConcreteComponent对象添加附加的责任(对起始对象进行装饰)。 不同的ConcreteDecorator代表对ConcreteComponent的不同装饰类型。

image

注:

装饰器模式所包含的 4 个角色不一定都要存在,在有些应用环境下模式可以简化:

①如果只有一个具体构件而没有抽象构件时,可以让抽象装饰继承具体构件。

②如果只有一个具体装饰时,可以将抽象装饰和具体装饰合并。


使用装饰器模式的例子

尝试使用装饰器模式实现有多种组合的Cook过程。

组成部分如下:

① Component:接口CookComponent,接口中只定义了一个展示烹饪过程的公共操作cookProcess。

public interface CookComponent {
    void cookProcess();
}

② ConcreteComponent:ConcreteCook起始对象类,在里面重写了cookProcess,打印"Prepare materials.",这是所有被装饰对象的基础功能

public class ConcreteCook implements CookComponent {
    public ConcreteCook(){}
    @Override
    public void cookProcess() {
        System.out.println("Prepare materials.");
    }
}

③ Decorator:Decorator类,包含CookComponent类型的成员变量,一个构造函数和cookProcess方法的定义。这里将Decorator类声明为abstract。

public abstract class Decorator implements CookComponent{
    protected CookComponent cookComponent;
    public Decorator(CookComponent i){
        this.cookComponent=i;
    }

    public abstract void cookProcess();
}

④ ConcreteDecorator:包括Decorator的三个子类:CookCut、CookWash、CookStir。分别扩展打印"Cut." 、"Wash vegetables." 和"Stir dishes."三个功能

public class CookCut extends Decorator{
    public CookCut(CookComponent i){
        super(i);
    }
    @Override
    public void cookProcess() {
        cookComponent.cookProcess();
        System.out.println("Cut.");
    }
}
public class CookWash extends Decorator {
    public CookWash(CookComponent i){
        super(i);
    }

    @Override
    public void cookProcess() {
        cookComponent.cookProcess();
        System.out.println("Wash vegetables.");
    }
}
public class CookStir extends Decorator {
    public CookStir(CookComponent i){
        super(i);
    }

    @Override
    public void cookProcess() {
        cookComponent.cookProcess();
        System.out.println("Stir dishes.");
    }
}

main中使用装饰如下:

public class Cook {
    public static void main(String[] args) {
        CookComponent cook=new CookStir(new CookWash(new CookCut(new CookWash(new ConcreteCook()))));
        cook.cookProcess();
    }
}

打印结果如图:

image


对装饰器模式的一些解读

1.递归方式体现在方法调用时

Client调用被修饰后的方法时,最外层执行时先委派里面一层同名的方法执行,以此类推直到起始对象的方法执行时不再进行委派。起始对象的方法执行完后,再返回到外面一层执行。相同方式进行直到回到最外层执行的方法。

2.装饰关键在于委派

ConcreteDecorator中进行修饰的方法具有相同的命名,可以以统一的形式(component.operation();)实现委派,达到任意组合的目的。

3.装饰应该体现在需要装饰的方法内部

装饰器模式要求不改变现有对象结构,任何装饰后的对象都应匹配Component抽象接口中的结构。所以装饰主要体现在对接口中定义的方法进行重写,重写时在方法中加入更多的功能,以这种形式达到装饰的目的。

实际上也可以在ConcreteDecorator中加入了额外的方法(函数)。加入的方法如果在类本身的方法内部被调用,不会产生问题。但如果在类的外部想要直接使用加入的新方法时,应该注意:在实际使用时,为满足任意的特性组合需求,指向被装饰对象的成员变量通常被声明为Component接口的类型。这意味着装饰后的对象不能直接调用扩展的但是没有在接口中定义的方法。如想调用该方法,一是要保证成员变量最后指向的对象的ConcreteDecorator类中包含该方法,二是或者将成员变量的类型直接定义为需要的ConcreteDecorator类,或者进行强制类型转换将成员变量有Component接口类型转换为具体ConcreteDecorator类。如果采用强制类型转换,显然会增加出错可能,降低了可维护性,使用时编写代码会更加复杂。

4.Decorator可以被声明为abstract

Decorator被声明为abstract抽象类,借此表明该类还没有被完全实现,不能被实例化。同时可以将待装饰的方法声明为abstract,留给子类实现。这样做符合Decorator在装饰器模式中的角色定义,可以防止构造Decorator对象进行没有意义的装饰,或是装饰超出了Component中规范的可被装饰的范围。

关于abstract还有一个要注意的地方。借用上面的例子,如果在Decorator类中要被修饰的方法cookProcess被定义为abstract,那么在子类中不能通过super.cookProcess()进行委派,需要通过super.cookComponent.cookProcess()或cookComponent.cookProcess()的形式来进行。

如果不是abstract,需要有函数体cookComponent.cookProcess();若没有函数体,当不依靠父类中的方法,直接使用cookComponent.cookProcess();进行委派时,结果正常。若使用super.cookProcess(),因为父类中cookProcess函数体为空,无法进行委派,最终只能得到当前所在类的扩展输出。

也要关注Decorator中局部变量component声明的类型。优先选择protected和private,如果如果在Decorator类中要被修饰的方法被定义为abstract,那么component只能选择protected类型因为子类需要访问component。如果方法定义不是abstract,component可以定义为private,此时子类中进行委派只能使用super.cookProcess()。


参考

https://blog.csdn.net/qq_26219243/article/details/105419501

https://zhuanlan.zhihu.com/p/398437501

https://zhuanlan.zhihu.com/p/43368677

posted @ 2022-06-07 20:14  Twinblade_i  阅读(72)  评论(0)    收藏  举报