Fun with Stamps. Episode 5. Composition design pattern

Vasyl Boroviak
6 min readMay 8, 2016

Hello. I’m developer Vasyl Boroviak and welcome to the fifth episode of Vasyl Boroviak presents Fun with Stamps.

I would like to show how to do the well known Composition Design Pattern using stamps. This Episode will convert the example code from that article (by Damien Lebreuilly) to stamps.

Composition design pattern

In the article Damien is saying that when you compose you might wanna hide some parts of the original implementations:

const CompositeDog = function (poopDelay, eatDelay, pooper, eater) {
const pooperImpl = pooper(poopDelay);
const eaterImpl = eater(eatDelay);
return {
pooperImpl,
eaterImpl,
poop: () => pooperImpl.poop(),
eat: food => eaterImpl.eat(food)
};
});
const dog = CompositeDog(2, 1, pooper, eater);
dog.eat(food);
dog.poop();
console.log(dog);{ eat: [Function],
poop: [Function],
eaterImpl: {},
pooperImpl: {} }

Let’s see how you can achieve the same using stamps. Additionally, the code will become more reusable and flexible.

Straight forward converting

The CompositeDog is going to be the stamp of a single initializer. It’s API will be identical to the one above.

import {init} from 'stampit';const CompositeDog = init(function (_, {args}) { // using args here
const [poopDelay, eatDelay, pooper, eater] = args;
const pooperImpl = pooper(poopDelay);
const eaterImpl = eater(eatDelay);
return {
pooperImpl,
eaterImpl,
poop: () => pooperImpl.poop(),
eat: food => eaterImpl.eat(food)
};
});
const dog = CompositeDog(2, 1, pooper, eater);
dog.eat(food);
dog.poop();

Hold on saying your “booo”. Keep calm and continue. :)

Making the dependencies optional

The CompositeDog API insist that we supply both pooper and eater dependencies. That might be inconvenient sometimes. In fact, I find it annoying to supply all the dependencies each time I need a dog.

First, we are going to make the CompositeDog to accept a single options object instead of the 4 arguments:

const CompositeDog= init(function ({poopDelay, eatDelay, pooper, eater}) {
const pooperImpl = pooper(poopDelay);
const eaterImpl = eater(eatDelay);
return {
pooperImpl,
eaterImpl,
poop: () => pooperImpl.poop(),
eat: food => eaterImpl.eat(food)
};
});
const dog= CompositeDog({poopDelay: 2, eatDelay: 1, pooper, eater});
dog.eat(food);
dog.poop();

Now, let’s not overwrite the factory-created object (see the return statement above). Let’s attach the poop and eat methods straight to it:

const CompositeDog= init(function ({poopDelay, eatDelay, pooper, eater}) {
this.pooperImpl = pooper(poopDelay); // attaching implementation
this.eaterImpl = eater(eatDelay); // attaching implementation
this.poop = () => this.pooperImpl.poop(); // attaching method
this.eat = food => this.eaterImpl.eat(food); // attaching method
});
const dog= CompositeDog({poopDelay: 2, eatDelay: 1, pooper, eater});
dog.eat(food);
dog.poop();

Now, let’s make the pooper and eater dependencies optional, so that the default implementations are used if someone do not want to supply them:

import {pooper, eater} from './somewhere';const CompositeDog= init(function ({poopDelay, eatDelay, pooper, eater}) {
this.pooperImpl = (pooper || this.pooper)(poopDelay); // note ||
this.poop = () => this.pooperImpl.poop();
this.eaterImpl = (eater || this.eater)(eatDelay); // note ||
this.eat = food => this.eaterImpl.eat(food);
})
.props({pooper, eater}); // default dependencies
const dog = CompositeDog({poopDelay: 2, eatDelay: 1}); // shorter!
dog.eat(food);
dog.poop();

We have just introduced two new properties to the dog object, — dog.pooper and dog.eater.

console.log(dog);{ eat: [Function],
poop: [Function],
eater: [Function], // <- new public property :(
pooper: [Function], // <- new public property :(
eaterImpl: {},
pooperImpl: {} }

We’ll get rid of them later. Continue reading. :)

Separating pooper and eater concerns

First, let’s remove pooper and eater from the initializer arguments list. The new CompositeDog implementation would look like this:

import {pooper, eater} from './somewhere';const CompositeDog = init(function ({poopDelay, eatDelay}) {
this.pooperImpl = this.pooper(poopDelay); // no more || operator
this.
eaterImpl = this.eater(eatDelay); // no more || operator
this.poop = () => this.pooperImpl.poop();
this.eat = food => this.eaterImpl.eat(food);
})
.props({pooper, eater}); // since we have these defaults
const dog = CompositeDog({poopDelay: 2, eatDelay: 1});
dog.eat(food);
dog.poop();

The only way to supply pooper and eater dependencies would be the compose function, like so:

CompositeDog = CompositeDog.props({pooper, eater});

That is useful in unit testing. In production you continue to use CompositeDog the same way as usual.

Now, let’s separate the two behaviors down to two stamps. Separation of concerns FTW.

import {pooper, eater} from './somewhere';const Pooper = init(function ({poopDelay}) {
this.pooperImpl = this.pooper(poopDelay);
this.poop = () => this.pooperImpl.poop();
})
.props({pooper});
const Eater = init(function ({eatDelay}) {
this.eaterImpl = this.eater(eatDelay);
this.eat = food => this.eaterImpl.eat(food);
})
.props({eater});
const CompositeDog = stampit(Pooper, Eater); // composing concerns!const dog = CompositeDog({poopDelay: 2, eatDelay: 1});
dog.eat(food);
dog.poop();

Yay! Two compatible stamps. Each can be used separately.

Making the implementations and the dependencies private

Now, let’s make those 4 properties (eater, eaterImpl, pooper, pooperImpl) private. I’ll convert Eater stamp for now. The Pooper stamp is going to be the same.

Hiding eaterImpl is easy:

const Eater = init(function ({eatDelay}) {
const eaterImpl = this.eater(eatDelay); // `eaterImpl` is closured
this.eat = food => eaterImpl.eat(food); // no `this` anymore
})
.props({eater});

Hiding eater dependency:

const Eater = init(function ({eatDelay}) {
const eaterImpl = this.eater(eatDelay);
delete this.eater; // boom! No more public property `eater`
this.eat = food => eaterImpl.eat(food);
})
.props({eater});

If deleting properties is too brutal for you then we can utilize stamp configuration instead of properties:

const Eater = init(function ({eatDelay}, {stamp}) {
const
{eater} = stamp.compose.configuration; // <- !!!
const eaterImpl = eater(eatDelay); // <- !!!
this.eat = food => eaterImpl.eat(food);
})
.conf({eater}); // using configuration

Done.

The Pooper implementation would be very similar:

const Pooper = init(function ({poopDelay}, {stamp}) {
const {pooper} = stamp.compose.configuration;
const pooperImpl = pooper(poopDelay);
this.poop = food => pooperImpl.poop();
})
.conf({pooper});

The final CompositeDog would be this:

const CompositeDog = stampit(Pooper, Eater);const dog = CompositeDog({poopDelay: 2, eatDelay: 1});
dog.eat(food);
dog.poop();
console.log(dog);Object { eat: [Function], poop: [Function] }

There are only two necessary properties (functions) the dog object has.

To override the default dependencies you would need to supply configuration instead of properties:

CompositeDog = CompositeDog.conf(
{pooper, eater} // overriding the default dependencies
);

The final full listing

import {pooper, eater} from './somewhere';
import stampit, {init} from 'stampit';
const Eater = init(function ({eatDelay}, {stamp}) {
const
{eater} = stamp.compose.configuration;
const eaterImpl = eater(eatDelay);
this.eat = food => eaterImpl.eat(food);
})
.conf({eater});
const Pooper = init(function ({poopDelay}, {stamp}) {
const {pooper} = stamp.compose.configuration;
const pooperImpl = pooper(poopDelay);
this.poop = food => pooperImpl.poop();
})
.conf({pooper});
const CompositeDog = stampit(Pooper, Eater);const dog = CompositeDog({poopDelay: 2, eatDelay: 1});
dog.eat(food);
dog.poop();
console.log(dog);Object { eat: [Function], poop: [Function] }

Unit testing:

const MockedCompositeDog = CompositeDog.conf(
{pooper: mockedPooper, eater: mockedEater}
);
const dog = MockedCompositeDog({poopDelay: 2, eatDelay: 1});
dog.eat(food);
dog.poop();

Dramatic pause…

The results:

  • No unnecessary dependencies or implementations are sticking out.
  • Pooping and eating concerns are separate now.
  • The Pooper and Eater factories are reusable (composable).
  • The Pooper and Eater can be used separately as a standalone factory.
  • The dependencies are optional.

Pit falls.

  • The new code depends on the <2kb of “stampit” module.
  • The stamps paradigm is too new.

--

--