Classes that encapsulate business rules lay the foundations of a true object oriented application.
Implementing Business Objects
This article is the first of a series in which we'll be exploring the many facets of implementing a true object oriented application. Along the way we'll take in almost every aspect of application design and challenge some of the accepted ways of writing a Delphi application. The fundamental concept behind this approach is encapsulation: to design a set of classes with well-defined interfaces (methods) that operate on properties. These concepts will pervade the entire application and will greatly affect the way in which data is stored and presented. I would recommended readers to study Francis Glassborow's C++ column; although the Delphi object model lacks the completeness (and the complexity) of C++, the concepts of good class design are independent of language.
Most typical Delphi applications written today are not object oriented. Just because the language has an object model and many existing and new classes are used, this does not mean that the application can be regarded as truly OO. Code reuse has finished with dropping third party components onto forms, and interdependencies between forms and units quickly proliferate. Future opportunities for changing application fundamentals (such as switching database or moving from 2-tier to 3-tier implementation) are severely limited or very expensive to contemplate. Writing the application in a true OO fashion would facilitate, rather than restrict, these opportunities. However, writing such applications requires a mind shift, accompanied by an initial lack of productivity, that most development teams are loath or unable to consider. Over the course of these articles I hope to demonstrate some of the fundamentals which will help developers make the move to implementing better applications. The resultant systems will always be more reliable, maintainable, consistent, flexible, reusable and generally perform better than an application written in the standard fashion. In particular, the benefits of code clarity are such that for large applications, one written in a truly OO fashion can require significantly fewer maintenance resources than the same application written traditionally. I should perhaps justify these benefits of an OO application further: I believe that few IT advocates with something to sell (in this case the vision of better applications) should get away unchallenged!
The enhanced reliability of OO applications comes from the fact that data and operations are encapsulated in well-defined classes. The compiler itself will encourage correct class, method and property usage through strong type checking, and having unique rather than duplicated code means that future changes to a single routine are pervasive throughout the application. A consequence of the correct usage of classes is that the relationships between them are self-evident and a far greater proportion of the code written is actually implementing the "meat" of the application, rather than worrying about details such as how data is actually stored persistently. This makes the application significantly easier to maintain, a factor further simplified by greater consistency throughout. As we shall see, using class inheritance extensively both increases productivity and reliability, but also imposes consistency. This consistency is evident in the way that code is laid out and how classes behave, but also to the extent of how data is stored and how the user interface is presented. As much of the functionality is provided in base classes, it is possible to quickly change their behaviour to fundamentally affect the application (such as changing the interface to be html-based, rather than form-driven). These base classes can be designed to be application-independent, so that the second application written this way gains an immediate productivity boost. A good set of base classes can provide up to 50% of the code in a medium-sized application, an obvious benefit from a time, cost and reliability standing. It is only reasonable to highlight that making the switch to "real" OO development is non-trivial and should only be undertaken for the first time with experienced assistance, or for a project of small size without urgent deadlines. It should also be stressed that an OO solution does not dictate other classes that must (or must not) be used within the application. If a company has developed their own visual components, uses those of a third party, or has standardised on a particular database platform then there is no reason why these cannot be used (with one significant exception). Developing an OO application is about applying a consistent set of design patterns rather than dictating what components should be used.
The first step in designing any object-oriented application is thinking about the classes that will be required. This is an absolutely fundamental step, as with any other development technique, as getting it wrong at an early stage will be expensive to correct. In designing our classes we should generally strive for low coupling and high cohesion - classes should be as independent as possible from other classes, but should be possible to be combined in powerful ways. One way to achieve this is to categorise sets of classes by the roles that they will have within the application. Judicious selection of these roles will result in a cohesive set of classes.
There is one fundamental rule that recurs throughout class role design: the strong separation of the classes responsible for presentation, application and persistence. Presentation can be generally catered as the user interface, and persistence as anything that stores data (typically in a database). Although this is the same separation as exists for 3-tier development, it should be noted that this is a conceptual design separation that can be implemented in many ways: as a single monolithic application all the way through to a fully distributed multi-tier system. The set of classes that constitute the application logic are those that actually do the hard work of responding to user stimuli to manipulate and process data. A subset of classes within this layer is identified as representing the real world entities which will be modelled by the system. These are very often labelled as "business" or "problem domain" classes. They constitute a vital part of any OO system, as most other classes will be supporting them one way or another, and they form the focus of all developer involvement.
Identifying the business objects for a given application generally becomes instinctive with experience, although there is a whole science (or is it art?) behind the process. The beauty of OO compared with traditional techniques such as SSADM is that the processes of analysis and design that have evolved maintain the same entities throughout: it is possible to see a representation of each business object throughout OOA (analysis), OOD (design) and eventually the implementation itself (OOP). We will be exploring some of the techniques for identifying the appropriate business objects in future columns. For the time being, we will assume that these processes have been performed.
The communication between classes that exist in the different layers (presentation, application, persistence) is well-defined and should be adhered to absolutely. This is shown in Figure 1, and the arrows indicate that a class in one layer can make method calls into another class. This diagram shows that the presentation layer (the user interface) can manipulate the application layer or other classes within the user interface, and that the application layer (business objects) can manipulate itself and make calls into the persistence layer, which can only respond to requests from the application layer.
To demonstrate how business objects can be implemented, we will be following a worked example with a small set of classes exposing the classic Stock, Customer and Order entities (a company offers a number of items of stock for sale, which can be bought (ordered) by customers). Our (trivial) analysis has confirmed that initially we will be requiring three business objects to represent each of these entities. In our Delphi application we will have three classes: TStockItem, TCustomer and TOrder. Listing 1 shows an abridged public interface for these classes. It should be noted that all attributes of the class are exposed as properties: this is simply good class design and facilitates control over the access to the properties (should this be required). It should also be noted that the properties are exposed in the published section of the class (for reasons we will discuss in the future), and that they are fully qualified with default values where appropriate. Although this property qualifier is ostensibly used when streaming components, it serves a useful purpose to denote the initial value of each property and helps the code to be self-descriptive.
Something that is fundamental for business (problem domain) objects is that they can have no knowledge of how they are stored persistently. They do not know how they are stored in database tables, in fact they can make absolutely no assumptions about storage whatsoever. A business object is a very focused class that exposes only appropriate properties and the methods necessary to manipulate them. When designing the public interface of the class no concession should be made as to the types of the properties for persistent (database) storage. You should always select the most appropriate type for the property concerned, irrespective of whether or not the intended database supports them natively. A good example is a set property: few databases support sets directly but if this is a natural way of exposing a property of a business object then it should be chosen in preference to another representation (possibly a number of Booleans). Another example is to expose something like an address as a TStringList. It is not the role of the business object to prejudice it's interface to favour database storage: this is the task of the persistence layer.
Our first framework
We have identified and implemented three business objects as the initial basis for our application. These three objects share a common role: to represent real-world entities in some way. We therefore expect them to have similar properties and therefore a class hierarchy is appropriate. We will give each of these objects a common ancestor, called TPDObject (for Problem Domain object). This TPDObject class will be extended over time as we introduce common methods and properties to our classes. It should be noted that TPDObject does not contain (and never will) any application-specific elements. As an application-independent construct it should be placed in a separate unit and forms the basis of a framework: a set of classes that can be re-used in many applications. Over time our framework will expand greatly to provide many base classes that provide significant application-independent functionality, ready to be specialised for specific systems. Our TStockItem, TCustomer and TOrder classes are examples of specialised versions of our generic TPDObject. A closer examination of Listing 1 shows that, in fact, our three problem domain classes are each descended from another class, TMyAppPDObject, itself a descendant of TPDObject. Although the implementation of TMyAppPDObject is empty, it is another example of good class design so that if we want to provide any application-specific elements to all of our problem domain objects there is an appropriate class in our hierarchy.
Listing 2 shows our embryonic Framework unit. At the moment the TPDObject class provides only a read-only property called ID, which is of type TObjectID. This property is used to give each class the concept of identity: we will define two instances as representing the same object if they are of the same type and have the same ID value. This has been deliberately defined as it's own type so that it is not directly assignment compatible with other scalar types. The choice of type for TObjectID is arbitrary: I have chosen in this instance to make it an Integer type, but there is a case to make it it's own class. Although a more "pure" approach, I have decided against this on the basis that our problem domain objects will be constructed (and destroyed) many thousands of times during an application and we can avoid the overhead of constructing and destroying the extra TObjectID class (my purist argument for this implementation choice is that the separate TObjectID class has been subsumed into the TPDObject class as a compound object). There are a couple of extra methods in this unit to convert our object identity type to a string representation, and a constant for the initial "has no identity" value.
There are a number of schools of thought on object identity; one says that all business objects should be allocated an identity when it is constructed and that the identity should be unique within an application (or even globally as in a GUID). In practice, it is important only to be able to distinguish between two objects in a given context, and keeping this simple has obvious performance and storage benefits, although you certainly won't find me arguing against a choice for a more complex type in appropriate circumstances.
A question of ethics
In order to highlight some of the design issues and implementation decisions taken when designing a framework I will occasionally pose questions and invite readers to consider the rationale behind them. At the beginning of the column I alluded to a significant exception to the statement that a truly OO design does not proscribe the use of particular classes. What is the exception to this statement (hint: the answer lies within Figure 1).
((( Listing 1 - An application-specific Problem Domain unit (abridged) )))
TMyAppPDObject = class (TPDObject)
TStockItem = class (TMyAppPDObject)
property Name: String;
property QuantityInStock: Cardinal default 0;
property TradePrice: Currency;
property RetailPrice: Currency;
TCustomer = class (TMyAppPDObject) ... ;
TOrder = class (TMyAppPDObject) ... ;
((( End Listing 1 )))
((( Listing 2 - An application-independent Framework unit )))
NotAssigned = 0;
TObjectID = type Integer;
TPDObject = class
property ID: TObjectID read FID default NotAssigned;
function StrToID (Value: String): TObjectID;
function IDToStr (Value: TObjectID): String;
((( End Listing 2 )))
((( Figure 1 - Class interaction overview )))
((( End Figure 1 )))
Next in series