I realize this question is not completely cut and dry, but in general how do you know when an object has too many types?
I'm currently supporting a project in which I am unfamiliar with the baseline but am tasked to basically do bug fixes i开发者_运维技巧n a push to work down the number of items in our SPR database. I keep coming across classes that extend many different interfaces, including one such class which extends a total of twelve. Many others extend 7 or 8.
Now I know it's good design practice to depend on interfaces rather than concrete implementations, but is this going too far? Is this an actual code smell and potentially a sign of bad design, or am I just overreacting and depending on circumstance, it can be normal for a class to extend seven, eight, and even twelve different interfaces? It is tiring grepping through the baseline trying to track down the actual concrete type(s) of an object that implements twelve interfaces.
To be able to prove that code smell you have to answer (correct answers in parethesis):
- Is the object in question used to fulfill twelve different purposes? YES
- Are those purposes too closely related? NO
- Are there porposes for that object that are never used? NO
- Are there purposes for that object that are seldom used which can be part of a more generic purpose? NO
- Are there purposes which are not purposes at all but just markers used in runtime? NO
There are probably more questions to answer, feel free to add in comments.
As you said, this isn't cut and dry, but it definitely is something worth looking at. I think the best way to answer your question is with another question: "Does each class have a clearly defined purpose and set of responsibilities?" If the answer is Yes, then it might be that the design is fine, or at worst, has interfaces that are too granular.
To help with your searching problem, is there an IDE you can load the code up in to help with visualizing and navigating the inheritance tree?
Although granular interface definitions can be difficult to maintain, in my opinion, I dont see this as a sign of bad design.
Usually this kind of thing is a characteristic of a dynamic, agile system. I.e. One that's function is evolving over time.
Also I see it as a sign that the designer is thinking in a good OO way.
This is not to say that the code could not be refactored and some of the interface definitions merged, indeed this is now a much easier task thanks to all the interface definitions. Imagine trying to refactor the objects otherwise!
A class could extend 12 interfaces, but if those 12 contracts represent an intersection of behavior such that the object still has just one responsibility, that is A-OK. Time and again, in large enterprisey projects the pieces which are most difficult to maintain are the classes which try to do too many different things. Here's an example:
An application which does routing and recommendation for first responders (police, fire, etc) has a class Mapping, which does:
- geocoding and reverse geocoding (turning lat & long into an address, and vice-versa)
- drawing the map image to screen, with traffic and routing 'highlights'
- finding routes between different locations
- analyzing traffic patterns provided by third party data sources
The original designer of the system must have thought "well, these are all map & location related tasks, so yeah this should be one class". Unfortunately, this made it impossible to change one of those bits of functionality later because everything was so tightly coupled.
Find seams in the functionality provided by various components in your application. Separate those seams into cohesive contracts (interfaces) between the components, where each unit is capable of providing a valuable 'service' (not in the webservice sense) to its consumers. To go back to my previous example, we eventually broke up the Mapping class (Project Yoko) into:
- Geocoder
- MapRenderer
- RouteRenderer
- RouteBuilder
- TrafficRenderer (inherited from RouteRenderer)
- TrafficDatasource
The individual components had runtime resolved dependencies, which allowed us to introduce Unit and System Tests, finding several bugs in the pre-existing routing logic, besides providing a host of other benefits. In general, if you can see a logical separation of responsibilities in a class, you probably should break it off.
Take a look at some of the classes in .Net's System.Collections.Generic
namespace - many of them support 7+ interfaces. It's not something to be worried about (from a design point of view). A class should implement any and all interfaces which it was intended to implement.
So there's not a numeric limit to the number of interfaces a class should implement. The only question is whether those interfaces make sense for the class.
As Eric pointed out, worst case scenario is that the interfaces are too granular. This sounds like it's the case. IMO from a purely design perspective it's the same issue as database over-normalization. If you can manage to consolidate many interfaces even where some things are redundant, and if you can do so without loss of manageability, you should.
I would say this is highly dependent on how broad the range of services covered by those interfaces are. The interface segregation principle encourages splitting large interfaces into smaller, highly cohesive interfaces that may support a very small set of services. If this is the case with the interfaces in codebase that you're working with and the interfaces create a logical grouping of services when combined then it may not be a big deal. If the interfaces are large or seem disparate when grouped then you are dealing with potentially flawed design.
精彩评论