设计模式装饰模式

来源:互联网 发布:蜂群算法 abc 编辑:程序博客网 时间:2024/06/05 09:02

设计模式6原则:
1、开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。
3、依赖倒转原则(Dependence Inversion Principle)
真对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
使用多个隔离的接口,比使用单个接口要好
5、迪米特法则(最少知道原则)(Demeter Principle)
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承

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

demo:动态的给图形控件加各种属性,比如滑动框,边框颜色等等
这里写图片描述
装饰模式的特点
(1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
(2) 装饰对象包含一个真实对象的引用(reference)
(3) 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。

适用性
以下情况使用Decorator模式
1. 需要扩展一个类的功能,或给一个类添加附加职责。
2. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。
4. 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类(final)。

优点
1. Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。
2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

缺点

  1. 装饰模式会导致设计中出现许多小类,些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。如果过度使用,会使程序变得很复杂, 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。。

  2. 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择

设计原则

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

例:某个人上班有多种交通工具,通过一系列修饰可以修饰它上班的一个过程
接口:

public interface Human {void move();}

上班族:

public class WorkPerson implements Human{    /* (non-Javadoc)     * @see team.design.decoration.Human#move()     */    public void move() {        // TODO Auto-generated method stub        System.out.println("\nwork--------------->");    }}

如果要形容它走路去上班,使用继承实现:

public class WorkPersonWalk extends WorkPerson{    /* (non-Javadoc)     * @see team.design.decoration.WorkPerson#move()     */    @Override    public void move() {        // TODO Auto-generated method stub        super.move();        System.out.println("walk walk");    }}

但是如果另外一个上班族不但走路还坐了跑了一段,我们又得在这个基础上继续继承跑的过程,如果我们用了包装类跑修饰human,就减少了继承,而且还可以讲这个包装类应用到购物者,散步者上。
包装类抽象类如下,它持有对原有类的引用和方法的调用,继承这个类只需要扩展相关动作并增加到和包装类共有的父类的方法中即可:

public abstract class Decoration implements Human{    Human human;    public  Decoration(Human human)    {this.human=human;}    public void move() {        // TODO Auto-generated method stub        human.move();    }}

比如走:

public class WorkOne extends Decoration{    /**     * @param human     */    public WorkOne(Human human) {        super(human);        // TODO Auto-generated constructor stub    }    public void moveOne() {        System.out.println("walk walk");    }    @Override    public void move() {        // TODO Auto-generated method stub        super.move();        moveOne();    }}

在保证原有方法完整的同时增加了新的方法。
main函数调用如下:

    Decoration onestep=new WorkOne(humanwork);            onestep.move();

对于购物者,走这个装饰类同样可用:

public class ShopPerson implements Human{    public void move() {        // TODO Auto-generated method stub        System.out.println("\nshop------------->");    }}

调用方法相同。还可以增加一系列跑,坐车的过程,调用方法类似。
io例子如下:

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对象则相当于装饰器。

4.3半透明的装饰模式
然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能。在增强性能的时候,往往需要建立新的公开的方法。上面成绩单的例子中,显示前十名学生信息。这就意味着SortDecorator类中应当有一个新的displayTopTen()方法。再比如,显示显示各科最高分学生信息,这就意味着在HighScoreDecorator类里应当有一个新的showTop()方法。
这就导致了大多数的装饰模式的实现都是“半透明”的,而不是完全透明的。换言之,允许装饰模式改变接口,增加新的方法。这意味着客户端可以声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法:

IO例子如下:

// TODO Auto-generated method stub         //文件路径可自行更换        final String filePath = "D:/io.txt";        //InputStream相当于被装饰的接口或者抽象类,FileInputStream相当于原始的待装饰的对象,FileInputStream无法装饰InputStream        //另外FileInputStream是以只读方式打开了一个文件,并打开了一个文件的句柄存放在FileDescriptor对象的handle属性        //所以下面有关回退和重新标记等操作,都是在堆中建立缓冲区所造成的假象,并不是真正的文件流在回退或者重新标记        InputStream inputStream = new FileInputStream(filePath);        final int len = inputStream.available();//记录一下流的长度        System.out.println("FileInputStream不支持mark和reset:" + inputStream.markSupported());        System.out.println("---------------------------------------------------------------------------------");        /* 下面分别展示三种装饰器的作用BufferedInputStream,DataInputStream,PushbackInputStream,LZ下面做了三个装饰器的功能演示  */        //首先装饰成BufferedInputStream,它提供我们mark,reset的功能        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);//装饰成 BufferedInputStream        System.out.println("BufferedInputStream支持mark和reset:" + bufferedInputStream.markSupported());        bufferedInputStream.mark(0);//标记一下        char c = (char) bufferedInputStream.read();        System.out.println("LZ文件的第一个字符:" + c);        bufferedInputStream.reset();//重置        c = (char) bufferedInputStream.read();//再读        System.out.println("重置以后再读一个字符,依然会是第一个字符:" + c);        bufferedInputStream.reset();        System.out.println("---------------------------------------------------------------------------------");        //装饰成 DataInputStream,我们为了又使用DataInputStream,又使用BufferedInputStream的mark reset功能,所以我们再进行一层包装        //注意,这里如果不使用BufferedInputStream,而使用原始的InputStream,read方法返回的结果会是-1,即已经读取结束        //因为BufferedInputStream已经将文本的内容读取完毕,并缓冲到堆上,默认的初始缓冲区大小是8192B        DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);        dataInputStream.reset();//这是BufferedInputStream提供的功能,如果不在这个基础上包装会出错        System.out.println("DataInputStream现在具有readInt,readChar,readUTF等功能");        int value = dataInputStream.readInt();//读出来一个int,包含四个字节        //我们转换成字符依次显示出来,可以看到LZ文件的前四个字符        String binary = Integer.toBinaryString(value);        int first = binary.length() % 8;        System.out.print("使用readInt读取的前四个字符:");        for (int i = 0; i < 4; i++) {            if (i == 0) {                System.out.print(((char)Integer.valueOf(binary.substring(0, first), 2).intValue()));            }else {                System.out.print(((char)Integer.valueOf(binary.substring(( i - 1 ) * 8 + first, i * 8 + first), 2).intValue()));            }        }        System.out.println();        System.out.println("---------------------------------------------------------------------------------");    }

inputStream为抽象类,FileInputStream相当于原始的待装饰的对象,BufferedInputStream, DataInputStream为装饰类,都继承于FilterInputStream,FilterInputStream继承了inputstream, DataInputStream中的readint方法实现的是DataInput接口,它增加了其它方法,但是reset方法为调用的创建对象时InputStream(此处为BufferedInputStream)中的reset方法,它持有对象InputStream的引用,但是它调用了修饰对象外的readint方法,所以为半透明装饰方法
这里写图片描述

原创粉丝点击