2.重构为Strategy模式:
上节中给出的getRecommend()方法存在这样几个问题:首先,它太长了,需要我们加注释以解释不同的部分。另外,getRecommend()方法先是选择一个策略,然后执行这个策略;这是两个不同的并且可以分离的功能。我们可使用Stragegy模式来整理这段代码。为了做到这点,需要完成以下几点:
(1)创建一个接口来定义策略操作;
(2)分别在不同的类中以不同的策略来实现这个接口;
(3)重构上一节的代码,选择并使用合适的策略类实例。
假设创建了Advisor接口,如图2所示:
Advisor接口定义多个类可以使用不同策略的操作
Advisor接口说明实现这个接口的类可以接收客户,并推荐焰火制品。重构Customer类中getRecommend()方法代码的下一步是创建表示推荐策略的类。每个类使用不同方式来实现Advisor接口指定的recommend()方法。
突破题:如图3所示,推荐逻辑经过重构之后变成了一个策略类的集合。请补充完整该类图。
答:类图如下:
每种策略分别表现为一个公共接口的不同实现
策略类创建之后,接下来的步骤是把代码从Customer类中已有的getRecommend()方法迁移到新的类中。两个最简单的类是GroupAdvisor类和ItemAdvisor类,通过使用现有的推荐引擎来实现接口中的recommend()操作。接口可以只定义实例方法,所以GroupAdvisor类和ItemAdvisor类必须被实例化以支持Advisor接口。然而,只有一个这样的对象是始终有效的,所以在Customer类中为每个实现提供一个单例对象。图4给出了这些类之间的层次关系。
Advisor接口的实现类依赖于现有的引擎来提供recommend()方法
Advisor类把对recommend()方法的调用转发给其背后所使用的推荐引擎类。例如,GroupAdvisor类把对recommend()的调用转发给Rel8引擎的advise()方法:
突破题:除了Strategy模式之外,请问GroupAdvisor类和ItemAdvisor类还应用了哪些模式?
答:GroupAdvisor和ItemAdvisor类是Adapter模式的实例,提供客户期望的接口,借助于其他不同接口来利用类的服务。
GroupAdvisor和ItemAdvisor类将把对recommend()的调用转发给其背后所使用的推荐引擎类。我们还需要创建PromotionAdvisor类和RandomAdvisor类,通过把当前getRecommended()方法的代码重构到Customer类来实现这一步。与GroupAdvisor和ItemAdvisor类一样,其余类都分别会提供recommend()操作。
在PromotionAdvisor类的构造器中,我们应该判断一下是否有促销活动。另外,还应该在这个类中提供一个hasItem()方法,用它来指示是否有需要促销的焰火制品。
RandomAdvisor类比较简单,具体代码如下:
通过对Customer进行重构,我们实现了对策略选择逻辑(或advisor)和策略使用逻辑的分离。Customer对象的advisor属性保存当前选择的策略。重构后的Customer2类对这个属性进行了滞后初始化,其代码逻辑体现了Oozinoz公司的推销广告策略。
突破题:请写出Customer.getRecommend()方法。
答:
一旦知道了具体的Advisor对象,多态机制就可以完成所有的工作。
3.比较Strategy模式和State模式:
经重构之后,代码、类以及方法都显得相当简单。这就是这种重构的一个优势,而且重构之后,我们可以更加容易地添加新的策略。这种重构的核心思想就是:将一个操作分布在一组相关的类中。从这点来说,Strategy模式和State模式是一样的。事实上,很多开发人员也不知道这两种策略有什么不同。
一方面,状态和策略的建模方式存在细微差别。当然,依赖多态性会使得State模式和Strategy模式在结构上看起来几乎相同。
另一方面,在现实世界中,策略和状态是明显不同的概念。当我们在对状态和策略进行建模的时候,这个实实在在的差异会导致产生完全不同的问题。例如,在对状态进行建模的时候,状态迁移是一个重要方面;而在对策略进行建模的时候,迁移与策略的选择并不相关。另外一个不同之处是,Strategy模式可能允许客户选择或者提供一个策略,而State模式却很少设计这样的思路。
State模式与Stragety模式的目标存在差异,所以我们继续把它们作为不同的模式。但是应该注意到,并不是每个人都意识到这种差别。
4.比较Strategy模式和Template Method模式:
前面曾将排序算法描述为Template Method模式的一个实例。只要一个列表中的对象提供比较两个对象的步骤,那么我们就可以使用Array或者Collection类的sort()算法对这个列表进行排序。但有人可能会争辩说,为一个排序算法提供比较步骤,就是在改变策略。例如,在销售火箭的时候,将火箭按照价格排列和将火箭按照火箭的推进力排列就是两种不同的行销策略。
突破题:请问Arrays.sort()方法是Template Method模式的例子,还是Strategy模式的例子?请给出理由。
答:认为该排序算法是Strategy模式的范例的观点是:Template Method模式可以让子类重新定义算法的某些具体步骤。但是,Collections.sort()方法并没有使用子类;该方法需要使用一个Comparator实例。每个Comparator实例都提供一个新方法,因此,也提供一个新的算法和一个新的策略。因而sort()方法是Strategy模式的一个很好的范例。
认为该排序算法是Template Method模式的范例的观点是:排序算法有很多,但是Collections.sort()方法仅使用了一种排序算法(Quicksort)。改变算法意味着要把算法变为堆排序或者冒泡排序。Strategy模式的目的在于方便我们使用不同的算法。但是现在却不行。Template Method模式的目的在于方便我们在某个算法中插入一个特定步骤。这正是sort()方法的工作机制。
5.小结:
对不同策略建模的逻辑可能只出现在一个类中,并且常常只出现在一个方法中。这样的方法可能会变得过于复杂,并且可能将策略选择与策略执行混在了一起。
为了简化这样的代码,我们可以创建一组类,并用每个类来表示不同的策略。具体而言,我们可以在这些类中定义一个相同的操作,并在每个类对这个操作的实现中封装一种策略。另外,我们还需要为用户提供策略选择逻辑。这种策略选择代码逻辑,即使是在重构后仍然可能比较复杂;但是,我们应该能够简化它,使之接近于问题域中描述策略选择的伪代码。
在典型情况下,客户将使用一个上下文变量来保存选择的策略,借助多态性来执行合适的策略,从而使得策略的执行变得非常地简单,客户在收到策略操作调用请求之后只需将该请求转发给上下文变量即可。通过将可选的策略封装在单独的类中,并让每个类都实现一个共同的操作,我们可通过Strategy模式创建一系列整洁而又简单的代码,以此来完成对各种解决问题的方案的建模。