我正在尝试创建一个工厂。我希望客户端向create方法发送一个代码,该方法将用于实例化一个用于处理该类型“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;
}
}
答案 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
添加新条目但以隐藏方式添加)。