结构型模式: 装饰者模式(Decorator Pattern)

一、设计模式的分类

(如果以前看过关于设计模式的分类的话,这部分可以忽略!)

经过很多大神的总结,目前Java中一共23种经典的设计模式

按照目的,设计模式可以分为以下三种用途:

1.创建型模式:用来处理对象的创建过程

2.结构型模式:用来处理类或者对象的组合

3.行为型模式:用来对类或对象怎样交互和怎样分配职责进行描述

创建型模式用来处理对象的创建过程,主要包含以下5种设计模式:

 工厂方法模式(Factory Method Pattern)

 抽象工厂模式(Abstract Factory Pattern)

 建造者模式(Builder Pattern)

 原型模式(Prototype Pattern)

 单例模式(Singleton Pattern)

结构型模式用来处理类或者对象的组合,主要包含以下7种设计模式:

 适配器模式(Adapter Pattern)

 桥接模式(Bridge Pattern)

 组合模式(Composite Pattern)

 装饰者模式(Decorator Pattern)

 外观模式(Facade Pattern)

 享元模式(Flyweight Pattern)

 代理模式(Proxy Pattern)

行为型模式用来对类或对象怎样交互和怎样分配职责进行描述,主要包含以下11种设计模式:

 责任链模式(Chain of Responsibility Pattern)

 命令模式(Command Pattern

 解释器模式(Interpreter Pattern)

 迭代器模式(Iterator Pattern)

 中介者模式(Mediator Pattern)

 备忘录模式(Memento Pattern)

 观察者模式(Observer Pattern)

 状态模式(State Pattern)

 策略模式(Strategy Pattern)

 模板方法模式(Template Method Pattern)

 访问者模式(Visitor Pattern) 

本篇文章主要为讲解一下 装饰者模式(Decorator Pattern)

注:概念性的东西可以忽略不看,可以在看完例子以后再看概念,这样更有利于理解!

二、装饰着模式概述

(注:建议没有看过装饰者模式的朋友们,先看实例,然后再看枯燥的概念)

1、定义:

装饰者模式:指在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

2、类图:

3、结合在装饰模式的类图说明它的角色:

  ●  抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。

  ●  具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。

  ●  装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。

  ●  具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。

4、实例说明:实例1

总结一个从别人博客上看过的例子来说一下!(网址:http://www.cnblogs.com/java-my-life/archive/2012/04/20/2455726.html)

孙悟空有七十二般变化,他的每一种变化都给他带来另外一种本领。他变成鱼儿时,就可以到水里游泳;他变成鸟儿时,就可以在天上飞行。

实例中,Component角色便由齐天大圣扮演;ConcreteComponent角色便属于大圣的本尊、Decorator角色由大圣的七十二变扮演。而ConcreteDecorator角色便是鱼儿、鸟儿等七十二般变化之后的结果。

抽象构件角色“齐天大圣”接口定义了一个move()方法,这是所有的具体构件类和装饰类必须实现的。

//大圣的尊号
public interface TheGreatestSage {
    	public void move();
    }

具体构件角色:大圣本尊类

public class Monkey implements TheGreatestSage {
    @Override
    public void move() {
        //代码
        System.out.println("Monkey Move");
    }
}

抽象装饰角色:七十二变

public class Change implements TheGreatestSage {
    private TheGreatestSage sage;
    public Change(TheGreatestSage sage){
        this.sage = sage;
    }
    @Override
    public void move() {
        // 代码
        sage.move();
    }
}

具体装饰角色:鱼儿

public class Fish extends Change {
    public Fish(TheGreatestSage sage) {
        super(sage);
    }
    @Override
    public void move() {
        // 代码
        System.out.println("Fish Move");
    }
}

具体装饰角色:鸟儿

public class Bird extends Change {
    public Bird(TheGreatestSage sage) {
        super(sage);
    }
    @Override
    public void move() {
        // 代码
        System.out.println("Bird Move");
    }
}

 客户端类

public class Client {
    public static void main(String[] args) {
        TheGreatestSage sage = new Monkey();
        TheGreatestSage bird = new Bird(sage);
        TheGreatestSage fish = new Fish(bird);
        fish.move(); 
    }

}

“大圣本尊”是ConcreteComponent类,而“鸟儿”、“鱼儿”是装饰类。要装饰的是“大圣本尊”。

例子中,系统把大圣从一只猢狲装饰成了一只鸟儿,也就把猢狲的功能替换成了鸟儿的功能,然后又把鸟儿装饰成了一条鱼儿,又把鸟儿的功能替换成了鱼儿的功能。

上面的例子便是一个标准的装饰者模式!

5、透明性的要求

  装饰者模式对客户端的透明性要求程序不要声明一个ConcreteComponent类型的变量,而应当声明一个Component类型的变量。

  用上面例子来说,必须永远把孙悟空的所有变化都当成孙悟空来对待,而如果把老孙变成的鱼儿当成鱼儿,而不是老孙,那就被老孙骗了,而这时不应当发生的。

下面的做法是对的:

TheGreatestSage sage = new Monkey();
TheGreatestSage bird = new Bird(sage);

而下面的做法是不对的:

Monkey sage = new Monkey();
Bird bird = new Bird(sage);

6、半透明的装饰模式1

  但是,现实中纯粹的装饰者模式很难找到。

装饰者模式的用意是在不改变接口的前提下,增强所考虑的类的功能。在增强功能的时候,却往往需要建立新的公开的方法。即便是在孙大圣的系统里,也需要新的方法。比如齐天大圣类并没有飞行的能力,而鸟儿有。这就意味着鸟儿应当有一个新的fly()方法。再比如,齐天大圣类并没有游泳的能力,而鱼儿有,这就意味着在鱼儿类里应当有一个新的swim()方法。

  这就导致了大多数的装饰模式的实现都是“半透明”的,而不是完全透明的。换言之,允许装饰模式改变接口,增加新的方法。这意味着客户端可以声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法:

TheGreatestSage sage = new Monkey();
Bird bird = new Bird(sage);
bird.fly();

  半透明的装饰模式是介于装饰模式和适配器模式之间的。适配器模式的用意是改变所考虑的类的接口,也可以通过改写一个或几个方法,或增加新的方法来增强或改变所考虑的类的功能。大多数的装饰模式实际上是半透明的装饰模式,这样的装饰模式也称做半装饰、半适配器模式。

7、半透明的装饰模式2

  装饰模式和适配器模式都是“包装模式(Wrapper Pattern)”,它们都是通过封装其他对象达到设计的目的的,但是它们的形态有很大区别。

  理想的装饰模式在对被装饰对象进行功能增强的同时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。而适配器模式则不然,一般而言,适配器模式并不要求对源对象的功能进行增强,但是会改变源对象的接口,以便和目标接口相符合。

  装饰模式有透明和半透明两种,这两种的区别就在于装饰角色的接口与抽象构件角色的接口是否完全一致。透明的装饰模式也就是理想的装饰模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。相反,如果装饰角色的接口与抽象构件角色接口不一致,也就是说装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,这种装饰模式也是可以接受的,称为“半透明”的装饰模式,如下图所示。

  在适配器模式里面,适配器类的接口通常会与目标类的接口重叠,但往往并不完全相同。换言之,适配器类的接口会比被装饰的目标类接口宽。

  显然,半透明的装饰模式实际上就是处于适配器模式与装饰模式之间的灰色地带。如果将装饰模式与适配器模式合并成为一个“包装模式”的话,那么半透明的装饰模式倒可以成为这种合并后的“包装模式”的代表。

8、实例说明:实例2.设计模式在JAVA I/O库中的应用

  装饰者模式在Java语言中的最著名的应用莫过于Java I/O标准库的设计了。

  由于Java I/O库需要很多功能的各种组合,如果这些功能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量功能重复的类出现。但是,如果采用装饰者模式,那么类的数目就会大大减少,功能的重复也可以减至最少。因此装饰模式是Java I/O库的基本模式。

  Java I/O库的对象结构图如下,由于Java I/O的对象众多,因此只画出InputStream的部分。

  根据上图可以看出:

  ●抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。

  ●具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。

  ●抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。

  ●具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。


  InputStream类型中的装饰模式是半透明的。为了说明这一点,看一看作装饰模式的抽象构件角色的InputStream的源代码。这个抽象类声明了九个方法,并给出了其中八个的实现,另外一个是抽象方法,需要由子类实现。

public abstract class InputStream implements Closeable {
    public abstract int read() throws IOException;
    public int read(byte b[]) throws IOException {...}
    public int read(byte b[], int off, int len) throws IOException {...}
    public long skip(long n) throws IOException {...}
    public int available() throws IOException {...} 
    public void close() throws IOException {...}
    public synchronized void mark(int readlimit) {...}
    public synchronized void reset() throws IOException {...}
    public boolean markSupported() {...}

}

  下面是作为装饰模式的抽象装饰角色FilterInputStream类的源代码。可以看出,FilterInputStream的接口与InputStream的接口是完全一致的。也就是说,直到这一步,还是与装饰模式相符合的,属于完全透明!

public class FilterInputStream extends InputStream {
    protected FilterInputStream(InputStream in) {...}
    public int read() throws IOException {...}
    public int read(byte b[]) throws IOException {...}
    public int read(byte b[], int off, int len) throws IOException {...}
    public long skip(long n) throws IOException {...}
    public int available() throws IOException {...}
    public void close() throws IOException {...}
    public synchronized void mark(int readlimit) {...}
    public synchronized void reset() throws IOException {...}
    public boolean markSupported() {...}
}

  下面是具体装饰角色PushbackInputStream的源代码。

public class PushbackInputStream extends FilterInputStream {
    public PushbackInputStream(InputStream in, int size) {...}
    public PushbackInputStream(InputStream in) {...}
    public int read() throws IOException {...}
    public int read(byte[] b, int off, int len) throws IOException {...}
    //public int read(byte b[]) throws IOException {...}从类 java.io.FilterInputStream 继承的方法
    public void unread(int b) throws IOException {...}
    public void unread(byte[] b, int off, int len) throws IOException {...}
    public void unread(byte[] b) throws IOException {...}
    public int available() throws IOException {...}
    public long skip(long n) throws IOException {...}
    public boolean markSupported() {...}
    public synchronized void mark(int readlimit) {...}
    public synchronized void reset() throws IOException {...}
    public synchronized void close() throws IOException {...}
}

   查看源码,你会发现,这个装饰类提供了额外的方法unread(),这就意味着PushbackInputStream是一个半透明的装饰类。换言 之,它破坏了理想的装饰模式的要求。如果客户端持有一个类型为InputStream对象的引用in的话,那么如果in的真实类型是 PushbackInputStream的话,只要客户端不需要使用unread()方法,那么客户端一般没有问题。但是如果客户端必须使用这个方法,就 必须进行向下类型转换。将in的类型转换成为PushbackInputStream之后才可能调用这个方法。但是,这个类型转换意味着客户端必须知道它 拿到的引用是指向一个类型为PushbackInputStream的对象。这就破坏了使用装饰模式的原始用意。

  现实世界与理论总归是有一段差距的。纯粹的装饰模式在真实的系统中很难找到。一般所遇到的,都是这种半透明的装饰模式。

  下面是使用I/O流读取文件内容的简单操作示例。

public class IOTest {
    public static void main(String[] args) throws IOException {
        // 流式读取文件
        DataInputStream dis = null;
        try{
            dis = new DataInputStream(
                    new BufferedInputStream(
                            new FileInputStream("test.txt")
                    )
            );
            //读取文件内容
            byte[] bs = new byte[dis.available()];
            dis.read(bs);
            String content = new String(bs);
            System.out.println(content);
        }finally{
            dis.close();
        }
    }

}

观察上面的代码,会发现最里层是一个FileInputStream对象,然后把它传递给一个BufferedInputStream对象,经过BufferedInputStream处理,再把处理后的对象传递给了DataInputStream对象进行处理,这个过程其实就是装饰器的组装过程,FileInputStream对象相当于原始的被装饰的对象,而BufferedInputStream对象和DataInputStream对象则相当于装饰器。

8、总结

一、装饰者模式的特点:

1)装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
2)装饰对象包含一个真实对象的引用(reference)
3)装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
4)装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。
二、装饰者模式的使用场景:
1)需要扩展一个类的功能,或给一个类添加附加职责。
2)需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
3)需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
4)当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
三、装饰者模式的优点:

1)Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。 装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
2)通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
四、修饰者模式的缺点:

1)这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
2)装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。
3)装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。
五、装饰者模式的设计原则:

1)多用组合,少用继承。
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。
2)类应设计的对扩展开放,对修改关闭。

菜鸟一枚,上面有些是摘录别人的,感觉大神写的不错!如果大家有什么好的例子希望共享一下!


你可能感兴趣的:(设计模式)