Skip to content

Instantly share code, notes, and snippets.

@mageekguy
Last active August 29, 2015 14:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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);
@TheSecretSquad
Copy link

This is really cool and funny! I think you did a good job with the design and object communication. I'm still learning and experimenting with East, so I'm not sure if my interpretation of East is accurate yet. I can offer a few notes based on some conversations I've had with James and on some ideas I have. Keep in mind this is all my opinion, and in the end I think you should do what makes sense to you.

One thing James and I discussed while working on our East chess project is past tense naming of methods similar to your alcoholDrinkIsAskedByClient. I started thinking of methods as events that objects respond to, so I started naming them in the past tense. James pointed out that we should try to keep method names in the present tense and active voice. Kind of like you're saying to the object, "do this thing" or "make this happen".

For example, I would imagine a client would want to "order a drink", so we might pass a barman object to the client. Instead of saying barman->alcoholDrinkIsAskedByClient($alcoholDrinkName, $client); it might be better to say to the barman, barman->orderDrinkAsClient($alcoholDrinkName, $client);.

Naming is something I've had the most difficulty with. You can read this method two different ways. I read this as the client is ordering a drink and the barman is responding. On the other hand, this might look to some like we're telling the barman to order a drink, but the barman is not ordering a drink (he's making a drink order happen). I've had trouble deciding how to phrase messages.

Maybe something like barman->fulfillDrinkOrderFromClient($alcoholDrinkName, $client);, or barman->submitDrinkOrderAsClient($alcoholDrinkName, $client);, or barman->requestDrinkAsClient($alcoholDrinkName, $client);.

Same with client->newAlcoholDrink($drink). It might be better as client->serveDrink($drink) because that's what the barman is doing, serving a drink to the client. Who knows, maybe the barman doesn't like the client and serves him an old, stale drink instead of a new one?

On a side note, while working with James on East chess, we use a method naming convention adopted from Smalltalk. I don't really know Smalltalk, but this method would look something like order:drink as:client. For C-style languages (we're using Java), we name the methods with the last preposition in the method name referring to the last parameter, so instead of saying orderDrinkAsClient($drink, $client), we shorten it to just orderDrinkAs($drink, $client). It doesn't translate perfectly to the C-style syntax, but I think it's pretty readable and helps to shorten method names. Leaving the "client" in doesn't hurt either. YMMV.

Another thing I can talk about is also related to naming and has to do with implied context. By implied context I mean, what do our message names imply about an object that receives a message? It has to do with the verbs and nouns we include in message names. What I'm thinking is: Messages should be verbs or verbs with prepositional phrases. Prepositional phrases include nouns. If messages include nouns, then they should only be nouns that indicate parameters, or finite and inherent properties of the receiving object.

Take for example the message client->ageIsRequiredByBarman($barman);. Lets rename this first to an active/present tense message. client->tellAgeTo($barman);. Now we have a verb "tell" and a prepositional phrase "to barman". Barman is implied in the parameter, but it wouldn't hurt to say tellAgeToBarman, because like I said before it indicates a parameter. The barman is already part of our context, so it's ok.

Let's look at the nouns in the message. We have "Age". Why is the client telling the barman its age? We want to let the barman know if the client can legally drink. Is telling your age to the barman an inherent property of the domain of clients interacting with a barman? I think we can all agree it is. Is age the only thing that makes you legally able to drink at a bar (is it finite?)? There may be other things about a client that would make it illegal for a client to drink at a bar. In real life the barman would know these things, but in OOP the objects with the knowledge should make the decisions.

For example, it's illegal to serve alcohol to someone who is visibly drunk. We would need to change the barman to request a client's drunkenness. We would continue to change the barman's implementation and the client's interface every time we needed to add something to determine client legality.

Instead, since clients know their state (whether they are a certain age, or are drunk) then we can let the clients decide if they are legal and tell the barman if so.

class Teenager implements client
{
    function reportLegalityTo($barman)
    {
        // teenagers can't drink
        $barman->reportIllegalClient();
        return $this;
    }
}
class SomePerson implements client
{
    private
        $age,
        $isVisiblyDrunk
    ;

    function reportLegalityTo(barman $barman)
    {
        if($age < 21 || $isVisiblyDrunk)
            $barman->reportIllegalClient();
        else
            $barman->reportLegalClient();

        return $this;
    }
}

One way to think about it is in real life we show our ID to the barman who reads our information and determines if we are legal. The barman may also assess other things about us like looking at us to see if we are visibly drunk. In a perfect world where nobody lies, clients can just tell the barman "I can legally drink" and expect the barman to believe it. OOP can mimic this perfect world. It's best when objects share as little information as possible and it's easier to share less if objects always communicate honestly, and collaborating objects always trust them.

This way the barman object can serve drinks anywhere in the world and the legality of clients is determined by the local clients.

// It's illegal to feed alcohol to a moose
// in Fairbanks, Alaska (regardless of age)
class Moose implements client
{
    private 
        $location;

    function reportLegalityTo(barman $barman)
    {
        if($location == "Fairbanks, Alaska")
            $barman->reportIllegalClient();
        return $this;
    }
}

What do you think? Any thoughts?

@syrm
Copy link

syrm commented Apr 27, 2015

I like your naming convention.
But about the last example, my opinion is the responsability of barman to know when a customer can drink or not, not the customer to tell to the barman.

A customer can lie to the barman, but its still the barman responsability to serve drink or not.

@mageekguy
Copy link
Author

Firstly, thanks for your comments which is very helpful for us (on Freenode > ##east) to discuss and debate about different east implementation.
Secondly, i have a lot feedback about your comment.
About past tense, i have read and re-read your discussion with James about that, but i'm prefer an another paradigm (and your article about your chess is the source of my idea): OOP is declarative, not imperative.
In my opinion "I know what I want and I trust you to do your part" mean that the caller should indicate its requirements and only its requirement to do its job, in a declarative manner (Alan Kay says something like that in the early history of Smalltalk).
The caller (aka the barman) known nothing about the client, it know just its interface, and this interface can not say anything about client's capabilities.
When you say $client->tellAgeTo($barman), the client SHOULD tell its age to the barman, it's a command, not a requirement. If you say $client->ageIsRequiredByBarman($barman), it's a requirement, and the abstraction level is more higher. To paraphrase you (respectfully ;)), with $client->tellAgeTo($barman), we could predict the behavior of the method. However, with ``$client->ageIsRequiredByBarman($barman), we can not expect anything from the client, because all semantic in the message is related to the barman. Same for $barman->orderDrinkAsClient($drink, $client)`: you expect that a barman accept an order from a client, so your "black box" disappear (or is grey instead of black).
About implied context, we have an interesting debate about it since two weeks on IRC on the ##east channel. I'm agree that the client can tell its age to the barman when it receive a message `$client->barmanIs($barman)`. However, i think that it's a too big responsibility for the client: it SHOULD know all barman's requirements, and it should be ALWAYS aware of these requirements. So, in the future, if the barman want to know if the client is drunk before serve him, as a developer, you should be aware that you should update the client's code accordingly.
With my implementation, the `client` and the `barman` interface established a contract between the two classes, and if the barman need more informations to do its joj, the client interface is updated accordingly and the language can throw an error if the client does not implement it correctly.
Any feedback about that?

@TheSecretSquad
Copy link

I agree with you that OOP is declarative and not imperative. My suggestion for $client->tellAgeTo($barman) was definitely incorrect. Good catch.

Now, $barman->orderDrinkAsClient($drink, $client) I'm not so sure about. This is one of those cases where I've had trouble deciding if I'm phrasing the message correctly. Like you said, "orderDrink" can be interpreted differently by the reader. Are we commanding the barman to order a drink?

What I often do is imagine the object receiving the message doesn't exist, so in this case I pretend the barman is gone. I say to myself, "if I were a client in a bar, something I would do is order a drink". That's how I rationalize that something a client object would do is send an orderDrink message. Maybe the client's order is fulfilled, maybe the bar is out of rum and can't make Mojitos, maybe the client is a teenager and can't legally receive the drink.

It makes sense to me that a client object can place an order for a drink without having any expectation of the receiver of the message. orderDrinkAs($drink, $client) I think also works. When a client orders a drink at a bar, they must identify themselves to the barman so he/she knows who to send the order to, or who to tell if the client can't be served for whatever reason.

I typically force myself to ignore the receiver of messages, viewing them as incidental. I look at messages as a set of tasks in a closed system performed by the calling object. Some object needs to respond to the message though. In this case we've defined a receiving interface called barman because it's familiar in the domain. If we decided our domain was not just bars but restaurants, we might need to change barman to server because we can order drinks not just from the barman, but from anyone on the waitstaff.

Despite everything I just said, I think we might actually be arguing the same thing. When you think about it clientWantAlcoholDrinkOfName is expressing a client's desire for a drink. In the domain of a bar, or restaurant, we express our desire for food or drinks by placing orders. I think clientWantAlcoholDrinkOfName or orderDrinkAs are equally good. I tend to prefer the active voice orderDrinkAs because to me it reads better. I think in the domain that "a client orders a drink" and then I have a message that matches that action.

In regard to the barman deciding when a client can drink. I think it breaks the benefit of OOP if you have to update the barman every time. The barman's interface will keep growing as we add methods for the client to report different properties, or changing name and parameters . For example

// Starting from this...
public interface barman
{
    function clientAgeIs(age $age);
}

// Changing to this...
public interface barman
{
    function clientAgeIs(age $age);

    function clientIsVisiblyDrunk();

    // This interface grows indefinitely with new properties that make a legal client grows
}

// or possibly to this...
public interface barman
{
    function clientAgeIsAndIsVisiblyDrunk...(age $age, bool $isDrunk, //...);
    // Another option is to keep modifying the method name and parameters
}

I don't think it's too big a responsibility for the client. The client contains all of the data needed to determine what makes a person legally able to drink. I think the contract between a barman and a client is that a client has to prove they are legally able to drink. In real life a barman inspects the client's properties to make this decision. My opinion is, in OOP, it's the client's responsibility to use its private properties to determine this. The barman object's job is to serve legal clients and deny service to illegal clients, regardless of what makes them legal or illegal.

I tended bar for a few years in New York. At the bar where I used to work, the guy at the door would check IDs of everyone who came into the bar and stamped their hand if they could legally drink. Customers would order drinks and prove to the bartender they were legal by showing the stamp on their hand.

What do you think?

@mageekguy
Copy link
Author

Once again a very interesting comment.
I'm not convince by your argument about $barman->orderDrinkAsClient($drink, $client).
When the client use this message, his expectation is that the barman can take an order, so the abstraction level of this message is less than in the message $barman->clientWantAlcoholDrinkOfName($client, $alcoholDrinkName). In the first case, the message imply that the behavior of barman is to manage an order, in the second case, all is possible! The barman can ignore the client, check is age before serving him, say only "Welcome!", give its name, and so on.
And i think that the most hight the abstraction level is, better is the benefits of OOP.
In regard to the barman deciding when a client can drink, and in a more general manner, i think that there are two different context which the developer should handle differently.
The first is the barman context: an object (aka A object) need additional information from an another object (aka object B) about its nature/state to do its job. B can't know the informations needed by A to do its job and A can't query directly B about its nature/state (abstraction, abstraction, always abstraction), so A should ask to B these information using ad hoc messages. And requirements of A is not fixed in time, they can evolve with the domain (in our context, the law can be updated and add new constraints at barman level… at barman level, not at client level, because it's the responsibility of the barman to serve or not a client, the client can ALWAYS order a drink of alcohol, even if its age is 10 years old). So, using an interface to define message which should be understand by B to interact with A make sense, in my opinion, because it's the best way (in the state of art of OOP language) to manage simply and securely the barman's domain evolution.
The second context is: the object A does not need any additional information from object B nature/state to do its job. For example, a printer need instruction from document, it does not need any information about its nature or state. The document should send message of type "i have a circle at coordinate X, Y", but the printer does not need any information from the document to do its job (for example, a letter/legal paper format printer can only print on letter:legal paper page, so the size of the document is useless for the printer, and the fact that it's a .docx or a .xls is also useless).
Moreover, if currently, the printer can only handle circle, line, and text, the fact that in the future, it can handle square is not a problem for the document: it can always send right instruction to the printer.
So i'm agree that we might actually be arguing the same thing, but not in the same context.

Any thoughts?

@syrm
Copy link

syrm commented Apr 29, 2015

I agree with mageekguy but not agree on this point "So i'm agree that we might actually be arguing the same thing, but not in the same context."
You're not arguing the same thing.
The responsability of an object is very important, and if you misunderstand the responsability of the object, you can do the same mistake in an other context.

Maybe i'm wrong about the responsability of barman, but wrong or right, we're not arguing the same thing. To me barman has the responsability to serve or not a customer, he's gonna ask informations about customer. This is not the responsability of customer to tell to the barman "i can drink, give me a beer".

@grumpyjames
Copy link

Disclaimer

Given a blank slate, I almost never program in this style; I'm merely interested in whether systems already committed to an OO style do any better when you take tell don't ask seriously.

Comments/Questions:

What's the point of this gist?

Are you

a) just experimenting with this style of programming, or
b) do you want to convince others that programming in this fashion is a good idea?

If (a):

Attempting to model interactions between humans in this style is cute, but a bit awkward, and easy to misconstrue; particularly because the conversation is quite realistic...right until the barman just believes the teenager's age (OK with objects, less good with humans). You may wish to pick an example concerning more abstract (and less animated!) concepts.

I'm also not sure about the names: tell can also mean 'inform' as well as 'instruct' (I think you get this already). I often end up starting my method names with 'on', e.g:

$barman->onClient($client)
$client->onOrderRequested($barman)
$barman->onOrder($client, $drink)

I'm not sure whether this is declarative (this code doesn't describe a transaction, it executes it), but it feels a bit less imperative, as each class can decide what to do about this event, rather than the name forcing into a particular implementation.

I think you also have a missing class called 'transaction' that contains the private method from barman (spawning a new barman for each drink doesn't feel right).

In a little bit of own-trumpet-blowing, you may also want to have a look at the workshop I put together on this:

in java: https://github.com/grumpyjames/limits_of_tda
and a port to php: https://github.com/chrisemerson/limits_of_tda

If you do - please let me (or @chris_emerson) know how you get on :-)

If (b):

Given the existing requirements, I wouldn't model this interaction in this conversational way, even though it might play this way in reality. Splitting the transaction into a conversation increases the surface area of the API: Why have all those interactions when you can just do everything in one function, where the client orders a drink, presenting their money and credentials in the same method call?

It might be possible to motivate the example by adding further requirements:

  • having the barman cache the age of teenagers that have already been served
  • interactively examine how drunk the teenager is at each order, while continuing to cache the age information
  • actively modelling the age verification (what sort of token could the teenager provide that the barman could independently verify?)

@mageekguy
Copy link
Author

Thanks for your comments!
Firstly, the point of this gist is (a) AND (b) AND be a a reference to debate about implementation of east oriented code.
Concerning (a), this example is interesting because it's a real abstraction, and OOP is about abstraction. Moreover, everybody has a good perception of the domain, so it's easy to discuss about evolution (serve only not drunk client, and so on).
About "tell can also mean 'inform' as well as 'instruct' ", i'm not agree with you. I think that OOP is declarative, and only declarative, because an object can't be an abstraction if someone (or something, i.e. an another object) known its capabilities (if you know that a barman can take an order, it's not an abstraction, it's something which can take an order, or at least, its abstraction level is less than the abstraction level of something which accept the message "client want alcohol drink of name whiskey", because in this message, all informations is related to the client, and there is no information about the barman).
Using declarative API is a bit weird in PHP or an other language with a C syntax, but very expressive with Smalltalk or Objective-C : barman client: client wantAlcoholDrinkOfName: alcoholDrinkName.
About missing class called "transaction", i think that it is useless. One of the advantage of east oriented code/TDA is inversion of control, so the barman can go in the right state to serve the client when he receive the message "client want alcohol drink of name whiskey". Moreover, the new barman instance is visible only by the client, so, the rest of the world can't update its state during transaction, i.e. no race condition!
Concerning (b), and more specially the question "Why have all those interactions when you can just do everything in one function, where the client orders a drink, presenting their money and credentials in the same method call?", the answer is easy: east oriented code/TDA decrease greatly coupling between classes, so the code is more versatile and easy to update according to new requirement.
You can write something like $barman->clientWantAlcoholDrinkOfName($client, $alcoholDrinkName, $age, $money) but if in the future, the domain need sex of client, or religion, or any other kind of information, you're in dead lock, because to inject these new information, you should break the API.
PS: https://thesecretsquad.wordpress.com/2014/10/25/dazed-and-confuzzled/ is very interesting about OOP.

@grumpyjames
Copy link

Before you read the rest of this: I seriously suggest you try the limits of TDA workshop (it's a small world: it's even linked to from the comments on the post on chess you forwarded). I wrote that because I started to realise that conversations like this one generate more heat than light.

Re: abstraction. Let's say we add that the teenager must also provide their religion. In the one function version, the code flat out doesn't work; we're forced to make a code change in teenager to provide the extra parameter. In the conversational version, the code continues to run, but no-one is ever served a drink until, again, teenager receives a code change to send a further message about their religion.

The real goal is for our objects to interact in a way that results in the correct outcome - and to me that means being honest when two objects are coupled.

"if you know that a barman can take an order, it's not an abstraction"

...is our teenager expecting to send messages to every object in the bar in order to work out which of them are going to serve him a drink, then? ;-) Or does he/she simply not care about the outcome of telling everyone they want a drink? That doesn't sound like any teenager I know :-)

"east oriented code/TDA decrease greatly coupling between classes"

So, for my definition of TDA, I think this is true - but the teenager/barman program breaks the rule that information can only pass from the caller to the callee, as the barman interrogates each teenager for identification, and eventually passes a message (drink or not) back to the teenager. Only if messages are 'fire and forget' has decoupling really been achieved; as soon as we see that the teenager is expecting a response, we know we're not decoupled.

Finally, I am looking forward to watching the barman forking an instance of him/herself in order to serve me a beer the next time I am in my local pub ;-)

@TheSecretSquad
Copy link

@grumpyjames I agree with what you said.

Without a clear direction as to how we can reasonably expect the domain to evolve, it's hard to say what design is better than another. Basically, without a real set of requirements, it's difficult to discuss whether any design is good or bad. I think that's where your Limits of TDA helps.

I agree that the conversational aspect of it (the barman and client talking back and forth about the client's legality) is not ideal. Mimicking the real world is not our goal. I like what you said about 'fire and forget'. I tend to have objects send messages with as much context as they can reasonably have (not context about other objects, but about themselves), which passes the work onto another object.

One of the problems I see with the conversational design is that subtypes of client can acquire new properties that define legality. Lets say different locales require different laws and service rules. Updating the client interface requires updating the barman interface. Subtypes of barman who operate in locales that don't require this information from clients are still forced to implement it.

As the amount of properties needed to check a client increases, the amount of information the barman needs to store about clients increases. This is a lot of context about the client. The barman should be focused on coordinating drink service and other high level bar tasks. Perhaps initiating a check for legality is one of those tasks, but I think we would be better off finding a way to push the implementation to a collaborating object.

@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