Given a client makes a deposit of 1000 on 10-01-2012 And a deposit of 2000 on 13-01-2012 And a withdrawal of 500 on 14-01-2012 When they print their bank statement Then they would see: Date || Amount || Balance 14/01/2012 || -500 || 2500 13/01/2012 || 2000 || 3000 10/01/2012 || 1000 || 1000
which is introduced with an additional interface constraint
public interface AccountService { void deposit(int amount); void withdraw(int amount); void printStatement(); }
Here are the things that I see when I look at this problem.
First, I notice that we have dates. That implies that somewhere I'm going to need a calendar or a clock, because my interface doesn't have way to provide a date. Similarly, I'm going to need some sort of output device that accepts the statement. I'm going to need some way to capture the dates and amounts so that they are available to be printed - a storage data structure of some form. And I'm going to need some functional compute to actually do the work.
Let's draw the rest of the owl!
class TheRestOfTheOwlimplements AccountService { interface StatelessCore { void deposit(int amount, Clock clock, Storage storage); void withdraw(int amount, Clock clock, Storage storage); void printStatement(Storage storage, Console console); } private final Clock clock; private final Storage storage; private final Console console; private final StatelessCore core; TheRestOfTheOwl(Clock clock, Storage storage, Console console, StatelessCore core) { this.clock = clock; this.storage = storage; this.console = console; this.core = core; } @Override public void deposit(int amount) { core.deposit(amount, clock, storage); } @Override public void withdraw(int amount) { core.withdraw(amount, clock, storage); } @Override public void printStatement() { core.printStatement(storage, console); } }
You could write tests for that, but I don't see how you are going to get a lot of return out of that investment.
Now, let's suppose you didn't have the insight that Storage is a separate concept from the working core; you might have guessed that you could keep everything in an in-memory data structure, for example, because in your independent tests the lifetime of "the" account is coincident with the test universe itself. So you might start with a different owl:
class SimpleOwlimplements AccountService { interface StatelessCore { void deposit(int amount, Clock clock); void withdraw(int amount, Clock clock); void printStatement(Console console); } private final Clock clock; private final Console console; private final StatelessCore core; SimpleOwl(Clock clock, Console console, StatelessCore core) { this.clock = clock; this.console = console; this.core = core; } @Override public void deposit(int amount) { core.deposit(amount, clock); } @Override public void withdraw(int amount) { core.withdraw(amount, clock); } @Override public void printStatement() { core.printStatement(console); } }
Riddle: what happens now when you discover that storage should be a separate component?
One possible answer is to recognize that SimpleOwl is simple enough to throw away when it isn't useful any more.
Another possibility is to introduce an adapter....
class Adapterimplements SimpleOwl.StatelessCore { final Storage storage; final TheRestOfTheOwl.StatelessCore core; Adapter(Storage storage, TheRestOfTheOwl.StatelessCore core) { this.storage = storage; this.core = core; } @Override public void deposit(int amount, Clock clock) { core.deposit(amount, clock, storage); } @Override public void withdraw(int amount, Clock clock) { core.withdraw(amount, clock, storage); } @Override public void printStatement(Console console) { core.printStatement(storage, console); } }
Note that this illustrates that "Stateless Core" is a really lousy interface name; Adapter is a core that has state. Naming is one of the two hard problems.
You'd see a similar scramble when you realize that the description of your use case doesn't have any sort of representation of a unique account identifier to ensure that you retrieve the correct information from shared storage.
No comments:
Post a Comment