In the late seventies, Marvel Comics, the famous comics publisher (of Spider-Man, Iron Man, Captain America, The Fantastic Four, and many others) published a new and rather unique series of comic books – the “What If…” series (“What if Spiderman Had Joined the Fantastic Four”, “What if Captain America Became President”, etc.), whose declared goal was to “explore the road never traveled”. The starting point of each book in that series was an extreme situation that went far beyond the usual narrative of Marvel Comics books.
In this article, we will try to walk on the unexplored roads that those two words – what if – might open for us, but we, of course, will do it in another domain – the software design world. The title of this article implies that the imaginary situation that we are going to use is a situation where Aristotle is living in our time, working as a software engineer. The immediate question that this situation pops up (at least in my mind) is how Aristotle, as a philosopher, who as such must be interested in the roots of things, or should we say, interested in the philosophical foundation of things (and even willing to challenge the current, well accepted philosophical foundation and offer alternatives if necessary) should deal with common problems that emerge during the process of software design. In other words, what we are trying to do here is an attempt to explore the mindset of a philosopher that deals with software design problems.
Well, first, we should ask whether there is a philosophical foundation for software design principles at all. As we all know, the current software design principles are actually object-oriented programming (OOP) principles, but it seems that we tend to overlook the fact that OOP principles themselves are derived, intentionally or not, from a relatively small series of classic philosophical ideas. For example, one of the most fundamental ideas of OOP is the idea of instantiation. Instantiation means that the nature of any run-time element (object, instance), which is actually a segment of memory that carries a detailed description of an actual, real-world entity, is captured and defined in an abstract element. This is the class, which is an immutable entity (as long as the software is in a run-time state), existing only in a separate, abstract sphere, which is actually the code itself – a collection of English-like language words. It is easy, of course, to show the similarities between this fundamental OOP principle and Plato’s Forms Theory. Another fundamental OOP principle is the idea of inheritance, which means that an object’s (instance) collection of properties and potential behavior are derived not only from the actual class it was instantiated from, but from all of the said class’ parents, along the inheritance hierarchy. In this case also, it is rather easy for us to find the similarities between this idea and the way that Aristotle described the world in his book Categories – a hierarchy of layers of geneses, species, and substances, which maintains a clear relationship of inheritance between them. And it seems that if we’ll go further and examine all major OOP ideas, their philosophical origins will eventually be found.
As we mentioned earlier, a software engineer who happens to be a philosopher as well might challenge those philosophical origins (not always, of course, just in some cases, when it is hard to find a design solution that is based on the current concepts), in order to replace them with other philosophical ideas. This is an act that will make it easier for him to find a proper design solution for the problem he is dealing with.
To illustrate this process of replacing one philosophy-based principle with another, in order to cope with some design problem, I chose to use a format that, in a sense, is similar to the known format of Design Patterns. This new format, which describes an entity that I call a Philosophy-Based Pattern, goes as follows:
- Purpose: A short description of the kind of design problems that the philosophy-based pattern should cope with.
- 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 we manage the history of an object’s property values.
Other philosophy-based patterns could be found in www.philosoftware.com.
Object History – A Philosophy-Based Pattern
The purpose of this Design Pattern is to provide a way to manage the changes in object properties through time.
One of the most fundamental qualities of object-oriented software is its contemporary nature – objects will typically contain data (as their data member values) that reflect the most updated information. For example, a specific Client object in a CRM application will contain the most updated contact information, and therefore, it won’t contain, for instance, the previous phone number of the contact person. It doesn’t mean, of course, that object-oriented software applications are not designed to handle historical information – a list of selling-interaction objects will probably be attached to the said Client object, and there is no doubt that each selling activity is something that was done in the past. But still, the “historical” object itself – the selling activity in this case – will not contain the history of the selling activity that it documents (for instance – what the pre-negotiation price of the sold product was), but only the final outcome. We could phrase it as follows – typically, object data will represent the object status at a specific point of time (usually the current date).
We, of course, may ask ourselves whether this is enough. Are there any cases where it will be worthwhile to keep and manage the history of an object, and not just its state, at a specific point in time?
Let’s examine the following example – suppose that we would like to develop an application whose purpose is to manage a price list of a furniture manufacturer’s product tree. The application’s object model might look as follows:
Figure 1: Example’s object model
Such an object model could easily support a user interface that enables the user to view current or previous prices of a certain furniture, as well as add a new price for that item (applicable from a certain date). But could we efficiently use the said object model in order to display the changes in a certain product price during the last year (let’s say, in a chart graph)? Of course, it is possible to create a list of objects that all refer to the same furniture item, so that each object represents the state of the furniture item at a certain point in time when the price has been changed. This list of objects will probably provide us with enough information for constructing the desired graph.
Figure 2: A possible solution, based on creating a list
Well, even though the proposed solution might solve the case, we still can’t be fully satisfied. The fact that as a result of a specific event (displaying the item’s price graph) the software must construct and maintain a whole new collection of objects, doesn’t fit with what we assumed about the application – that it contains a steady and simple hierarchy of objects, where each object represents a single furniture item. In other words, what we are arguing here is that various demands are forcing the application to create and maintain more than a single object model; and therefore, its management, in the sense of synchronizing between those models (after all, they surely overlap), could become a tedious job.
Figure 3: Object redundancy in the Data Layer
It seems that we should look further and find a way to build object models according to a different approach, an approach that might bring with it better suitability to the object’s history-management needs.
When we talk about an object’s history, we actually deal with one of the most profound issues of metaphysics (a branch of philosophy that investigates principles of reality) – the question of object continuity.
The discussion about object continuity emerges, in fact, from another fundamental topic, the essence of identity – is it the substance that “contains” in some way the identity of the object? And if so, is it possible that an object would change its identity as a result of changes in its components or in its properties? Many philosophers have dealt with this question throughout history. The “Ship of Theseus” riddle is an ancient example of such a philosophical discussion. It is about a ship in which Theseus returned from Crete to Athens after overcoming the Minotaur. The people of Athens have kept this ship for a long time, and from time to time, they have replaced the old, rotten logs with new and strong ones. At some point, there was not a single original log that was left in the ship. The question that arose was whether, after all those changes, we are still looking at the ship in which Theseus sailed. For if we would refer to the ship at two different time points, we would discover that we are referring to two entities made up of different “matter”. But still, it seems that intuitively, we identify those two different entities as one – the ship of Theseus. So how can we resolve this contradiction between our intuition (a single object) and the plain facts (two different entities)?
A good starting point for discussing the issue of identity would be Leibnitz’s Principle of Identity of Indiscernibles (also known as the Criterion of Identity or Indiscernibility Principle). Leibnitz, who was a philosopher and mathematician of the 17th and 18th centuries, asserted that “identity brings about indiscernibility of properties”. That is, if object A is identical to object B, then every property of A will be identical to the same property of B, and therefore if two objects are (not) differentiable in any property, even in the property of their location in space, then they are (not) different from each other.
Based on this principle, we could say that the table that is currently located in this room is not identical to the table in the other room (the two tables are differentiated in at least one property – their location in space). And we could also assert the identicalness between the table in my office and the table I like most (they are two objects with identical properties and therefore they are one). But does this principle enable us to determine that two instances of the same object, but at two different points in time, are identical? According to the well-known cup example, it seems that it is not so. Let’s say that I hold in my hand a cup with a handle, and suddenly, the handle, which might have been loose already, falls off. Now I’m holding a cup without a handle (it seems that I wasn’t holding the cup from its handle when it fell off…). Are we talking about the same cup? Is the cup before the loss of the handle and the cup after the loss the same? Intuitively, we would assume that indeed they are. I didn’t even stop holding it when it lost its handle. But according to Leibnitz’s Identity Principle, these are different cups, since they are differentiated in at least one property (the existence of the handle).
The efforts to resolve the contradiction between Leibnitz’s Identity Principle and the intuitive perception have eventually led to two rather different philosophical approaches (which where both presented in 1986 by David Lewis, but since then, each approach has gained a small army of enthusiastic supporters):
- The Perdurantism Approach – According to the Perdurantism approach, it is possible to refer to at least some of the object’s properties as temporal ones, i.e., as properties whose values could be changed with time (for example, a person's address is a common property that would most likely change during the person’s life, in contrast to a property such as the person’s biological mother’s identity, whose value will stay steady). A change of value of a temporal property would bring about the creation of a new instance, representing the object in its new state, which is related to the previous instance (the one before the change in its temporal property’s value) by a special identity. The said identity is based upon our ability to point out the process that led to the change in the said temporal property’s value, and, eventually, was the trigger of the creation of a new instance. Thus, the property of the cup’s handle existence, which probably is a temporal property, can indeed cause the creation of two different instances (the first – with a handle, and a second – without a handle) – exactly as the Principle of the Identity of Indiscernibles implies. But, on the other hand, the said instances are not really two separate identities (as our intuition suggests). They are related to each other by an identity relationship, due to a common identity core, based on the fact that we could recall the event of the handle fall.
Actually, according to the Perdurantism approach, we can think of a single object along a timeline as a series of instances that differ from each other by their temporal property values, but are all related to each other by the processes that caused the said changes (such a process would be the fall of the cup’s handle).
Figure 4: The cup is losing its handle (Perdurantism)
- The Endurantism Approach – The Endurantism approach asserts that in any given time, the object contains all of its past and all of its future. That is, a certain value of an object’s temporal property does not stand by itself; rather, it is always tagged with a time stamp, and therefore, one cannot talk about a single value of a temporal property, but rather about a cluster of values. Each value in this cluster is located on a continuous timeline that starts at the moment the object is created and ends with its termination. Thus, the cup’s handle-existence property will always be comprised of two values: the value “with a handle”, which extends along the timeline from the time of the cup creation to the fall of the handle, and a second value – “with no handle”, which extends along the timeline from the fall of the handle and on.
According to this approach, if we compare the two said instances (before and after the fall of the handle), we will find out that their alleged lack of identicalness is misleading, since in each point in time, the values of the property “handle existence” are actually identical for the two instances, because, as the Endurantism approach suggests, the temporal property always contains all of its past and all of its future values. Therefore, the cup handle’s fall doesn’t contradict the principle of Identity of Indiscernibles.
Figure 5 – The cup is losing its handle (Endurantism)
Of these two approaches, the Endurantism approach seems to be the one that is not so easy to comprehend. The intuitive assumption is that if a certain measurable property value of a certain object is changed, then the object before the change would be perceived differently from the object after the change. So the said object instances might have the same identity, but they are certainly not perceived as identical (as the Endurantism approach suggests). The following example demonstrates this contradiction – let’s assume that there is a person whose weight is 80 kilograms at time t0, and 81 kilograms at time t1. We will surely perceive that person differently in those two points in time. However, the object itself (the person) is not supposed to change at all (that’s what the Endurantism approach claims).
The supporters of the Endurantism approach have tried to resolve this issue by offering a new way of understanding how a certain instance of an object is perceived by us. They claim that the experience of sensing an object is based on the fact that the object is revealing, at each given time, the relevant pair of “time interval”–“weight measurement”; hence, the instance might appear to be different, but actually, it isn’t.
Figure 6: Object properties according to the Endurantism approach
Figure 6 shows how, according to the Endurantism approach, properties and their values are contained within an object. As noted, each property is actually a cluster of “time interval”–“measured value” pairs, which enables the appearance of different instances at different times. For example, an instance that occurs during the intersecting intervals P1 1, P2 1, and P3 1 will differ from an instance that occurs during the intersecting periods P1 1, P2 1, and P3 2, but yet those two instances refer to the same object.
Figure 7 – Two different instances of the same object
Current Philosophy-Based Pattern
Since the purpose of the philosophical discussion above was to look for a way to maintain the history of an object within itself, we should now ask ourselves how object-oriented software is related to the discussed theories, or in other words, we should ask, how do we perceive the object identity along time in the context of current software applications?
It seems that the way that object properties are defined today (as data members in a class) is very much drawn from the Perdurantism approach. In order to examine this assumption, let’s implement the “cup that lost its handle” example and see how the code behaves.
When this code runs, in time t0, an instance of the class
Cup that has a handle will be instantiated in the computer’s memory. In time t1, after invoking the method
RemoveHandle, that same instance (an identical location in memory, which serves here as the identity core) will change, and now the said memory will contain a slightly different object (with no handle), without leaving a trace of the cup’s previous state (with a handle). This is exactly how the Perdurantism approach would describe the process – temporal properties of an object are changed within a context of object identity, without leaving a trace to the previous state or value.
Suggested Philosophy-Based Pattern
However, the Endurantism approach might be more suitable to what we are looking for – the Endurantism approach offers a way to maintain the whole object history (in the sense of changes in its temporal properties) within itself, and therefore, we could use this approach as our model for the proposed philosophy-based pattern.
According to the Endurantism approach, there is no direct relation between a property (parameter) and its value, but rather a relation between a property (parameter) and a list of “time interval”–“value” pairs. This means that when we activate an object’s method that uses such a parameter (implemented according to the Endurantism approach), or when we directly approach such a parameter, we have to be clear about the time interval that is relevant for the request.
Figure 9 – UML diagram of the object history management solution
As described in the UML diagram, the essence of a history-managed parameter (
HistoryManagedParam) is a list of
TimePeriodValuePair objects, where each
TimePeriodValuePair is actually a pair of a
ParamValue object, which contains the value itself, and a
DateTime object, which indicates the time period in which its pair mate (the value) is valid. The parameter object itself could be attached to any type of object (
HistoryManagedObject in the diagram).
When there is a need to create a new value for a certain parameter (for example, the person weighed 70 kg on 1.1.2010, and 71 kg on 2.1.2010), we use the
HistoryManagedParam.AddValueByPeriod method in order to add another
DateTime pair to the
HistoryManagedParam list of pairs.
And when there is a need to perform a certain task that involves such a parameter, we get the relevant value by using the
HistoryManagedParam.GetValueByPeriod method, which returns the relevant
ParamValue according to the desired time, which it gets as its input.
We use an application that enables a furniture manufacturer to produce date-based price lists for its products, in order to demonstrate the suggested solution for parameter-history management.
The application catalogs the furniture items according to their type (tables, chairs, etc.) and their sub-type (for instance, table sub-types would be writing table, dining room table, etc.), and it of course enables the user to add, edit, or delete furniture items.
The interesting parameter here is the price-data member of the Furniture object, which was defined here as a HistoryManagedParam object. It enables the application to manage the price history of each furniture item.
As a result, the information that could be provided by the suggested model is sufficient for operations like displaying a time-based price list (push the “Price List” button), and there is no need to further construct any data structure (as we would have to do in a case in which we weren’t using such a history-management model).
When the application is executed, the
DataManager object, which functions as the application manager/coordinator, initializes the domain objects, according to data taken from the repository (an XML file - Furnitures.xml).
Figure 10 – Example’s domain classes
As mentioned above, each one of the domain objects (furniture items) contains a price parameter of type
HistoryManagedParam, and in addition, each one of those classes has a collection of other parameters whose values are stable through the object’s history. Such parameters would be the furniture’s catalog number or the furniture’s designer name. The following are the common-to-all-furniture-items parameters, which are defined in the
Figure 11 – Data members of the Furniture class
Adding a New Price
When a new price value is about to be added to the price’s history list of a certain furniture item (that happens when the object is initialized or when it is edited), we use
AddValueByPeriod member function to carry out this task. As already discussed, this method takes a pair of value and date as its input, builds accordingly a
TimePeriodValuePair, and adds it (in a sorted manner in order to ease retrieval by date operations) to the parameter’s history list.
Figure 12 – AddValueByPeriod code
When an object of type
Furniture is queried for its price on a certain date, it returns an answer based on activating the function
GetValueByPeriod of the
Price data member.
Figure 13 – GetValueByPeriod code
The function receives the desired date as a parameter, and returns the appropriate value for that date (or
null, if there is no matched date).
- The history mechanism enables us to add a parameter to an object starting from a certain date, so that in an earlier date, the object is actually lacking this parameter.
- For a parameter that is an inseparable part of an object, and hence, its life cycle is identical to the object’s life cycle, we should add a mechanism that makes sure that the parameter’s life cycle will match the object’s life cycle.