关于在另一个类中使用类对象,最佳做法是什么?传递类_construct语句中的类对象还是创建一个新的类对象?
示例1:
class Foo {
private $bar;
public function __construct($bar){
$this->bar = $bar;
}
}
或示例2:
class Foo {
private $bar;
public function __construct(){
$this->bar= NEW bar;
}
}
我知道显然已经理所当然地认为类文件必须已经包含在其他地方,并且在第一个实例中,这种类型的类对象需要已经存在,但我想知道它的优点是什么每个方法都是,因为我有很多类需要编写使用数据库对象的类,我需要最好的方法将它传递给类。还有第三种选择比这两种选择更好吗?
根据我的理解,第一个的优点可能是少一些编码行,而在DB的情况下,没有创建新的连接。第二个可能会更好,但因为它更自包含?无论如何我以为我会问专家。
答案 0 :(得分:13)
第一个。 (这种方法称为依赖注入)。
构造函数要求需要的任何对象才能工作。这样,从单独的方法(他们需要什么,他们返回什么),它做了什么,它是非常清楚的。甚至没有查看源代码。
改进代码的方法是在方法中引入类型提示:
class Foo {
private $bar;
public function __construct(Bar $bar){
$this->bar = $bar;
}
}
因此只能传入Bar
个对象。
*免责声明:在此答案的表现中没有小猫受到伤害
答案 1 :(得分:10)
你应该选择选项1,因为这是依赖注入的最简单形式。
在选项1中:
答案 2 :(得分:8)
总的来说,由于How to Think About the “new” Operator with Respect to Unit Testing中列出的原因,我会与DI人群联系:
但是依赖注入如此重要的原因是,在单元测试中,您希望测试应用程序的一小部分。要求是您可以独立于整个系统构建应用程序的这个小子集。如果将应用程序逻辑与图形构造混合(新运算符),除了应用程序中的叶节点之外,任何其他内容都不可能进行单元测试。
将代码分离为创建者图表和协作者图表有助于保持代码的可维护性和可测试性。更好的是,针对接口的代码将很容易与其他实现交换具体实现。这使得更改代码变得简单,因为您不必花费时间来搜索硬编码的依赖项。
例如,假设您的Bar
需要记录器,您可以
class Foo
{
private $logger;
public function __construct(LogInterface $logger)
{
$this->logger = $logger;
}
}
然后传递实现LogInterface
的任何具体实现,如数据库记录器或StdOutLogger,或者包含这两者的复合记录器。另一个例子是Database对象。您可以在引导程序中创建一次,然后将其传递给使用它的对象。
如有疑问,请使用依赖注入。
但是,你并不总是要注入东西。这取决于对象(您的条形图)是否为Injectable or a Newable。引用Misko Hevery:
Injectable类可以在其构造函数中请求其他Injectable。 [...]注射器往往有接口,因为我们可能不得不用对测试友好的实现来替换它们。但是,Injectable永远不能在其构造函数中请求非Injectable(Newable)。这是因为DI框架不知道如何生成Newable。 [...] Newables的一些例子是:Email,MailMessage,User,CreditCard,Song。如果你保持这种区别,你的代码将很容易测试和使用。如果违反此规则,您的代码将很难测试。
简而言之,当您拥有无法合理注入的内容时,因为它基于用户提供的或运行时信息,您可以new
它。对于值对象和数据类型尤其如此:
class Foo
{
private $storage;
public function __construct()
{
$this->storage = new SplObjectStorage;
}
}
注入SplObjectStorage
没有意义。它只是一种数据类型。
答案 3 :(得分:0)
其他人已经回答了你的问题 - 绝对采用第一种方法,即使用依赖注入。
我只想填写另一个您可能不知道的流行替代方案:使用依赖注入容器。
一个很好的简单例子是Pimple;由Symfony框架背后的人Fabien Potencier开发。
示例3:
# In a bootstrap file...
require_once '/path/to/Pimple.php';
$container = new Pimple();
$container['bar'] = function ($c) {
return new Bar(/** If bar has dependencies, put them here **/);
};
$container['foo'] = function ($c) {
return new Foo($c['bar']);
};
# You'd still inject the service using DI, because it's not good
# practice for your objects to rely directly on the container
class Foo {
private $bar;
public function __construct($bar){
$this->bar = $bar;
}
}
# The difference would be how you call Foo...
$foo = $container['foo'];
# So now your code doesn't need to know about the dependencies, and it's easy
# to change them at any time by making a single change in your configuration
Symfony2使用更健壮的Container,即also available as a standalone compenent。但是,除非你正在开发一个大规模的应用程序,否则Pimple可能是你最好的选择。
答案 4 :(得分:0)
我会说使用第一个选项。在这样做时,我会说对抽象进行编程比编写实现更好。
您的第一个选项是聚合形式,而第二个选项是合成形式。使用抽象获得的好处是,使用类FOO的客户端类将能够让FOO根据它决定发送到构造函数的接口的特定实现来执行某些活动。
下面的C#示例
class Foo {
private IBar bar;
public Foo(IBar obj){
this.bar = obj;
}
public void myFooMethod()
{
bar.ExecuteMethod();
}
}
调用FOO的课程
public class MyCallingClass
{
public void CallFooMethod()
{
IBar bar1Obj = new BAR1();
Foo fooObject = new Foo(bar1Obj);
fooObject.ExecuteMethod();
//or
IBar bar2Obj = new BAR2();
fooObject = new Foo(bar2Obj);
fooObject.ExecuteMethod();
//or
IBar bar3Obj = new BAR3();
fooObject = new Foo(bar3Obj);
fooObject.ExecuteMethod();
}
}
现在我的调用类可以将任何IBar实现传递给FOO类。
希望这会有所帮助。
答案 5 :(得分:0)
好的,Dependency Injection很精彩,很有帮助,saves kittens,所以我不打算详细讨论。
相反,我建议你实施两种解决方案:
class Foo {
private $bar;
public function __construct($bar = null){
$this->bar = isset($bar) ? $bar : new Bar();
}
}
这样,您可以默认使用默认类,如果需要更改功能,也可以这样做。