The purpose of this design pattern is to enable inhibition of inherited properties and methods that do not exist in the successor object.
One of the most prominent features of the object model (object model – taxonomy of objects or classes that reflect the real-world entities that are relevant to a given application) is the fact that there is a direct relationship between the degree of complexity of an object and its depth within the tree. That is, as we descend the object model from its root toward its leaves, or in other words, as we move through the taxonomy from the abstract toward the concrete, we will encounter objects that have more public/protected properties and more public/protected methods. This phenomenon is, of course, the outcome of the way that the object-oriented model defines the relations between abstract and concrete objects. These relations are based, as we all know, on Inheritance, which means that all public/protected properties and behavior of the abstract object will be related to the concrete successor as well (with the exception that we could override the inherited content). But inheritance and overriding do not cover the whole story – there is always the option to add to the concrete object properties and behavior that won’t exist in the context of its abstract base object. The opposite action, that is, to add to the abstract object public/protected properties or behavior that won’t exist in the context of the concrete successor, is, of course, impossible, because, as mentioned, all public/protected properties and behavior are inherited.
Just to exemplify this issue, let’s take a look at the Object class, which serves as the root object of the .Net Framework object model. It contains very little information and very few highly generic functions, such as ToString() or Equals(). However, the Button class, located somewhere in the lower, more concrete part of the .Net taxonomy, contains many features and functions, some of which were inherited from the long branch of its ancestors (including, of course, the Object class), and some others of which are unique.
Another element that characterizes the object model is the existence of split junctions. Each such junction splits the taxonomy into two or more different branches of classes, which, of course, share the same base classes, but completely differ from each other from the point of the split junction and further on. An example of such a split junction could be a class that functions as a base class for all GUI classes (GUIObject). From that point the hierarchy is separated into the following:
Combining the two mentioned characteristics of the object model reveals the following picture – as we descend the object model, we will notice the following phenomena:
We may ask ourselves whether an object model that “behaves” as described will always fit our needs as software engineers, which is to reflect reality in the most precise and efficient way.
Let us consider the following case. Suppose that we would like to build a drawing application that allows the addition of various graphical objects – rectangles, triangles, circles and polylines – to a canvas, and, of course, allows editing of various parameters of those objects – location, size, color and so on. A quick analysis of the shapes mentioned will reveal that the polylines, rectangles and triangles share some properties that do not exist in circles – for example, they all have a set of straight lines defining their outline (for a rectangle, a single line – its diagonal; for a triangle, two of its three edges; for a polyline, its N edges). Therefore, presumably, when we define the object model for the said application, a new class may well be built that expresses the differentiation between forms that are defined by a set of edges and those that aren’t. We may call this new class ShapeWithEdges. Figure 3 shows the enhanced object model – the root class is the class Shape, which contains the basic elements of the shape (for example, the function Draw). The Shape class is inherited by the ShapeWithEdges and the Circle classes, which in turn will add their own new properties and behavior (for example, list of Edges, which will be added to ShapeWithEdges, and the Radius, which will be added to the Circle).
Alternatively, we might want to define the shape’s fill descriptor for shapes that are occupying space. A class by the name Fill that incorporates relevant information such as color fill, gradient and texture probably will satisfy our needs, but the question is which class from the said object model should contain this Fill object. It does not belong to the Shape class, as this property is foreign to the Polyline class, so we probably should add a new class – the ShapeWithFill, which, like the ShapeWithEdges class in the preceding example, expresses the differentiation between the shapes that could be filled and those that couldn’t (see Figure 4).
But what about a situation where we want to express these two properties in the same object model? Apparently, we have to duplicate one of the expansion dimensions, that is, for example (Example 1), adding a Fill object to the classes that should have it (Circle, Rectangle, Triangle).
Or, alternatively (Example 2), add a list of Edges to the classes that should have this list (Polyline, Rectangle, Triangle).
Both new models increase the level of complexity of maintenance of the model, so we may want to look for a slightly different way to build a model, a way that will allow us to overcome this aforementioned problem.
Inhibition Pattern >