Так что я — разработчик 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 Design Patterns и пытаюсь максимально использовать книгу. На этом этапе каждый раз, когда я создаю новое животное, мне нужно будет реализовать метод make sound, который будет отличаться в большинстве случаев.
Таким образом, книга говорит мне, что я должен кодировать интерфейс 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, чтобы автоматически передавать new Bark()
в то время как создание экземпляров Dog
класс, так как он будет лаять, и я не хочу писать это каждый раз, когда я создаю новый экземпляр Dog
объект.
Итак, как мне использовать подобную магию, которую использует Laravel для автоматической передачи объекта Bark при создании экземпляра Dog.
PS: я все еще учусь, поэтому, возможно, я пойду в неправильном направлении, понимая эти принципы. Пожалуйста, ведите меня, если вы знаете лучше.
Прежде всего, небольшая заметка: книга не права. Интерфейс должен называться Audible
или же Vocal
, «Звук» не является реальным словом, и автор должен быть смущен. Кроме того, вызывать переменную с тем же именем, что и интерфейс, довольно плохо.
Другое дело: IoC Laravel на самом деле просто прославленный локатор сервисов, так что он вам здесь не поможет.
Обычно у вас есть два варианта:
Я склонен рекомендовать Auryn в этом случае. Хотя, если вы хотите перепрыгнуть несколько дополнительных обручей и испытываете трудности из-за ограниченной конфигурации, вы также можете использовать DIC Symfony.
Если бы вы использовали Auryn, инициализация вашей собаки и кошки была бы просто:
$injector = new Auryn\Injector;
$dog = $injector->make('Dog');
$cat = $injector->make('Cat');
Библиотека будет самостоятельно искать отражение конструктора для Dog
и обнаружить, что ему также нужно будет создать новый Bark
пример.
Вы можете просто создать фабрику животных (звучит странно) с помощью фабричных методов.
Фабричные классы часто реализуются, потому что они позволяют проекту более точно следовать принципам 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();
Я думаю, что использование IoC здесь было бы хорошим решением.
От Laravel Docs (Контекстная привязка)
Иногда у вас может быть два класса, которые используют один и тот же интерфейс, но вы хотите внедрить разные реализации в каждый класс. Например, когда наша система получает новый Заказ, мы можем захотеть отправить событие через 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.
Контейнер для инъекций зависимостей будет соответствовать вашим потребностям
Я не знаю 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»).