设计模式-装饰模式

£可爱£侵袭症+ 提交于 2020-02-27 01:15:33

一、简介

1、概念
装饰模式又名包装模式,对客户端以透明的方式扩展对象的功能,是继承关系的一个替代方案。
但是大部分装饰模式都是半透明的<介于装饰模式和适配器模式直接>,允许装饰模式改变接口,增加方法。

2、应用场景
动态地给一个对象添加一些额外的职责。装饰模式相比继承更为灵活。不改变接口的前提下,增强所考虑的类的性能。
1)需要扩展一个类的功能,或给一个类增加附加责任。
2)需要动态的给一个对象增加功能,这些功能可以再动态地撤销。
3)需要增加一些基本功能的排列组合而产生的非常大量的功能,从而使继承变得不现实。

3、角色
l 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象
l 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类
l 装饰角色(Decorator):持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口
l 具体装饰角色(ConcreteDecorator):负责给构件对象“贴上”附加的责任

二、举例说明

 咖啡是一种饮料,咖啡的本质是咖啡豆+水磨出来的。咖啡店现在要卖各种口味的咖啡,如果不使用装饰模式,那么在销售系统中,各种不一样的咖啡都要产生一个类,如果有4中咖啡豆<构件>,5种口味<装饰>,那么将至少20个类。使用了装饰模式,只需要11个类即可生产任意口味咖啡(包括混合口味)。
1、类图

2、源码

/**
 * 抽象构件
 * @author Administrator
 *
 */
public interface Beverage {
	//返回商品描述
	public String getDescription();
	//返回价格
	public double getPrice();
}

CoffeeBean1——具体构件1

public class CoffeeBean1 implements Beverage {
	private String description = "选了第一种咖啡豆";
	@Override
	public String getDescription() {
		return description;
	}
	@Override
	public double getPrice() {
		return 50;
	}
 
}

CoffeeBean2——具体构件2

public class CoffeeBean2 implements Beverage {
	private String description = "第二种咖啡豆!";
	@Override
	public String getDescription() {
		return description;
	}
 
	@Override
	public double getPrice() {
		return 100;
	}
 
}
//装饰角色  与抽象构件接口一致

public class Decorator implements Beverage {
	private String description = "我只是装饰器,不知道具体的描述";
	@Override
	public String getDescription() {
		return description;
	}
	@Override
	public double getPrice() {
		return 0;		//价格由子类来决定
	}
 
}

Milk——具体装饰类1

public class Milk extends Decorator{
	private String description = "加了牛奶!";
	private Beverage beverage = null;
	public Milk(Beverage beverage){
		this.beverage = beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+"\n"+description;
	}
	public double getPrice(){
		return beverage.getPrice()+20;	//20表示牛奶的价格
	}
}

Mocha——具体装饰类2

public class Mocha extends Decorator {
	private String description = "加了摩卡!";
	private Beverage beverage = null;
	public Mocha(Beverage beverage){
		this.beverage = beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+"\n"+description;
	}
	public double getPrice(){
		return beverage.getPrice()+49;	//30表示摩卡的价格
	}
}

Soy——具体装饰类3

public class Soy extends Decorator {
	private String description = "加了豆浆!";
	private Beverage beverage = null;
	public Soy(Beverage beverage){
		this.beverage = beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+"\n"+description;
	}
	public double getPrice(){
		return beverage.getPrice()+30;	//30表示豆浆的价格
	}
}

测试类

public class Test {
 
	public static void main(String[] args) {
		Beverage beverage = new CoffeeBean1();	//选择了第一种咖啡豆磨制的咖啡    抽象构件实现1-咖啡豆1
		beverage = new Mocha(beverage);		//为咖啡加了摩卡                    第一种装饰-摩卡
		beverage = new Milk(beverage);                                        第二种装饰-牛奶
		System.out.println(beverage.getDescription()+"\n加了摩卡和牛奶的咖啡价格:"+beverage.getPrice());
		
	}
}

结果:

三、装饰模式与适配器模式

1、相同点:都是“包装模式”,通过封装其他对象达到设计的目的;
2、不同点:理想的装饰模式在对被装饰对象进行功能增强的同时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。
                    而适配器模式并不要求对源对象的功能进行增强,但是会改变源对象的接口,以便和目标接口相符合。

四、装饰模式的透明性

装饰模式分为透明和半透明两种。区别就在于装饰角色接口与抽象构件角色的接口是否完全一致。
1、透明装饰模式<理想化>:要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致;
透明性要求:   装饰模式对客户端的透明性要求程序不要声明一个具体构件类型的变量,而应当声明一个抽象构件类型的变量。

//正确
Beverage beverage = new CoffeeBean1();
Beverage mochaBeverage = new Mocha(beverage);

//错误
Beverage beverage = new CoffeeBean1();
Mocha mochaBeverage = new Mocha(beverage);


2、半透明装饰模式:装饰角色接口与抽象构件角色的接口不完全一致,装饰角色实际上已经成了一个适配器角色;

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

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

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

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

五、优缺点

优点
1、对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
2、可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。
3、可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
4、具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合 “开闭原则”。

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

六、典型应用

1、Java I/O中的装饰者模式
使用 Java I/O 的时候总是有各种输入流、输出流、字符流、字节流、过滤流、缓冲流等等各种各样的流,这里用到了装饰模式。

由上图可知在Java中应用程序通过输入流(InputStream)的Read方法从源地址处读取字节,然后通过输出流(OutputStream)的Write方法将流写入到目的地址。
流的来源主要有三种:本地的文件(File)、控制台、通过socket实现的网络通信。

由上图可以看出只要继承了FilterInputStream的类就是装饰者类,可以用于包装其他的流,装饰者类还可以对装饰者和类进行再包装。

2、spring cache 中的装饰者模式

public class TransactionAwareCacheDecorator implements Cache {
    private final Cache targetCache;
    
    public TransactionAwareCacheDecorator(Cache targetCache) {
        Assert.notNull(targetCache, "Target Cache must not be null");
        this.targetCache = targetCache;
    }
    
    public <T> T get(Object key, Class<T> type) {
        return this.targetCache.get(key, type);
    }

    public void put(final Object key, final Object value) {
        // 判断是否开启了事务
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            // 将操作注册到 afterCommit 阶段
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                public void afterCommit() {
                    TransactionAwareCacheDecorator.this.targetCache.put(key, value);
                }
            });
        } else {
            this.targetCache.put(key, value);
        }
    }
    // ...省略...
}

该类实现了 Cache 接口,同时将 Cache 组合到类中成为了成员属性 targetCache,所以可以大胆猜测 TransactionAwareCacheDecorator 是一个装饰类。

该类的主要功能:通过 Spring 的 TransactionSynchronizationManager 将其 put/evict/clear 操作与 Spring 管理的事务同步,仅在成功的事务的 after-commit 阶段执行实际的缓存 put/evict/clear 操作。如果没有事务是 active 的,将立即执行 put/evict/clear 操作。

3、spring session 中的装饰者模式

public class ServletRequestWrapper implements ServletRequest {
    private ServletRequest request;
    
    public ServletRequestWrapper(ServletRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("Request cannot be null");
        }
        this.request = request;
    }
    
    @Override
    public Object getAttribute(String name) {
        return this.request.getAttribute(name);
    }
    //...省略...
}    

可以看到该类对 ServletRequest 进行了包装,这里是一个装饰者模式,再看下图,spring session 中 SessionRepositoryFilter 的一个内部类 SessionRepositoryRequestWrapper 与 ServletRequestWrapper 的关系。

可见 ServletRequestWrapper 是第一层包装,HttpServletRequestWrapper 通过继承进行包装,增加了 HTTP 相关的功能,SessionRepositoryRequestWrapper 又通过继承进行包装,增加了 Session 相关的功能。

4、Mybatis 缓存中的装饰者模式

org.apache.ibatis.cache 包的文件结构如下所示

Cache 为抽象构件类,PerpetualCache 为具体构件类,decorators 包下的类为装饰类,没有抽象装饰类;

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!