访问者模式实例
① java开发中的23种设计模式详解(转)_Java开发模式
设计模式(Design Patterns)
——可复用面向对象软件的基础
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程猜模唤化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
一、设计模式的分类
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、码敬外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:
二、设计模式的六大原则
1、开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。
里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。
LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Bai 网络
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
5、迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量穗凯少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
三、Java的23中设计模式
从这一块开始,我们详细介绍Java中23种设计模式的概念,应用场景等情况,并结合他们的特点及设计模式的原则进行分析。
1、工厂方法模式(Factory Method)
工厂方法模式分为三种:
11、普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。首先看下关系图:
举例如下:(我们举一个发送邮件和短信的例子)
首先,创建二者的共同接口:
[java]view plain publicinterfaceSender{publicvoidSend();}
其次,创建实现类:
[java]view plain {@OverridepublicvoidSend(){System.out.println("thisismailsender!");}} [java]view plain {@OverridepublicvoidSend(){System.out.println("thisissmssender!");}}
最后,建工厂类:
[java]view plain publicclassSendFactory{publicSenderproce(Stringtype){if("mail".equals(type)){returnnewMailSender();}elseif("sms".equals(type)){returnnewSmsSender();}else{System.out.println("请输入正确的类型!");returnnull;}}}
我们来测试下:
publicclassFactoryTest{publicstaticvoidmain(String[]args){SendFactoryfactory=newSendFactory();Sendersender=factory.proce("sms");sender.Send();}}
输出:this is sms sender!
22、多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。关系图:
将上面的代码做下修改,改动下SendFactory类就行,如下:
[java]view plainpublicclassSendFactory{publicSenderproceMail(){ returnnewMailSender();}publicSenderproceSms(){returnnewSmsSender();}}
测试类如下:
[java]view plain publicclassFactoryTest{publicstaticvoidmain(String[]args){SendFactoryfactory=newSendFactory();Sendersender=factory.proceMail();sender.Send();}}
输出:this is mailsender!
33、静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。
[java]view plain publicclassSendFactory{publicstaticSenderproceMail(){returnnewMailSender();}publicstaticSenderproceSms(){returnnewSmsSender();}} [java]view plain publicclassFactoryTest{publicstaticvoidmain(String[]args){Sendersender=SendFactory.proceMail();sender.Send();}}
输出:this is mailsender!
总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。
2、抽象工厂模式(Abstract Factory)
工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。因为抽象工厂不太好理解,我们先看看图,然后就和代码,就比较容易理解。
请看例子:
[java]view plain publicinterfaceSender{publicvoidSend();}
两个实现类:
[java]view plain {@OverridepublicvoidSend(){System.out.println("thisismailsender!");}} [java]view plain {@OverridepublicvoidSend(){System.out.println("thisissmssender!");}}
两个工厂类:
[java]view plain {@OverridepublicSenderproce(){returnnewMailSender();}} [java]view plain {@OverridepublicSenderproce(){returnnewSmsSender();}}
在提供一个接口:
[java]view plain publicinterfaceProvider{publicSenderproce();}
测试类:
[java]view plain publicclassTest{publicstaticvoidmain(String[]args){Providerprovider=newSendMailFactory();Sendersender=provider.proce();sender.Send();}}
其实这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好!
3、单例模式(Singleton)
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
首先我们写一个简单的单例类:
[java]view plain publicclassSingleton{/*持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载*/=null;/*私有构造方法,防止被实例化*/privateSingleton(){}/*静态工程方法,创建实例*/(){if(instance==null){instance=newSingleton();}returninstance;}/*如果该对象被用于序列化,可以保证对象在序列化前后保持一致*/publicObjectreadResolve(){returninstance;}}
这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对getInstance方法加synchronized关键字,如下:
[java]view plain (){if(instance==null){instance=newSingleton();}returninstance;}
但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:
[java]view plain (){if(instance==null){synchronized(instance){if(instance==null){instance=newSingleton();}}}returninstance;}
似乎解决了之前提到的问题,将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的,看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:
a>A、B线程同时进入了第一个if判断
b>A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
所以程序还是有可能发生错误,其实程序在运行过程是很复杂的,从这点我们就可以看出,尤其是在写多线程环境下的程序更有难度,有挑战性。我们对该程序做进一步优化:
[java]view plain {=newSingleton();}(){returnSingletonFactory.instance;}
实际情况是,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式:
[java]view plain publicclassSingleton{/*私有构造方法,防止被实例化*/privateSingleton(){}/*此处使用一个内部类来维护单例*/{=newSingleton();}/*获取实例*/(){returnSingletonFactory.instance;}/*如果该对象被用于序列化,可以保证对象在序列化前后保持一致*/publicObjectreadResolve(){returngetInstance();}}
其实说它完美,也不一定,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实现方法。也有人这样实现:因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized关键字,也是可以的:
[java]view plain publicclassSingletonTest{=null;privateSingletonTest(){}(){if(instance==null){instance=newSingletonTest();}}(){if(instance==null){syncInit();}returninstance;}}
考虑性能的话,整个程序只需创建一次实例,所以性能也不会有什么影响。
补充:采用"影子实例"的办法为单例对象的属性同步更新
[java]view plain publicclassSingletonTest{=null;privateVectorproperties=null;publicVectorgetProperties(){returnproperties;}privateSingletonTest(){}(){if(instance==null){instance=newSingletonTest();}}(){if(instance==null){syncInit();}returninstance;}publicvoipdateProperties(){SingletonTestshadow=newSingletonTest();properties=shadow.getProperties();}}
通过单例模式的学习告诉我们:
1、单例模式理解起来简单,但是具体实现起来还是有一定的难度。
2、synchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)。
到这儿,单例模式基本已经讲完了,结尾处,笔者突然想到另一个问题,就是采用类的静态方法,实现单例模式的效果,也是可行的,此处二者有什么不同?
首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)
其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。
再次,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。
最后一点,单例类比较灵活,毕竟从实现上只是一个普通的Java类,只要满足单例的基本需求,你可以在里面随心所欲的实现一些其它功能,但是静态类不行。从上面这些概括中,基本可以看出二者的区别,但是,从另一方面讲,我们上面最后实现的那个单例模式,内部就是用一个静态类来实现的,所以,二者有很大的关联,只是我们考虑问题的层面不同罢了。两种思想的结合,才能造就出完美的解决方案,就像HashMap采用数组+链表来实现一样,其实生活中很多事情都是这样,单用不同的方法来处理问题,总是有优点也有缺点,最完美的方法是,结合各个方法的优点,才能最好的解决问题!
4、建造者模式(Builder)
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的。我们看一下代码:
还和前面一样,一个Sender接口,两个实现类MailSender和SmsSender。最后,建造者类如下: [java]view plain publicclassBuilder{privateList list=newArrayList ();publicvoidproceMailSender(intcount){for(inti=0;i0){pos--;}returncollection.get(pos);}@OverridepublicObjectnext(){if(pos
② 在组合模式中实现访问者(Visitor)模式
本文从一个给定的实现了组合(Composite)模式的例子开始 说明怎么在这个数据结构上实现业务逻辑代码 依次介绍了非面向对象的方式 在组合结构中加入方法 使用访问者(Visitor)模式以及用改进后的访问者(Visitor)模式来实现相同的业务逻辑代码 并且对于每种实现分别给出了优缺点
读者定位于具有Java程序开发和设计模式经验的开发人员
读者通过本文可以学到如何在组合(Composite)模式中实现各种不同的业务方法及其优缺点
组合(Composite)模式
组合模式是结构型模式中的一种 GOF的《设计模式》一书中对使用组合模式的意图描述如下 将对象组合成树形结构以表示 部分 整体 的层次结构 Composite使得用户对单个对象和组合对象的使用具有一致性
组合模式应用广泛 根据GOF中对组合模式的定义 Composite模式一般由Component接口 Leaf类和Composite类组成 现在需要对一个软件产品管理系统的实体建模 某公司开发了一系列软件集(SofareSet) 包含了多橘正种品牌(Brand)的软件产品 就象IBM提供了Lotus WebsPhere等品牌 每个品牌下面又有各种产品(Proct) 如IBM的Lotus下面有Domino Server/Client产品等 建模后的类图如下(代码可以参见随本文带的附件中 包 test entity下所有的源文件)
如图所示
( )接口SofareComponent就是对应于组合模式中的Component接口 它定义了所有类共有接口的缺省行为
( )AbsSofareComposite类对应于Composite类 并且是抽象类 所有可以包含子节点的类都扩展这个类 这个类的主要功能是用来存储子部件 实现了接口中的方法 部分可以重用的代码写在此类中
( )SofareSet类继承于AbsSofareComposite类 对应于软件集 软件集下直接可以包含品牌(Brand) 也可以直接包含不属于任何品牌的产品(Proct)
( )Brand类继承于AbsSofareComposite类 对应于品牌 包含了品牌名属性 并且用来存储Proct类的实例
( )Proct类就是对应的Leaf类 表示叶子节点 叶子节点没有子节点
用不同的方法实现业务逻辑
数据结构建立好之后 需要在这个数据结构上添加方法实现圆氏悔业务逻辑 比如现在的这个例子中 有这样的需求 给定一些用户选择好的产品 需要计算出这些选中后软件的总价格 下面开始介绍如何使用各种不同的方法来实现这个业务逻辑
非面向对象的编程方式
这种方式下 编程思路最简单 遍历SofareSet实例中的所有节点 如果遍历到的当前对象是Proct的话就累加 否则继续遍历下一层直到全部遍历完毕 代码片断如下
- /** *取得某个SofareComponent对象下面所有Proct的价格 *@parambrand *@return */ public double getTotalPrice(){ SofareComponenttemp=sofareComponent; double totalPrice= ; //如果传入的实例是SofareSet的类型 if (temp instanceof SofareSet){ Iteratorit=((SofareSet)sofareComponent) getChilds() &erator(); while (it hasNext())核山{ //遍历 temp=(SofareComponent)it next(); //如果子对象是Proct类型的 直接累加 if (temp instanceof Proct){ Proctproct=(Proct)temp; totalPrice+=proct getPrice(); } else if (temp instanceof Brand){ //如果子对象是Brand类型的 则遍历Brand下面所有的产品并累加 Brandbrand=(Brand)temp; totalPrice+=getBrandPrice(brand); } } } else if (temp instanceof Brand){ //如果传入的实例是SofareSet的类型 则遍历Brand下面所有的产品并累加 totalPrice+=getBrandPrice((Brand)temp); } else if (temp instanceof Proct){ //如果子对象是Proct类型的 直接返回价格 return ((Proct)temp) getPrice(); } return totalPrice; } /** *取得某个Brand对象下面所有Proct的价格 *@parambrand *@return */ private double getBrandPrice(Brandbrand){ IteratorbrandIt=brand getChilds(erator(); double totalPrice= ; while (brandIt hasNext()){ Proctproct=(Proct)brandIt next(); totalPrice+=proct getPrice(); } return totalPrice; }
这段代码的好处是实现业务逻辑的时候无需对前面已经定好的数据结构做改动 并且效率比较高 缺点是代码凌乱而且频繁使用了instanceof判断类型和强制类型转换 代码的可读性不强 如果层次多了代码就更加混乱
面向对象的编程方式(将计算价格的方法加入数据结构中)
下面我们采用面向对象的方式 可以这么做 在接口SoftWareComponent中加入一个方法 名叫getTotalPrice 方法的声明如下
- /** *返回该节点中所有子节点对象的价格之和 *@return */ public double getTotalPrice();
- public double getTotalPrice(){ Iteratorit=erator(); double price= ; while (it hasNext()){ =(SofareComponent)it next(); //自动递归调用各个对象的getTotalPrice方法并累加 price+=sofareComponent getTotalPrice(); } return price; }
- public double getTotalPrice(){ return price; }
- //getMockData()方法返回数据 SofareComponentdata=getMockData(); //只需直接调用data对象的getTotalPrice方法就可以返回该对象下所有proct对象的价格 double price=data getTotalPrice(); //找到某个对象后直接调用其getTotalPrice方法也可以返回总价格 price=data findSofareComponentByID( id ) getTotalPrice();
现在把业务逻辑的实现都放在了数据结构中(组合模式的结构中) 好处很明显 每个类只管理自己相关的业务代码的实现 跟前面举的面向过程方式的实现方式相比 没有了instanceof和强制类型转换 但是不好的地方是如果需要增加新的业务方法的话就很麻烦 必须在接口SoftWareComponent中首先声明该方法 然后在各个子类中实现并且重新编译
使用访问者模式
使用访问者模式就能解决上面提到的问题 如果要经常增加或者删除业务功能方法的话 需要频繁地对程序进行重新实现和编译 根据面向对象设计原则之一的SRP(单一职责原则)原则 如果一个类承担了多于一个的职责 那么引起该类变化的原因就会有多个 就会导致脆弱的设计 在发生变化时 原有的设计可能会遭到意想不到的破坏 下面我们引入了一个叫做Visitor的接口 该接口中定义了针对各个子类的访问方法 如下所示
- public interface Visitor{ public void visitBrand(Brandbrand); public void visitSofareSet(SofareSetsofareSet); public void visitProct(Proctproct); }
- public void accept(Visitorvisitor);
- public void accept(Visitorvisitor){ visitor visitSofareSet( this ); Iteratorit=erator(); while (it hasNext()){ SofareComponentponent=(SofareComponent)it next(); ponent accept(visitor); } }
- public void accept(Visitorvisitor){ visitor visitBrand( this ); Iteratorit=erator(); while (it hasNext()){ SofareComponentponent=(SofareComponent)it next(); ponent accept(visitor); } }
- public void accept(Visitorvisitor){ visitor visitProct( this ); }
- public class CaculateTotalPriceVisitor implements Visitor{ private double totalPrice; public void visitBrand(Brandbrand){ } public void visitSofareSet(SofareSetsofareSet){ } public void visitProct(Proctproct){ //每次在组合的结构中碰到Proct对象节点的时候 就会调用此方法 totalPrice+=proct getPrice(); } public double getTotalPrice(){ return totalPrice; } }
- //建立一个新的Visitor对象 = new CaculateTotalPriceVisitor(); //将该visitor对象传到结构中 data accept(visitor); //调用visitor对象的getTotalPrice()方法就返回了总价格 double price=visitor getTotalPrice();
下面是它的时序图 在类SofareManager中的main方法中 调用软件集对象(data)的accept方法 并将生成的visitor对象传给它 accept方法开始递归调用各个子对象的accept方法 如果当前的对象是SofareSet的实例 则调用visitor对象visitSofareSet方法 在visitor对象中对该节点的数据进行一些处理 然后返回 依次类推 遍历到Brand对象和Proct对象也与此类似 当前的逻辑是计算软件产品的总价格 因此当遍历到Proct对象的时候 取出产品的价格并且累加 最后当结构遍历完毕后 调用visitor对象的getTotalPrice方法返回给定软件集对象的(data)的总的价格 如果需要加入一个新的计算逻辑 只实现Visitor接口 并且将该类的实例传给data对象的accept方法就可以实现不同的逻辑方法了
点击小图看大图
我们可以看到通过访问者模式很好地解决了如何加入新的业务代码而无需重新改动 编译既有代码 但是该模式也不是没有缺点 如果在组合模式中结构加入新的子类的话会导致接口Visitor也跟着改动 导致所有Visitor的子类都需要实现新增的方法 因此这种访问者模式适合于结构不经常变动的情况
改进访问者模式
前面我们说到了如何使用Visitor模式及使用该模式后的优缺点 下面举具体的例子说明 假设现在客户提出了一个产品集(ProctSet)的概念 随着公司软件版本的增多 需要将同一个版本的产品(Proct)都放到产品集(ProctSet)中 而一个品牌包含有多个产品集 因为现在组合结构中增加了一个节点 所以在Visitor接口中也必须随之增加一个叫做visitProctSet的方法 并且会导致原有系统中所有已经实现了Visitor接口的类都需要重新实现并编译 用Java的反射机制可以解决这个问题
使用Java的Method Reflection机制实现访问者模式
首先我们需要改变一下Visitor接口 接口名叫做ReflectionVisitor 如下所示
- public interface ReflectionVisitor{ /** *定义了一个访问节点的方法 *@paramsofareComposite */ public void visitSofareComposite( Object sofareComposite); }
在现在的接口的方法里 能接受任意的对象(参数是Object)
下面实现接口ReflectionVisitor 名叫ReflectionVisitorImpl 代码如下所示
- public class ReflectionVisitorImpl implements ReflectionVisitor{ public void visitSofareComposite( Object sofareComposite){ //判断是否是null if (sofareComposite== null ){ throw new NullPointerException ( Thevisitnodeshouldnotbenull! ); } //组装class数组 即调用动态方法的时候参数的类型 Class []classes= new Class []{sofareComposite getClass()}; //组装与class数组相对应的值 Object []objects= new Object []{sofareComposite}; try { //查找visit方法 Methodm=getClass() getMethod( visit classes); //调用该方法 m invoke( this objects); } catch ( NoSuchMethodException e){ //没有找到相应的方法 System out println( : +sofareComposite getClass()); } catch ( Exception e){ //发生了别的异常 System out println( ); e printStackTrace(); } } }
这段代码首先判断传入的对象是否是空指针 然后创建class数组和object数组 然后用getMethod方法取得方法名是 visit 方法的参数是 对象sofareComposite对应的类 的方法 最后调用该方法 调用该方法的时候可能会发生NoSuchMethodException异常 发生这个异常就表明它的子类或者当前类中没有与参数中传入相对应的visit方法
下面再来写新版本Visitor类 扩展刚写好的那个ReflectionVisitorImpl类 名叫 如下所示
- public class extends ReflectionVisitorImpl{ private double totalPrice; public void visit(Proctproct){ totalPrice+=proct getPrice(); } public void visit(SofareSetsofareSet){ System out println( Nopriceforsofareset ); } public double getTotalPrice(){ return totalPrice; } }
- public void visit(ProctSetproctSet){ //实现的代码 }
在组合结构的接口SofareComponent中改一下accept方法 参数是修改后的Visitor接口 如下所示
public void accept(ReflectionVisitor visitor);
由于在类SofareSet Brand和ProctSet中实现上面accept方法的代码都一样 因此把代码抽象到上层共有的抽象类AbsSofareComposite中 如下所示
- public void accept(ReflectionVisitorvisitor){ visitor visitSofareComposite( this ); Iteratorit=erator(); while (it hasNext()){ SofareComponentponent=(SofareComponent)it next(); //递归调用子对象的accept方法 ponent accept(visitor); } }
- //建立一个新的Visitor对象 reflectionVisitor = new (); //将该visitor对象传到结构中 data accept(reflectionVisitor); //调用visitor对象的getTotalPrice()方法就返回了总价格 double price=reflectionVisitor getTotalPrice();
另外由于没有实现Brand类的visit方法 在组合结构遍历到Brand的节点的时候会抛出NoSuchMethodException异常 就是没有关于该节点方法的实现 在当前的程序中会打印出一句话
You did not implement the visit method for class:class test entity Brand
如果运行程序时发生了别的异常 请参见相应的Java API文档
在现在的改进后的访问者模式中 如果在组合的结构中新增或删除节点并不会对已经实现了的Visitor产生任何影响 如果新增了业务方法 只需扩展类ReflectionVisitorImpl就可以了 因此很好地解决了访问者模式的问题
改进访问者模式实现与既有代码对接
到现在为止 改进后的访问者模式好像已经很好地解决了所有出现的问题 但是考虑到有下面的这种情况 现在需要写一个JSP的标签库(TagLib) 这个标签库还必须具有Visitor的功能(就是需要有遍历节点的功能) 可以将节点的内容根据需要打印到HTML页面中 由于标签本身需要继承相应的类(如TagSupport) 如果继续使用上面提供的方法将无法实现 因为Java不允许多重继承 不过我们可以将原有ReflectionVisitorImpl的代码再改进一下以解决这种情况 新的Visitor的实现类叫NewReflectionVisitorImpl 代码如下所示
- public class NewReflectionVisitorImpl implements ReflectionVisitor{ //实现visit方法的类 private Object targetObject; //构造方法 传入实现了visit方法的类 public NewReflectionVisitorImpl( Object targetObject){ if (targetObject== null ) throw new NullPointerException ( ! ); this targetObject=targetObject; } public void visitSofareComposite( Object sofareComposite){ //……与上个例子相同 try { //从目标的对象中查找visit方法 Methodm=targetObject getClass() getMethod( visit classes); //调用该方法 m invoke(targetObject objects); } catch ( NoSuchMethodException e){ //……与上个例子相同 } catch ( Exception e){ //……与上个例子相同 } } }
- //从目标的对象中查找visit方法 Methodm=targetObject getClass() getMethod( visit classes); //调用该方法 m invoke(targetObject objects);
本来的代码中从本身的类及其子类中查找visit方法 而现在是从维护的目标类中查找visit方法
现在需要写Tag类 这个类扩展了TagSupport类 如下所示(为说明的方便 随本文的例子提供了一个模拟的TagSupport类)
- public class MyTag extends TagSupport{ = null ; private double totalPrice= ; public int doEngTag(){ //创建一个visitor对象 并且将本身传入visitor对象中 ReflectionVisitorvisitor= new NewReflectionVisitorImpl( this ); //遍历结构 sofareComponent accept(visitor); //打印出价格 out println(totalPrice); return ; } //实现了针对Proct的visit方法 public void visit(Proctproct){ totalPrice+=proct getPrice(); } public void visit(Brandbrand){ out println(brand getId()+brand getDescription()); } //别的代码请参见随本文带的源程序 …… }
- //getMockData()方法返回数据 SofareComponentdata=getMockData(); MyTagmyTag= new MyTag(); myTag setSofareComponent(data); //计算总价格 并打印出来 myTag doEngTag();
可以看到通过Java的反射机制很好地解决了多重继承的问题 使该访问者模式能够更好地应用于你的应用中 另外可以看到 那些visit方法所在的类已经不是实现了接口ReflectionVisitor 可以说是访问者模式在Java语言的支持下的一种特殊实现
如果担心引入类反射机制后带来的效率问题 你可以将Method对象通过某种方式缓冲起来 这样不会每次从传入的对象中找visit方法 可以部分地提高效率
结论
lishixin/Article/program/Java/gj/201311/11152
③ 访问者模式的特点
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。
访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。
访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。
④ java中常用的设计模式有哪些
1.单例模式(有的书上说叫单态模式其实都一样)
该模式主要目的是使内存中保持1个对象
2.工厂模式
该模式主要功能是统一提供实例对象的引用。看下面的例子:
public class Factory{
public ClassesDao getClassesDao(){
ClassesDao cd = new ClassesDaoImpl();
return cd;
}
}
interface ClassesDao{
public String getClassesName();
}
class ClassesDaoImpl implements ClassesDao {
public String getClassesName(){
System.out.println("A班");
}
}
class test
{
public static void main(String[] args){
Factory f = new Factory();
f.getClassesDao().getClassesName();
}
}
这个是最简单的例子了,就是通过工厂方法通过接口获取对象的引用
3.建造模式
该模式其实就是说,一个对象的组成可能有很多其他的对象一起组成的,比如说,一个对象的实现非常复杂,有很多的属性,而这些属性又是其他对象的引用,可能这些对象的引用又包括很多的对象引用。封装这些复杂性,就可以使用建造模式。
4.门面模式
这个模式个人感觉像是Service层的一个翻版。比如Dao我们定义了很多持久化方法,我们通过Service层将Dao的原子方法组成业务逻辑,再通过方法向上层提供服务。门面模式道理其实是一样的。
5.策略模式
这个模式是将行为的抽象,即当有几个类有相似的方法,将其中通用的部分都提取出来,从而使扩展更容易。
⑤ 访问者模式的优点
1、符合单一职责原则:凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,使用访问者模式一方面符合单一职责原则,另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展。
2、扩展性良好:元素类可以通过接受不同的访问者来实现对不同操作的扩展。
⑥ 访问者模式的适用情况
1、 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2、 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor模式使得你可以将相关的操作集中起来 定义在一个类中。
3、 当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
4)、定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
⑦ java observer模式 怎么设计
在JDK(Java Development Kit)类库中,开发人员使用了大量设计模式,正因为如此,我们可以在不修改JDK源码的前提下开发出自己的应用软件,研究JDK类库中的模式实例也不失为学习如何使用设计模式的一个好方式。
创建型模式:
(1) 抽象工厂模式(Abstract Factory)
• Java.util.Calendar#getInstance()
• java.util.Arrays#asList()
• java.util.ResourceBundle#getBundle()
• java.NET.URL#openConnection()
• java.sql.DriverManager#getConnection()
• java.sql.Connection#createStatement()
• java.sql.Statement#executeQuery()
• java.text.NumberFormat#getInstance()
• java.lang.management.ManagementFactory (所有getXXX()方法)
• java.nio.charset.Charset#forName()
• javax.xml.parsers.DocumentBuilderFactory#newInstance()
• javax.xml.transform.TransformerFactory#newInstance()
• javax.xml.xpath.XPathFactory#newInstance()
(2) 建造者模式(Builder)
• java.lang.StringBuilder#append()
• java.lang.StringBuffer#append()
• java.nio.ByteBuffer#put() (CharBuffer, ShortBuffer, IntBuffer,LongBuffer, FloatBuffer 和DoubleBuffer与之类似)
• javax.swing.GroupLayout.Group#addComponent()
• java.sql.PreparedStatement
• java.lang.Appendable的所有实现类
(3) 工厂方法模式(Factory Method)
• java.lang.Object#toString() (在其子类中可以覆盖该方法)
• java.lang.Class#newInstance()
• java.lang.Integer#valueOf(String) (Boolean, Byte, Character,Short, Long, Float 和 Double与之类似)
• java.lang.Class#forName()
• java.lang.reflect.Array#newInstance()
• java.lang.reflect.Constructor#newInstance()
(4) 原型模式(Prototype)
• java.lang.Object#clone() (支持浅克隆的类必须实现java.lang.Cloneable接口)
(5) 单例模式 (Singleton)
• java.lang.Runtime#getRuntime()
• java.awt.Desktop#getDesktop()
结构型模式:
(1) 适配器模式(Adapter)
•java.util.Arrays#asList()
•javax.swing.JTable(TableModel)
•java.io.InputStreamReader(InputStream)
•java.io.OutputStreamWriter(OutputStream)
•javax.xml.bind.annotation.adapters.XmlAdapter#marshal()
•javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal()
(2) 桥接模式(Bridge)
• AWT (提供了抽象层映射于实际的操作系统)
•JDBC
(3) 组合模式(Composite)
•javax.swing.JComponent#add(Component)
•java.awt.Container#add(Component)
•java.util.Map#putAll(Map)
•java.util.List#addAll(Collection)
•java.util.Set#addAll(Collection)
(4) 装饰模式(Decorator)
•java.io.BufferedInputStream(InputStream)
•java.io.DataInputStream(InputStream)
•java.io.BufferedOutputStream(OutputStream)
•java.util.zip.ZipOutputStream(OutputStream)
•java.util.Collections#checked[List|Map|Set|SortedSet|SortedMap]()
(5) 外观模式(Facade)
•java.lang.Class
•javax.faces.webapp.FacesServlet
(6) 享元模式(Flyweight)
•java.lang.Integer#valueOf(int)
•java.lang.Boolean#valueOf(boolean)
• java.lang.Byte#valueOf(byte)
•java.lang.Character#valueOf(char)
(7) 代理模式(Proxy)
• java.lang.reflect.Proxy
•java.rmi.*
行为型模式:
(1) 职责链模式(Chain of Responsibility)
•java.util.logging.Logger#log()
•javax.servlet.Filter#doFilter()
(2) 命令模式(Command)
• java.lang.Runnable
• javax.swing.Action
(3) 解释器模式(Interpreter)
• java.util.Pattern
• java.text.Normalizer
• java.text.Format
• javax.el.ELResolver
(4) 迭代器模式(Iterator)
• java.util.Iterator
• java.util.Enumeration
(5) 中介者模式(Mediator)
• java.util.Timer (所有scheleXXX()方法)
• java.util.concurrent.Executor#execute()
• java.util.concurrent.ExecutorService (invokeXXX()和submit()方法)
• java.util.concurrent.ScheledExecutorService (所有scheleXXX()方法)
•java.lang.reflect.Method#invoke()
(6) 备忘录模式(Memento)
•java.util.Date
•java.io.Serializable
•javax.faces.component.StateHolder
(7) 观察者模式(Observer)
•java.util.Observer/java.util.Observable
•java.util.EventListener (所有子类)
•javax.servlet.http.HttpSessionBindingListener
•javax.servlet.http.HttpSessionAttributeListener
•javax.faces.event.PhaseListener
(8) 状态模式(State)
•java.util.Iterator
•javax.faces.lifecycle.LifeCycle#execute()
(9) 策略模式(Strategy)
• java.util.Comparator#compare()
• javax.servlet.http.HttpServlet
• javax.servlet.Filter#doFilter()
(10) 模板方法模式(Template Method)
•java.io.InputStream, java.io.OutputStream, java.io.Reader和java.io.Writer的所有非抽象方法
•java.util.AbstractList, java.util.AbstractSet和java.util.AbstractMap的所有非抽象方法
•javax.servlet.http.HttpServlet#doXXX()
(11) 访问者模式(Visitor)
•javax.lang.model.element.AnnotationValue和AnnotationValueVisitor
•javax.lang.model.element.Element和ElementVisitor
•javax.lang.model.type.TypeMirror和TypeVisitor