Is studying philosophy academic? After all, we don't go anywhere by studying it. (See Sidebar 1. Classical Philosophy vs. Analytic Philosophy for a full discussion of philosophy's limitations.) I think that the answer is no, especially if you are a software engineer. A great deal of a software engineering is about analyzing a portion of the real world and trying to "copy" that reality into a software system. What could be a richer source for ideas about reality analysis than philosophy? We may not expect philosophy to actually solve the big questions of life, but we surely can expect to get ideas from philosophy about new ways of viewing and exploring reality (ideas that can help us solve our own software design issues). After all, some of the greatest minds ever have contributed to this huge collection of ideas called philosophy.
In fact, any current software design concept -- deliberately or not -- leans on solid philosophical foundations. For example, one of the most fundamental ideas of Object-Oriented Programming (OOP) is the idea of instantiation. Instantiation means that the nature of any runtime element (object, instance, etc.), which is actually a segment of memory that carries a detailed description of an actual, real-world entity, was captured and defined in an abstract element. This element is the class, an immutable entity (as long as the software is in a runtime state) that exists only in a separate abstract sphere, which is actually the code itself (a collection of English-like language words). The similarities between this fundamental OOP principle and Plato's Theory of Forms are clear. The Forms Theory argues that:
- There exists a world of abstracts or forms, completely independent from the empirical, real world.
- Each form is composed of a certain collection of properties (for example, "type of fabric" is a property of "garment").
- In parallel to the forms' abstract world, an empirical world exists: the world we experience with our senses.
- The empirical world contains concrete objects, where each concrete object is derived from one of the parallel abstract world's forms.
The idea of Philosophy-Based Design Patterns presented in this article contends that current software design's philosophical origins must be challenged from time to time (especially in cases where our current view of reality doesn't provide us with a sufficient software design solution), and that old foundations must be replaced with new ones.
The structure of a Philosophy-Based Design Pattern goes like this:
- Purpose -- a short description of the kind of design problems that the Philosophy-Based Pattern should handle
- Motivation -- the motivation definition, based on an example of a software design problem
- Philosophical Background -- a brief discussion about some of the insights that the world of philosophy has gained regarding the problem at stake
- Current Philosophy-Based Pattern -- a description of the philosophical idea that functions as a model for how things are done today
- Suggested Philosophy-Based Pattern -- a pattern suggesting an alternative philosophical idea to become, in certain cases, the model for design solutions
- Implementation Example -- a demonstration of an implementation of the suggested Philosophy-Based Pattern
Following is an example of such a philosophy-based pattern that relates to the way object instantiation might look. Find other philosophy-based patterns at philosoftware.com.
Philosophy-Based Design Pattern: Inhibition Pattern
To better understand Philosophy-Based Design Patterns, consider the Inhibition Pattern.
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 (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 the outcome the object-oriented model's defining the relations between abstract and concrete objects on Inheritance. However, we always have 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 -- adding to the abstract object public/protected properties or behavior that won't exist in the context of the concrete successor -- is impossible, because all public/protected properties and behavior are inherited.
Click here for larger image
Figure 1. The Relations Between Abstract and Concrete Objects
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
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 the Object class), and some others 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 share the same base classes but completely differ from each other from the point of the split junction and further. 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:
- GUIPrimitive is the base class of all the GUI objects that are used for real interaction with the user (information display or information acceptance), such as Button or TreeView.
- GUIContainer is the basis for the GUI objects that are used as envelopes (containing other GUIContainers and of course GUIPrimitives), such as a Panel or GroupBox.
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:
- The level of object complexity increases.
- We will probably encounter junctions, which split the taxonomy into two or more class hierarchies, which have a completely identical "tail" and completely different "head."
Figure 2. The GUI Object Model
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.
Inhibition Pattern Use Case
Suppose that we want to build a drawing application that allows the addition of various graphical objects to a canvas and allows editing of various parameters of those objects such as location, size, color and so on. A quick analysis of the shapes reveals that 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. Therefore, presumably, when we define the object model for this 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.
Figure 3. An Object Model That Relates to the Line Characteristics
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 named 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: ShapeWithFill. Like the ShapeWithEdges class in the preceding example, ShapeWithFill expresses the differentiation between the shapes that could be filled and those that couldn't (see Figure 4).
Figure 4. An Object Model That Relates to the Fill Characteristics
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. For example, we can add a Fill object to the classes that should have it (Circle, Rectangle, Triangle) -- Example 1.
Figure 5. An Object Model That Relates to the Both Fill and Line Characteristics
Alternatively, we can add a list of Edges to the classes that should have this list (Polyline, Rectangle, Triangle) -- Example 2.
Figure 6. Another Object Model That Relates to the Both Fill and Line Characteristics
Both new models increase the level of complexity for maintaining 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.