装饰模式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");
??? }
}
?????? 对象组合是不是也很简单,而且更灵活了:
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");
??? }
}
?????? 最后再说一点,就是关于对象组合中,何时创建被组合对象的实例:
示例如下:
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)退化形式
如果仅仅只是想要添加一个功能,就没有必要再设计装饰器的抽象类了,直接在装饰器里面实现跟组件一样的接口,然后实现相应的装饰功能就可以了。但是建议最好还是设计上装饰器的抽象类,这样有利于程序的扩展。
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部分对象层次图
?????? 查看上图会发现,它的结构和装饰模式的结构几乎是一样的:
?????? 同样的,输出流部分也类似,就不去赘述了。
?????? 既然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)先看看成功输出流中的内容的写法的运行过程:
(2)再来看看不能输出流中的内容的写法的运行过程:
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);
??? }
}
再测试看看,是不是跟其它的装饰器一样,可以随便换位了呢?
?????? 装饰模式和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的功能。
事实上,这种做法,完全可以应用在项目开发上,在后期为项目的业务对象添加数据检查、权限控制、日志记录等功能,就不需要在业务对象上去处理这些功能了,业务对象可以更专注于具体业务的处理。
ps:原文转载私塾在线