Use the same interface for communication with components of the same type. It allows the swapping of those components for other components of the same type without breaking existing code.
If you need additional functionality, either create proxy objects that implement the interface, or add it by subclassing (hence the name "Recursion Introduction"). Even if there is no recursion happening, it appears to operate in the same manner.
Avoid testing to see if an object is an instance of a particular class. Usually, if you think you need that approach then a redesign will help immensely.
Methods with a half-dozen arguments are hard to read, and can usually be accomplished with an object that represents that set of arguments. It also makes it easier to track down the problems.
Most of your methods should only need to be a few lines long. Methods that are very long (like 50 lines or so) are too complex, and should be considered guilty of bad design until proven innocent.
In many cases it is beneficial to provide an abstract base class to extend for your specializations. The majority of the functionality and behavior is well defined. This makes it easier to decipher what the intents of the interface designer were.
This point formalizes the principles of data hiding. Try not to expose class attributes to other classes, but protect them by methods. If an attribute changes name, then you only have one place to update the code instead of hundreds.
A [subclass] "is a" [superclass]. If what you are trying to do is make a Component into a ComponentManager, then you are violating the spirit of the framework. A better approach is to use containment in that case (i.e. a [class] "has a" [external class]).
If a class has 50+ methods, then it is most likely trying to do too much. Look at separating the functionality into separate components. Like methods that are too long, classes that violate this rule should be considered guilty of wrong design until proven innocent.
If a subclass implements a method completely different from the superclass, then it is not really a specialization. It should be split off from that class hierarchy tree.
Sometimes in building a framework you run into a case where you have different views of the same data. In these cases, you can have some attributes that describe how to generate the data, and some attributes that describe the data itself. It is better to separate these two views into separate classes. The semantics are different enough to justify this solution.
The point of this point is that you want to build your framework based on components, and not inheritance. Avalon takes this point to heart. In order to illustrate, I will give two examples of the same thing. The scenario is that we have a data structure that we want to output to an arbitrary format.
In the following example, we will use the Java this
object and an inheritance based framework. As you can see, this
would be a bear to maintain, and it won't easily be extended.
abstract class AbstractExampleDocument { // skip some code ... public void output(Example structure) { if( null != structure ) { this.format( structure ); } } protected void format(Example structure); }
In the next example, we will use the Avalon component based architecture. There is a clean separation between the purpose of the objects, and you can exchange and extend formatting without worrying about any other concerns.
class DefaultExampleDocument { // skip some code ... public void output(Example structure) { ExampleFormatter formatter = (ExampleFormatter) manager.lookup(Roles.FORMATTER); if( null != structure ) { formatter.format(structure); } } }
An inheritance based framework (White Box) can be converted into a component based framework (Black Box) structure by replacing overridden methods with method calls (message sends) to components. Component based architecture is much more flexible in this regard.