如何在创建对象时应用开闭原则

时间:2011-06-09 08:40:43

标签: php open-closed-principle

我正在忙于解析xml文档(google docs api)并将单个文档放入对象中。

有不同类型的文档(文档,电子表格,演示文稿)。关于这些文件的大多数信息是相同的,但有些是不同的。

我们的想法是创建一个基础文档类,它包含所有共享信息,同时为每种特定文档类型使用子类。

问题是为不同类型创建正确的类。有两种方法可以区分文档的类型。每个条目都有一个category元素,我可以在其中找到类型。将使用的另一种方法是resourceId,格式为type:id

最天真的选择是创建一个if语句(或switch语句)来检查条目的类型,并为它创建相应的对象。但如果要添加新类型,则需要编辑代码。

现在我不确定是否还有其他方法可以解决这个问题,所以这就是我在这里问的原因。我可以在工厂方法中封装正确类型的对象的创建,因此所需的更改量很小。

现在,我有这样的事情:

public static function factory(SimpleXMLElement $element)
{
    $element->registerXPathNamespace("d", "http://www.w3.org/2005/Atom");
    $category = $element->xpath("d:category[@scheme='http://schemas.google.com/g/2005#kind']");

    if($category[0]['label'] == "spreadsheet")
    {
        return new Model_Google_Spreadsheet($element);
    }
    else
    {
        return new Model_Google_Base($element);
    }
}

所以我的问题是,是否有其他方法我没有看到处理这种情况?

修改 添加了示例代码

3 个答案:

答案 0 :(得分:4)

使用您的代码示例

更新了答案

这是你的新工厂:

public static function factory(SimpleXMLElement $element)
{
    $element->registerXPathNamespace("d", "http://www.w3.org/2005/Atom");
    $category = $element->xpath("d:category[@scheme='http://schemas.google.com/g/2005#kind']");
    $className = 'Model_Google_ '.$category[0]['label'];
    if (class_exists($className)){
       return new $className($element);
    } else {
        throw new Exception('Cannot handle '.$category[0]['label']);
    }
}

我不确定我是否完全理解你的观点......为了重新解释这个问题,我理解“如果不在我的客户代码中硬编码选择,我怎样才能创建正确的对象”

使用自动加载

让我们从基本客户端代码

开始
class BaseFactory
{
    public function createForType($pInformations)
    {
       switch ($pInformations['TypeOrWhatsoEver']) {
           case 'Type1': return $this->_createType1($pInformations);
           case 'Type2': return $this->_createType2($pInformations);
           default : throw new Exception('Cannot handle this !');
       }
    }
}

现在,让我们看看我们是否可以改变它以避免if / switch语句(并非总是必要,但可以)

我们在这里将使用PHP自动加载功能。

首先,考虑自动加载到位,这是我们的新工厂

class BaseFactory
{
    public function createForType($pInformations)
    {
       $handlerClassName = 'GoogleDocHandler'.$pInformations['TypeOrWhatsoEver'];
       if (class_exists($handlerClassName)){
           //class_exists will trigger the _autoload
           $handler = new $handlerClassName();
           if ($handler instanceof InterfaceForHandlers){
               $handler->configure($pInformations);
               return $handler;
           } else {
               throw new Exception('Handlers should implements InterfaceForHandlers');
           }
       }  else {
           throw new Exception('No Handlers for '.$pInformations['TypeOrWhatsoEver']);
       }
   }
}

现在我们必须添加自动加载功能

class BaseFactory
{
    public static function autoload($className)
    {
        $path = self::BASEPATH.
                $className.'.php'; 

        if (file_exists($path){
            include($path); 
        }
    }
}

您只需注册自动加载器

即可
spl_autoload_register(array('BaseFactory', 'autoload'));

现在,每次您必须为类型编写新的处理程序时,它将自动添加。

具有责任链

你可能不想在你的工厂里写一些更“动态”的东西,一个子类可以处理多个Type。

例如

class BaseClass
{
    public function handles($type);
}
class TypeAClass extends BaseClass
{
    public function handles($type){
        return $type === 'Type1';
    }
}
//....

在BaseFactory代码中,您可以加载所有处理程序并执行类似

的操作
class BaseFactory
{ 
    public function create($pInformations)
    {
        $directories = new \RegexIterator(
            new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator(self::BasePath)
            ), '/^.*\.php$/i'
        );

        foreach ($directories as $file){
            require_once($fileName->getPathName());
            $handler = $this->_createHandler($file);//gets the classname and create it
            if ($handler->handles($pInformations['type'])){
                return $handler;
            }
        }
        throw new Exception('No Handlers for '.$pInformations['TypeOrWhatsoEver']);
    }
}

答案 1 :(得分:1)

我同意Oktopus,通常有两种没有硬编码的方法;第一种方法是通过动态附加字符串来查找类名,并确保正确命名类,第二种方法是加载所有处理程序类,并使用表示它可以处理该类型的类。我会去第一个。使用示例代码,它将类似于以下内容:

<?php
public static function factory(SimpleXMLElement $element)
{
    $element->registerXPathNamespace("d", "http://www.w3.org/2005/Atom");
    $category = $element->xpath("d:category[@scheme='http://schemas.google.com/g/2005#kind']");

    $classname = sprintf( 'Model_Google_%s', ucfirst( strtolower( (string) $category[0]['label'] ) ) );

    if( class_exists( $classname, true /* true means, do autoloading here */ ) ) {
        return new $classname( $element );
    }
    return new Model_Google_Base($element);
}

在责任链上:虽然这是一个如何编写它的一个很好的例子,但我发现责任链意味着必须加载所有可能的处理程序并询问它们是否可以处理那种类型。从我的角度来看,生成的代码更清晰,更明确,但同时存在性能损失和复杂性增加。这就是为什么我会选择动态类名解析。

答案 2 :(得分:0)

也许您可以在输入文件中应用xsl-tranformation(在工厂方法中)。 那么转换的结果应该是一个统一的xml文件,给出关于要使用哪个类的输入?

我知道这并不比很多if更好,但这样,你至少可以避免重写你的代码。