装饰者模式

begin 2018年9月29日14:19:22

装饰者模式(Decorator Pattern)

定义

金玉其外,败絮其中。

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式比生成子类更为灵活。——《设计模式:可复用面向对象软件的基础》

装饰者模式是一种对象结构型模式。

使用场景

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

  • 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。

  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类).

角色

抽象构件角色(Component):定义一个对象接口或抽象类,可以给这些对象动态地添加职责。

具体构件角色(ConcreteComponent):实际被动态地添加职责的对象。

抽象装饰者角色(Decorator):实现了Component接口,用来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的。

具体装饰者角色(ConcreteDecorator):动态地添加职责的对象。

图示

装饰者模式(Decorator)结构图
装饰者模式(Decorator)结构图

代码示例

装饰者模式(Decorator)代码示例类图
装饰者模式(Decorator)代码示例类图

这是一个关于相亲的故事。某家有女初长成,七大姑八大姨欲帮忙觅得佳婿,后在各种群、圈、网站,收集了各种男人的资料。这里需要一个应用生成收集过来的男人的描述,如:是否有车、是否有房、是否有存款、是否有好品质。然后这个应用了装饰者模式的应用就诞生了,动态添加男人的描述,最后得到男人的综述。

抽象构件角色(Man.java):

// 男人
public interface Man {
    public void getManDesc();
}

具体构件角色(NormalMan.java):

// 普通男人
public class NormalMan implements Man{
    private String name = null;
    
    public NormalMan(String name) {
        this.name = name;
    }
    
    @Override
    public void getManDesc() {
        System.out.print(name + ": ");
    }
}

抽象装饰者角色(AttachedPropertiesDecorator.java):

// 附加属性装饰者
public abstract class AttachedPropertiesDecorator implements Man{
    private Man man;
    
    public AttachedPropertiesDecorator(Man man) {
        this.man = man;
    }
    
    public void getManDesc() {
        man.getManDesc();
    }
}

具体装饰者角色(CarDecoratorImpl.java、HouseDecoratorImpl.java、DepositDecoratorImpl.java、QualityDecoratorImpl.java):

// 小车装饰者
public class CarDecoratorImpl extends AttachedPropertiesDecorator{
    private String car = "有车";
    
    public CarDecoratorImpl(Man man) {
        super(man);
    }
    
    public void addCar() {
        System.out.print(car + " ");
    }
    
    @Override
    public void getManDesc() {
        super.getManDesc();
        addCar();
    }
}

// 房子装饰者
public class HouseDecoratorImpl extends AttachedPropertiesDecorator{
    private String house = "有房";
    
    public HouseDecoratorImpl(Man man) {
        super(man);
    }
    
    public void addHouse() {
        System.out.print(house + " ");
    }
    
    @Override
    public void getManDesc() {
        super.getManDesc();
        addHouse();
    }
}

// 存款装饰者
public class DepositDecoratorImpl extends AttachedPropertiesDecorator{
    private String deposit = "有存款";
    
    public DepositDecoratorImpl(Man man) {
        super(man);
    }
    
    public void addDeposit() {
        System.out.print(deposit + " ");
    }
    
    @Override
    public void getManDesc() {
        super.getManDesc();
        addDeposit();
    }
}

// 品质装饰者
public class QualityDecoratorImpl extends AttachedPropertiesDecorator{
    private String quality = "有好品质";
    
    public QualityDecoratorImpl(Man man) {
        super(man);
    }
    
    public void addQuality() {
        System.out.print(quality + " ");
    }
    
    @Override
    public void getManDesc() {
        super.getManDesc();
    }
}

有车、有房、有存款、有品质具体修饰者。

装饰者模式测试类(DecoratorTest.java):

public class DecoratorTest {

    public static void main(String[] args) {
        Man man = new NormalMan("张三");
        Man man1 = new CarDecoratorImpl(man);
        Man man2 = new HouseDecoratorImpl(man1);
        Man man3 = new DepositDecoratorImpl(man2);
        System.out.println("层层装饰:");
        man3.getManDesc();
        System.out.println();
        
        System.out.println("重复装饰(有两个'有存款'):");
        Man man4 = new DepositDecoratorImpl(man3);
        man4.getManDesc();
        System.out.println();
        
        System.out.println("任意修饰:");
        Man man5 = new QualityDecoratorImpl(man1);
        man5.getManDesc();
        System.out.println();
        
        System.out.println("直接得到修饰结果:");
        Man man6 = new HouseDecoratorImpl(new DepositDecoratorImpl(new NormalMan("李四")));
        man6.getManDesc();
        System.out.println();

    }
}

运行结果:
运行结果

优点

1、装饰者模式和继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活型。

2、通过使用不同的具体装饰者类及它们不同的组合顺序,可以得到不同装饰后具有不同行为或者状态的对象。例如上面的CarDecoratorImpl可以多次修饰一个男人,证明他有很多车。

3、符合开闭原则。

缺点

1、增加了抽象装饰者类和具体装饰者类,一定程度增加了系统的复杂度,加大了系统的学习和理解成本。

2、灵活性也意味着更容易出错,对于多次被多次修饰的对象,调试时寻找错误可能需要找到多个地方。

装饰者模式进阶

透明与半透明的装饰者模式

透明的装饰者模式:要求客户端完全针对抽象编程(依赖倒置原则),装饰者模式的透明型要求客户端程序不应该声明具体构件类型和具体装饰者类型,而应该全部声明为抽象构件类型。当然调用的也是抽象构件类声明的接口方法了。

Man man = new NormalMan("张三");
Man man1 = new CarDecoratorImpl(man);
Man man2 = new HouseDecoratorImpl(man1);
Man man3 = new DepositDecoratorImpl(man2);

半透明的装饰者模式:允许用户在客户端声明具体装饰者类型的对象,允许在具体装饰者中新增方法且客户端可以调用这些新增的方法。

Man man = new NormalMan("张三");
CarDecoratorImpl man1 = new CarDecoratorImpl(man);
HouseDecoratorImpl man2 = new HouseDecoratorImpl(man);
DepositDecoratorImpl man3 = new DepositDecoratorImpl(man);

man1.addCar();
man2.addHouse();
man3.addDeposit();

java.io中装饰者模式的应用

java I/O类继承结构图:
java I/O类继承结构图

虽然java I/O中类比较多,但是装饰者模式的应用方式是差不多的。下面举一个例子:InputStream及其子类
InputStream及其子类

装饰者模式对照上图可得:

抽象构件角色:InputStream,这是一个抽象类。

具体构件角色:ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream(已弃用)、AudioInputStream等,实现了InputStream抽象类。

抽象装饰者角色:FilterInputStream,实现了抽象构件角色,且成为具体装饰者的父类。

具体装饰者模式:FilterInputStream的子类如常用的BufferedInputStream、DataInputStream,还有其他不常用的CheckedInputStream、CipherInputStream、LineNumberInputStream、PushBackInputStream。这里其实具体的装饰者还有直接继承InputStream的ObjectInputStream、SequenceInputStream,这里涉及到下面介绍的装饰者模式的简化

这里的装饰者模式大多都是半透明的,具体装饰者提供了比抽象构件多的方法,客户端可以使用这些方法。

装饰者模式的简化

装饰者有3种简化模式:

第一种情况,只有一个具体装饰者,这样就不需要抽象装饰者,具体装饰者直接继承抽象构件就可以了。
装饰者模式简化的第一种情况

第二种情况,只有一个具体构件,这样就不需要抽象构件,抽象装饰者可以直接继承具体构件就可以了。
装饰者模式简化的第二种情况

第三种情况,就是上面组合起来:只有一个具体构件和一个具体装饰者,这样抽象角色都不需要了,具体装饰者直接继承集体构件就可以了。
装饰者模式简化的第三种情况

总结

需要动态给某个对象添加职责,使用装饰者模式。

end 2018年10月16日15:24:03

posted on 2018-10-16 15:27 mingmingcome 阅读() 评论() 编辑 收藏

版权声明:本文为mingmingcome原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/mingmingcome/p/9798248.html