【问题标题】:Does this break open closed principle这是否打破了开放封闭的原则
【发布时间】:2014-06-09 06:11:28
【问题描述】:

我正在尝试创建一个工厂。我希望客户端向 create 方法发送代码,该代码将用于实例化一个用于处理该类型“事物”的类。

代码列表是该类的成员,因为它们永远不应该改变。但是,为了使其更具可测试性,我为 codeMap 数组添加了一个 setter。

这是否打破了开闭原则,如果是,如何正确测试?

<?php

class My_ThingFactory
{
    /**
     * @var array
     */
    private $codeMap = array(
        'A111' => 'My_Thing_ConcreteA'
    );

    public function create($code)
    {
        if (isset($this->codeMap[$code])) {
            return new $this->codeMap[$code];
        }
    }

    public function setCodeMap(array $codeMap)
    {
        $this->codeMap = $codeMap;
    }
}

【问题讨论】:

标签: php design-patterns factory-pattern


【解决方案1】:

开放/封闭原则与扩展一些代码以添加功能有关,而无需修改类的核心行为(即不编辑源代码)。你的类保持它自己的内部,并提供清晰的公共接口来与它们交互。从这个角度来看,不,你没有打破开放/封闭原则。 至少不是表面价值。

但是,话虽如此,我也从您的问题中得到印象,您想知道为您的私人 $codeMap 数组设置一个设置器是否违反了原则。它不是直接的,但是如果另一个开发人员想要更精细地访问$codeMap 数组,那么该实现也很有吸引力。基本上,即时更新此数组的唯一方法是清除它并使用setCodeMap() 重置它。您没有提供将单个代码添加到地图的机制。一旦您发现自己需要更精细地访问此地图,您也会发现自己违反了开放/封闭原则。

考虑到这一点,假设另一个开发人员正在使用您的代码,并且 $codeMap 数组有 20 或 30 个元素;他们必须破解您的核心代码,以便更好地访问该数组。由于无法添加单个代码,因此他们必须创建一个新数组以传递给setCodeMap(),该数组由当前$codeMap 数组以及他们希望添加的任何其他元素组成。在不打开My_ThingFactory 并添加类似以下内容的情况下,没有其他方法(除了对原始数组进行硬编码):

public function getCodeMap()
{
    return $this->codeMap;
}

然后在他们的扩展类中,他们可以执行以下操作:

class AnotherThingFactory extends My_ThingFactory
{    
    public function addCodes(array $newCodes)
    {
        $this->setCodeMap(array_merge($this->getCodeMap(), $newCodes));
    }    
}

但同样,这只能通过进入您的课程并在之前添加所需的功能来实现,这确实打破了打开/关闭原则。您也可以通过简单地创建$codeMap 属性protected 来纠正这个问题,然后扩展类可以在不破解您的代码的情况下完成他们需要做的事情。扩展类也有责任确保他们正确地操作它。

所以回答开放/封闭的问题:如果您打算通过设计将$codeMap 锁定并且不打算以其他方式使用它,那么您很好。但是正如我上面所说,一旦您需要更好地控制$codeMap 数组,您将需要违反原则来这样做。我的建议是集思广益,您希望在类中内置多少工厂管理,并使其成为类核心功能的一部分。

至于测试,我不明白你为什么不能按原样测试这段代码。您可以设置您的代码映射,然后测试使用create() 方法返回的相应实例。

class FactoryTest extends PHPUnit_Framework_TestCase
{

    private $factory;

    public function setUp()
    {
        $this->factory = new My_ThingFactory();
    }

    public function tearDown()
    {
        $this->factory = null;
    }

    public function testMadeConcreteA()
    {
        $this->assertInstanceOf('My_Thing_ConcreteA', $this->factory->create('A111'));
    }

    public function testMadeStealthBomber()
    {
        $this->factory->setCodeMap(array('B-52', 'StealthBomber'));  //Assume the class exists.
        $this->assertInstanceOf('StealthBomber', $this->factory->create('B-52'));
    }

    public function testDidntMakeSquat()
    {
        $this->assertNotInstanceOf('My_Thing_ConcreteA', $this->factory->create('Nada'));
    }

}

【讨论】:

    【解决方案2】:

    开闭原则并不普遍。您需要对哪些可能会改变(开放部分)和哪些不会改变(封闭部分)做出假设。

    由于您使用的是工厂,因此关闭的部分是 create 服务(工厂使该部分关闭)。开放部分是工厂将要创建的东西。工厂允许以后扩展这些东西。

    一个小而重要的一点是,您的模式不是 GoF 工厂,而是Simple Factory。因此,它可能不是利用开闭原则的最强工厂形式。也就是说,如果你添加新的东西来创建,你必须修改类($codeMap 数组)。

    你的问题突出的是,当你说:

    代码列表是该类的成员,因为它们永远不应该改变。

    在我看来,如果您使用的是工厂,代码列表预计会更改。

    至于您的set 函数,它是一个公共方法,因此根据定义是封闭的(否则您不应该透露它)。另一方面,您公开了实现的细节(如Crackertastic's answer 中所述)。您可能更担心使用此方法违反封装。

    我认为一个更简单的解决方案(尽管我在 PHP 中不确定)是用另一个类创建的 $codeArray 来初始化你的工厂。我认为这就是 Kamal Wickamanayake 在他对您的问题的评论中所指的内容。另一种解决方案是添加/删除元素的服务(关闭)(归结为将新条目添加到您的 $codeArray 但以隐藏的方式)。

    【讨论】:

      猜你喜欢
      • 2021-12-15
      • 1970-01-01
      • 2010-12-20
      • 2018-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-08
      相关资源
      最近更新 更多