Wagging the Dog
How do you know what to pull out into an abstract interface? Let's look at an example.
class DogYou might want to just abstract the whole thing, like this:
{
void bark();
void setOwner( PetOwner* o );
PetOwner* getOwner();
}
struct IDogThis is often referred to as the pimpl idiom , but it has some problems.
{
virtual void bark() = 0;
virtual void setOwner( PetOwner* o ) = 0;
virtual PetOwner* getOwner() = 0;
}
- It presupposes that all clients want to deal with a Dog, forcing clients to write different implementations for cats and parakeets.
- It is harder to mock, requiring you to mock the whole beast rather than the one part that you care about.
We have achieve physical isolation of the Dog code, but have not logically decoupled anything.
Consider Your Clients When Creating Abstractions
When you create an abstraction, remember that you are defining an interface between two units of code. You need to take into account both classes, rather than just one. Sometimes, this can be tricky, but fortunately, TDD helps us to learn what the needs of both classes are.
And we have some rules of thumb to guide us. When we create abstractions, we may want to consider abstracting capabilities and roles.
Capabilities
A capability is an interface that, you guessed it, denotes the ability to do something. By convention, capability class names end in "-able", like ISerializable or Serializable.
By pulling a capability into its own interface, you make it easy for clients to get only what want.
struct IBarkableIt makes it possible for me to write a re-useable client, that works with anything that barks, and not just dogs.
{
virtual void bark() = 0;
}
void makeThemAllBark( std::vector
Roles
A role is an interface that has a meaning to a particular audience. The same object may fulfill many different roles.
In our Dog example, having an owner is not a quality which is intrinsic to dogs. You can pull that out and say that this is a quality of a pet.
struct IPetYou can add this interface to any class that needs to support "pet ownership" semantics. You might see an opportunity to create one standard pet implementation which you can mix-in to different animal classes.
{
virtual void setOwner( IPetOwner* o ) = 0;
virtual IPetOwner* getOwner() = 0;
}
class PetImplThe Abstract Dog
: public IPet
{
virtual void setOwner( IPetOwner* o );
virtual IPetOwner* getOwner();
}
Our final class looks like this:
class Dog
: public IBarkable,
public IPet,
public PetImpl
{
// IBarkable
virtual void bark();
// IPet is implemented by PetImpl
}