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

How to automatically bind implementations in PHP?

所以我是一名 laravel 开发人员,尽管我已经使用它一段时间了,但我喜欢表面下发生的魔法,它在实例化时如何自动绑定实现 classes 通过 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 Design Patterns 并且我正在尝试充分利用这本书。此时每次我创建一个新的 Animal 时,我都必须实现 make sound 方法,这在大多数情况下会有所不同。

所以这本书告诉我应该编写一个 Soundable 接口,然后在 Animal 扩展 classes 中实现该接口。

我终于想到了这样的东西:

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 class 时自动传递 new Bark() 因为它会吠叫而我不想每次我实例化一个新的 Dog 对象时都要写它。

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

PS:我还在学习中,所以我在理解这些原则时可能会完全走错方向。有知道的请多指教

您可以使用工厂方法创建简单的动物工厂(听起来很奇怪)。

工厂

Factory classes are often implemented because they allow the project to follow the SOLID principles more closely. In particular, the interface segregation and dependency inversion principles.

有关详细信息,请查看此处 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();

我不知道 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 class。事实上,我维护它只是为了证明它仍然有用,以便实现一些对所有下降 classes 有用的方法(例如,get_name,即 return 受保护的属性 "name").

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

来自 Laravel Docs(上下文绑定)

Sometimes you may have two classes that utilize the same interface, but you wish to inject different implementations into each class. For example, when our system receives a new Order, we may want to send an event via PubNub rather than Pusher. Laravel provides a simple, fluent interface for defining this behavior:

$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 的依赖注入容器实例化您的 class

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

总结一下,并与您的问题相关:

So how do I use a similar magic that Laravel uses to pass the Bark object automatically while instantiating Dog.

Dependency Injection Container 将满足您的需求

首先小提示:书错了。该接口应称为 AudibleVocal。 "Soundable" 不是一个真正的词,作者应该感到尴尬。此外,调用与接口同名的变量有点不好。

另一件事是:Laravel 的 IoC 实际上只是一个美化的服务定位器,所以它在这里不会真正帮助你。

通常,您有两种选择:

  • 使用工厂(在这种特殊情况下会很痛苦 and/or 棘手)
  • 使用依赖注入容器——最好是基于反射的容器

我倾向于推荐Auryn in these case. Though, if you are willing to jump few additional hoops and suffer through limited configuration, you can also use Symfony's DIC

如果您使用的是 Auryn,则您的狗和猫的初始化将只是:

$injector = new Auryn\Injector;
$dog = $injector->make('Dog'); 
$cat = $injector->make('Cat'); 

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