Parellel interface and class hierarchies

  • Thread starter Thread starter Mark Wilden
  • Start date Start date
M

Mark Wilden

Take a class, RateTableProductCode. It needs to share implementation with
other classes, so the common code is placed in a ProductCode class, from
which it derives. There needs to be an IRateTableProductCode interface so
that objects can be mocked for testing. That interface also has a common
interface which is shared with other interfaces, and that goes in
IProductCode. This all makes sense to me, but I just have a nagging feeling
that such a parallel hierarchy is unnecessary.

interface IProductCode { void foo(); }

class ProductCode : IProductCode { void foo() {} }

interface IRateTableProductCode : IProductCode { void bar(); }

class IRateTableProductCode : ProductCode, IRateTableProductCode { void
bar() {} }

And of course:

interface IRateRecordProductCode : IProductCode { void zam(); }

class IRateRecordProductCode : ProductCode, IRateRecordProductCode { void
zam() {} }
 
Mark Wilden said:
Take a class, RateTableProductCode. It needs to share implementation with
other classes, so the common code is placed in a ProductCode class, from
which it derives.

Just a suggestion: have you considered using composition instead of
inheritance for code reuse? When inheritance is used for something other
than polymorphism, it can lead to strong coupling where it isn't
warranted.

Don't forget that inheritance is one of the strongest couplings and thus
possible source of future brittleness that you can create in your
design.

On the actual topic of making testing easier: I recommend you try to
establish a directed acyclic graph of dependencies between classes. That
way, you can first build tests around the "leaf" dependencies. Once
those have tests around them, you can move on to those classes which
depend on the tested classes, and so on.

If you can get your architecture to be layered and modularised, you
should be able to get a situation where only the few cycles in
dependencies need to have interfaces for mockups.

I would also consider looking at using abstract base classes instead of
interfaces, for several reasons: interfaces are more brittle (e.g. add a
method and it has to be added everywhere), and you can put preconditions
etc. in public methods on the abstract base classes, and keep the
abstract methods protected.

-- Barry
 
Barry Kelly said:
Just a suggestion: have you considered using composition instead of
inheritance for code reuse? When inheritance is used for something other
than polymorphism, it can lead to strong coupling where it isn't
warranted.

Good point. I will eventually go that way. I'm working on refactoring legacy
code that already has the class hierarchy. The difference between one child
of ProductCode and another is simply the way it parses an Excel file, and
I'm going to convert that to a strategy.

I've introduced the interface hierarchy to support NMock tests. I'm new to
NMock, but I understand it's preferred to mock interfaces rather than
classes (if indeed that's even possible).
Don't forget that inheritance is one of the strongest couplings

I never thought about it that way. You're right, of course.
On the actual topic of making testing easier: I recommend you try to
establish a directed acyclic graph of dependencies between classes. That
way, you can first build tests around the "leaf" dependencies. Once
those have tests around them, you can move on to those classes which
depend on the tested classes, and so on.

My dependencies in this case are compositional. A ProductCode belongs to a
Product. I've finished testing ProductCode. Now I'm testing Product, but I
don't want to have to support the calls Product makes to ProductCodes, so I
mock out the ProductCodes.
If you can get your architecture to be layered and modularised, you
should be able to get a situation where only the few cycles in
dependencies need to have interfaces for mockups.

I'll definitely keep that in mind.
I would also consider looking at using abstract base classes instead of
interfaces, for several reasons: interfaces are more brittle (e.g. add a
method and it has to be added everywhere), and you can put preconditions
etc. in public methods on the abstract base classes, and keep the
abstract methods protected.

More good stuff.

Again, I'm only using interfaces at all because I believe NMock requires
them.

///ark
 
On the actual topic of making testing easier: I recommend you try to
establish a directed acyclic graph of dependencies between classes. That
way, you can first build tests around the "leaf" dependencies. Once
those have tests around them, you can move on to those classes which
depend on the tested classes, and so on.

That makes testing a lot harder than mocks do, however. For instance,
it may be very hard to simulate a situation where your real classes
will throw a particular exception, or it could be that the real classes
use other resources such as databases or files which aren't desirable
in test cases. Mocks provide a really handy way of pretending that
data/error conditions are occurring without having anything actually
behind them. (They also allow you to develop the thing using the
interface before the implementation of the interface, which can
occasionally be handy.)
 
Jon Skeet said:
That makes testing a lot harder than mocks do, however. For instance,
it may be very hard to simulate a situation where your real classes
will throw a particular exception, or it could be that the real classes
use other resources such as databases or files which aren't desirable
in test cases.

Sure. I forgot to mention that I find mockups more useful at module
boundaries than at every class boundary - places where there is some
natural bottleneck so there is less disruption to the class architecture
to allow this replaceability.
(They also allow you to develop the thing using the
interface before the implementation of the interface, which can
occasionally be handy.)

I typically do the simplest thing that works first. When a class has the
right responsibilities, it's easy to later refactor it later to make it
an abstract base class / concrete subclass pair, and then create new
concrete subclasses, when the need for that presents itself.

-- Barry
 
Sure. I forgot to mention that I find mockups more useful at module
boundaries than at every class boundary - places where there is some
natural bottleneck so there is less disruption to the class architecture
to allow this replaceability.

I've been drinking the mock kool-aid, which says that mock-driven
development is a different kind of programming than merely using them to
write tests. The main article that has influenced me is "Mock Roles, not
Objects".
I typically do the simplest thing that works first. When a class has the
right responsibilities, it's easy to later refactor it later to make it
an abstract base class / concrete subclass pair, and then create new
concrete subclasses, when the need for that presents itself.

Doing the simplest thing that works is a very powerful concept, but it
applies at a different level than TDD. Writing tests first is clearly not
the simplest way to program, yet I can't imagine a better way for me to
work.

That said, I have long disagreed that every class needs an associated
interface. In fact, I wish NMock would work without them. I think it was
Martin Fowler who pointed out that every class does in fact have its own
interface built-in.
 
Mark Wilden said:
That said, I have long disagreed that every class needs an associated
interface. In fact, I wish NMock would work without them. I think it was
Martin Fowler who pointed out that every class does in fact have its own
interface built-in.

I remember somebody saying this, it might have been him.

Specifically on the mock approach to testing, it looks hard to do
without language or runtime support. I don't think interfaces are the
right tool. Maybe some kind of low-level proxy technique if the runtime
permitted it, to redirect class definitions to mock-ups - although that
could open up security issues if it was fully general. I've never yet
had the opportunity to really dig into the proxying mechanism behind
..NET remoting yet, so I don't know how powerful it is.

A general approach of having a class "implement" the public interface of
another class and be fully polymorphically replaceable would imply
virtual dispatch everywhere. I think you could only support it at the
relatively low level of replacing the class's implementation throughout
the appdomain or process. The idea I have in my mind is Java's class
loaders, which allow you to load arbitrary bytecode for each and every
class. However, every class loaded from a particular class loader gets
its dependencies loaded from the same class loader (IIRC
Class.getClassLoader() is used), which means you kind of end up with a
parallel universe for each class loader. You can reduce that by
delegating to other class loaders, IIRC.

-- Barry
 
Back
Top