Java IO 装饰者模式

简介: 装饰模式(Decorator)   装饰模式又名包装(Wrapper)模式。   装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。   装饰模式通过创建一个包装对象,也就是装饰,来包裹真实的对象。

 装饰模式(Decorator)

  装饰模式又名包装(Wrapper)模式

  装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

  装饰模式通过创建一个包装对象,也就是装饰,来包裹真实的对象。

  装饰模式以对客户端透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。

  装饰模式可以在不创造更多子类的情况下,将对象的功能加以扩展。

  装饰模式把客户端的调用委派到被装饰类。装饰模式的关键在于这种扩展是完全透明的。

 装饰模式的角色

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

  具体构件角色(Concrete Component):定义将要接收附加责任的类。

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

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

 Java IO中的装饰模式

  在IO中,具体构件角色是节点流,装饰角色是过滤流

  FilterInputStream和FilterOutputStream是装饰角色,而其他派生自它们的类则是具体装饰角色。

 装饰模式的特点

  装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。

  装饰对象包含一个真实对象的引用(reference)。

  装饰对象接收所有来自客户端的请求,它把这些请求转发给真实的对象。

  装饰对象可以在转发这些请求之前或之后附加一些功能。

  这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。

 程序实例

public interface Component{
        public void doSomething();
}

这是抽象构件角色,是一个接口。具体构件角色实现这个接口:

 
public class ConcreteComponent implements Component{ 
        @Override 
         public void doSomething() { 
                        System.out.println("功能A");
         }
  }

装饰角色:

public class Decorator implements Component{
                             private Component component;
                             public Decorator(Component component) { 
                                         this.component = component; 
                                    } 
                             @Override 
                             public void doSomething() {
                                                component.doSomething(); 
                                }    
}

 

其中包含了构件角色的引用,方法调用中利用构件角色的方法。

具体装饰角色(两个):

public class ConcreteDecorator1 extends Decorator{ 
                    public ConcreteDecorator1(Component component) {
                                super(component);
                    } 
                @Override
                 public void doSomething() { 
                            super.doSomething(); 
                            this.doAnotherThing(); 
                    }
                 private void doAnotherThing() { 
                            System.out.println("功能B"); 
                    }
}
 
public class ConcreteDecorator2 extends Decorator{ 
                 public ConcreteDecorator2(Component component) { 
                             super(component); 
                     }
                 @Override 
                 public void doSomething() {
                         super.doSomething();
                         this.doAnotherThing();
                 }
                 private void doAnotherThing() {
                         System.out.println("功能C"); 
                }
}

 使用测试:

public class Client{
         public static void main(String[] args) {
                     Component component = new ConcreteComponent();
                     Component component1 = new ConcreteDecorator1(component);
                     component1.doSomething();
                     System.out.println("-----------");
                     Component component2 = new ConcreteDecorator2(component1); 
                    component2.doSomething(); 
         }
}

问题引入

  咖啡店的类设计:

  一个饮料基类,各种饮料类继承这个基类,并且计算各自的价钱。

  饮料中需要加入各种调料,考虑在基类中加入一些布尔值变量代表是否加入各种调料,基类的cost()中的计算各种调料的价钱,子类覆盖cost(),并且在其中调用超类的cost(),加上特定饮料的价钱,计算出子类特定饮料的价钱。

  缺点:类数量爆炸、基类加入的新功能并不适用于所有的子类、调料价钱的改变、新调料的出现都会要求改变现有代码;有的子类并不适合某些调料等情况……

 设计原则

  类应该对扩展开放,对修改关闭。

  我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。

  如能实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。

  要让OO设计同时具备开放性和关闭性,不是一件容易的事,通常来说,没有必要把设计的每个部分都这么设计。

  遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度。

  我们需要把注意力集中在设计中最有可能改变的地方,然后应用开放-关闭原则。

 用装饰者模式解决问题

  解决咖啡店饮料问题的方法:

  以饮料为主体,然后在运行时以调料来“装饰”饮料。

  比如,顾客想要摩卡(Mocha)和奶泡(Whip)深焙咖啡(DarkRoast):

  DarkRoast继承自Beverage,有一个cost()方法。

  第一步,以DarkRoast对象开始;

  第二步,顾客想要摩卡,所以建立一个Mocha装饰者对象,并用它将DarkRoast对象包装(wrap)起来;

  第三步,顾客想要奶泡,所以建立一个Whip装饰者对象,并用它将Mocha对象包起来;(Mocha和Whip也继承自Beverage,有一个cost()方法);

  最后,为顾客算钱,通过调用最外圈装饰者(Whip)的cost()就可以。Whip()的cost()会先委托它装饰的对象(Mocha)计算出价钱,然后在加上奶泡的价钱。Mocha的cost()也是类似。

 装饰者模式的特点

  装饰者和被装饰对象有相同的超类型

  可以用一个或多个装饰者包装一个对象。

  因为装饰者和被装饰者具有相同的类型,所以任何需要原始对象的场合,可以用装饰过的对象代替。

  装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。

  对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。

 装饰者模式的定义

  装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

 装饰者模式的实现

  实现类图如下:

2013-1-3 星期四 22-08-13

 

  装饰者和被装饰者具有共同的超类,利用继承达到“类型匹配”,而不是利用继承获得“行为”;将装饰者和被装饰者组合时,加入新的行为。

   解决本文中饮料的具体问题时,图中Component即为Beverage(可以是抽象类或者接口),而ConcreteComponent为各种饮 料,Decorator(抽象装饰者)为调料的抽象类或接口,ConcreteDecoratorX则为各种具体的调料。

  因为使用对象组合,可以把饮料和调料更有弹性地加以混合与匹配。

  代码外部细节:

  代码中实现的时候,通过构造函数将被装饰者传入装饰者中即可,如最后的调用形式如下:

   

 Beverage beverage = new DarkRoast();

    beverage = new Mocha(beverage);

    beverage = new Whip(beverage);

 即完成了两层包装,此时再调用beverage的cost()函数即可得到总价。

 java.io包内的装饰者模式

2013-1-3 星期四 22-27-07

  装饰者模式的缺点:在设计中加入大量的小类,如果过度使用,会让程序变得复杂。

作者: intsmaze(刘洋)
老铁,你的--->推荐,--->关注,--->评论--->是我继续写作的动力。
微信公众号号:Apache技术研究院
由于博主能力有限,文中可能存在描述不正确,欢迎指正、补充!
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
2月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
86 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
3月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
2月前
|
Java 数据处理 开发者
揭秘Java IO流:字节流与字符流的神秘面纱!
揭秘Java IO流:字节流与字符流的神秘面纱!
39 1
|
2月前
|
自然语言处理 Java 数据处理
Java IO流全解析:字节流和字符流的区别与联系!
Java IO流全解析:字节流和字符流的区别与联系!
95 1
|
2月前
|
Java
Java 中 IO 流的分类详解
【10月更文挑战第10天】不同类型的 IO 流具有不同的特点和适用场景,我们可以根据具体的需求选择合适的流来进行数据的输入和输出操作。在实际应用中,还可以通过组合使用多种流来实现更复杂的功能。
58 0
|
3月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
220 12
|
2月前
|
存储 Java 程序员
【Java】文件IO
【Java】文件IO
38 0
|
3月前
|
数据采集 Java 数据挖掘
Java IO异常处理:在Web爬虫开发中的实践
Java IO异常处理:在Web爬虫开发中的实践
|
4月前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
50 2
|
4月前
|
NoSQL Java Redis
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常