Skip to content

Instantly share code, notes, and snippets.

@mageekguy
Last active August 29, 2015 14:19
Show Gist options
  • Save mageekguy/6f7339723c12b75bfb2d to your computer and use it in GitHub Desktop.
Save mageekguy/6f7339723c12b75bfb2d to your computer and use it in GitHub Desktop.
A teenager want alcool in east oriented manner!
<?php
/*
Un barman peut servir un verre d'alcool à un client :
1) s'il a plus de 18 ans
2) s'il dispose du ou des alcools nécessaires (dans le cas d'un cocktail) en stock.
*/
interface barman
{
function clientWantAlcoholDrinkOfName(client $client, alcoholDrinkName $alcoholDrinkName);
function clientAgeIs(age $age);
}
interface client
{
function barmanIs(barman $barman);
function newAlcoholDrink(drink $drink);
function ageIsRequiredByBarman(barman $barman);
function alcoolDrinkIsRefusedByBarman(barman $barman);
}
final class quantity
{
private
$value
;
function __construct($value)
{
$this->value = $value;
}
function __toString()
{
return (string) $this->value;
}
}
final class age
{
private
$value
;
function __construct($value)
{
$this->value = $value;
}
function __toString()
{
return (string) $this->value;
}
}
final class alcoholDrinkName
{
private
$value
;
function __construct($value)
{
$this->value = (string) $value;
}
function __toString()
{
return $this->value;
}
}
class drink
{
function __construct(alcoholDrinkName $alcoholDrinkName, quantity $quantity)
{
$this->name = $alcoholDrinkName;
$this->quantity = $quantity;
}
}
class legalBarman implements barman
{
private
$clientAgeIsLegal,
$client
;
function clientWantAlcoholDrinkOfName(client $client, alcoholDrinkName $alcoholDrinkName)
{
(new self)->giveAlcoholDrinkOfNameToClient($alcoholDrinkName, $client);
return $this;
}
function clientAgeIs(age $age)
{
if ($this->client)
{
$this->clientAgeIsLegal = (string) $age >= 18;
}
return $this;
}
private function giveAlcoholDrinkOfNameToClient(alcoholDrinkName $alcoholDrinkName, client $client)
{
$this->client = $client;
$this->client->ageIsRequiredByBarman($this);
if ($this->clientAgeIsLegal)
{
$this->client->newAlcoholDrink(new drink($alcoholDrinkName, new quantity(5)));
}
return $this;
}
}
abstract class teenager implements client
{
private
$age,
$drink
;
function __construct()
{
$this->age = rand(14, 18);
}
function alcoolDrinkIsRefusedByBarman(barman $barman)
{
echo 'Fuck you!' . PHP_EOL;
}
function newAlcoholDrink(drink $drink)
{
echo 'Thanks!' . PHP_EOL;
$this->drink = $drink;
return $this;
}
function ageIsRequiredByBarman(barman $barman)
{
$barman->clientAgeIs(new age($this->age + (18 - $this->age)));
return $this;
}
}
class teenagerWhichLikeMojito extends teenager
{
function barmanIs(barman $barman)
{
$barman->clientWantAlcoholDrinkOfName($this, new alcoholDrinkName('Mojito'));
return $this;
}
}
(new teenagerWhichLikeMojito)->barmanIs(new legalBarman);
@mageekguy
Copy link
Author

Thanks to all for their comments!
To grumpyjames: If the teenager must also provide its religion to a barman, the method religionIsRequiredByBarman(barman $barman) should be add to the client interface, and the method clientReligionIs(religion $religion) should be add to the barman interface. All classes which implements client interface should be updated accordingly, but the developer should only add a new method, not modifying already exists methods. And it's the same for all classes which implements the barman interface. Moreover, the language will throw a warning if one of classes which implements these interfaces does not respect them, so the developer will be notify of any update in the conversational protocol and will be able to add the missing methods. From my point of view, it's pretty near the open/close principle.
Moreover, the conversational aspect imply that the caller has the control: in our context, to forward information to the barman, the client should respect its interface (the client SHOULD provide an age instance, so if the client handle its age differently internally (for example with a simple float), it's not a problem for the barman, and the client can be change its age abstraction without any impact for the barman.
And conversational aspect respect "fire and forget" philosophy: the client instance can not inject its age in the barman instance, it's not a problem for the barman because the barman has a fallback strategy in this case
To TheSecretSquad: it's the barman responsibility to check constraints before serve the client, not the client responsibility, because the client does not know the barman's domain. And a collaborating object is a good idea, but the interface between the client and this collaborating object will be always the barman, so…

@grumpyjames
Copy link

@TheSecretSquad : great! Good to know I'm not totally incomprehensible.

@mageekguy:

"If the teenager must also provide its religion to a barman, the method religionIsRequiredByBarman(barman $barman) should be add to the client interface, and the method clientReligionIs(religion $religion) should be add to the barman interface. All classes which implements client interface should be updated accordingly."

This is the very definition of strong coupling - to add a new feature we have to change both sides. We can't really talk about open/closed with two classes that work so closely together.

Let's compare (excuse the syntax; I haven't written any php for over ten years):

One function before:

// this is in teenager
function barmanIsAvailable($barman) {
     $barman->onDrinkRequest($this, mojito, $this->age);
}

// this is in barman
function onDrinkRequest($client, $drink, $age) {
     if ($age > 18) {
         $client->onDrink(makeDrink($drink)); 
     } else {
         $client->drinkRequestRefused();
     }  
}

one function after

function barmanIsAvailable($barman) {
     $barman->onDrinkRequest($this, mojito, $this->age, $this->religion);
}

function onDrinkRequest($client, $drink, $age, $religion) {
     if ($age > 18 && religion->permitsAlcohol()) { // technically this is not TDA, see note at the end
         $client->onDrink(makeDrink($drink)); 
     } else {
         $client->drinkRequestRefused();
     }  
}

conversation before (I'll skip the implementation)

interface barman {
    onDrinkRequest(client, drink);
    clientAgeIs(age);
}

interface teenager {
    // I'll skip the drink callbacks;
    onAgeRequest(barman);
}

conversation after

interface barman {
    onDrinkRequest(client, drink);
    clientAgeIs(age);
    clientReligionIs(religion);
}

interface teenager {
    // I'll skip the drink callbacks;
    onAgeRequest(barman);
    onReligionRequest(barman);
}

Both approaches change both caller and callee simultaneously, neither case preserves a working system until both teenager and barman have been updated (java wouldn't compile, I imagine php throws a runtime error of some sort).

We might be able to make this open closed if certain bits of ascertaining legality were optional, and we had an abstract way of talking about each bit of verification (although you'd have to use double dispatch to keep this logic inside the barman, unless you are in something loosely typed where method dispatch is dynamic).

re: the conversation suggesting the caller has control. In a pure tell don't ask system, control can only ever be held by the object currently acting on a message. Both approaches end up passing the barman exactly the same amount of information in the end (one could argue that the conversational approach might save an underage drinker having to reveal their religion, but that's not exactly high on my list of things to optimize for...).

Making religion tda

I'm just playing here, this is not meant to be a good design, just showing where this principle can lead if you apply it blindly.

before:

     if ($age > 18 && religion->permitsAlcohol()) { // technically this is not TDA, see note at the end
         $client->onDrink(makeDrink($drink)); 
     } else {
         $client->drinkRequestRefused();
     } 

after

    if ($age > 18 && religion->permitsAlcohol()) { // technically this is not TDA, see note at the end
         $religion->passDrinkIfAllowed($client, makeDrink($drink)); 
     } else {
         $client->drinkRequestRefused();
     }

// religion gains this method
    function passDrinkIfAllowed($client, $drink) {
         if ($this->permitsAlcohol) $client->onDrink($drink) else $client->drinkRequestRefused();
    }

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