我们先定义一个产品抽像类,是所有产品的基类,我特地在这里加了__get/__set两个魔术方法,因为他在Magento中的Varien_Object也定义了这两个方法,这两个方法在一些框架经常使用到,如yii.这两个方法很好用,不像java了,要定义很多set/get方法。
代码块1:
abstract class Product
{
private $_data = array();
public function __set($var, $value){
$this->_data[$var] = $value;
}
public function __get($var){
if (isset($this->_data[$var])) {
return $this->_data[$var];
}
return false;
}
}
代码块2:水果的定义
class Fruit extends Product{
//方法或者变量定义
}
代码块3:蔬菜的定义
class Vegetables extends Product{
//方法或者变量定义
}
假设我们农场为了节约成本只有一个农民,而且刚开张只生产苹果和萝卜,那他又生产菜果,又生产萝卜.
菜果类:代码块2.1
class Apple extends Fruit {
//方法或者变量定义
}
萝卜类:代码块3.1
class Radish extends Vegetables {
//方法或者变量定义
}
代码块4:农场的定义,他的方法farmerAll相当一个农民
class Farm
{
public static function farmerAll($productType) {
if ('fruit' == $productType) {
$fruit = new Apple ();
return $fruit;
} else if ('vegetables' == $productType) {
$vegetables = new Radish();
return $vegetables;
} else {
echo '暂不生产';
return false;
}
}
}
现在来了一个客户买,他必须经过农场的农民来买,不然是就算偷菜了,呵呵。
代码如下:代码块5
$fruit = Farm::farmerAll('fruit');
$vegetables = Farm::farmerAll('vegetables');
农场发展了,决定专业化生产,一个农民生产一种产品,那么农场的定义发生变化:
代码块6:
class Farm {
public static function farmerFruit() {
$fruit = new Apple();
return $fruit;
}
public static function farmerVegetables() {
$vegetables = new Radish();
return $vegetables;
}
}
现在来了一个客户买,他找到相应的农民进行。
代码块7:
$fruit = Farm::farmerFruit();
$vegetables = Farm::farmerVegetables();
因为农场只生产菜果和萝卜,所以客户买的时候不要指明是那种水果和蔬菜,现在农场 发展了,准备再生产香蕉和白菜,类如下
香蕉类:代码块2.2
class Banana extends Fruit {
//方法或者变量定义
}
白菜类:代码块3.2
class Cabbage extends Vegetables{
//方法或者变量定义
}
但是为了成本考察不再追加农民.那么水果农民将生产两种水果,蔬菜农民生产两种蔬菜,此时,外界客户来买必须指定要那一种,不然农民会搞不清给那种给客户(只有一种时只能给那一种,呵呵)。此时的农场类变成了:
代码块8:
class Farm {
public static function farmerFruit($fruitType) {
if ('apple' == $fruitType) {
$apple = new Apple();
return $apple;
}else if ('banana' == $fruitType) {
$banana = new Banana();
return $banana;
}else {
echo '暂不生产';
return false;
}
}
public static function farmerVegetables($vegetablesType) {
if ('radish' == $vegetablesType) {
$radish = new Radish();
return $radish;
}else if ('cabbage' == $vegetablesType) {
$cabbage = new Cabbage();
return $cabbage;
}else {
echo '暂不生产';
return false;
}
}
}
我们已一个农场的发展,分析了工厂模式。在这个工厂模式中他有两类产品,由两个工厂方法来生产这两类产品。
XML与工厂模式
现在公司再次发展,需要再增加一个水果和蔬菜,那么我们再增加两个产品类,同时我们要修改农场中的两个工厂方法(农民),这与开闭原则是不相符的。
所以在这里我们引与xml文件,来处理这个问题。
代码块9:
<?xml version="1.0"?>
<config>
<fruits>
<apple >
<file>test/apple.php</file>
</apple>
<banana >
<file>test/banana.php</file>
</bananagt>
</fruits>
<vegetables>
<radish >
<file>test/radish.php</file>
</radish>
<cabbage>
<file>test/cabbage.php</file>
</cabbagegt>
</vegetables>
</config>
上面xml表示有两类产品下都有两个产品,其file节点表示产品类存在那个地方。如果此节点为空,则不引入。apple/banana这类节点表示类名,要把第一个字母输入为大写就可以了。现在我们来修改一下农场类.
代码块10:
class Farm {
public static function getClassName($type) {
//通过file_get_contents得到xml文件内容
//通过simplexml_load_string解析xml文件
//根据$type 找到file文件并include.
//return 类名
}
public static function farmerFruit($fruitType) {
$className = self::getClassName('fruit/' . $fruitType);
return new $className();
}
public static function farmerVegetables($vegetablesType) {
$className = self::getClassName('vegetables/' . $vegetablesType);
return new $className();
}
}
在这里我并没有写全getClassName,读者可以自行完成,当作一个练习。
这样当要再增加两个产品类时,我们不要修改农场类,只要在xml中加入节点就,农民就知道要生产他了。这样在没有修改源程序的情况下(当然修改了配置文件)实现了系统的扩展。同时由于类名是从getClassName统一生成的,所以以后要增强功能,也可以只修改这个方法就可以了。
在很多框架的工厂模式的应用都使用xml文件,如spring等。下面我们进入我们的Magento中是怎样应用的。
Magento工厂的应用
1、找出Magento中的产品分类。有如下几类:
Helper/model/resourceModel/block
2、Magento谁充当农场类? 由Mage.php中的Mage类充当。
3、谁充当农民?
helper的农民:由helper方法充当
model的农民:由getModel/getSingleton(单例)充当
resourceModel的农民:由getResourceModel/getResourceSingleton(单例)充当
block的农民:由getBlockSingleton(单例)充当,这个农民把生产的任务分给了Mage_Core_Model_Layout的getBlockSingleton农民.
Block还有一个农场类叫:Mage_Core_Model_Layout,里面的农民是:createBlock/getBlockSingleton.
4、xml的应用:
helper 的xml:
代码块11:
<helpers>
<homepage>
<class>Youthor_Homepage_Helper</class>
</homepage>
</helpers>
取得一个helper产品对象$helper = Mage::helper(‘homepage/data’); 相当于找helper节点下的homepage节点,只不过返回的类名是class值加上data,如:Youthor_Homepage_Helper_Data(记得把data第一个字母大写),这与我们分析中的那个xml文件有一点不一样,但原理是一样的,不过这里的文件是根据类名自动引入的。如果是这样$helper = Mage::helper(‘homepage/homepage_test’),那么类名是Youthor_Homepage_Helper_Homepage_Test.由于Magento是基于Zend Frame的,所以它的类名同时表示了他的类文件所在的文件夹和类文件名,Youthor/Homepage/Helper/Homepage/Test.php有了这个规则,Magento才知道自动引入那个文件,以上的分析对model/resourceModel/block同样实用。
block的的xml:
<blocks>
<homepage>
<class>Youthor_Homepage_Blcok</class>
</homepage>
</blocks>
取得一个block产品对象$block = Mage::getBlockSingleton(‘homepage/test’);
model/resourceModel的的xml:
<models>
<homepage>
<class>Youthor_Homepage_Model</class>
<resourceModel>homepage_mysql4</resourceModel>
</homepage>
<homepage_mysql4>
<class>Youthor_Homepage_Model_Mysql4</class>
</homepage_mysql4>
</models>
取得一个model产品对象$model = Mage::getModel(‘homepage/test’); 取得单例model对象:$model = Mage::getSingleton(‘homepage/test’);
取得资源model,$resourceModel = Mage::getResourceModel(‘homepage/test’); 取得单例资源model对象:$resourceModel= Mage::getResourceSingleton(‘homepage/test’); 资源model的类名为Youthor_Homepage_Model_Mysql4_Test,它是从homepage找到homepage节点从而找到resourceModel节点的值homepage_mysql4,再由homepage_mysql4找到homepage_mysql4节点下的class节点的值Youthor_Homepage_Model_Mysql4再加上test(记得第一个字母大写)。
在工厂方法的第一个参数的/前的那个值 相应产品类型下的一个节点,(如homepage),在magento中一般认为是模块名,其实这不是必须的,他只是一个标示工厂方法怎样来找这个节点的名称而已,只要与你的工厂方法的第一个参数的/前的那个值相同就可以了,只不过在Magento中最好与模块名一致.