首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

装饰模式三

2013-12-13 
装饰模式3public class A {??? public void a1(){?????? System.out.println(now in A.a1)??? }}C1对象

装饰模式3

public class A {

??? public void a1(){

?????? System.out.println("now in A.a1");

??? }

}

C1对象示例代码如下:

public?class C1 extends A{

??? public void c11(){

?????? System.out.println("now in C1.c11");

??? }

}

另外一个方案就是使用对象组合,怎么组合呢?就是在C1对象里面不再继承A对象了,而是去组合使用A对象的实例,通过转调A对象的功能来实现A对象已有的功能,写个新的对象C2来示范,示例代码如下:

public class C2 {

????/**

??? ?*?创建A对象的实例

??? ?*/

??? private A a = new A();

???

?


??? public void a1(){

???????//转调A对象的功能

?????? a.a1();

??? }

??? public void c11(){

?????? System.out.println("now in C2.c11");

??? }

}

?????? 对象组合是不是也很简单,而且更灵活了:

  • 首先可以有选择的复用功能,不是所有A的功能都会被复用,在C2中少调用几个A定义的功能就可以了;
  • 其次在转调前后,可以实现一些功能处理,而且对于A对象是透明的,也就是A对象并不知道在a1方法处理的时候被追加了功能;
  • 还有一个额外的好处,就是可以组合拥有多个对象的功能,假如还有一个对象B,而C2也想拥有B对象的功能,那很简单,再增加一个方法,然后转调B对象就好了,B对象示例如下:

    public class B {

    ??? public void b1(){

    ?????? System.out.println("now in B.b1");

    ??? }

    }

    同时拥有A对象功能,B对象的功能,还有自己实现的功能的C3对象示例代码如下:

    public class C3 {

    ????private?A a = new A();

    ??? private B b = new B();

    ?

    ??? public void a1(){

    ???????//转调A对象的功能

    ?????? a.a1();

    ??? }

    ??? public void b1(){

    ???????//转调B对象的功能

    ?????? b.b1();

    ??? }

    ??? public void c11(){

    ?????? System.out.println("now in C3.c11");

    ??? }

    }

    ?????? 最后再说一点,就是关于对象组合中,何时创建被组合对象的实例

    • 一种方案是在属性上直接定义并创建需要组合的对象实例
    • 另外一种方案是在属性上定义一个变量,来表示持有被组合对象的实例,具体实例从外部传入,也可以通过IoC/DI容器来注入

      示例如下:

      public class C4 {

      ????//示例直接在属性上创建需要组合的对象

      ??? private A a = new A();

      ?

      ??? //示例通过外部传入需要组合的对象

      ??? private B b = null;

      ??? public void setB(B b){

      ?????? this.b = b;

      ??? }

      ??? public void a1(){

      ?????? //转调A对象的功能

      ?????? a.a1();

      ??? }

      ??? public void b1(){

      ?????? //转调B对象的功能

      ?????? b.b1();

      ??? }

      ??? public void c11(){

      ?????? System.out.println("now in C4.c11");

      ??? }

      }

      (3)装饰器

      ?????? 装饰器实现了对被装饰对象的某些装饰功能,可以在装饰器里面调用被装饰对象的功能,获取相应的值,这其实是一种递归调用。

      在装饰器里不仅仅是可以给被装饰对象增加功能,还可以根据需要选择是否调用被装饰对象的功能,如果不调用被装饰对象的功能,那就变成完全重新实现了,相当于动态修改了被装饰对象的功能。

      ?????? 另外一点,各个装饰器之间最好是完全独立的功能,不要有依赖,这样在进行装饰组合的时候,才没有先后顺序的限制,也就是先装饰谁和后装饰谁都应该是一样的,否则会大大降低装饰器组合的灵活性。

      (4)装饰器和组件类的关系

      ?????? 装饰器是用来装饰组件的,装饰器一定要实现和组件类一致的接口,保证它们是同一个类型,并具有同一个外观,这样组合完成的装饰才能够递归的调用下去。

      组件类是不知道装饰器的存在的,装饰器给组件添加功能是一种透明的包装,组件类毫不知情。需要改变的是外部使用组件类的地方,现在需要使用包装后的类,接口是一样的,但是具体的实现类发生了改变。

      (5)退化形式

      如果仅仅只是想要添加一个功能,就没有必要再设计装饰器的抽象类了,直接在装饰器里面实现跟组件一样的接口,然后实现相应的装饰功能就可以了。但是建议最好还是设计上装饰器的抽象类,这样有利于程序的扩展。

      22.3.2? Java中的装饰模式应用

      1:Java中典型的装饰模式应用——I/O

      装饰模式在Java中最典型的应用,就是I/O流,简单回忆一下,如果使用流式操作读取文件内容,会怎么实现呢,简单的代码示例如下:

      public class IOTest {

      ??? public static void main(String[] args)throws Exception? {

      ?????? //流式读取文件

      ?????? DataInputStream din = null;

      ?????? try{

      ???????????din = new DataInputStream(

      ????????????? new BufferedInputStream(

      ???????????????????? new FileInputStream("IOTest.txt")

      ????????????? )

      ?????????? );

      ?????????? //然后就可以获取文件内容了

      ?????????? byte bs []= new byte[din.available()];

      ?????????? din.read(bs);

      ?????????? String content = new String(bs);

      ?????????? System.out.println("文件内容===="+content);

      ?????? }finally{

      ?????????? din.close();

      ?????? }?????

      ??? }

      }

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

      ?????? 可能有朋友会问,装饰器和具体的组件类是要实现同样的接口的,上面这些类是这样吗?看看Java的I/O对象层次图吧,由于Java的I/O对象众多,因此只是画出了InputStream的部分,而且由于图的大小关系,也只是表现出了部分的流,具体如图22.4所示:

      ?装饰模式三

      图22.4? Java的I/O的InputStream部分对象层次图

      ?????? 查看上图会发现,它的结构和装饰模式的结构几乎是一样的:

      • InputStream就相当于装饰模式中的Component。
      • 其实FileInputStream、ObjectInputStream、StringBufferInputStream这几个对象是直接继承了InputSream,还有几个直接继承InputStream的对象,比如:ByteArrayInputStream、PipedInputStream等。这些对象相当于装饰模式中的ConcreteComponent,是可以被装饰器装饰的对象。
      • 那么FilterInputStream就相当于装饰模式中的Decorator,而它的子类DataInputStream、BufferedInputStream、LineNumberInputStream和PushbackInputStream就相当于装饰模式中的ConcreteDecorator了。另外FilterInputStream和它的子类对象的构造器,都是传入组件InputStream类型,这样就完全符合前面讲述的装饰器的结构了。

        ?????? 同样的,输出流部分也类似,就不去赘述了。

        ?????? 既然I/O流部分是采用装饰模式实现的,也就是说,如果我们想要添加新的功能的话,只需要实现新的装饰器,然后在使用的时候,组合进去就可以了,也就是说,我们可以自定义一个装饰器,然后和JDK中已有的流的装饰器一起使用。能行吗?试试看吧,前面是按照输入流来讲述的,下面的示例按照输出流来做,顺便体会一下Java的输入流和输出流在结构上的相似性。

        2:自己实现的I/O流的装饰器——第一版

        ?????? 来个功能简单点的,实现把英文加密存放吧,也谈不上什么加密算法,就是把英文字母向后移动两个位置,比如:a变成c,b变成d,以此类推,最后的y变成a,z就变成b,而且为了简单,只处理小写的,够简单的吧。

        ?????? 好了,还是看看实现简单的加密的代码实现吧,示例代码如下:

        /**

        ?* 实现简单的加密

        ?*/

        public class EncryptOutputStream??extends?OutputStream{

        ??? //持有被装饰的对象

        ??? private OutputStream os = null;

        ??? public EncryptOutputStream(OutputStream os){

        ?????? this.os = os;

        ??? }??

        ??? public void write(int a) throws IOException {

        ?????? //先统一向后移动两位

        ?????? a = a+2;

        ?????? //97是小写的a的码值

        ?????? if(a >= (97+26)){

        ?????????? //如果大于,表示已经是y或者z了,减去26就回到a或者b了

        ?????????? a = a-26;

        ?????? }

        ?????? this.os.write(a);

        ??? }

        }

        测试一下看看,好用吗?客户端使用代码示例如下:

        public class Client {

        ??? public static void main(String[] args) throws Exception {

        ?????? //流式输出文件

        ?????? DataOutputStream dout = new DataOutputStream(

        ?????????? new BufferedOutputStream(

        ????????????? new EncryptOutputStream(

        ????????????????? new FileOutputStream("MyEncrypt.txt"))));

        ?????? //然后就可以输出内容了

        ?????? dout.write("abcdxyz".getBytes());

        ?????? dout.close();

        ??? }

        }

        运行一下,打开生成的文件,看看结果,结果示例如下:

        cdefzab

        很好,是不是被加密了,虽然是明文的,但已经不是最初存放的内容了,一切显得非常的完美。

        再试试看,不是说装饰器可以随意组合吗,换一个组合方式看看,比如把BufferedOutputStream和我们自己的装饰器在组合的时候换个位,示例如下:

        public class Client {

        ??? public static void main(String[] args) throws Exception {

        ?????? //流式输出文件

        ?????? DataOutputStream dout = new DataOutputStream(

        ?????????? new EncryptOutputStream (

        ????????????? new BufferedOutputStream(

        ????????????????? new FileOutputStream("MyEncrypt.txt"))));

        ?????? dout.write("abcdxyz".getBytes());

        ?????? dout.close();

        ??? }

        }

        再次运行,看看结果。坏了,出大问题了,这个时候输出的文件一片空白,什么都没有。这是哪里出了问题呢?

        要把这个问题搞清楚,就需要把上面I/O流的内部运行和基本实现搞明白,分开来看看具体的运行过程吧。

        (1)先看看成功输出流中的内容的写法的运行过程:

        • 当执行到“dout.write("abcdxyz".getBytes());”这句话的时候,会调用DataOutputStream的write方法,把数据输出到BufferedOutputStream中;
        • 由于BufferedOutputStream流是一个带缓存的流,它默认缓存8192byte,也就是默认流中的缓存数据到了8192byte,它才会自动输出缓存中的数据;
        • 而目前要输出的字节肯定不到8192byte,因此数据就被缓存在BufferedOutputStream流中了,而不会被自动输出
        • 当执行到“dout.close();”这句话的时候:会调用关闭DataOutputStream流,这会转调到传入DataOutputStream中的流的close方法,也就是BufferedOutputStream的close方法,而BufferedOutputStream的close方法继承自FilterOutputStream,在FilterOutputStream的close方法实现里面,会先调用输出流的方法flush,然后关闭流。也就是此时BufferedOutputStream流中缓存的数据会被强制输出;
        • BufferedOutputStream流中缓存的数据被强制输出到EncryptOutputStream流,也就是我们自己实现的流,没有缓存,经过处理后继续输出;
        • EncryptOutputStream流会把数据输出到FileOutputStream中,FileOutputStream会直接把数据输出到文件中,因此,这种实现方式会输出文件的内容。

          (2)再来看看不能输出流中的内容的写法的运行过程:

          • 当执行到“dout.write("abcdxyz".getBytes());”这句话的时候,会调用DataOutputStream的write方法,把数据输出到EncryptOutputStream中;
          • EncryptOutputStream流,也就是我们自己实现的流,没有缓存,经过处理后继续输出,把数据输出到BufferedOutputStream中;
          • 由于BufferedOutputStream流是一个带缓存的流,它默认缓存8192byte,也就是默认流中的缓存数据到了8192byte,它才会自动输出缓存中的数据;
          • 而目前要输出的字节肯定不到8192byte,因此数据就被缓存在BufferedOutputStream流中了,而不会被自动输出
          • 当执行到“dout.close();”这句话的时候:会调用关闭DataOutputStream流,这会转调到传入DataOutputStream流中的流的close方法,也就是EncryptOutputStream的close方法,而EncryptOutputStream的close方法继承自OutputStream,在OutputStream的close方法实现里面,是个空方法,什么都没有做。因此,这种实现方式没有flush流的数据,也就不会输出文件的内容,自然是一片空白了。

            3:自己实现的I/O流的装饰器——第二版

            ?????? 要让我们写的装饰器跟其它Java中的装饰器一样用,最合理的方案就应该是:让我们的装饰器继承装饰器的父类,也就是FilterOutputStream类,然后使用父类提供的功能来协助完成想要装饰的功能。示例代码如下:

            public class EncryptOutputStream2??extends?FilterOutputStream{

            ??? private OutputStream os = null;

            ??? public EncryptOutputStream2(OutputStream os){

            ?????? //调用父类的构造方法

            ?????? super(os);

            ??? }

            ??? public void write(int a) throws IOException {

            ?????? //先统一向后移动两位

            ?????? a = a+2;

            ?????? //97是小写的a的码值

            ?????? if(a >= (97+26)){

            ?????????? //如果大于,表示已经是y或者z了,减去26就回到a或者b了

            ?????????? a = a-26;

            ?????? }

            ?????? //调用父类的方法

            ?????? super.write(a);

            ??? }

            }

            再测试看看,是不是跟其它的装饰器一样,可以随便换位了呢?

            22.3.3? 装饰模式和AOP

            ?????? 装饰模式和AOP在思想上有共同之处。可能有些朋友还不太了解AOP,下面先简单介绍一下AOP的基础知识。

            1:什么是AOP——面向方面编程

            ???????AOP是一种编程范式,提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)

            ??? 在面向对象开发中,考虑系统的角度通常是纵向的,比如我们经常画出的如下的系统架构图,默认都是从上到下,上层依赖于下层,如图22.5所示:

            图22.5? 系统架构图示例图

            ?????? 而在每个模块内部呢?就拿大家都熟悉的三层架构来说,也是从上到下来考虑的,通常是表现层调用逻辑层,逻辑层调用数据层,如图22.6所示:

            ?装饰模式三

            图22.6? 三层架构示意图

            慢慢的,越来越多的人发现,在各个模块之中,存在一些共性的功能,比如日志管理、事务管理等等,如图22.7所示:

            图22.7? 共性功能示意图

            这个时候,在思考这些共性功能的时候,是从横向在思考问题,与通常面向对象的纵向思考角度不同,很明显,需要有新的解决方案,这个时候AOP站出来了。

            AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化。

            ??? AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

            ?????? AOP之所以强大,就是因为它能够自动把横切关注点的功能模块,自动织入回到软件系统中,这是什么意思呢?

            先看看没有AOP,在常规的面向对象系统中,对这种共性的功能如何处理,大都是把这些功能提炼出来,然后在需要用到的地方进行调用,只画调用通用日志的公共模块,其它的类似,就不去画了,如图22.8所示:

            ?装饰模式三

            图22.8? 调用公共功能示意图

            看清楚,是从应用模块中主动去调用公共模块,也就是应用模块要很清楚公共模块的功能,还有具体的调用方法才行,应用模块是依赖于公共模块的,是耦合的,这样一来,要想修改公共模块就会很困难了,牵一而发百。

            看看有了AOP会怎样,还是画个图来说明,如图22.9所示:

            ?装饰模式三

            图22.9? AOP的调用示意图

            乍一看,跟上面不用AOP没有什么区别嘛,真的吗?看得仔细点,有一个非常非常大的改变,就是所有的箭头方向反过来了,原来是应用系统主动去调用各个公共模块的,现在变成了各个公共模块主动织入回到应用系统。

            不要小看这一点变化,这样一来应用系统就不需要知道公共功能模块,也就是应用系统和公共功能解耦了。公共功能会在合适的时候,由外部织入回到应用系统中,至于谁来实现这样的功能,以及如何实现不再我们的讨论之列,我们更关注这个思想。

            如果按照装饰模式来对比上述过程,业务功能对象就可以被看作是被装饰的对象,而各个公共的模块就好比是装饰器,可以透明的来给业务功能对象增加功能。

            所以从某个侧面来说,装饰模式和AOP要实现的功能是类似的,只不过AOP的实现方法不同,会更加灵活,更加可配置;另外AOP一个更重要的变化是思想上的变化——“主从换位”,让原本主动调用的功能模块变成了被动等待,甚至毫不知情的情况下被织入了很多新的功能。

            2:使用装饰模式做出类似AOP的效果

            下面来演示一下使用装饰模式,把一些公共的功能,比如权限控制,日志记录,透明的添加回到业务功能模块中去,做出类似AOP的效果。

            (1)首先定义业务接口

            这个接口相当于装饰模式的Component。注意这里使用的是接口,而不像前面一样使用的是抽象类,虽然使用抽象类的方式来定义组件是装饰模式的标准实现方式,但是如果不需要为子类提供公共的功能的话,也是可以实现成接口的,这点要先说明一下,免得有些朋友会认为这就不是装饰模式了,示例代码如下:

            /**

            * 商品销售管理的业务接口

            */

            public interface GoodsSaleEbi {

            ??? /**

            ??? ?* 保存销售信息,本来销售数据应该是多条,太麻烦了,为了演示,简单点

            ??? ?* @param user 操作人员

            ??? ?* @param customer 客户

            ??? ?* @param saleModel 销售数据

            ??? ?* @return 是否保存成功

            ??? ?*/

            ??? public boolean sale(String user,String customer,

            SaleModel saleModel);

            }

            顺便把封装业务数据的对象也定义出来,很简单,示例代码如下:

            /**

            * 封装销售单的数据,简单的示意一些

            */

            public class SaleModel {

            ??? /**

            * 销售的商品

            */

            ??? private String goods;

            ??? /**

            * 销售的数量

            */

            ??? private int saleNum;

            ??? public String getGoods() {?

            return goods;

            }

            ??? public void setGoods(String goods) {

            this.goods = goods;?

            }

            ??? public int getSaleNum() {

            ?????? return saleNum;

            ??? }

            ??? public void setSaleNum(int saleNum) {

            ?????? this.saleNum = saleNum;

            ??? }

            ??? public String toString(){

            ?????? return "商品名称="+goods+",购买数量="+saleNum;

            ??? }

            }

            (2)定义基本的业务实现对象,示例代码如下:

            public class GoodsSaleEbo implements GoodsSaleEbi{

            ??? public boolean sale(String user,String customer,

            SaleModel saleModel) {

            ?????? System.out.println(user+"保存了"

            +customer+"购买 "+saleModel+" 的销售数据");

            ?????? return true;

            ??? }

            }

            (3)接下来该来实现公共功能了,把这些公共功能实现成为装饰器,那么需要给它们定义一个抽象的父类,示例如下:

            /**

            * 装饰器的接口,需要跟被装饰的对象实现同样的接口

            */

            public?abstract class Decorator implements GoodsSaleEbi{

            ??? /**

            * 持有被装饰的组件对象

            */

            ??? protected GoodsSaleEbi ebi;

            ??? /**

            ??? ?* 通过构造方法传入被装饰的对象

            ??? ?* @param ebi被装饰的对象

            ??? ?*/

            ??? public Decorator(GoodsSaleEbi ebi){

            ?????? this.ebi = ebi;

            ??? }

            }

            (4)实现权限控制的装饰器

            先检查是否有运行的权限,如果有就继续调用,如果没有,就不递归调用了,而是输出没有权限的提示,示例代码如下:

            /**

            ?* 实现权限控制

            ?*/

            public class CheckDecorator extends Decorator{

            ??? public CheckDecorator(GoodsSaleEbi ebi){

            ?????? super(ebi);

            ??? }

            ??? public boolean sale(String user,String customer

            , SaleModel saleModel) {

            ???????//简单点,只让张三执行这个功能

            ?????? if(!"张三".equals(user)){

            ?????????? System.out.println("对不起"+user

            +",你没有保存销售单的权限");

            ?????????? //就不再调用被装饰对象的功能了

            ?????????? return false;

            ?????? }else{

            ?????????? return this.ebi.sale(user, customer, saleModel);

            ??? ??? }??????

            ??? }

            }

            (5)实现日志记录的装饰器,就是在功能执行完成后记录日志即可,示例代码如下:

            /**

            * 实现日志记录

            */

            public class LogDecorator extends Decorator{

            ??? public LogDecorator(GoodsSaleEbi ebi){

            ?????? super(ebi);

            ??? }

            ??? public boolean sale(String user,String customer,

            SaleModel saleModel) {

            ?????? //执行业务功能

            ?????? boolean f = this.ebi.sale(user, customer, saleModel);

            ?

            ???????//在执行业务功能过后,记录日志

            ?????? DateFormat df =

            new?SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");

            ?????? System.out.println("日志记录:"+user+""+

            df.format(new Date())+"时保存了一条销售记录,客户是"

            +customer+",购买记录是"+saleModel);

            ?????? return f;

            ??? }

            }

            (6)组合使用这些装饰器

            在组合的时候,权限控制应该是最先被执行的,所以把它组合在最外面,日志记录的装饰器会先调用原始的业务对象,所以把日志记录的装饰器组合在中间。

            前面讲过,装饰器之间最好不要有顺序限制,但是在实际应用中,要根据具体的功能要求来,有需要的时候,也可以有顺序的限制,但应该尽量避免这种情况。

            此时客户端测试代码示例如下:

            public class Client {

            ??? public static void main(String[] args) {

            ?????? //得到业务接口,组合装饰器

            ?????? GoodsSaleEbi ebi = new CheckDecorator(

            new LogDecorator(

            new GoodsSaleEbo()));

            ?????? //准备测试数据

            ?????? SaleModel saleModel = new SaleModel();

            ?????? saleModel.setGoods("Moto手机");

            ?????? saleModel.setSaleNum(2);

            ?????? //调用业务功能

            ?????? ebi.sale("张三","张三丰", saleModel);

            ?????? ebi.sale("李四","张三丰", saleModel);

            ??? }

            }

            运行结果如下:

            张三保存了张三丰购买 商品名称=Moto手机,购买数量=2 的销售数据

            日志记录:张三于2010-02-12 16:38:56 730时保存了一条销售记录,客户是张三丰,购买记录是商品名称=Moto手机,购买数量=2

            ???

            ?

            ?


            对不起李四,你没有保存销售单的权限

            ?????? 好好体会一下,是不是也在没有惊动原始业务对象的情况下,给它织入了新的功能呢?也就是说是在原始业务不知情的情况下,给原始业务对象透明的增加了新功能,从而模拟实现了AOP的功能。

            事实上,这种做法,完全可以应用在项目开发上,在后期为项目的业务对象添加数据检查、权限控制、日志记录等功能,就不需要在业务对象上去处理这些功能了,业务对象可以更专注于具体业务的处理。

            22.3.4? 装饰模式的优缺点

            ps:原文转载私塾在线

热点排行