资源描述
GoF 几种设计模式举例及其应用1090379131 易余GOF即Gang of Four,就Java语言体系来说,GOF的设计模式是Java基础知识 和 J2EE 框架知识之间一座隐性的桥。实际上, GoF 的设计模式并不是一种具体 技术,它讲述的是思想,它不仅仅展示了接口或抽象类在实际案例中的灵活应用和 智慧,让你能够真正掌握接口或抽象类的应用,从而在原来的 Java 语言基础上跃进 一步,更重要的是, GoF 的设计模式反复向你强调一个宗旨:要让你的程序尽可能的 可重用。本文介绍几种常用的设计模式的举例和应用。适配器(Adap ter)如何解决不相容的接口问题,或者如何为具有不同接口的类似构件提供稳定的接 口?这样的问题的解决方案就是通过中介适配器对象,将构件的原有接口转换为其 他接口。应用举例:POS系统需要支持多种第三方外部服务,其中包括税金计算器, 信用卡授权服务,库存系统和账务系统。这些外部服务都具有不同的API,并且无法 改变。这样的问题解决方案就是:增加一层间接性对象,通过这些间接对象将不同的外 部接口调整为在应用程序内使用的一致接口。ITaxCalculatorAdaptergetTaxes(Sale) : List of TaxLineltemGoodAsGoldTaxProAdapterTaxMasterAdaptergetTaxes(Sale) : List of TaxLineItemgetTaxes(Sale) : List of TaxLineItemIAccountingAdapterpostReceivable(CreditPayment) postSale(Sale)SAPAccountingAdapterGreatNorthernAccountingAdapterpostReceivable(CreditPayment) postSale(Sale)postReceivable(CreditPayment) postSale(Sale)如上图所示,对于选定的外部服务,将选用一个特定的适配器实例来表示。例如 针对账务系统的SAP,当向外部接口发出postSale请求时,首先通过适配器进行转 换,使其能够通过 HTTP 上的 SOAP XML 接口来访问在 SAP 在局域网上提供的 Web Service。单实例类(Singleton) 工厂设计模式也称为简单工厂,经常描述为抽象工厂的变种。工厂设计模式引发 出另一个新的问题,即谁来创建工厂自身和如何访问工厂。代码在不同的位置都需要访问适配器以调用外部服务,所以就需要代码在不同的 位置调用工厂中的方法。在这里就存在可见性的问题,即如何获得单个工厂实例的 可见性以及单点访问。解决方案:对类定义静态方法返回单实例。其中的关键思想是,对类定义静态方 法get Ins tance,该方法提供了类的唯一实例。例如:public class Register public void Initialize do some work AccountingAdapter=ServicesFactory.getInstance().getAccountingAdapter(); do somework 由于公共类的可见性是全局的,因此代码的任何一点,在类的任何方法中,都可 以写为:SingletonClass.getInstance()例如SingletonClass.getinstance().doFoo(),这种写法就是为了获得对于单 实例类的实例可见性,并且对其发送消息。单实例类还存在另一个问题,为什么不将所有服务的方法都定义成类自己的静态 方法,而是使用有实例方法的实例对象?原因如下:实例方法允许定义单实例类的子类以对其进行精化。静态的方法不是多态的,而 且大多数语言中不允许在子类中对其覆写。大多数面向对象的远程通信机制只支持实例方法的远程使用而不支持静态方法。 类并非在所有的场景中都是单实例类。在一个应用中是单实例的,而在另一个实 例中却可能是多实例的,并且在开始设计的时候考虑使用单实例的不多见。因此使 用实例方法的解决方案有更大的灵活性。ServicesFactoryin sta nee : Serv icesFactory acco un ti ngAdapter : lAcco untin gAdapter inven toryAdapter : llnv en toryAdapter calculatorAdapter : ICalculatorAdapter/ getl nsta nce() : Serv icesFactory getAcco untin gAdapter() : IAcco un ti ngAdapter getI nven toryAdapter() : llnv en toryAdapter getCalculatorAdapter() : ICalculatorAdapter/static methodpublic static syn chr oni zedServicesFactory getl nsta nce()if(i nsta nce=n ull)in sta nce=new Serv icesFactory()return in sta nee观察者模式(Observer)观察者模式支持低耦合,它允许现有视图或者表示层使用新的窗口来代替特定的 窗口,同时不会对非 UI 对象产生影响。例如,如果模型对象不知道 Java Swing 对 象,那么久可以拔掉 Swing 接口或者特定的窗口,然后插入其他的东西。不同类型的观察者关注观察对象的状态变化或事件,并且在观察对象产生事件时 以自己独特的方式的做出反应。观察对象与观察者保持低耦合,就使用观察者设计 模式。解决方案为:定义观察者接口,观察者实现此接口。对象可以动态关注某事件的 观察者,并在事件发生时通知他们。下图描述了此类解决方案。此例的主要思想和步骤是:定义接口。在本例中式具有 onPropertyEvent 操作的 PropertyListener。定义实现该接口的窗口。SaleFramel实现onPropertyEvent方法。在 SaleFrame1 窗口初始化时,向其传递 Sale 实例以显示总额。SaleFramel窗口通过addPropertyListener消息向Sale实例注册或者订阅特性 事件的通知。也就是说总额变化时,该窗口会得到通知。Sale 并不知道 SaleFramel 对象,它只知道实现了 PropertyListener 对象。这 样就降低了 Sale 和窗口的耦合。Sale 实例就成为了特性事件的发布者,当总额发生变化时,它会遍历所有订阅 了的PropertyListener,并且通知每一个订阅者。SaleFramel 就是观察者组合(Composi te)商场有不同的定价策略,一般商店在解决冲突策略时会应用“对顾客最有利”的 策略来解决冲突。但有时在财政困难时期,商店也会采用最高价格的策略来解决冲 突。因此商场存在多个策略,同样定价策略可以与所购买的产品类型相关,那么如何 能够像处理非组合对象一样,多态地处理一组对象或者具有组合结构的对象?这里提出的解决方案是:采用组合模式,定义组合和原子对象的类,使他们实现 相同的接口。如下图所示。PercentDiscountPricingStrategypercentage : floatAbsluteDiscountOverThresho IdPricingStrategydiscount : Money threshold : MoneyCompositePricingStrategygetTotal(sale) : Moneyadd(ISalePricingStrategy) getTotal(sale) : MoneygetTotal(sale) : MoneyCompositeBestForCoustomerstrategyCompositeBestForStoreStrategygetTotal(sale) : MoneygetTotal(sale) : Moneysale|datagetTotal()pricingstrategy,IsalePricingStrategy1getTotal(sale) : Moneyz川r-.strategies1.*在该设计中, 其组合类 CompositeBestForCoustomerstrategy 继承了 属性 Pricingstrategy,该属性包含一组IsalePricingStrategy对象。组合对象的标志 性特征:外部的组合对象包含一组内部对象,外部和内部对象实现相同的接口。因此,我们对 Sale 对象附加的策略既可以是组合的 CompositeBestForCoustomerstrategy 对象,又可以是原子的PercentDiscountPricingStrategy对象。Sale不关心其定价策略 是组合的还是原子的。策略(st ra tegy)销售的定价策略有多样性,一段时期内对于所有的销售可能会有 10%的折扣,后 期可能会对超出 200 元的销售给予 10%的折扣,而且还会有其他大的变化。那么如何 设计适应灵活变化的相关的算法或政策?解决方案为:在单独的类中分别定义每种算法,使其具有共同的接口。由于定价 行为根据策略不同,因此我们需要创建多个 SalePricingStrategy 类,每个类都具 有多态地get To tal方法,每个get To tal方法都将Sale作为参数。这样便能够使定 价策略对象在 Sale 中找到打折之前的价格。saledatapricingstra1tegylsalePrici ngStrategygetTotal()getTotal(sale) : MoneyAbsoluteDiscou ntOverThreshPerce ntDisco un tPricioldPric in gStrategyn gStrategydisco unt : Moneyperce ntage : floatthreshold : MoneygetTotal(sale) : MoneygetTotal(sale) : Money策略对象依附于语境对象,本例中,语境对象是Sale。当get To tal消息发送给 Sale时,它会把工作委派给它的策略对象。类型为Sale的S被传递给了策略,因此 策略对象在将来的协作中对其具有参数可见性。项目实践:mySocketi2DB系统中设计模式的具体应用 开发意图:上游系统组织并下发的数据无法存储至下游数据库系统,需要有一个 中间软件系统作为数据传送通道完成该功能。系统特征:系统可以通过 TCP socket 连接从若干个不同的端口实时接收数据, 数据源是其他外部具有网络发送数据功能的系统。将接收的数据存储至现在市面上 各主流的数据库系统。1)单一实例类模式 系统运行时参数都存储在 CGlobalData 实例对象中,要求多线程可以读其中的 数据,所以这些参数数据只需要一份,所以使用单一实例类。CGIobalDatainstance : CGIobalDataGetlnstance(void) : CGIobalDataKillGIobalData(void)class CGlobalDataprivate:CGlobalData();virtual CGlobalData();static CGlobalData* _instance;public:static CGlobalData* Getlnstance(void);static void KillGlobalData(void);2)观察者模式系统运行时信息及错误需要显示在对话框中,定义接口 ITrackListener,对话 框对象实例实现该接口,并且向发布者CMainProcessorThread登记。当有这样的信 息需要显示时,发布者发布该事件。PublishTrackEvent(CString slnfo, int clientID, bool bShowPort, int IsError)for (it=this-m_pTrackListeners- begin(); it!=this-m_pTrackListeners- end(); it+)(*it)-OnTrackEvent(sInfo, clientID, bShowPort, IsError);CMainProcessorThreadPublishTrackEve nt()RegisterTrackListe ner(ITrackListe ner *liste ner)1ITrackListenerOnTrackEvent(CString sInfo, int clientID, bool bShowPort, int IsError)CGlobalData* gData = CGlobalData:GetInstance();while (!gData-CanAddLog) Sleep(100); gData-CanAddLog = false; this-OutPut2Screen(sInfo, clientID, bShowPort, IsError);gData-CanAddLog = true;On TrackEve nt() CSocketi2DBDIgOn TrackEve nt()interface ITrackListenerpublic:virtual void OnTrackEvent(CString sInfo, int clientID, bool bShowPort, int IsError) = 0;void CMainProcessorThread:RegisterTrackListener(ITrackListener *listener)this-m_pTrackListeners-push_back(listener);void CMainProcessorThread:PublishTrackEvent(CString sInfo, int clientID, bool bShowPort, int IsError)list:iterator it;for (it=this-m_pTrackListeners-begin(); it!=this-m_pTrackListeners-end(); it+)(*it)-OnTrackEvent(sInfo, clientID, bShowPort, IsError);void CSocketi2DBDlg:OnTrackEvent(CString sInfo, int clientID, bool bShowPort, int IsError)CGlobalData* gData = CGlobalData:GetInstance();while (!gData-CanAddLog) Sleep(100);gData-CanAddLog = false;this-OutPut2Screen(sInfo, clientID, bShowPort, IsError); gData-CanAddLog = true;总结从案例中可以重新体会面向对象分析和开发的过程,从面向过程向面向对象进行 转换,由于使用设计模式后是分析问题更加透彻,明白。设计的过程中始终贯穿着 正向UML建模,对项目的开发很有指导意义。在项目中我们使用的设计模式主要有前面 2 种,使用单实例模式,可以获得单个 工厂实例的全局可见性以及单点访问。使用观察者模式使观察对象与观察者保持低 耦合。在很大程度上这些设计模式的解决方案提供了防止变异的手段,使用接口和 多态等基本技术结合以实现系统的可扩展。
展开阅读全文