3

So I am a laravel developer and even though I have worked with it for a while now, and I love how the magic happens beneath the surface, how it automatically binds implementations when instantiating classes via the IoC container, right now I am trying to go to the basics of design patters and learn how things actually work.

So I started with the Animal example:

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";
    }
}

So I am reading Head First Design Patterns and I am trying to make the most of the book. At this point every time I create a new Animal, I will have to implement the make sound method which will differ in most cases.

So the book tells me that I should code a Soundable interface and then have implementations of that interface in the Animal extended classes.

I finally came up with something like this:

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();

So now when I have another animal that barks or meows, I can simple instantiate that implementation while making an animal.

Now my question is how do I tell PHP to automatically pass the new Bark() while instantiating the Dog class since it will bark and I don't want to write it every time I instantiate a new Dog object.

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

PS: I am still learning so I might be going in the wrong direction altogether while understanding these principles. Please guide me if you know better.

4

4 回答 4

2

First of all, a small note: the book is wrong. The interface should be called Audible or Vocal. "Soundable" is not a real word an the author should be embarrassed. Also, calling a variable in the same name as an interface is kinda bad.

Another thing is: the Laravel's IoC is actually just a glorified service locator, so it wouldn't really help you here.

Usually, you would have two options:

  • use a factory (which in this particular case would painful and/or tricky)
  • use a dependency injection container - preferably a reflection-base one

I tend to recommend 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.

If you were using Auryn, initialization of your dog and cat would be just:

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

The library would on its own look up the reflection of the constructor for Dog and detect that it will also need to create a new Bark instance.

于 2016-08-31T21:44:08.483 回答
1

You can create simply animal factory (sounds strange) with factory methods.

Factories

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.

For more information look here https://www.sitepoint.com/understanding-the-factory-method-design-pattern/ Just remember if you want to write down some unit test don't use static methods, just instantiate factory, there may be need in future to create factory with some dependencies.

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

$factory = new AnimalFactory();
$dog = $factory->createDog();
于 2016-08-31T10:05:33.333 回答
0

I think using IoC here would be good solution.

From Laravel Docs (Contextual binding)

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');

So your service provider in laravel could look like

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);
}

And then you could instantiate your class using Laravel's dependency injection containter

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

So summing it up, and relating to your question:

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

Dependency Injection Container will suit your needs

于 2016-08-31T12:05:07.310 回答
-1

I don't know laravel, but in my opinion your code should have been:

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"

So, you can see that if you want only to implement the interface, you don't even need the Animal class. In fact, I have mantained it only to shot that it can still be useful in order to implement some method that can be useful to all descending classes (for example, get_name, that return the protected attribute "name").

于 2016-08-31T10:06:09.100 回答