如何在PHP中自动绑定实现?

4

我是一名Laravel开发者,虽然我已经使用它一段时间了,我喜欢它在表面下自动绑定实现的魔法,通过IoC容器实例化类时。但现在,我正在尝试回到设计模式的基础,并学习事物如何实际工作。

所以,我从动物示例开始:

abstract class Animal
{
    abstract function makeSound();
}

class Dog extends Animal
{
    public function makeSound()
    {
        echo "Bark!\n";
    }
}

class Cat extends Animal
{
    public function makeSound()
    {
        echo "Bark!\n";
    }
}

我正在阅读《Head First设计模式》一书,并尽力充分利用这本书。在此时,每当我创建一个新的动物时,我都必须实现发出声音的方法,而这种方法在大多数情况下都是不同的。

因此,书中告诉我应该编写一个Soundable接口,并在Animal扩展类中实现该接口。

最终我得到了以下代码:

interface Soundable
{
    function sound();
}

class Bark implements Soundable
{
    public function sound()
    {
        return "Bark!\n";
    }
}

class Meow implements Soundable
{
    public function sound()
    {
        return "Meow!\n";
    }
}

class Animal
{
    public $soundable;

    public function __construct(Soundable $soundable)
    {
        $this->soundable = $soundable;
    }

    public function makeSound()
    {
        echo $this->soundable->sound();
    }
}

class Dog extends Animal
{

}

class Cat extends Animal
{

}

function makeAnimal(Animal $animal){
    return $animal; 
}

// Making a dog
$animal = makeAnimal(new Dog(new Bark()));
$animal->makeSound();

// Making a cat
$animal = makeAnimal(new Cat(new Meow()));
$animal->makeSound();

现在当我有另一个会叫或喵喵叫的动物时,我可以简单地实例化该实现并制作一个动物。
现在我的问题是如何告诉PHP在实例化Dog类时自动传递new Bark(),因为它会叫,我不想每次实例化新的Dog对象时都编写它。
所以我应该如何使用类似Laravel使用的魔法来自动传递Bark对象,同时实例化Dog。
PS:我仍在学习,如果您知道更好的方法,请指导我。

1
你的函数 makeAnimal() 是不必要的。只需要这样做:$animal = new Dog(new Bark())。因为你的函数只是返回它所接收到的东西,所以这两种方法是一样的。 - M. Eriksson
在通用情况下,使用工厂与OP一起使用。注入实现接口的实例可以帮助您保持代码的可重用性。全局函数makeAnimal替换为工厂,稍后可以注入一些东西,例如“Owner”或其他内容。 - brzuchal
所以我刚刚查了一下工厂模式。@brzuchal,你的意思是说,我应该有一个方法,它返回一个带有叫声的狗类,这样我就可以调用那个方法并获取狗,而不必再次实例化它? - Rohan
使用工厂,您不需要实例化 new Bark()。每次调用该方法时,您将收到 Dog 实例(而不是类),其中包含 Soundable 对象实例,例如,在 Dog 的情况下为 Bark - brzuchal
基本问题:Dog实例在运行时/请求时是否是单个的,并且具有某些责任,或者可能有很多Dog实例,每个实例都代表自己(它们不相等)? - brzuchal
4个回答

2
首先,需要说明的是:这本书错了。接口应该被称为AudibleVocal。“Soundable”不是一个真正的单词,作者应该感到尴尬。此外,将变量命名为相同的接口名称有点糟糕。
另一件事是:Laravel的IoC实际上只是一个过度美化的服务定位器,所以它并不能真正帮助你。
通常,您有两个选择:
  • 使用工厂(在这种特殊情况下可能会很痛苦和/或棘手)
  • 使用依赖注入容器 - 最好是基于反射的容器
我倾向于推荐在这些情况下使用Auryn。虽然,如果您愿意跳过额外的步骤并忍受有限的配置,也可以使用Symfony的DIC
如果您正在使用Auryn,则狗和猫的初始化将仅为:
$injector = new Auryn\Injector;
$dog = $injector->make('Dog'); 
$cat = $injector->make('Cat'); 

这个库会自动查找Dog的构造函数的反射,并检测到它还需要创建一个新的Bark实例。


我认为这取决于DogCat的目的。这些对象没有命运。对于实例化不是服务的对象,使用DIC并不能完全说服我。如果DogCat只是带有注入依赖项的实体,那该怎么办? - brzuchal
这个例子不是来自书本,我是即兴创作的。没错,你说得对,我应该把它改成Audible。但现在我更关心编程概念而非词汇。总之,这就是我想要的答案。我会研究一下Auryn。感谢你详细的回答。 - Rohan

1
你可以使用工厂方法创建简单的动物工厂(听起来有些奇怪)。

工厂

通常实现工厂类是因为它们使项目更好地遵循SOLID原则,特别是接口隔离和依赖反转原则。

欲了解更多信息,请查看https://www.sitepoint.com/understanding-the-factory-method-design-pattern/ 请记住,如果您想编写一些单元测试,请勿使用静态方法,只需实例化工厂即可,在将来可能需要创建带有某些依赖项的工厂。

<?php
class AnimalFactory
{
    public function createDog() : Dog
    {
        return new Dog(new Bark());
    }
}

$factory = new AnimalFactory();
$dog = $factory->createDog();

我也考虑过通过工厂来解决这个问题。但是我在这里寻找不同的东西。顺便说一下,我没有对你的答案进行投票。我非常感谢任何帮助。 :) - Rohan
通常工厂的作用就是以特定的方式创建某些对象的实例。因此,在Dog的情况下,工厂知道它在叫而不是喵。任何其他的预设设置,例如注入额外的依赖项,都可以注入到工厂中,并且它会知道如何处理它,比如在创建时注入一个新的Owner并获得一个注入了Owner的新Dog - brzuchal

0

我认为在这里使用IoC会是一个好的解决方案。

来自Laravel文档(上下文绑定)

有时您可能有两个使用相同接口的类,但希望将不同的实现注入到每个类中。例如,当我们的系统收到新订单时,我们可能希望通过PubNub而不是Pusher发送事件。 Laravel提供了一个简单,流畅的界面来定义此行为:

$this->app->when('App\Handlers\Commands\CreateOrderHandler')
          ->needs('App\Contracts\EventPusher')
          ->give('App\Services\PubNubEventPusher');

因此,您在Laravel中的服务提供程序可能如下所示

public function register()
{
    $this->app->when(Dog::class)->needs(Soundable::class)->give(Bark::class);
    $this->app->when(Cat::class)->needs(Soundable::class)->give(Meow::class);
}

然后你可以使用 Laravel 的依赖注入容器来实例化你的类

$dog = app()->make(Dog::class);
$dog->sound(); // Bark!    
$cat = app()->make(Cat::class);
$cat->sound(); // Meow!

总结一下,并回答您的问题:

那么我该如何使用类似于Laravel使用的魔法,在实例化Dog时自动传递Bark对象。

依赖注入容器将满足您的需求


是的,这就是我的问题。那么你的意思是,我应该拉取IoC容器包并在我的代码中开始使用它,对吗?我想要理解的是IoC容器首先是如何工作的,并且尝试自己复制它,不是因为我认为我会做得更好,而是因为我想要了解底层发生了什么。 - Rohan
哦,你提供了一个维基链接。谢谢。我会研究一下并尝试实现它。 - Rohan
PHP中的依赖注入依赖于Reflection。简单来说,在每个类被实例化之前,容器会检查它的构造函数/方法并尝试解决类的依赖关系。 - Skysplit
@Skysplit 仅使用工厂服务即可。 您没有使用 DIC 实例化实体,对吧? 简单的 new Bark() 可以以某种方式由 DIC 处理,然后会有一个单一的实例,但是不会创建实体 Dog!并且我认为在现实世界中,并不是每个 Dog 都拥有共享的 Bark - brzuchal
@brzuchal 对于这个特定的例子,工厂服务可能是一个解决方案,但这只是一个例子,作者问道“那么我该如何使用类似 Laravel 的魔法,在实例化 Dog 时自动传递 Bark 对象。”。我的帖子直接涉及到这个问题:Laravel 使用 DI 容器,这就是你可以像 Laravel 一样实例化类的方法。 - Skysplit
显示剩余5条评论

-1

我不熟悉Laravel,但是依据我的看法,你的代码应该是:

interface Soundable
{
    function sound();
}

class Animal
{
    protected name;

    public function __construct($name) {
        $this->name=name;
    }

    public function get_name() { return $this->name; }
}

class Dog extends Animal implements Soundable
{
    public function sound()
    {
        return "Bark!\n";
    }
}

class Cat extends Animal implements Soundable
{
    public function sound()
    {
        return "Meow!\n";
    }
}

// Making a dog
$animal = new Dog("Fido");
$animal->sound();

// Making a cat
$animal = new Cat("Fuffi");
$animal->sound();
echo $animal->get_name(); // print "Fuffi"

所以,你可以看到,如果你只想实现接口,甚至不需要 Animal 类。事实上,我仅仅保留它是为了说明它仍然有用,以便实现一些对所有派生类有用的方法(例如,get_name,返回受保护的属性“name”)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接