Skip to content

Instantly share code, notes, and snippets.

@mhlavac
Last active August 29, 2015 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mhlavac/465a0efc33ddaae61218 to your computer and use it in GitHub Desktop.
Save mhlavac/465a0efc33ddaae61218 to your computer and use it in GitHub Desktop.
Container vs Composite vs Chain
<?php
interface Greeter
{
public function greet($name);
}
class ChainGreeter implements Greeter
{
private $greeters = [];
public function addGreeter(Greeter $greeter)
{
$this->greeters[] = $greeter;
}
public function greet($name)
{
foreach ($this->greeters as $greeter) {
$greeter->greet($name);
}
}
}
class StringGreeter() implements Greeter
{
private $sentence;
public function __construct($sentence)
{
$this->sentence = $sentence;
}
public function greet($name)
{
spritnf($sentence, $name);
}
}
$chainGreeter = new ChainGreeter();
$chainGreeter->add(new StringGreeter('Hello %s'));
$chainGreeter->add(new StringGreeter('Good morning %s'));
$chainGreeter->greet('John Doe');
@mhlavac
Copy link
Author

mhlavac commented Jul 10, 2015

The question is what the name of ChainGreeter should be? Should it be Chain, Composite, Container?

From https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern I would expect chain to to behave like example below, but still when I search for Chain usage in my project using Symfony, I get a lot of chains that behave exactly like ChainGreeter in example:

new StringGreeter('Hello %s', new Greeter('Good morning %s'));

Container sounds more like a DI and I never saw anyone use Composite even though it feels like Composite.

What do you think?

@Podbi
Copy link

Podbi commented Jul 10, 2015

From what I know I would be pick Chain because it's Chain of Responsibility pattern. Container would be confusing and I think that Composite is something different, isn't it?

@JakubZapletal-awin
Copy link

<?php
interface Greeter
{
    public function greet($name);
}

class StringGreeter() implements Greeter
{
    private $sentence;

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

    public function greet($name)
    {
        spritnf($sentence, $name);
    }
}

class GreeterCollection
{
    private $greeters = [];

    public function addGreeter(Greeter $greeter)
    {
        $this->greeters[] = $greeter;
    }

    public function getArray()
    {
        return $this->greeters;
    }
}

class GreeterCommand
{
    private $greeterCollection;

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

    public function execute($name)
    {
        foreach ($this->greeterCollection->getArray() as $greeter) {
            $greeter->greet($name);
        }
    }
}

$greeterCollection = new GreeterCollection();
$greeterCollection->add(new StringGreeter('Hello %s'));
$greeterCollection->add(new StringGreeter('Good morning %s'));

$greeterCommand = new GreeterCommand($greeterCollection);
$greeterCommand->execute('John Doe');

I would rather use the design above, responsibilities are kept separately then.
P.S.: GreeterCollection should have a design of iterator, but I didn't focus on that.

@mhlavac
Copy link
Author

mhlavac commented Jul 10, 2015

Jakub I like your approach, it's a nice way how to separate responsibilities and btw. another name for original Chain can be also Collection ;-)

@JakubZapletal-awin
Copy link

@mhlavac I see Collection as a collection of objects being on the same level, but the Chain is an object inside an object inside an object (auto na auta na auta) :-).

@mhlavac
Copy link
Author

mhlavac commented Jul 10, 2015

@jakubzapletal exactly, or at least if there is a foreach that goes over inner objects it ends as soon as it finds one. Like this example from ChainUserProvider:

    /**
     * {@inheritdoc}
     */
    public function loadUserByUsername($username)
    {
        foreach ($this->providers as $provider) {
            try {
                return $provider->loadUserByUsername($username);
            } catch (UsernameNotFoundException $notFound) {
                // try next one
            }
        }

        $ex = new UsernameNotFoundException(sprintf('There is no user with name "%s".', $username));
        $ex->setUsername($username);
        throw $ex;
    }

@JakubZapletal-awin
Copy link

@mhlavac accurate example of Chain of Responsibilities

@pavel-dohnal-momentumft

@dmaicher
Copy link

Haha the naming problem :)

Would agree with @pavel-dohnal-momentumft. To me this seems like the composite pattern.

So CompositeGreeter? 😄

@mhlavac
Copy link
Author

mhlavac commented Jul 10, 2015

@pavel-dohnal-momentumft I also thought it's a composite, because this is how usuall composites are implemented.

So in the end I came with 2 ideas.

Command where we can use just __invoke. So it feels almost like named closure.

class GreetCommand
{
    /**
     * @var Greeter[]
     */
    private $greeters;

    public function __construct(array $greeters = [])
    {
        $this->greeters = $greeters;
    }

    public function __invoke($name)
    {
        foreach ($this->greeters as $greeter) {
            $greeter->greet($name);
        }
    }
}

And simple Greeters, because it's just multiple Greeters anyway.

class Greeters
{
    /**
     * @var Greeter[]
     */
    private $greeters;

    public function __construct(array $greeters = [])
    {
        $this->greeters = $greeters;
    }

    public function greet($name)
    {
        foreach ($this->greeters as $greeter) {
            $greeter->greet($name);
        }
    }
}

What do you think? ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment