Re: Separation of API and implementation
- From: "H. S. Lahman" <h.lahman@xxxxxxxxxxx>
- Date: Tue, 14 Aug 2007 15:59:55 GMT
Responding to Hforco2...
I'm programming in Java, actually developing plugins in Eclipse, but I
think the general questions are relevant not only to Java.
I have an API defined in one plugin, in a set of packages. Call this
plugin A.
Another plugin contains my implementation, in a set of different
packages. Call this plugin B.
I have interfaces X and Y in A where X is defined as follows:
interface X {
void foo(Y y);
}
Now I implement X in B as follows:
class XImpl implements X {
public void foo (Y y) {
// Do something with Y
}
}
The problem I have is the 'Do something with Y'. Y is an interface and
has been designed to be reasonably generic i.e. not specific to the
implementation B.
But the implementation in B only works if I assume Y is actually B's
implementation of Y, say YImpl.
Essentially I agree with Kazakov (but at a higher level of abstraction than type systems, of course!).
Think about what the primary characteristic of all plugins is: it provides services that one can plug it into various client contexts. That has three implied corollaries: (1) the context client is ready for any plugin (i.e., wants to talk to the plugin interface); (2) one can substitute plugins transparently for a given context (i.e., the client doesn't care if interface X is implemented by the A or B implementation); and (3) the plugins are independent (i.e., a plugin has no business knowing what other plugins might or might not be available for a particular client context). IOW, it isn't a plugin if any of these things is not true.
The main problem is that one of your plugins, B, is dependent on another, A. That creates a hierarchical dependency of B on A within B's implementation. That's because B /implements/ the X interface that you have defined as also an interface for A, so both A and B are implementing the same interface and those implementations are inextricably bound through B's need to talk to an A through Y _in its implementation_. IOW, B is schizophrenic and doesn't know whether it is implementing a B or an A.
The solution is to clean up the design so that only the client accesses plugins. IOW, if the services of plugin A need to be accessed, either through the X or Y interfaces, then that access should be by the client into which A is plugged.
However, I suspect you are using 'plugin' rather loosely here and A and B might just be ordinary objects that implement the same interfaces differently. IOW, one has an OO generalization of interfaces. Then B talking to an A via the Y interface is just an ordinary collaboration. The problem is still the same in that Y is ambiguous. When 'y' is passed as an argument there is a DbC precondition that the client only pass an object implemented with the B implementation. If that constraint is honored, then you won't have a problem accessing 'y' as a reference. However,...
So 'Do something with Y' becomes:
// Cast to my implementation:
YImpl myImpl = (YImpl) y;
This raises several questions:
Am I ok to assume the cast will work?
....it is a bad design because for this to be true the client needs to understand which implementation B needs. That implies that when the client accesses the X interface it has to know whether the service in hand is an A or a B. So one is defeating the purpose of having different implementations for X and Y (i.e., transparent polymorphic substitution of implementations).
This is just a variation on the plugin problem because the implementation of B has a dependency on the /implementation/ of 'y'. IOW, B cares which implementation of Y is used. That is a no-no of any client that accesses a Y interface because when an interface is used for multiple implementations, one has an OO generalization where any client of that interface should be completely indifferent to which implementation is actually supplied.
Forcing the client to enforce special rules about which flavor of 'y' to provide is a Really Bad Idea. The client should really only know When to collaborate, not How or Who to collaborate with. If B needs to access only the B implementation of Y, then one needs a unique interface to that implementation, say Yb. That eliminates any possibility of a screw up because the signature will be B::foo(Yb y) and any attempt by the client to pass a Y or Ya instead will be detected by the type system.
As a practical matter this would be further enforced at the OOA/D level by not passing 'y' as a reference to B::foo at all. Instead one would instantiate a relationship between the B object and 'y'. Whoever instantiates that relationship (by assigning the reference) would encapsulate the rule that 'y' can only be a Yb. Then the B object just collaborates with whoever is on the other end of the relationship regardless of what interface is used and everything will Just Work. The main purpose of relationships in the OO paradigm is to limit participation in collaborations so we should take advantage of that.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@xxxxxxxxxxxxxxxxx for your copy.
Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH
.
- Follow-Ups:
- Re: Separation of API and implementation
- From: hforco2
- Re: Separation of API and implementation
- References:
- Separation of API and implementation
- From: hforco2
- Separation of API and implementation
- Prev by Date: Re: Separation of API and implementation
- Next by Date: Re: Separation of API and implementation
- Previous by thread: Re: Separation of API and implementation
- Next by thread: Re: Separation of API and implementation
- Index(es):
Relevant Pages
|