这是否打破了封闭原则

时间:2014-06-06 16:29:36

标签: php design-patterns factory-pattern

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

代码列表是类的成员,因为它们永远不会改变。但是,为了使它更易于测试,我为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;
    }
}

2 个答案:

答案 0 :(得分:0)

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

然而,也就是说,我也从你的问题中得到了一个印象,你是想知道你的私人$codeMap数组的setter是否违反了原则。它并不是直接的,但如果另一个开发人员想要对$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'));
    }

}

答案 1 :(得分:0)

开放封闭原则并不普遍。你需要假设可能会发生什么变化(开放部分)和什么不变(封闭部分)。

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

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

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

时,你似乎与开放原则相矛盾
  

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

在我看来,如果你正在使用工厂,代码列表预期要改变。

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

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

相关问题