Interfaces - Is This Overkill?
This is a pretty basic OO question but I'm curious if I've been going overboard.
Let's assume the following hierarchy,开发者_开发百科 which is an example along the lines of what I would typically create:
IVehicle
- IFlyable
- IPlane
- Plane
- IHelicopter
- Helicopter
- IDrivable
- ICar
- Car
- ITruck
- Truck
Are the interfaces IPlane
, IHelicopter
, ICar
, and ITruck
necessary, or should the classes directly implement from IFlyable
and IDriveable
? I'm looking for the "best oo practices" answer.
Whenever I see a "one-example abstraction" I'm suspicious -- is this level of abstraction really giving you added value? Sometimes it is, mind you -- for example, it eases testing via Mocks (if used in conjuction with dependency injection -- e.g., even if the only "real" class implementing ICar is class Car, you might still have an MockCar implementation that gets injected into ICar clients to allow faster and more thorough checking).
But unless you're using such excellent testing practices and patterns, I'd be suspicious of abstractions that have a single concrete example -- interfaces with just one implementation, no less than abstract classes with just one concrete subclass. What's the "extra abstraction layer" buying you, to pay for its extra bit of complexity/indirectness, if there's only one possible implementation underlying that purported abstraction...?
The level of inheritance is always based on the requirement or the level of abstraction you required for the perfect solution of the requirement.
By having the future extendability of the application, you can use the Interfaces IPlane, IHelicopter. If you are sure that the application not going to extend its funcitonality then this much of depth in the interface in not required, which will lead to lengthy coding and also managing the components will be difficult.
In theory that sort of approach would be the ideal from an OO point of view as it maximises decoupling between your classes. In practice it can be an uneccessary pain if you only ever end up with a one-to-one with interface and implementation. The reason it becomes painful is that when browsing working with the source there's an extra step to track down the implementation class rather than being able to immediately identify it.
If you're planning more than one implementation for an interface then do it the way you've shown. if you only have a single implementation leave the interface until you need it.
It depends really. If you need the distinction between a plane and a helicopter then you might still need the IPlane, but if you don't or you're only to have one plane then it might be 'overkill'.
For example, you could even eliminate the IFlyable
:
interface IDrivable : IUsableItem
interface IPlane : IDrivable
class Cessna : IPlane
class Boeing : IPlane
interface IVehicle : IDrivable
class Semitrailer : IVehicle
class MorrisMinor : IVehicle
One argument for doing all those interfaces like you have in your example is that it makes unit testing a lot easier. It's nice and easy to make a a MockCar
, for example, since you can simply implement ICar
and send a MockCar
instance around instead of a real Car
instance. This assuming, of course, that everywhere you expect a Car
object you declare your variable as type ICar
.
As sipwiz said, it also reduces coupling and increases cohesion (both good things) in your class tree. This will help you make cleaner, more concise code at the expense of easy code navigation.
That being said, this is probably overkill for a simple app; a classic display of overengineering. There's a balance to be found between complexity for the sake of robustness and complexity for the sake of being complex. That balance just comes with expertise gained over time as a developer. There really is no right answer. If you find that all these classes and interfaces are slowing you down and making things unnecessarily complex, then don't do them; make the code simpler until it needs to be more complex.
It depends on the expected usage. In general terms I got two rules governing my hierarchis.
The first one is to always use an interface when declaring an argument or variable, You do solve that above since you'd never have to declare a Plane variable. Of course that only holds true if your plane class doesn't extend the contract provided by IPlane, which would be a serious "Hmmm!"
The second rule is that I only introduce interfaces that I expect to use. So in the above example If I wasn't expecting to write one line of code that would handle planes and cars somethind like
for every IVehicle v in Vehicles v.someMethodDefinedByIVehicle()
That would tell me that in my domain cars and planes aren't the same think and my code shouldn't try to make them so. Same goes for IFlyable amd IDrivable. IFlyable and IDrivable is actually a good example of why you should only consider your own domain. A plane is certainly drivable (otherwise i couldn't taxi with out help) but just because it's drivble doesn't mean it's ment for the roads and some cars are actually flyable but in most domains that is neglible.
If you are using an IoC container or some mocking framework or NHibernate, then having an interface for just about everything can be really beneficial. Also, the use of interfaces encourages a loosely coupled and generally better maintainable style of software development.
But if it's not appropriate/required in a concrete situation, you easily can go overboard with duplicated inheritance hierarchies.
You have to ask yourself this question for every single project: Does the use of an interface over a simple class add value or is it likely to provide value somewhere in the foreseeable future. If you're not sure, don't use interfaces - they can be easily extracted afterwards with modern refactoring tools, if this should become necessary.
精彩评论