装饰模式是在不改变对象结构的情况下,动态的给该对象添加一些功能的模式。属于结构型模式。
- 1,不改变原类文件。
- 2,不使用继承。
- 3,动态扩展。
- 需要在运行时动态的给一个对象增加额外的职责时候
- 需要给一个现有的类增加职责,但是又不想通过继承的方式来实现的时候(应该优先使用组合而非继承),或者通过继承的方式不现实的时候(可能由于排列组合产生类爆炸的问题)。
使用 Java I/O 的时候总是有各种输入流、输出流、字符流、字节流、过滤流、缓冲流等等各种各样的流,因为整个Java IO体系都是基于字符流(InputStream/OutputStream) 和 字节流(Reader/Writer)作为基类,加上各种功能进行装饰的。
对ServletRequest进行了包装,ServletRequestWrapper 是第一层包装,HttpServletRequestWrapper 通过继承进行包装,增加了 HTTP 相关的功能,SessionRepositoryRequestWrapper 又通过继承进行包装,增加了 Session 相关的功能
制作咖啡为例,咖啡制作过程是动态变化的。
例如有的需要原味咖啡,有的需要加奶咖啡,有的需要加糖咖啡,而有的需要先加奶再加糖咖啡,而有的需要先加糖再加奶的咖啡,。。。
这是一个排列组合问题
首先我们有一个ICoffee接口,里面有一个制作咖啡的接口方法makeCoffee()。要进行装饰的类 OriginalCoffee 和装饰者基类CoffeeDecorator(一般为抽象类)实现了此接口。CoffeeDecorator类里面持有一个ICoffee引用,我们第一步会把要装饰那个原始对象赋值给这个引用,那样在装饰者类中才可以调用到那个被装饰的对象的方法。MilkDecorator 和SugarDecorator 都继承至CoffeeDecorator, 都是具体的装饰者类。
public interface ICoffee {
void makeCoffee();
}
此处为原味咖啡对象,它实现了ICoffee接口。
public class OriginalCoffee implements ICoffee {
@Override
public void makeCoffee() {
System.out.print("原味咖啡 ");
}
}
它要实现与原始对象相同的接口ICoffee,其内部持有一个ICoffee类型的引用,用来接收被装饰的对象
public abstract class CoffeeDecorator implements ICoffee {
private ICoffee coffee;
public CoffeeDecorator(ICoffee coffee){
this.coffee=coffee;
}
@Override
public void makeCoffee() {
coffee.makeCoffee();
}
}
他们都继承至装饰者基类 CoffeeDecorator。此处生成了两个,一个是加奶的装饰者,另一个是加糖的装饰者。
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(ICoffee coffee) {
super(coffee);
}
@Override
public void makeCoffee() {
super.makeCoffee();
addMilk();
}
private void addMilk(){
System.out.print("加奶 ");
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(ICoffee coffee) {
super(coffee);
}
@Override
public void makeCoffee() {
super.makeCoffee();
addSugar();
}
private void addSugar(){
System.out.print("加糖");
}
}
public static void main(String[] args) {
//原味咖啡
ICoffee coffee=new OriginalCoffee();
coffee.makeCoffee();
System.out.println("");
//加奶的咖啡
coffee=new MilkDecorator(coffee);
coffee.makeCoffee();
System.out.println("");
//先加奶后加糖的咖啡
coffee=new SugarDecorator(coffee);
coffee.makeCoffee();
}
- 采用装饰者模式扩展对象的功能比采用继承更加灵活
- 可以设计出多个不同的具体装饰者,创造出不同行为的组合
- 装饰者模式增加了许多子类,如果过度使用会使得程序变得很复杂。
那么我们什么时候使用这个模式呢?
定义一个包装类,用于包装不兼容接口的对象
包装类 = 适配器Adapter;
被包装对象 = 适配者Adaptee = 被适配的类
把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。
适配器模式的形式分为:类的适配器模式 & 对象的适配器模式
Java语言的数据库连接工具JDBC,JDBC给出一个客户端通用的抽象接口,每一个具体数据库引擎(如SQL Server、Oracle、MySQL等)的JDBC驱动软件都是一个介于JDBC接口和数据库引擎接口之间的适配器软件
如果去美国,我们随身带的电器是无法直接使用的,因为美国的插座标准和中国不同,所以,我们需要一个适配器: 实现
public interface Target {
//客户端需要请求处理的方法
public void request();
}
//源接口(已经存在的接口)
//需要被转换的对象
//这个接口需要重新配置以适应目标接口
public class Adaptee {
public void specifiRequest() {
System.out.println("源接口对象调用源接口中的方法");
}
}
public class Adapter implements Target {
//持有源接口对象
private Adaptee adaptee;
/**
* 构造方法,传入需要被适配的对象
* @param adaptee
*/
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
/**
* 重写目标接口的方法,以适应客户端的需求
*/
@Override
public void request() {
//调用源接口的方法
System.out.println("适配器包装源接口对象,调用源接口的方法");
adaptee.specifiRequest();
}
}
public class Client {
public static void main(String[] args){
//创建源对象(被适配的对象)
Adaptee adaptee = new Adaptee();
//利用源对象对象一个适配器对象,提供客户端调用的方法
Adapter adapter = new Adapter(adaptee);
System.out.println("客户端调用适配器中的方法");
adapter.request();
}
}
目标类和适配者类解耦,增加了类的透明性和复用性,同时系统的灵活性和扩展性都非常好,更换适配器或者增加新的适配器都非常方便,符合“开闭原则”
过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现
- 持有的是待适配的对象,实现的是目标接口,两个不一样
- 扩展了待适配类新的功能
- 适配器模式拓展了新的功能
- 持有对象和继承的对象一般是同一个类或接口
- 装饰模式中装饰过的类力求与原来对外接口一致,这使得在调用方看来,装饰过后的类与之前没有什么区别,只是某一方面功能增强了
- 装饰模式增强了原有功能