应用GoF设计模式

目标:

  • 介绍和应用一些GoF设计模式。
  • 说明GRASP原则是对其他设计模式的归纳。

本章探讨了NextGen案例分析的用例实现种的OO设计,该用例实现对具有不同接口的第三方外部服务、更为复杂的产品定价规则和可拔插的业务规则提供了支持。本章将重点介绍如何应用GoF模式和更多的GRASP基本模式。它说明可以基于对模式的应用来学习和解释对象设计和职责分配模式指的是可以于设计对象结合的设计原则和习惯用法的一个词汇。

适配器

  • 名称:适配器(Adapter)
  • 问题:如何解决不相容的接口问题,或者如何为具有不同接口的类似构件提供稳定的接口?
  • 解决方案:通过中介适配器对象,将构件的原有接口转换为其他接口。
  • 相关模式:隐藏外部系统的资源适配器也可以被视为外观对象,因为资源适配器使用单一对象封装了对子系统或系统的访问。然而,当包装对象是为不同外部金额口提供适配时,该对象才被特成为资源适配器。

一些GRASP原则是对其他设计模式的归纳

上述适配器模式的使用可以视为某些GRASP构造模块的特化:适配器支持防止变异,因为它通过应用了接口和多态的间接对象,改变了外部接口或第三方软件包。

问题使什么?模式过多?

Pattern Almanac 2000 列出了大越500种设计模式,并且此后又发布了数百种模式。如此之多的模式,使求置于强烈的程序员都没有事件去实际编程了。

解决方案:找到根本原则

是的,对于有经验的设计者来说,详细了解和记住50种以上最重要的设计模式非常重要,但是很少有人能够学习或记住1000个模式,因此需要对这些过量的模式进行有效分类。

但是,现在有好消息了:大多数设计模式可以被视为少数几个GRASP基本原则的特化。这样除了能够有助于加速对详细设计模式的学习之外,而且对发现其根本的基本主题更为有效,它能够帮助我们透过大量细节发现因公设计技术的本质。

示例:适配器和GRASP

可以用GRASP原则的基本列表来分析详细的设计模式。

设计中发现的“分析”:领域模型

在设计和编程过程种总是会发现有价值的领域概念,而且对需求也会产生经过精化的理解-迭代开发支持这种增量式的发现。

这种发现是否应该在领域模型种得到反映?如果领域模型在将来会被作为后续设计工作的输入,或者作为学习关键领域概念的手段,那么对领域模型的增补使有价值的。

工厂(Factory)

该模式也称为简单工厂或具体工程。它使抽象工程模式的简化。

适配器在设计种会引发一个新问题:在前述适配器模式的解决方案种,对外部服务有不同的接口,那么使谁创建了这些适配器?并且如何决定创建哪种类型的适配器?

如果使某个领域对象来创建这些适配器,那么领域对象的职责会超出了单纯的应用逻辑,并且会涉及到与外部软件构件连接相关的其他内容。

这一点强调了另一个基本涉及原则:设计要保持关注分离。也就是说,将不同关注分离或模块化为不同领域,以确保内聚。

因此,选择领域对象来创建适配器不能支持关注分离的目标,也降低了内聚。

在这种情况下,常用的替代方案使使用工厂模式,其定义纯虚构的“工厂”对象来创建对象。工程对象有如下一些优势:

  • 分离复杂创建的职责,并将其分配给内聚的帮助者对象。
  • 隐藏潜在的复杂创建逻辑
  • 允许引入提高性能的内存挂历策略。

  • 名称:工厂

  • 问题:当有特殊考虑时,应该由谁来复杂创建对象?
  • 解决方案:创建称为工厂的纯虚构对象来处理这些创建职责。
  • 相关模式:通常使用单实例类模式来访问工厂模式。

单实例类(GoF)

ServicesFactor的设计又引发了另一个新问题:有谁来创建工厂自身,如何访问工厂?

首先,注意在该过程种只需要一个工厂实例。其次,因为代码的不同位置都需要访问适配器以调用外部服务,所有我们很快就会提出要在代码的不同位置调用工厂种的方法。因此,这里存在可见性问题:如何获得单个ServicesFactory实例的可加性?

有一种解决方案时,将ServicesFactor作为参数传递给任何需要其可见性的地方,或者在初始化需要其可见性对象时,使该对象持有ServicesFactory的永久性应用。这是可能实现的,但是并不方便,另一种替代方案使单实例类模式。

有时,我们更期望支持单一实例的全局可见性或单点访问,而不是其他形式的可见性。

  • 名称:单实例类
  • 问题:只有唯一实例的类即为“单实例类”。对象需要全局可见性和单点范文。
  • 解决方案:对类定义静态方法用以返回单实例。
  • 相关模式:单实例类模式通常运用于工厂对象和外观对象。

实现和设计问题

单实例类的getInstance方法通常会被频繁调用。在多线程的应用种,使用lazy initialization逻辑的传教步骤使需要现场并发控制的临界区。通常要采用并发控制对其进行封装。

为什么不使用eager initialization呢?

  • 如果该实例永远不会被真正访问,就会节省创建工作(并避免可能会占用的大量资源)。
  • lazy initialization放入getInstance有时会包含复杂和有条件的创建逻辑。

单实例类的实现还存在另一个问题:为什么不将所有服务方法都定义成类子集的静态方法?

  • 实例方法允许应以单实例类的子类,以对其进行精化。
  • 大多数面对对象的原创通信机制只支持实例方法的原创使用。
  • 类并发所有应用场景种都是单实例类。

具有不同接口的外部服务问题的结论

我们的解决方案使:使用适配器、工厂和单实例类模式的结合,为具有不同接口的外部税金计算器、账务系统等提供防止变异。

该设计可能并不理想,并且总是具有改进的余地。但是,该案例研究所要努力实现的目标使:说明设计至少可以使用一组原则或模式成分来进行构造,同时完成和解释这种设计使具有系统化方法的。我很希望读者能够发现设计使由基于模式的推理而来,这些模式包括:控制器、创建者、防止变异、低耦合、高内聚、间接性、多态、适配器、工厂和单实例类。

策略(GoF)

下一个要解决的设计问题使提供更为复杂的定价逻辑。

销售的定价策略具有多样性,如何对这些各种二氧的定价算法进行设计呢?

  • 名称:策略
  • 问题:如何设计变化但相关的选方或正常?如何车技才能使这些算法或正常具有可变更更的能力?
  • 解决方案:在单独的类种分别定义每种算法/正常/策略,并且使其具有共同接口。
  • 相关模式:策略是基于多态的,并且对于变化的算法提供了防止变异。策略通常由工厂创建。

策略对象依附于语境对象-策略对象对其应用算法。并不要求发给语境对象和策略对象的消息具有i相同的米高处,但是这种做法十分常见。无论如何,语境对象会将其自身的引用传递给策略对象,这种做法十分普遍,这样策略对象便能够拥有语境对象的参数可见性,一边用于将来协作。

使用工厂创建策略

不同的定价算法或策略会根据时间而变化。谁应该创建策略呢?有一种直接的方法就是再次应用工厂模式。

读取和初始化百分比值

最后,到现在为止,我们还没有考虑入场处理不同的百分比或绝对值折扣的数值。

同时还要注意的使,百分比折扣不仅与时间端有关,可能还与购买者类型相关。

这些数据将存储与某种外部数据存储种以便修改。那么,应该由那个对象读取这些数据,并确保将其分配给相应的策略呢?StrategyFactor本身就是一种合理选择,因为定价策略使由StrategyFactory创建的,并且它能够指导从数据存储区读取那个百分比值。

从外部数据存储种读取数值的设计可能会非常不同,可能简答,也可能复杂。对数据存储的变化点和进化点进行分析后,将会发现十分需要防止变异。

总结

对于动态变化的定价策略的防止变异可以通过策略和工厂模式实现。建立在多态和接口基础上的策略可以实现具有可拔插的对象设计。

组合(GoF)和其他设计原则

这里可以提出另一个有趣的需求和设计问题:我们如何来处理多个互相冲突的定价策略?

这个问题的部分答案需要对该商定一定解决冲突的策略。

首先要注意的是,可以同时存在多个策略,也就是说,一个销售可能有多个定价策略。零一点需要注意的是,定价策略可以与顾客类型相关。统一,定价策略可以与所购买的商品类型相关。

有没有办法来改变设计,不需要指导十分要处理一个或多个定价策略,而且同时还能够提供一种设计来解决冲突?

  • 名称:组合
  • 问题:如何能够像处理非组合(原子)对象一样,(多态地)处理一组对象或具有组合结构的对象呢?
  • 解决方案:定义组合和原子对象的类,使它们实现相同的接口。
  • 相关模式:组合模式通常与策略和命名模式一起使用。组合使基于多态的,并且对客户提供了防止变异,使其不会因为与之相关的对象使原子的还是组合的而受到影响。

创建多个SalePricingStrategies

使用组合模式,我们使一组多个的定价策略对于Sale对象来说与单个定价策略一样对于该设计问题更具有挑战性的部分是:我们什么时候创建这些策略。

良好的设计应该在开始时为商店创建一个包含当前所有折扣正常的组合。然后,如果在该常见的下一步种,发现还需要应用另一个定价策略,此时通过使用继承来的add方法,能够轻松地在组合种增加这一测试。

在该常见种,可能有三个地方需要在组合种增加策略:

  1. 商店当前定义的折扣,在创建销售时增加。
  2. 顾客类型这条,在POS获取顾客类型时添加。
  3. 产品类型折扣,在向销售输入该产品条码时增加。

在设计种考虑GRASP和其他原则

ID到对象

将聚合对象作为参数传递

总结

外观(GoF)

本次迭代选择的另一个需求使可插拔的业务规则。也就是说,在场景种的某些可预测点,想要购买NextGen POS的客户可能会对其行为进行少量定制。

更准确地说,假设需要可以使活动无效的规则:

  • 假设创建新销售时,可能要识别该销售十分以礼券方式进行只是。然后,商店可能会规定如果使用礼券只可以购买一件商品。此时,enterItem在完成第一次操作和应该变为无效。
  • 如果销售使用礼券支付,在对该顾客找零时,处理礼券之外所有其他支付类型的找零都应该置为无效。
  • 假设创建新销售时,可以是被气味慈善捐助。商店可能会规定每次输入的条码价值只能低于250美元,并且只有当前登录着为经理是才允许对该销售添加条目。

对于需求分析来说,必须是被整个用例种的特点场景点。

假设软件架构师想要设计此类方案,并且不想对现有软件构件造成过多影响。也就是说,想要实现关注分离的设计,并且要把这些规则处理解析成为独立的关注。进一步说,架构师并不确定这种可拔插规则处理的额最佳实现方式,并且可能想要对表示、装载和评估这些规则的各种解决方案进行试验。

为解决该设计问题,可以使用外观模式:

  • 名称:外观
  • 问题:对异族完全不同的实现或接口需要公共、统一的接口。可能会与子系统内部额大量事物产生耦合,或者子系统的实现可能会改变,怎么办?
  • 解决方案:对子系统定义唯一的接触点-使用外观对象封装子系统。该外观对象提供了唯一和统一的接口,并复杂与子系统构件进行协作。
  • 相关模式:外观通过通过单实例类模式机型访问。它们对子系统的实现提供了防止变异,并且通过增加间接性对象有助于对低耦合的支持。适配器对象可能用来封装对具有不同接口的外部系统的访问。这就是一种外观,但是其强调的时对不同接口的适配,因此被更具体地称为适配器。 外观时前端对象,时对系统服务的唯一入口;子系统的实现和其他构件时私有的,并不对外部构件可见。外观对子系统实现的变化提供了防止变异。

外观通常时通过单实例类进行访问的。

外观对象所英寸的子系统可能包含数十或数百个对象类,或者甚至时非面向对象的解决方案,但是作为该系统的客户,我们所看到的只是它的一个公共的访问点。

左右对规则处理的关注都被委派给了另一个子系统,这样也在一定程度上实现了关注分离的目的。

观察者/发布-订阅/委派事件模型

本次迭代的另一个需求是:当总额变化后,GUI窗口要能刷新销售总额的现实。

模型-视图分离原子不提倡此类解决方案。它认为“模型”对象不应该指导窗口这样的视图或表示对象。它改进了其他层于表示层对象的低耦合。

支持这种低耦合的结果时,它允许替换现有视图或表示层,或者可以使用新的窗口来代替特定窗口,同时不会对非UI对象产生影响。

因此,模型-视图分离对变化的用户界面提供了方式变异。

为解决这个设计问题,可以使用观察者模式。

  • 名称:观察者/发布-订阅
  • 问题:不同类型的定义中对象关注于发布者对象的状态变化或事件,并且想要在发布者产生事件时以子集独特的方式做出反映。此外,发布者想要保持于订阅者的低耦合。如果对此进行设计呢?
  • 解决方案:定义“订阅者”或“监听器”接口。订阅者实现此i恶口。发布者可以动态主成关注某事件的订阅者,并在事件发生时通知它们。
  • 相关模式:观察者时基于多态的。当发布者产生事件时,与之通信的对象在其所属的类和数量上可能会发生辩护,而观察者对其提供了防止变异。

在该模式种,模型对象到视图对象还存在一定的耦合。但这是于接口之间的松耦合,而该接口并不依赖于表示层。而且,该设计也不需要任何订阅者对象实际地向发布者主成。总而言之,与对象的泛化接口之间的耦合并不需要被表示处理,而且可以动态地增加其中的对象,这就实现了低耦合的支持。因此,通过使用接口和多态,对变化的用户加接口实现了防止变异。

为何称为观察者、发布-订阅或委派时间模型

最初,这一西域被称为发布-定义、这个名称也是为人们所熟知的。其中一个对象复杂发布事件。当有对象关注时,该对象将通过请求发布以对其进行通知,而成为了关注该事件的“订阅者”。

它被称为观察者时因为监听器或订阅者在对相应事件进行观察;这一术语在20实际80年代早期常见于Smalltalk。

它也被称为委派实际模型,因为发布者将实际处理委派给了家庭其。

观察者不仅仅用来连接UI和模型对象

发布者的一个事件剋拥有多个订阅者

实现

总结

观察者在对象通信方面提供了一种松耦合方式。发布者只通过接口获知订阅者,订阅者可以动态地向发布会注册。

结论

从上述描述种可以得到的主要教训时:基于模式的支持能够进行对象设计和职责分配。这些模式提供了一套可解释的惯用做法,设计良好的面向对象胸痛可以建立于该基础之上。

参考资料

results matching ""

    No results matching ""