装饰者模以及在JDK和Mybatis中使用

一、传统开发

1.1、介绍

有一个卖煎饼的店铺找上了你,希望你能给她们的店铺开发一个收银系统,已知一个煎饼的价格是8元,一个鸡蛋的价格是1元,一根香肠的价格是2元。

1.2、 代码如下:

1.2.1、煎饼类

package com.aop8.deginpattern0.decorator;

/**
 * 煎饼类
 */
public class Battercake {

	protected String getDesc() {
		return "煎饼";
	}
	
	protected int cost() {
		return 8;
	}
}

1.2.2、加鸡蛋的煎饼

package com.aop8.deginpattern0.decorator;

/**
 * 加鸡蛋的煎饼
 */
public class BattercakeWidthEgg extends Battercake {

	@Override
	public String getDesc() {
		return super.getDesc()+ " ,加一个鸡蛋";
	}
	
	@Override
	public int cost() {
		return super.cost()+1;
	}
}

1.2.3、加鸡蛋和香肠的煎饼

package com.aop8.deginpattern0.decorator;

/**
 * 加鸡蛋和香肠的煎饼
 */
public class BattercakeWidthEggSausage extends BattercakeWidthEgg {

	@Override
	public String getDesc() {
		return super.getDesc()+ " ,加一根香肠";
	}
	
	@Override
	public int cost() {
		return super.cost()+2;
	}
}

1.2.4、测试:

package com.aop8.deginpattern0.decorator;

/**
 * 测试类
 */
public class Test {

	public static void main(String[] args) {

		//煎饼的价格是8
		Battercake battercake=new Battercake();
		System.out.println(battercake.getDesc()+" 的价格是 "+battercake.cost());
		
		//煎饼加一个鸡蛋 的价格是9
		Battercake battercakeWithEgg=new BattercakeWidthEgg();
		System.out.println(battercakeWithEgg.getDesc()+" 的价格是 "+battercakeWithEgg.cost());
	
		//煎饼加一个鸡蛋,加一根香肠 的价格是11
		Battercake battercakeWithEggSausage=new BattercakeWidthEggSausage();
		System.out.println(battercakeWithEggSausage.getDesc()+" 的价格是 "+battercakeWithEggSausage.cost());
	}
}

运行结果是

煎饼 的价格是 8
煎饼 ,加一个鸡蛋 的价格是 9
煎饼 ,加一个鸡蛋 ,加一根香肠 的价格是 11

1.3、问题来了:

这样会造成一个问题,煎饼的搭配种类很多。比如,加1根香肠的煎饼,加2个鸡蛋的煎饼,加2个鸡蛋和1根香肠的煎饼,如果对每一种可能都写一个实现,会造成类爆炸。

这个时候你就应该想到用装饰者模式了。来看看如何改造上面的代码。

二、装饰者模式

2.1、代码实现:

2.1.1、组件类 – 煎饼

package com.aop8.deginpattern.decorator;

/**
 * 组件类 -- 煎饼
 */
public abstract class ABattercake {
 
	protected abstract String getDesc();
	protected abstract int cost();
	
}

2.1.2、具体组件类的实现类 – 煎饼

package com.aop8.deginpattern.decorator;

/**
 * 具体组件类的实现类 -- 煎饼
 */
public  class Battercake extends ABattercake {

	@Override
	protected String getDesc() {
		return "煎饼";
	}

	@Override
	protected int cost() {
		return 8;
	}
}

2.1.3、抽像 装饰器类

package com.aop8.deginpattern.decorator;

/**
 * 抽像 装饰器类
 */
public abstract class AbstractDecorator extends ABattercake {
 
	
	private ABattercake aBattercake;
	
	public AbstractDecorator( ABattercake aBattercake) {
		this.aBattercake=aBattercake;
	}

	protected String getDesc() {
		return this.aBattercake.getDesc();
	}

	protected int cost() {
		return this.aBattercake.cost();
	}
}

2.1.4、具体的装饰器实现类 – 加一个鸡蛋

package com.aop8.deginpattern.decorator;

/**
 * 具体的装饰器实现类 —— 加一个鸡蛋
 */
public class EggDecorator extends AbstractDecorator {
 
	public EggDecorator(ABattercake aBattercake) {
		super(aBattercake);
	}

	@Override
	protected String getDesc() {
		return super.getDesc()+" ,加一个鸡蛋";
	}

	@Override
	protected int cost() {
		return super.cost()+1;
	}
}

2.1.5、具体的装饰器实现类 – 加一根香肠

package com.aop8.deginpattern.decorator;

/**
 * 具体的装饰器实现类 -- 加一根香肠
 */
public class SausageDecorator extends AbstractDecorator {
	
	public SausageDecorator(ABattercake aBattercake) {
		super(aBattercake);
	}

	@Override
	protected String getDesc() {
		return super.getDesc()+" ,加一个香肠";
	}

	@Override
	protected int cost() {
		return super.cost() +2;
	}
}

2.1.6、测试类

如果有人想买加2个鸡蛋和1根香肠的煎饼,实现方式如下

package com.aop8.deginpattern.decorator;

/**
 * 测试类
 */
public class Test {

	public static void main(String[] args) {

		ABattercake aBattercake = new Battercake();
		aBattercake = new EggDecorator(aBattercake);
		aBattercake = new EggDecorator(aBattercake);
		aBattercake = new SausageDecorator(aBattercake);

		// 煎饼 加一个鸡蛋 加一个鸡蛋 加一根香肠 的价格为 12
		System.out.println(aBattercake.getDesc() + "的价格是 " + aBattercake.cost());
	}
}

运行结果:

煎饼 ,加一个鸡蛋 ,加一个鸡蛋 ,加一个香肠的价格是 12

可以看到当要添加新的功能时,我们可以使用继承,在子类中添加新能的扩展实现。

装饰者模式 和 继承 的区别:
有些类是被 final 修饰的,继承是不可行的。而且待添加的新功能存在多种组合,使用继承的方式会导致大量子类的的出现。
装饰者模式 则是通过组合的方式来替代继承,为对象添加功能 。

三、分析 —— UML图

上述代码的UML图 :

根据 上图 ,我们可以画出 装饰者模式 的UML图,如下:

Component(组件) :组件接口或抽象类定义了全部组件实现类以及所有装饰器实现的行为。

ConcreteComponent(具体组件实现类) :具体组件实现类实现了 Component 接口 或 抽象类。通常情况下,具体组件实现类就是被装饰器装饰的原始对象,该类提供了 Component 接口中定义的最基本的功能,其他高级功能或后序添加的新功能,都是通过装饰器的方式添加到该类的对象之上的。

Decorator(抽象装饰器) :所有装饰器的父类,它是一个实现了 Component 接口的类,并在其中封装了一个 Component 对象,也就是被装饰的对象。而这个被装饰的对象只要是 Component 类型即可,这就实现了装饰器的组合和复用

ConcreteDecorator(具体的装饰器) :该实现类要向被装饰对象添加某些功能 。

四、JDK java.io 包中 装饰者模式 的使用

4.1、IO 流体系


java.io

从上图可以看出,

  • InputStream 是组件,FileInputStream,ByteArrayInputStream是具体组件实现类,
  • FilterInputStream是抽象装饰器,DataInputStream、LineInputStream、BufferedInputStream 是具体的装饰器。

InputStream 和 OutputStream,Reade r和 Writer 体系都用到了 装饰者模式,不再概述。

举个例子,我们进行IO操作时,经常写如下代码,你是否意识到这个用到了装饰者模式呢?

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("D:/test.txt")));

当我们意识到这个用到 装饰器模式 时,想增加新功能时,就直接查找是否有相应的具体装饰器即可,或者自己实现一个装饰器,而不是陷入迷茫。

4.2、举个例子

我们想把从文件中读入的内容都转为小写时,只要自己继承 FilterInputStream ,实现相应的功能即可:

4.2.1 、LowerCaseInputStream (实现转换小写的类)

package com.aop8.deginpattern.decorator;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class LowerCaseInputStream extends FilterInputStream {

	protected LowerCaseInputStream(InputStream in) {
		super(in);
	}

	@Override
	public int read() throws IOException {
		int c = super.read();
		return (c == -1 ? -1 : Character.toLowerCase((char) c));
	}

	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		int result = super.read(b, off, len);
		for (int i = off; i <= off + result; i++) {
			b[i] = (byte) Character.toLowerCase((char) b[i]);
		}

		return result;
	}

}

D:/test.txt 的文件内容如下

THIS is JUST for TEST

4.2.2、测试类

package com.aop8.deginpattern.decorator;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;

public class InputTest {

	public static void main(String[] args) {

		int c;
		try {
			InputStream in =new LowerCaseInputStream(new BufferedInputStream(
						new FileInputStream("d:/test.txt")));
			
			while((c=in.read())>=0) {
				System.out.print((char)c);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

运行结果:

this is just for test

五、Mybatis缓存模块

Mybatis的缓存模块中,使用了 装饰器模式 的变体,其中将 Decorator 接口 和 Componet 接口 合并为一个Component接口,类间结构如下 :

Mybatis的Cache接口就是上图中的 Component

public interface Cache {
	// 省略一部分方法
	String getId();

	void putObject(Object key, Object value);

	Object getObject(Object key);

	Object removeObject(Object key);
}

看一下Cache接口的实现类

仔细看包名,由包名就可以看到PerpetualCache扮演着ConcreteComponent(具体组件实现类)的角色,其余的都是装饰类,为什么要弄这么多装饰类呢?

举个例子,我们可以在二级缓存中配置缓存回收策略。

可配置的选项有

LRU:最近最少使用,移除最长时间不被使用的对象

FIFO:先进先出,按对象进入缓存的顺序来移除它们

SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象

WEAK:弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象

再看上面的装饰类和这个配置选项的名字是不是很类似,Mybatis根据你配置的缓存回收策略来选择相应的装饰类,完成扩展功能。

转载:https://www.toutiao.com/a6667000470260695565

猜你喜欢

转载自blog.csdn.net/xiaojin21cen/article/details/88399659