我正在思考在PHP中实现工厂模式的两种不同方法之一。我不知道这些变种是否有正确的名称,所以现在我打算称它们为内部工厂和外部工厂。
内部工厂:工厂方法在类本身中实现为静态公共方法
<?php
class Foo
{
protected
$loadedProps = false;
public static factory ($id)
{
$class = get_called_class ();
$item = new $class ($id);
if ($item -> loadedProps ())
{
return ($item);
}
}
public function loadedProps ()
{
return ($this -> loadedProps);
}
protected function loadPropsFromDB ($id)
{
// Some SQL logic goes here
}
protected function __construct ($id)
{
$this -> loadedProps = $this -> loadPropsFromDB ($id);
}
}
?>
外部工厂:工厂及其初始化的项目实施为单独的实体
<?php
class Foo
{
protected
$loadedProps = false;
public function loadedProps ()
{
return ($this -> loadedProps);
}
protected function loadPropsFromDB ($id)
{
// Some SQL logic goes here
}
public function __construct ($id)
{
$this -> loadedProps = $this -> loadPropsFromDB ($id);
}
}
abstract class FooFactory
{
public static factory ($id)
{
$item = new Foo ($id);
if ($item -> loadedProps ())
{
return ($item);
}
}
}
?>
现在在我看来每个人都有其优点。
前者允许您隐藏外部世界的构造函数。这意味着您可以通过工厂创建Foo对象的唯一方法。如果无法从DB加载项目的状态,则工厂将返回NULL,您可以在代码中轻松检查。
if ($item = Foo::factory ($id))
{
// ...
}
else
{
// The item failed to load. Handle error here
}
工厂还可以在不做任何修改的情况下创建Foo的任何子类的对象。
然而,它似乎有一些缺点。首先,班级必须实施工厂,这可能会将责任放在真正属于其他地方的班级中。内部工厂版本肯定会产生比外部工厂版本更大的类别。
至于外部工厂,它更干净,因为工厂不属于班级本身,我不必担心班级承担的责任超出应有的范围。外部工厂也可能更适合依赖注入。
然而,它有其自身的一些缺点。首先,要在工厂中构建的项目的构造函数必须是公共的,因为PHP没有包的概念,并且没有类成员的“包”保护级别。这意味着没有什么能阻止编码器只是做新的Foo()并绕过工厂(这可能会使单元测试变得更容易)。
另一个问题是FooFactory只能创建Foo对象,而不能创建任何子类。这可以通过向FooFactory添加另一个参数来指定类名来解决,但是工厂必须进行内部检查,指定的对象类实际上是Foo的后代。
基本上,这两种方法的相对优点是什么,你会推荐哪一种?
另外,如果他们的名字比内部或外部工厂更合适,我想知道它们。
答案 0 :(得分:7)
实际上,工厂已经建立了创意设计模式,因此您可以在GoF Book或Sourcemaking:
中了解他们的目的。这些模式中存在一些重叠,特别是在Factory Method,Abstract Factory和Builder之间,当您不创建对象族而只是一种类型的对象时,区别更加模糊。所以,是的,为简单起见,我们假设内部和外部工厂都是正确的术语。
就个人而言,由于您已经给出的原因,我总是偏向于内部工厂的外部工厂:我可以使用Dependency Injection并且可以separate the responsibilities。由于static methods are death to testability由于它们引入的耦合而可能是considered harmful,所以我会将Factory作为一个真正的对象,而是使用非静态方法。
你提到的两个缺点根本不是缺点。
我想不出为什么我希望prevent a developer来实例化Factory创建的对象。事实上,当Unit-Testing我想要自己创建该对象并用Mocks and Stubs替换任何依赖项时。 I also dont believe that developers should be babysitted too much。考虑到PHP的脚本性质,将ctor从私有更改为公共是很容易被有效阻止的。
至于Factory无法创建其他类的其他问题,那不是真的。工厂的想法实际上是创建各种类型的对象系列。甚至工厂方法也被明确允许创建子类。无论您是使用开关/外壳还是在工厂采用各种方法实现,都取决于您。也没有理由不将工厂与建筑商或拥有工厂的工厂结合起来,这反过来又包含了创建对象的逻辑。因此,无需您提及的任何内部检查(也可以使用类型提示)。
工厂的另一个可行替代方案是使用Dependency Injection Container,例如Symfony Components DIC,并通过该容器管理您的对象。