Object Oriented Modeling and "Computer Stuff"Geoffrey Slinker
version 1.5 March 2006
version 1.4 April 2005
version 1.3 September 2004
AbstractDuring object oriented modeling one is tempted to adhere to a rigid approach of allowed behavior. Once I was working with a Person Object and needed to assign an instance of a Person to another Person. There was no assignment method. I asked the owner of the Person Object code if there could be an assignment operator provided. The response was, "In the real world you can not assign a person to be another person. I can not add the method, it breaks the model."
Objects may model a real world entity but they exist in the computer world. Objects are at least two-dimensional. Objects are made of "computer stuff" or "computer matter". Objects have their representation of the real world entity and they have the properties and laws of the computer realm in which they exist. It is not breaking the model to add methods from the second dimension. The object has certain behaviors that are fully acceptable in the second dimension of the computer world that are not acceptable in the real world.
IntroductionObject oriented modeling is attaches behavior to structure. Inheritence is typically used to model either static subtyping or runtime/behavior subtyping. Static subtyping is typically used in simple examples of inheritence. Runtime subtyping is usually more interesting and facilitates polymorphic behavior.
This paper considers object oriented modeling that uses runtime subtyping. Modeled objects will exist in a computer system and be defined using some computer programming language. Seems obvious enough doesn't it?
Let’s begin by modeling a person for use in a genealogical system.
A person has a name, a birth place, a birth date, a death place, and a death date. When you create the object you pass in the birth information. It can not be changed. You are born in one place at a specific time. The name information can be changed because in the real world you can change your name. Once the death information is entered then you can no longer change any of the information. You are dead and dead is dead.
Let's suppose that the methods are sufficient to model the person and the system has been in development for many months. During the testing of the system it becomes apparent that we need a way to generate people objects to drive these tests. Some of the people objects need to be valid and some need to be invalid in order to test thoroughly. So you create a Person generator and find that you would like to assign one person object to another. You need an assignment operator that can do a deep and correct copy of the Person object. You ask the owner of the person object to create this method for you and he says, "Sorry, in the real world people can not be assigned to people. It breaks the model." Since I am known for my tact my response would be, "I understand completely. Would you like me to remove the constructor and destructor? Real people are not constructed or destructed or at least they shouldn't be."
Objects are made of StuffObjects are made of stuff. Suppose you make a model of a cube. What are the properties of a cube? It is a regular solid with six congruent square faces. Are there any other properties for a cube? Not that I am aware of. Now suppose you make your model cube from pine wood. Now your cube floats in water. Do cubes float? This one does. It will burn as well. Suppose you make your cube out of steel. Now your cube sinks, rusts, and is subject to magnetic forces. Which is the correct model of a cube? Suppose you make your cube out of ice. Your ice cube will melt and could be used to chill a beverage. Will the true and proper cube model please stand up?
Since objects are made of "stuff" they take upon them properties of the stuff they are made of. The object is subject to constraints of the stuff from which it is made. An ice cube can not exist in boiling water. The object is destroyed. When you model something in a computer language the object will be made of "computer stuff". The constraints and rules of the computer world will act upon the object. Many of the constraints of the computer world are resource constraints. There is a finite amount of computer memory. A computer object can be replicated until the memory resource is exhausted. Similarly I guess you could say you can make pine wood cubes until all of the pine wood in the world is exhausted.
A computer object has a lifespan in the computer world. It has a behavior because it is a computer object. In most computer worlds objects can be constructed and destructed. Objects can be assigned to other objects. Objects can be cast into other objects. Objects can be turned into Xml descriptions of their state. Xml is like the transporter from Star Trek. We take the object, put it into the Xml pattern buffer and send it across the net where it is reassembled at the destination. Objects have scope in which they exist and when that scope is exited the object ceases to exist.
When defining an object be sure to consider that the object exists in a computer system and that it will need certain behaviors for that existence.
"Is A" may be "Is not A"In static subtyping the only thing you are interested in are the member variables and not the methods. In runtime or behavioral subtyping you are only concerned about the methods and not the member variables. Behavioral subtyping is used more often because of polymorphism and specified responsibilities.
The classic example of the "Is A" relationship is the Rectangle object and the Square object. In "The Liskov Substitution Principle" article found on the Object Mentor web site this interesting example is explained.
Suppose you model a rectangle object. It has a width and a height and methods to get and set these properties. Now you want to add a square to the system. You remember that a square is a rectangle with the constraint that the width and height are equal. You make sure the methods for your rectangle's properties are declared virtual and you create a new class called square that inherits from rectangle. You override the set methods for width and height so that if the width is changed then the height is changed to be the same value and visa versa. Now you have a square that can be used where ever a rectangle is used.
Well, we seemed to have forgotten that objects are made of computer stuff and are manipulated by other computer objects. Suppose you have a procedure that takes a reference to a rectangle.
boolean ChangeWidthAndValidate(Rectangle &rect, int newWidth)
int height = rect.GetHeight();
return ( (height == rect.GetHeight() && (newWidth == rect.GetWidth()) );
If you pass a square into this procedure and set the width to five then the square will set the height to five as well in order to maintain its squareness. Unless the new width is the same as the old width this routine will return false.
The problem here is behavior. A square object does not behave the same as rectangle object. The behavior difference is in the SetHeight and SetWidth. In the rectangle SetWidth only changes the value of the width and SetHeight only changes the height. For the square if you set the height there is the side effect of setting the width, and if you set the width the height is changed. So, the computer square does not behave the same as the computer rectangle. If they do not behave the same then they can not be substituted for each other.
So, what is the solution? One is to have a property of the rectangle that tells you if the rectangle is square. If you want a rectangle to stay square then you can instantiate a immutable instance of the rectangle with the width and height to be the same. Since this instance of the rectangle is immutable there will be no behavior available to change the width and height of this instance and thus it will remain square.
Or another solution could be that you make a square class that does not inherit from the rectangle class. The rectangle takes two variables for defining the width and the height. A square only needs one to define the length of the sides. In this case the computer square is clearly not a computer rectangle. They don't have the same methods or the same member variables. You can make a copy constructor that can create a rectangle from a square. The copy constructor for converting a rectangle to a square would throw an exception if the rectangle's sides are not the same length.
Suppose you decide to change the ChangeWidthAndValidate procedure to check the type of the object and have conditional logic for each type of rectangle. Then you have introduced a maintenance issue through coupling. If you take this approach you have to ask your self, "Did I find all of the procedures that may suffer from similar problems?"
Maybe you decide to make every procedure that operates on a rectangle a method of rectangle so that you can virtualize each one. In this case the ChangeWidthAndValidate method would be added to the base class and sub-classes as virtual and the square would override the rectangle's implementation of the method to correct the validation behavior. This means that you can not have any procedures that manipulate objects directly. All object manipulation behavior is part of the class. A class would then model the data, the behavior of the object, the behavior of any computer world needs, and the behavior of every thing that can manipulate the object. To ensure that all manipulation behavior is modeld in the class you can make every property setter private. This solution is very complicated and would be difficult to maintain or enforce. Think about all of the classes in your system and all of the sub-classes in the system and imagine changing every access specifier to private for each method that sets a property value. How much refactoring would that cause?
ConclusionObject oriented modeling is more interesting when it is modeling behavior. There are behaviors that the object will have because it exists in a computer program. Two well known behaviors are construction and destruction. Java and C# demonstrate this aspect by having the base class of Object. The class Object models behaviors that are needed to exist in the computer world. All objects have this second dimension of behavior because they are made of "computer stuff" or in other words exist in a computer program. It does not break the real world model to have the computer world behaviors as part of the class.
Because objects are made of computer stuff they are subject to the rules of the computer world. The Liskov Substitution Principle is something to be followed if you want inheritance and virtual functions to behave as designed. This principle applies to computer realms and not to the real world. It constrains how we model and forces us to remember that if a method of a subclass alters the state of the object in a different way than the base class then polymorphism is going to demonstrate inconsistent results depending on the real type of an object.