“工厂”促进多态
控制被送回对象的内在状态固然重要,但是如果促进多态即返回相同的接口多种类的对象
,可以使得工厂模式的功能更为强大
。 让我们再次看一下Monopoly的例子
,然后执行购买
游戏中的道具的行为
。在
游戏中,你的任务就是买道具,包括一些基本动作。更进一步说,有三种不同的道具:Street,RailRoad和Utility。所有三个类型的道具有一些共同点:每个道具都被一个玩家拥有;每个都有价格;而且每个都能为它的拥有者产生租金只要其他的玩家在它上面登陆。但道具之间还是存在差异的,举例来说,计算租金的多少就取决于道具的类型。
下列的代码展示了一个Property的基本类:
//
PHP5
abstractclassProperty{
protected$name;
protected$price;
protected$game;
function__construct($game,$name,$price){
$this->game=$game;
$this->name=$name;
$this->price=newDollar($price);
}
abstractprotectedfunctioncalcRent();
publicfunctionpurchase($player){
$player->pay($this->price);
$this->owner=$player;
}
publicfunctionrent($player){
if($this->owner
&&$this->owner!=$player
$player($this->calcRent())
);
}
}
}
这里,Property类和CalcRent()方法都被声明为基类。
注:术语–基类
一个基类就是不能被直接实例化的类。一个基础的类包含一个或更多的基础方法,这些方法必须在子类被覆盖。一旦所有的抽象方法被覆盖了,子类也就产生了。
基类为许多相似的类创造了好的原型。
CalcRent()方法必须在子类被覆盖,从而形成一个具体的类。因此,每个子类包括:Street,RailRoad和Utility,和必须定义的calcRent()方法。
为实现以上的情况,这三个类可以定义为:
classStreetextendsProperty{
protected$base_rent;
public$color;
publicfunctionsetRent($rent){
$this->base_rent=newDollar($rent);
}
protectedfunctioncalcRent(){
if($this->game->hasMonopoly($this->owner,$this->color)){
return$this->base_rent->add($this->base_rent);
}
return$this->base_rent;
}
}
classRailRoadextendsProperty{
protectedfunctioncalcRent(){
switch($this->game->railRoadCount($this->owner)){
case1:returnnewDollar(25);
case2:returnnewDollar(50);
case3:returnnewDollar(100);
case4:returnnewDollar(200);
default:returnnewDollar;
}
}
}
classUtilityextendsProperty{
protectedfunctioncalcRent(){
switch($this->game->utilityCount($this->owner)){
case1:returnnewDollar(4*$this->game->lastRoll());
case2:returnnewDollar(10*$this->game->lastRoll());
default:returnnewDollar;
}
}
}
每个子类都继承了Property类,而且包括它自己的protectedClacRent()方法。随着所有的基础方法都被定义,每个子类都被实例化了。
为了开始游戏,所有的Monopoly道具必须被创建起来。因为这章是介绍工厂模式的,所有Property的类型存在很多共性,你应该想到多态性,从而建立所有需要的对象。
我们还是以道具工厂类开始。在我住的地方,政府的Assessor(定税人)掌握了税务和契约,因此我命名它为的道具定税工厂。下一步,这个工厂将制造全部的专有道具。在真正应用时,所有的Monopoly道具的数值可能都取自于一个数据库或者一个文本,但是对于这一个例子来说,可以仅仅用一个数组来代替:
classAssessor{
protected$prop_info=array(
//streets
‘MediterraneanAve.’=>array(‘Street’,60,‘Purple’,2)
,’BalticAve.’=>array(‘Street’,60,‘Purple’,2)
//moreofthestreets...
,’Boardwalk’=>array(‘Street’,400,‘Blue’,50)
//railroads
,’ShortLineR.R.’=>array(‘RailRoad’,200)
//therestoftherailroads...
//utilities
,’ElectricCompany’=>array(‘Utility’,150)
,’WaterWorks’=>array(‘Utility’,150)
);
}
Property子类需要实例化Monopoly道具。现在,我们只是简单的用一个函数定义实例化变量$game,那么再把它加入Assessor类好了。
classAssessor{
protected$game;
publicfunctionsetGame($game){$this->game=$game;}
protected$prop_info=array(/*...*/);
}
也许你会偏向于选择使用数据库记录数据,不会用数组,因为有一大堆的参数不可避免地要被罗列。如果是这样的话,可以考虑使用"引入叁数对象"进行重构。
注:重构-引入叁数对象
方法中如果有很多参数,常常变得很复杂,而且容易导致错误。你可以引入一个封装参数的对象来替代一大堆的参数。举例来说,“startdate”and“enddate”叁数可以用一个DateRange对象一起代替。
在Monopoly这个例子中,这个参数对象应该是什么呢?PropertyInfo,怎样?它的目的是使每个道具参数数组引入PropertyInfo类的构造器中,然后返回一个新对象。目的就意味着设计,依照TDD,那意味着一个测试情形。