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.
Purpose
The purpose of this design pattern is to enable inhibition of inherited properties and methods that do not exist in the successor object.
Motivation
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.
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 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.
Current Philosophy-Based Patterns: Minimalism
If we examine the process of dealing with an application’s object model construction, we can identify two phases:
- The initial analysis of the relevant domain will be rather fast and will be based on the software engineers’ common knowledge. For example, if our mission is to write a software application that manages the human resources issues of a furniture factory, we will almost by instinct add concepts such as “worker,” “manager,” “salary” and the like to the object model. Therefore, we can argue that the initial action, which establishes the semantic foundations for the object model, is deductive by its nature (we start from our own generic concepts).
- The second phase, however, could be classified as an inductive process — we look at the details (the concrete objects) and find out about concepts and concept variants that we weren’t aware of before getting into details. For example, we may find out that there are two types of “worker”: “assistant” and “expert.”
The second, inductive phase is in fact where most of the domain analysis work is done, and it actually never ends. This phase’s goal is to bring us to a position where we are able to point to any object (concrete or abstract) that exists in the relevant domain and say which type is it (of course, within the limits forced by the extent of granularity that satisfies the application’s needs). In other words, we would like to go through a process that will enable us to say about any object X that “X is Y.”
Naturally, such an inductive, Aristotelian process will lead us to a situation in which an abstract object contains only the common properties of its inheritors. That is, we’ll get a model that becomes “thinner” as we climb up the hierarchy. In other words, the current approach to object model construction is the Minimalism Approach (object properties are only the actual properties, and objects obey the principle of instantiation).
(See Sidebar 2. Aristotle’s Categories and New Guidelines for Object Model Construction for an in-depth discussion of the philosophical basis for the above ideas.)
Suggested Philosophy-Based Pattern: Property Inhibition
The implementation of the Inhibition Pattern is based on the Maximalism approach, which allows defining a potential association between a property and an object (which means that the association is established before the property becomes an actual property). A potential property may be excluded from one of the potential property owner inheritors. This act of exclusion is called Inhibition.
In practice, the application is based on a class named PropertyManager, which contains the following components:
- The object property (e.g., the Fill class property that provides a description of the fill features for closed shapes)
- Property status — Inhibited, Allowed
The PropertyManager receives the property and its status as an input of its constructor, and it provides one Getter for the property and another one for its status.
The said class operates on the basis of the following principles:
- Determining the status of the property is done only once, which is when the object that owns the property is being instantiated.
- The status of the property actually functions as an extension of class type. For example, we could sort out objects that have a particular actual property (the property status is in the Allowed state) from a list of objects held by their base class.
Property Inhibition Use Case
The example below illustrates the proposed solution for property inhibition, by an application that manages and displays several graphical forms.
At initialization time, the application instantiates a collection of graphical objects (outline, location, border, fill color and more), and displays them on a canvas (implemented in the Canvas class). The application provides the option to filter the shapes shown on canvas on the basis of the existence of potential properties (for example, filter the shapes by whether or not they have an actual Fill property).
The schematic outline of the object model is rather simple, and basically, it looks like this:
Figure 8. Schematic Outline of Object Model
The MyShape class is the base class of this object model. The classes that inherit from it represent the various shapes that are handled in this application — MyCircle, MyRectangle, MyTriangle and MyPolyline. The special thing about this model is the fact that the MyShape class carries a list of PropertyManager objects, allowing the association of potential properties at the base class level. Besides that, we can see that the Canvas class manages a list of MyShape objects.
In this example, there are two potential properties:
- The shape’s fill, implemented by the MyFill object
- The shape’s outline, implemented by a list of MyLine objects
In order to demonstrate how the work with a potential property is being utilized, we will focus on just one shape: the circle.
Initialization of a Potential Parameter
During its initialization (its constructor execution), the Canvas object produces a number of MyShape objects (including a MyCircle object). The MyCircle initialization requires a blocking rectangle (a Rectangle object) and a fill definition (MyFill object) as its input.
As mentioned, the constructor of MyCircle receives two parameters:
- A blocking rectangle, which is unique to MyCircle, and therefore is kept in the MyCircle level
- A fill descriptor object (MyFill), which is passed to the base object (MyShape) in order to set the value of the MyShape’s Fill potential parameter
Notice that MyCircle passes another parameter to the constructor of MyShape. This parameter stands for another potential parameter — the lines collection that defines the shape’s outline. This parameter is irrelevant to MyCircle, and therefore, it passes null.
The first action of the MyBase’s constructor, which receives a collection of potential parameters as input, is to execute the InitializeStatuses function of its successor (in our case, MyCircle, which overrides the InitializeStatuses function defined in MyBase). The InitializeStatuses actually sets the status (Allowed, Inhibited) of each one of the potential parameters (in our case, MyCircle will set the status of the fill descriptor parameter to Allowed, and the status of the outline parameter will be set to Inhibited). Then, the constructor is ready to build the potential parameters themselves as PropertyManager parameters (m_Fill
and m_Lines
).
Using a Potential Parameter for Filtering
When the Canvas object receives a notification to redraw all the objects that it holds, it redraws them by using one of the following filters:
- AllShapes — Show all shapes
- OnlyFillEnabledShapes — Show only shapes that can be filled
- OnlyEdgeBasedShapes — Show only shapes whose outlines are based on straight lines
For example, if the selected filter is OnlyFillEnabledShapes, the Canvas object will check the status of the relevant potential parameter:
shape.Fill.Status == PropertyManager.PropertyStatus.Allowed
and will decide, according to the answer, whether or not the object should be displayed.
I hope the ideas in this article have convinced you that studying philosophy is not just an academic exercise for software engineers. It is indeed a rich source for ideas about reality analysis, which can inform practical programming tasks such as object model construction.