class Product {
function getList() { $db =& new MysqlConnection(DB_USER, DB_PW, DB_NAME);
//...
}
function getByName($name) { $db =& new MysqlConnection(DB_USER, DB_PW, DB_NAME);
//...
}
//...
}
为什么这样做不好? 数据库连接的参数出现的地方太多了,当你把这些参数设成常量,意味着你统一定义并对他们进行赋值,显然这种做法不是很妥当:
你可以轻松地改变连接数据库的参数,但你不能增加或改变这些参数地顺序,除非你把所有连接代码都改了。 你不能轻松的实例化一个新类去连接另一种数据库,比如说PostgresqlConnection。 这样很难单独测试和证实连接对象的状态。 使用工厂设计模式,代码将得到很大的改进:
class Product {
function getList() {
$db =& $this-_getConnection();
//...
}
function &_getConnection() {
return new MysqlConnection(DB_USER, DB_PW, DB_NAME);
}
} 先前的类中存在很多调用new MysqlConnection(DB_USER, DB_PW, DB_NAME)的方法,现在都被集中到的_getConnection()方法上。
[next]
下面是工厂的另一种变化,你静态地调用了一个工厂类:
class Product {
function getList() {
$db =& DbConnectionBroker::getConnection();
//...
}
}
class DbConnectionBroker {
function &getConnection() {
return new MysqlConnection(DB_USER, DB_PW, DB_NAME);
}
} 这里DbConnectionBroker::getConnection()产生的效果和前面的一样 ,但这样却很有好处: 我们不必在每个需要连接数据库的类中加入调用new MysqlConnection(DB_USER , DB_PW, DB_NAME)的方法。
当然另一种变化就是引用一个外部工厂对象的资源,和这个对象定义了数据库连接的参数:
class Product {
var $_db_maker;
function setDbFactory(&$connection_factory) {
$this-_db_maker =& $connection_factory;
}
function getList() {
$db =& $this-_db_maker-getConnection();
//...
}
} 最后,一个工厂可以用一个函数合理的组织,然后实现:
function &make_db_conn() {
return new MysqlConnection(DB_USER, DB_PW, DB_NAME);
}
class Product {
function getList() {
$bar =& make_db_conn();
//...
}
} 下面是一个工厂的理想化执行的 UML 类图:
[next]
例子:增加一些颜色
让我们更深入工厂模式吧。继续如前,先建立一个能为本章节的其它部分持续举例说明的简单类。 这是一个输出十六进制的HTML RGB Color类,包括了R, G, 和 B三个属性(在构造对象的时候引入)和 getRgb()方法,getRgb()的作用是返回一个十六进制颜色的字符串。
和以前一样,我们按照测试驱动开发(TDD)的方法:写一个测试,再写一段代码满足该测试,如果需要,反复下去。
下面是一个非常简单的起始测试:
function TestInstantiate() {
$this-assertIsA($color = new Color, ‘Color’);
$this-assertTrue(method_exists($color, ‘getRgb’));
} 为了满足这个测试,你可以设计这样一个类。测试看起来有点像伪代码:
class Color {
function getRgb() {}
} ( 这个Color类也许看起来像处于娃娃阶段, 但是 TDD是一个反复的过程。 代码非常少,当需要的时候:你开始有了新的想法或者想精确的执行代码时,可以增加的。)
接下来, 当颜色对象被建立时,getRgb() 方法应该返回以红色,绿色,和蓝色的十六进制字符串。 用一个测试说明一下:
function TestGetRgbWhite() {
$white =& new Color(255,255,255);
$this-assertEqual(‘#FFFFFF’, $white-getRgb());
} 每个 TDD, 你写最简单的可行的代码来满足测试, 并不需要满足人的审美观或者代码的正确执行。
下面是最简单的,能够通过测试的代码:
class Color {
function getRgb() { return ‘#FFFFFF’; }
} 这个Color类不是令人十分满意, 但是它确实表现了逐渐增加的过程。
下一步,让我们增加一个额外的测试使这个Color类的的确确地执行并返回正确的信息:
function TestGetRgbRed() {
$red =& new Color(255,0,0);
$this-assertEqual(‘#FF0000’, $red-getRgb());
} 这个Color类必须改变什么呢? 首先必须把红色,绿色,和蓝色的值储存在三个变量里,然后在调用一个方法把十进制数转化为十六进制数。按照这个要求执行的代码可以写作:
class Color {
var $r=0;
var $g=0;
var $b=0;
function Color($red=0, $green=0, $blue=0)
{
$this-r =$red;
$this-g = $green;
$this-b = $blue;
}
function getRgb() {
return sprintf(‘#%02X%02X%02X’, $this-r, $this-g, $this-b);
}
} 这个构造非常简单: 先收集红色,绿色,和蓝色的数值,然后储存在变量中, getRgb() 方法使用 sprintf() 函数将十进制数转换成十六进制数。
为了对代码充满更多的信心, 你可以用较多的数值来测试它。 这一个测试可以用以下代码实现:
function TestGetRgbRandom() {
$color =& new Color(rand(0,255), rand(0,255), rand(0,255));
$this-assertWantedPattern(
‘/^#[0-9A-F]{6}$/’,
$color-getRgb());
$color2 =& new Color($t = rand(0,255), $t, $t);
$this-assertWantedPattern(
‘/^#([0-9A-F]{2})11$/’,
$color2-getRgb());
} 注:assertWantedPattern
assertWantedPattern() 作用是:使它的第二个叁数匹配第一个参数,第一个参数是正则表达式。如果匹配,这个测试就通过; 否则不通过。
由于assertWantedPattern()具有进行正确的正则表达式匹配的功能,所以常被用作测试。
[next]
所有这些测试Color类功能的行为都在正常和期望的环境下实现的。但是每一个设计精良的类都必须考虑边界情况。例如, 被送入构造器执行的数值如果是负数,或者大于255的数值,或者根本不是数值,结果会出现什么呢?一个好的类定义应该适应测试中的多种边界情况。
function testColorBoundaries() {
$color =& new Color(-1);
$this-assertErrorPattern(‘/out.*0.*255/i’);
$color =& new Color(1111);
$this-assertErrorPattern(‘/out.*0.*255/i’);
} 注:assertErrorPattern
assertErrorPattern() 作用是:将产生的php错误进行正确的正则表达式匹配。如果这个错误不匹配指定的模式, 将不通过测试。
在那些测试的基础上,Color类将得到更进一步改进:
class Color {
var $r=0;
var $g=0;
var $b=0;
function Color($red=0, $green=0, $blue=0) {
$red = (int)$red;
if ($red < 0 || $red 255) {
trigger_error(color ‘$color’ out of bounds,
.please specify a number between 0 and 255);
}
$this-r = $red;
$green = (int)$green;
if ($green < 0 || $green 255) {
trigger_error(color ‘$color’ out of bounds,
.please specify a number between 0 and 255);
}
$this-g = $green;
$blue = (int)$blue;
if ($blue < 0 || $blue 255) {
trigger_error(color ‘$color’ out of bounds,
.please specify a number between 0 and 255);
}
$this-b = $blue;
}
function getRgb() {
return sprintf(‘#%02X%02X%02X’, $this-r, $this-g, $this-b);
}
} 这个代码通过了测试, 但是这种 " 剪切和粘贴 " 的风格有点使人厌倦。 在 TDD,一个经验法则就是将编码最简单的实现,如果你两次需要相同的代码,可以将其改进,但不要复制代码。 然而你往往需要三次或三次以上的一样代码。因此我们可以提取一个方法即重构实现这个工作。
注:重构 - 提取方法
当你的代码中有两个或者两个以上部分的代码相似的时候, 可以将它们提取出来成为一个独立的方法,并按它的用途命名。当你的类的方法代码中频频出现相同的成分,提取代码作为一个方法是非常有用的。
class Color {
var $r=0;
var $g=0;
var $b=0;
function Color($red=0, $green=0, $blue=0) {
$this-r = $this-validateColor($red);
$this-g = $this-validateColor($green);
$this-b = $this-validateColor($blue);
}
function validateColor($color) {
$check = (int)$color;
if ($check < 0 || $check 255) {
trigger_error(color ‘$color’ out of bounds,
.please specify a number between 0 and 255);
} else {
return $check;
}
}
function getRgb() {
return sprintf(‘#%02X%02X%02X’, $this-r, $this-g, $this-b);
}
} [next]
创建工厂来简化对象的创建过程
让我们为Color类增加一个工厂,使得建立新的实例更简单。增加一个可以命名颜色的方法,这样就可以不记颜色数值,只需要记住自己喜欢的颜色名字。
工厂对象或函数不一定都要被命名为 工厂。 当你读代码时,工厂是显而易见的。 相反的,它的名字最好取得有意义,这样可以反映出它解决了什么问题。
在这个代码例子中, 我要叫它CrayonBox颜色工厂。静态的方法CrayonBox::getColor()引入命名颜色的字符串后,返回一个带有相应颜色属性的Color类。
下面的例子就可以测试这一点:
function TestGetColor() {
$this-assertIsA($o =& CrayonBox::getColor(‘red’), ‘Color’);
$this-assertEqual(‘#FF0000’, $o-getRgb());
$this-assertIsA($o =& CrayonBox::getColor(‘LIME’), ‘Color’);
$this-assertEqual(‘#00FF00’, $o-getRgb());
} 通过这个测试,我们发现每个返回的对象都是一个实例化的Color类,getRgb() 方法也返回了正确的结果。第一种情况是以red都是小写测试,第二种情况是以LIME都是大写测试,这样可以测试代码的通用性。
保险起见, 我们再对其进行另外的测试,探究那些不合法的边界情况。TestBadColor() 方法的作用是:用一个不存在的颜色名字引发一个包含这个颜色名字的php错误,并返回黑色。
function TestBadColor() {
$this-assertIsA($o =& CrayonBox::getColor(‘Lemon’), ‘Color’);
$this-assertErrorPattern(‘/lemon/i’);
// got black instead
$this-assertEqual(‘#000000’, $o-getRgb());
} 以下是一个可以满足测试的CrayonBox类:
class CrayonBox {
/**
* Return valid colors as color name = array(red, green, blue)
*
* Note the array is returned from function call
* because we want to have getColor able to be called statically
* so we can’t have instance variables to store the array
* @return array
*/
function colorList() {
return array(
‘black’ = array(0, 0, 0)
,’green’ = array(0, 128, 0)
// the rest of the colors ...
,’aqua’ = array(0, 255, 255)
);
}
/**
* Factory method to return a Color
* @param string $color_name the name of the desired color
* @return Color
*/
function &getColor($color_name) {
$color_name = strtolower($color_name);
if (array_key_exists($color_name,
$colors = CrayonBox::colorList())) {
$color = $colors[$color_name];
return new Color($color[0], $color[1], $color[2]);
}
trigger_error(No color ‘$color_name’ available);
// default to black
return new Color;
} 这显然地是一个非常简单的工厂, 它确实制造了单一化的对象(使用了颜色名字,而不是RGB数值) ,它展示了在新的对象被调用之前,是如何建立一个内部对象的。
[next]
工厂促进多态
控制被送回对象的内在状态固然重要, 但是如果促进多态即返回相同的接口多种类的对象,可以使得工厂模式的功能更为强大。
让我们再次看一下Monopoly的例子,然后执行购买游戏中的道具的行为。在游戏中,你的任务就是买道具,包括一些基本动作。更进一步说, 有三种不同的道具: Street,RailRoad和Utility。所有三个类型的道具有一些共同点: 每个道具都被一个玩家拥有; 每个都有价格;而且每个都能为它的拥有者产生租金只要其他的玩家在它上面登陆。但道具之间还是存在差异的,举例来说, 计算租金的多少就取决于道具的类型。
下列的代码展示了一个Property的基本类:
// PHP5
abstract class Property {
protected $name;
protected $price;
protected $game;
function __construct($game, $name, $price) {
$this-game = $game;
$this-name = $name;
$this-price = new Dollar($price);
}
abstract protected function calcRent();
public function purchase($player) {
$player-pay($this-price);
$this-owner = $player;
}
public function rent($player) {
if ($this-owner
&& $this-owner != $player
$player($this-calcRent())
);
}
}
} 这里, Property类和CalcRent() 方法都被声明为基类。
注:术语 – 基类
一个基类就是不能被直接实例化的类。 一个基础的类包含一个或更多的基础方法,这些方法必须在子类被覆盖。一旦所有的抽象方法被覆盖了, 子类也就产生了。
基类为许多相似的类创造了好的原型。
CalcRent() 方法必须在子类被覆盖,从而形成一个具体的类。因此, 每个子类包括:Street,RailRoad和Utility,和必须定义的calcRent() 方法。
为实现以上的情况,这三个类可以定义为:
class Street extends Property {
protected $base_rent;
public $color;
public function setRent($rent) {
$this-base_rent = new Dollar($rent);
}
protected function calcRent() {
if ($this-game-hasMonopoly($this-owner, $this-color)) {
return $this-base_rent-add($this-base_rent);
}
return $this-base_rent;
}
}
class RailRoad extends Property {
protected function calcRent() {
switch($this-game-railRoadCount($this-owner)) {
case 1: return new Dollar(25);
case 2: return new Dollar(50);
case 3: return new Dollar(100);
case 4: return new Dollar(200);
default: return new Dollar;
}
}
}
class Utility extends Property {
protected function calcRent() {
switch ($this-game-utilityCount($this-owner)) {
case 1: return new Dollar(4*$this-game-lastRoll());
case 2: return new Dollar(10*$this-game-lastRoll());
default: return new Dollar;
}
}
} 每个子类都继承了Property类,而且包括它自己的protected ClacRent() 方法。随着所有的基础方法都被定义, 每个子类都被实例化了。
为了开始游戏, 所有的Monopoly道具必须被创建起来。因为这章是介绍工厂模式的,所有Property的类型存在很多共性,你应该想到多态性,从而建立所有需要的对象。
我们还是以道具工厂类开始。 在我住的地方,政府的Assessor(定税人)掌握了税务和契约,因此我命名它为的道具定税工厂。下一步,这个工厂将制造全部的专有道具。在真正应用时,所有的Monopoly道具的数值可能都取自于一个数据库或者一个文本, 但是对于这一个例子来说, 可以仅仅用一个数组来代替:
class Assessor {
protected $prop_info = array(
// streets
‘Mediterranean Ave.’ = array(‘Street’, 60, ‘Purple’, 2)
,’Baltic Ave.’ = array(‘Street’, 60, ‘Purple’, 2)
//more of the streets...
,’Boardwalk’ = array(‘Street’, 400, ‘Blue’, 50)
// railroads
,’Short Line R.R.’ = array(‘RailRoad’, 200)
//the rest of the railroads...
// utilities
,’Electric Company’ = array(‘Utility’, 150)
,’Water Works’ = array(‘Utility’, 150)
);
} Property子类需要实例化Monopoly道具。现在,我们只是简单的用一个函数定义实例化变量$game,那么再把它加入Assessor类好了。
class Assessor {
protected $game;
public function setGame($game) { $this-game = $game; }
protected $prop_info = array(/* ... */);
} 也许你会偏向于选择使用数据库记录数据,不会用数组, 因为有一大堆的参数不可避免地要被罗列。如果是这样的话,可以考虑使用" 引入叁数对象 " 进行重构。
注:重构-引入叁数对象
方法中如果有很多参数,常常变得很复杂,而且容易导致错误。你可以引入一个封装参数的对象来替代一大堆的参数。举例来说,start date and end date 叁数可以用一个 DateRange 对象一起代替。
在Monopoly这个例子中,这个参数对象应该是什么呢?PropertyInfo,怎样?它的目的是使每个道具参数数组引入 PropertyInfo 类的构造器中,然后返回一个新对象。目的就意味着设计, 依照 TDD, 那意味着一个测试情形。
[next]
下面一个测试代码就是测试 PropertyInfo 类的:
function testPropertyInfo() {
$list = array(‘type’,’price’,’color’,’rent’);
$this-assertIsA(
$testprop = new PropertyInfo($list), ‘PropertyInfo’);
foreach($list as $prop) {
$this-assertEqual($prop, $testprop-$prop);
}
} 这个测试证明:每个PropertyInfo类都有四个公共属性,而且具有按精确次序排列的叁数。
但是因为实例中 RailRoad 和 Utility 类并不需要颜色或者租用数据, 所以我们需要测试PropertyInfo 也能引入少量的参数而实例化为RailRoad 和 Utility 类对象:
function testPropertyInfoMissingColorRent() {
$list = array(‘type’,’price’);
$this-assertIsA(
$testprop = new PropertyInfo($list), ‘PropertyInfo’);
$this-assertNoErrors();
foreach($list as $prop) {
$this-assertEqual($prop, $testprop-$prop);
}
$this-assertNull($testprop-color);
$this-assertNull($testprop-rent);
} 注:assertNoErrors()
assertNoErrors() 方法的作用是:证实没有PHP 错误发生。如果有错误, 将不通过测试。
assertNull()
assertNull()方法的作用是:测试第一个参数是否为空。 如果第一个参数不为空, 将不通过测试。像大多数其他测试方法一样,, 你可以选择是否使用第二个叁数定义失败信息。
为了满足前面的测试,PropertyInfo 类定义为:
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/php/) class PropertyInfo {
const TYPE_KEY = 0;
const PRICE_KEY = 1;
const COLOR_KEY = 2;
const RENT_KEY = 3;
public $type;
public $price;
public $color;
public $rent;
public function __construct($props) {
$this-type =
$this-propValue($props, ‘type’, self::TYPE_KEY);
$this-price =
$this-propValue($props, ‘price’, self::PRICE_KEY);
$this-color =
$this-propValue($props, ‘color’, self::COLOR_KEY);
$this-rent =
$this-propValue($props, ‘rent’, self::RENT_KEY);
}
protected function propValue($props, $prop, $key) {
if (array_key_exists($key, $props)) {
return $this-$prop = $props[$key];
}
}
} 现在PropertyInfo 类可以构造各种不同的Property参数了。同时Assessor类可以提供数据来建立正确的PropertyInfo对象。
现在以Assessor-$prop_info数组提供的数据为基础,新建一个实例化 PropertyInfo 的类。
这样的代码可以是:
class Assessor {
protected $game;
public function setGame($game) { $this-game = $game; }
public function getProperty($name) {
$prop_info = new PropertyInfo($this-prop_info[$name]);
switch($prop_info-type) {
case ‘Street’:
$prop = new Street($this-game, $name, $prop_info-price);
$prop-color = $prop_info-color;
$prop-setRent($prop_info-rent);
return $prop;
case ‘RailRoad’:
return new RailRoad($this-game, $name, $prop_info-price);
break;
case ‘Utility’:
return new Utility($this-game, $name, $prop_info-price);
break;
default: //should not be able to get here
}
}
protected $prop_info = array(/* ... */);
} 这段代码实现了上述功能, 但却非常脆弱。如果代入的值是$this-prop_info数组中没有的值,结果会怎样呢?因为 PropertyInfo 已经被实例化并被加入到Assessor代码中, 没有有效的方法测试被产生的对象。比较好的解决就是:产生一个工厂方法使 PropertyInfo 对象更容易建立。 因此, 下一步将是写一个测试来实现Assessor类中的PropertyInfo方法。
但是,有一个问题: 这个方法不应该是Assessor类的公共接口(API)的一个部份。它能被测试吗?
这里有两个方法, 可以探究任何要求的合理数量的测试。简单的说, 你可以运行黑匣子测试或白匣子测试。
注:黑匣子测试(Black Box Testing)
黑匣子测试就是:把被测试的对象当成" 黑匣子 " ,我们只知道它提供的应用接口(API),但不知道其到底执行了什么。它主要测试对象公共方法的输入和输出。
白匣子测试(White Box Testing)
白匣子测试和黑匣子测试恰恰相反, 它假定知道测试对象中的所有代码信息。这种形式的测试是为了完善代码和减少错误。
关于白匣子测试的详细说明请见:http:// c 2.com/cgi/wiki?WhiteBoxTesting 。
[next]
别把话题扯远了。那么如何才在黑匣子和白匣子之间找到折中办法来实现TDD呢呢?一种选择就是使原来的类中的私有方法变为公有,并且在发布的时候变回私有。但这并不是十分令人满意的方式,所以我们建立一个子类,同时使子类中的方法可以从外部访问:
下面就是一个子类的例子:
class TestableAssessor extends Assessor {
public function getPropInfo($name) {
return Assessor::getPropInfo($name);
}
} 这样做的好处是你可以得到正确的Assessor公有接口(API), 但通过 TestableAssessor 类我们就可以来测试Assessor类了。另外, 你用于测试的代码也不会影响到Assessor类。
缺点是:外加的类会带来更多的问题,从而使测试变得更复杂。而且如果你在对象中的一些内部接口作出一些改动, 你的测试将随着你的重构而再次失效。
比较了它的优点和缺点,让我们来看看它的测试方法:
function testGetPropInfoReturn() {
$assessor = new TestableAssessor;
$this-assertIsA(
$assessor-getPropInfo(‘Boardwalk’), ‘PropertyInfo’);
} 为了要保证所有代码的正确执行, 我们可以使用异常处理。 SimpleTest的目前是基于PHP4 搭建的测试的结构,所以不具备异常处理能力。但是你还是可以在测试中使用如下。
function testBadPropNameReturnsException() {
$assessor = new TestableAssessor;
$exception_caught = false;
try { $assessor-getPropInfo(‘Main Street’); }
catch (InvalidPropertyNameException $e) {
$exception_caught = true;
}
$this-assertTrue($exception_caught); 最后, Assessor类的执行部分完成了:
class Assessor {
protected $game;
public function setGame($game) { $this-game = $game; }
public function getProperty($name) {
$prop_info = $this-getPropInfo($name);
switch($prop_info-type) {
case ‘Street’:
$prop = new Street($this-game, $name, $prop_info-price);
$prop-color = $prop_info-color;
$prop-setRent($prop_info-rent);
return $prop;
case ‘RailRoad’:
return new RailRoad($this-game, $name, $prop_info-price);
break;
case ‘Utility’:
return new Utility($this-game, $name, $prop_info-price);
break;
default: //should not be able to get here
}
}
protected $prop_info = array(/* ... */);
protected function getPropInfo($name) {
if (!array_key_exists($name, $this-prop_info)) {
throw new InvalidPropertyNameException($name);
}
return new PropertyInfo($this-prop_info[$name]);
}
} Assessor::getPropInfo()方法从逻辑上说明 PropertyInfo工厂类是作为了Assessor类的一个私有的方法。而Assessor::getProperty() 方法是用来返回三个Property子类的一个,至于返回哪一个子类这要看property的名字。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/php/)[next]
迟加载(Lazy Loading)的工厂
使用工厂的另一个好处就是它具有迟加载的能力。这种情况常被用在:一个工厂中包括很多子类,这些子类被定义在单独的PHP文件内。
注:术语 - 迟加载
在迟加载模式中是不预加载所有的操作(像包含PHP文件或者执行数据库查询语句),除非脚本中声明要加载。
用一个脚本可以有效地控制多个网页的输出,这是Web常用的方法了。比如一个博客程序,一些入口就有不同的页面来实现,一个简单的评论入口就有:发布评论的页面,一个导航的页面,一个管理员编辑的页面等。你可以把所有的功能放入一个单独的类中,使用工厂来加载他们。每一个功能类可以单独放在一个文件里,再把这些文件都放在pages这个子文件夹里,这样可以方便调用。
实现迟加载的页面工厂(page factory)的代码可以写作:
class PageFactory {
function &getPage() {
$page = (array_key_exists(‘page’, $_REQUEST))
? strtolower($_REQUEST[‘page’])
: ‘’;
switch ($page) {
case ‘entry’: $pageclass = ‘Detail’; break;
case ‘edit’: $pageclass = ‘Edit’; break;
case ‘comment’: $pageclass = ‘Comment’; break;
default:
$pageclass = ‘Index’;
}
if (!class_exists($pageclass)) {
require_once ‘pages/’.$pageclass.’.php’;
}
return new $pageclass;
}
} 你可以利用 PHP 的动态加载性质,然后使用实时的运行需求(run-time)来给你要建立的类命名。在这情况下, 根据一个 HTTP 请求叁数就能确定哪个页面被加载。你可以使用迟加载,这样只要当你需要建立新对象时才载入相应的类,不需要你载入所有可能用到的page类。在上述例子中就用了 require_once来实现这一点。这个技术对于一个装有PHP加速器的系统来说并不重要,因为包含一个外加的文件使用的时间对它来说可以忽略。但对于大多数典型的PHP服务器来说,这样做是很有好处的。
要想了解更多的关于迟加载的知识,请看第 11 章-代理模式。
小节
工厂模式是非常简单而且非常有用。如果你已经有很多关于工厂模式的例子代码,你会发现更多的东西。《GoF》这本书就介绍了一些关于构建的模式: AbstractFactory and Builder。 AbstractFactory用来处理一些相关组件,Builder模式则是使建立复杂对象更为容易。
在这章的多数例子里, 参数是通过工厂方法引入的(例如 CrayonBox::getColor(‘红色’);)。《GoF》中则称为参数化工厂(parameterized factory),它是PHP网页设计中典型的工厂方法。
你现在已经了解工厂模式了, 它是一种代码中建立新对象的管理技术。 你可以看到工厂模式是可以把复杂对象的建立集中起来,甚至用不同的类代替不同的对象。最后,工厂模式支持OOP技术中的多态也是很重要的。
下一节:《PHP设计模式介绍》第四章 单条模式