Back to Thoughts On Technology Page | Back to Peter's Home Page | Back to Family Home Page



Designing Effective Object Responsibility Separation
Peter Rose - 01/2005


ABSTRACT

SUMMARY

PROBLEM DOMAIN SEGMENT

DESIGNING BY INTERFACE

ADDING AGGREGATE OBJECTS

THE CONTAINER-CONTAINED RELATIONSHIP

DEFINING SERVICE CLASSES

BOLTING BEANS AND SERVICE OBJECTS TOGETHER

LOOKING AT THE PLUMBING





Abstract
The purpose of this article is to show how to design a system of objects that fulfill the solution of one part of an application's problem domain, and to demonstrate how to not only effectively delegate object responsibility among them but also to decouple the objects from each other.

Summary
What this article will demonstrate is how a graph of JavaBeans and supporting service classes are developed from each major requirement of the Problem Domain of the application.

Of importance during this process is to always think in terms of design by interface and to use constructors for object state initialization where appropriate.

In addition, one of the primary considerations of application design is to build objects and systems of objects that are highly encapsulated and loosely coupled.

Problem Domain Segment
Our overall problem domain is the construction of a system of objects that describe and control destroyers and submarines.

The part of this problem domain that this article will focus on is the establishment of a process for monitoring the engine of a submarine.

Designing By Interface
Modern object systems development is based upon interfaces, those classes that establish the contract by which all implementing classes must adhere to.

Our system contains destroyers and submarines. These are ships and so we will start with a ShipInterface class. What sorts of common functionality exist between destroyers and submarines? At this point we may not know. But at least we have defined the stub for this commonality knowing that each function that is shared between a destroyer and submarine must be indicated contractually here.

Since we have a ShipInterface, that leads us to the immediate need for an abstract ancestor Ship class from which Destroyer and Submarine classes can be extended from. Our object diagram thus becomes:

diagram

Though at this point we may not know the common functionality among these objects to reflect in the ShipInterface, we do have some idea of common properties between destroyers and submarines.

Adding Aggregate Objects
They each - as well as any other class to subclass the Ship class - will have a ControlRoom, WeaponsCenter, and EngineRoom, for example, to get us started. These properties, in fact, are aggregate objects of the Ship class (and thus of all of Ship's descendants):

diagram

Which brings us to our first class diagram outline:

diagram

The focus of this discussion is on the engine, which obviously will be an aggregate of the EngineRoom. But just to make sure our example here is a little more descriptive, we'll include other aggregates of the EngineRoom class that would be consistent across both destroyers and submarines.

diagram

Note that all of these classes are true objects in the sense that they will all have properties and methods, and they will all communicate with the EngineRoom in one manner or another (i.e. single or bi-directional). Generically, objects such as this are referred to as JavaBeans - or just "beans".

The Container-Contained Relationship
As aggregates, they know nothing of their containing object; only the container knows its aggregates. That is to say that the Engine does not know that it is in an EngineRoom nor does it know that there are FuelTank and Boiler objects that it is dependant upon. It only knows about itself. However, the EngineRoom has perfect knowledge of its contained objects, and thus it can organize those objects so that they work in harmony with each other.

From a strict object perspective, the EngineRoom is only a container for its aggregates. It really doesn't know how to "control" or "monitor" the Engine, for example. It only knows how to "contain" an Engine.

Defining Service Classes
We are going to need another class that knows how to operate an Engine and we are going to need another class that knows how to monitor an Engine. This begets another graph of objects as follows:

diagram

These classes are called service classes because they provide services to the system's beans - they give the beans something to do! This particular graph of objects describes an extendable combination of command and façade patterns for performing various engine room operations, i.e. at this point, controlling things and monitoring things.

The Engine class knows how to do those types of things an engine does: run, turn gears, take in fuel, burn that fuel, etc. But it might not necessarily know when it is running too hot; it just knows how to run. The purpose of an EngineMonitorSubmarine class will be to know the safe operating conditions for this engine and report to the EngineRoomOperationsMgr from where it was instantiated. Based on this information, the EngineRoomOperationsMgr may then need to request the services of the EngineControllerSubmarine object from its Controllers object aggregate.

I don't want to clutter up the object diagram, but note that both Controllers and Monitors classes will need to implement some type of generic EngineRoomOperationsInterface, and in turn there will be separate ControllersInterface and MonitorsInterface that will describe special contracts that each of their extended classes will need to follow.

Bolting Beans and Service Objects Together
To have these two systems of objects (i.e. beans and service objects) communicate with each other, there will be a uses or association relationship between the EngineRoom object and the EngineRoomOperationsMgr object.

diagram

Note that the EngineRoomOperationsMgr is not an aggregate of EngineRoom and we may ask ourselves why not: why wouldn't that be just as viable of a candidate for an aggregation relationship along with FuelTank, Boiler, and Engine?

Aggregate objects - basically properties of the containing object - characteristically only know how to deal with themselves and have no knowledge of the container or the container's content. Just looking at the name "EngineRoomOperationsMgr" is a clue that this object is going to provide services for lots of things in the EngineRoom. That is why it is a service class and has an association relationship with the EngineRoom. And if the EngineRoomOperationsMgr has no properties of its own or "state" - which it actually will have in our example - then it would be an "agent" service class (i.e. a class that has no state and provides services for other classes but not itself).

We might thus expect to pass as parameters to the abstract EngineRoomOperationsMgr an object of type Submarine and perhaps an int indicating EngineMonitor functionality, though as we will see: by not passing the int we more effectively decouple objects from each other.

We might also at first think that the EngineRoom object will receive the EngineMonitorSubmarine object as a return and will then cast the returned object appropriately and then call methods on that monitor. But this is not the case.

Doing this would couple the EngineRoom and EngineMonitor objects together. What is an EngineRoom supposed to know about monitoring the things that are in it? It might know that it needs to monitor its content, but it does not necessarily follow that it knows "how" to do this.

The object that knows how to monitor a submarine's engine is the EngineMonitorSubmarine object. Thus, the passed parameter of type Submarine not only informs the EngineRoomOperationsMgr as to what type monitor to instantiate, but also provides that monitor the submarine object it is to monitor!

Looking At The Plumbing
The next step in the overall design process is to see where and how some of these objects are constructed and how they could potentially communicate with each other. The idea is to start roughing in how these objects are going to solve the problem at hand, i.e. how to monitor the engine.

Instantiation And Initialization
Our driver object will instantiate and initialize all of the destroyer and submarine objects needed by the system. For example, for a Submarine:


Ship sub = new Submarine(
		initialConditionsObject );

The initialConditionsObject simply contains various types of startup and initialization information that a Ship object needs to get its systems up and running. The constructor brings the Ship's systems on line by setting a protected class property for the InitialConditionsObject so that all extended and aggregate systems of the Ship object have access to it. This is a process of initializing the state of an object via its constructor. This is better object construction then using a constructor to initialize individual class properties as that just couples objects together.


public Submarine(
		InitialConditionsObject aInitialConditions ) {
	super( aInitialConditions );
}

public Ship(
		InitialConditionsObject aInitialConditions ) {
    initialConditions = aInitialConditionsObject;
	controlRoom = new ControlRoom(this);
	weaponsCenter = new WeaponsCenter(this);
	engineRoom = new EngineRoom(this);
}

Note that we pass these objects an instance of Destroyer or Submarine.

EngineRoom
And in turn the engineRoom will know that it needs to bring its systems up, so it might go through the following:


fuelTank = new FuelTank();
boiler = new Boiler();
engine = new Engine();

Next, one of the things that the engineRoom must do is turn on all its controllers and monitors for its systems.


EngineRoomOperationsMgr engineRoomOperationsMgr =
	New EngineRoomOperationsMgr(aShip);

Note that this simple call gives the engineRoomOperationsMgr object and all of its objects access to all of the aShip objects. Thus, adding a CommunicationsRoom to the Ship or an ElectricalPanel to the engineRoom will automatically flow down to all objects that receive the aShip as a parameter.

EngineRoomOperationsMgr
The EngineRoomOperationsMgr knows that it has a bunch of controllers and monitors to activate. The specific type of monitor, for example, it needs to use is found by the Monitors class testing the cast of the aShip object parameter, finding out that it is a Submarine, and then instantiating all of its Submarine oriented aggregate monitor objects.

diagram

In our particular case, we need the EngineRoomOperationsMgr to activate the EngineMonitorSubmarine object. Note that the Monitors service class has abstract aggregate classes of FuelTankMonitor, BoilerMonitor, and EngineMonitor. Each one of these abstract classes knows how to delegate the request to the correct object.

EngineMonitorSubmarine
The EngineRoomOperationsMgr instantiates a Monitors class passing it the aShip parameter.


public Monitors( Ship aShip ) {
	EngineMonitor engnieMonitor =
			EngineMonitor.create( aShip );

We need an engine monitor for a Submarine. The Monitors class could do this by calling a static factory create method on the abstract EngineMonitor class which would then return an instance of EngineMonitorSubmarine.

Note that this returned engineMonitor object contains a private property of type Ship - which in this case is a Submarine.

The engineMonitor object will most likely be a thread so that it can continue to call methods on the aShip Submarine object's engine object to monitor it. Recall that as the engine systems change, they will be recognized via reference in the thread object's aShip object.


Back to Thoughts On Technology Page | Back to Peter's Home Page | Back to Family Home Page