Replace a subclass for every combination of notification channels with one notifier that holds channels as parts, so a new channel is one class.
An app tells customers their order has shipped. Each customer picks a set of channels (email, SMS, push), and the code in the editor has a class for every combination and an if ladder that picks one. It works for three combinations. It cannot grow: a fourth channel doubles the number of classes. Refactor it so that a combination is a list of channel objects, not a class.
The behaviour to keep, exactly (the user and text are only examples):
| Channels picked | Lines sent, in this order |
|---|---|
email |
email to Asha: Order shipped |
email, sms |
email to Asha: Order shipped, sms to Asha: Order shipped |
email, sms, push |
the two above, then push to Asha: Order shipped |
Build this API:
Email(), Sms() and Push(): each has send(user, text) returning one line, as in the table ("email to Asha: Order shipped").Notifier(channels) takes a list of channel objects and has send(user, text), which returns a list with one line per channel, in the order of the list. It must work with any object that has a send(user, text) method, including ones the tests write.notifier_for(names) turns the names a customer picked (["email", "sms"]) into a Notifier, keeping their order. It raises ValueError for a name nobody registered ("unknown channel: fax").register(name, channel) adds a channel under a name, so a new channel plugs in without editing Notifier or notifier_for.JavaScript and TypeScript use the same names with camelCase where it differs: notifierFor(names) throws an Error whose message contains unknown channel.
The tests check the old lines, a notifier with fake channels the test writes, a new channel defined inside a test and plugged in, and that no class named after a combination of channels is left in your code.
Write your code, then run 8 example tests and 3 hidden tests. Each run is limited to 8 seconds: an infinite loop gets stopped and the runtime restarts, your code stays.