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

发布于 2024-11-14 12:08:24 字数 967 浏览 3 评论 0原文

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

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

这个想法是创建一个基文档类来保存所有共享信息,同时为每个特定文档类型使用子类。

问题是为不同类型创建正确的类。有两种方法可以区分文档的类型。每个条目都有一个类别元素,我可以在其中找到类型。将使用的另一种方法是通过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);
    }
}

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

编辑: 添加了示例代码

I'm busy parsing xml documents (google docs api) and putting individual documents into objects.

There are different types of documents (documents, spreadsheets, presentations). Most information about these documents is the same, but some is different.

The idea was to create a base document class which holds all the shared information, while using subclasses for each specific document type.

The problem is creating the right classes for the different types. There are two ways to differentiate the type of the document. Each entry has a category element where I can find the type. Another method that will be used is by the resourceId, in the form of type:id.

The most naive option will be to create an if-statement (or switch-statement) checking the type of the entry, and create the corresponding object for it. But that would require to edit the code if a new type would be added.

Now i'm not really sure if there is another way to solve this, so that's the reason I'm asking it here. I could encapsulate the creation of the right type of object in a factory method, so the amount of change needed is minimal.

Right now, I have something like this:

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);
    }
}

So my question is, is there another method I'm not seeing to handle this situation?

Edit:
Added example code

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

煞人兵器 2024-11-21 12:08:24

用您的代码示例更新了答案

这是您的新工厂:

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'));

现在一样注册您的自动加载器,每次您必须为类型编写新的处理程序时,它都会自动额外。

使用责任链

您可能不想在工厂中编写更“动态”的东西,使用处理多个类型的子类。

例如

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']);
    }
}

Updated answer with your code example

Here is your new factory :

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']);
    }
}

I'm not sure that I got exactly your point... To rephrase the question, I understood "how can I create the right object without hardcoding the selection in my client code"

With autoload

So let's start with the base client code

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 !');
       }
    }
}

Now, let's see if we can change this to avoid the if / switch statments (not always necessary, but can be)

We're here gonna use PHP Autoload capabilities.

First, consider the autoload is in place, here is our new Factory

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']);
       }
   }
}

Now we have to add the autoload capability

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

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

And you just have to register your autoloader like

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

Now, everytime you'll have to write a new handler for Types, it will be automatically added.

With chain of responsability

You may wan't to write something more "dynamic" in your factory, with a subclass that handles more than one Type.

eg

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

In the BaseFactory code, you could load all your handlers and do something like

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']);
    }
}
月朦胧 2024-11-21 12:08:24

我同意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);
}

在责任链上:虽然这是一个关于如何编写它的漂亮示例,但我发现责任链意味着所有可能的处理程序必须加载并询问它们是否可以处理该类型。从我的角度来看,生成的代码更干净、更明确,但性能受到影响,复杂性也增加。这就是为什么我会选择动态类名解析。

I agree with Oktopus, there are generally two methods without hardcoding; the first would be to find the classname by dynamically appending strings, and make sure your classes are properly named, the second way would be to load all handler classes, and use the one that says it can handle that type. I'd go for the first. With your example code, this would look something like the following:

<?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);
}

On the chain of responsibility: although that is a beautiful example of how to write it, I find that a chain of responsibility means that all possible handlers have to be loaded and asked if they can handle that type. The resulting code is cleaner and more explicit from my point of view, but there is both a performance hit and added complexity. That's why I'd go for dynamic classname resolution.

独自←快乐 2024-11-21 12:08:24

也许您可以对输入文件应用 xsl 转换(在工厂方法中)。
转换的结果应该是一个统一的 xml 文件,提供有关要使用哪个类的输入?

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

Maybe you could apply a xsl-tranformation on your input file (in the factory method).
The Result of the transformation should then be a uniform xml file giving input about what class to use?

I know this not really better than a lot of if's but this way, you'll at least avoid rewriting your code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文