Wednesday, June 08, 2005

Open Close Principle

The open closed principle of object oriented design states:
Software entities like classes, modules and functions should be open for extension but closed for modifications.The Open Close Principle encourages software developers to design and write code in a fashion that adding new functionality would involve minimal changes to existing code. Most changes will be handled as new methods and new classes. Designs following this principle would result in resilient code which does not break on addition of new functionality.
In this article, we will explore the the open closed principle via an example of a resource allocator.
Original Code (Violates the Open Closed Principle)
The code below shows a resource allocator. The resource allocator currently handles timeslot and spaceslot resource allocation. It is clear from the code below that it does not follow the Open Closed Principle. The code of the resource allocator will have to be modified for every new resource type that needs to be supported. This has several disadvantages:
The resource allocator code needs to be unit tested whenever a new resource type is added.
Adding a new resource type introduces considerable risk in the design as almost all aspects of resource allocation have to be modified.
Developer adding a new resource type has to understand the inner workings for the resource allocator.
Thus we can say that extending the design involves considerable code change.
Resource Allocator (Violates Open Closed Principle)
class ResourceAllocator
{
public:
int Allocate(int resourceType)
{
int resourceId;

switch (resourceType)
{
case TIME_SLOT:
resourceId = FindFreeTimeslot();
MarkTimeslotBusy(resourceId);
break;

case SPACE_SLOT:
resourceId = FindFreeSpaceSlot();
MarkSpaceslotBusy(resourceId);
break;

default:
Trace(ERROR, "Attempted to allocate invalid resource\n");
break;
}
}


int Free(int resourceType, int resourceId)
{
switch (resourceType)
{
case TIME_SLOT:
MarkTimeslotFree(resourceId);
break;

case SPACE_SLOT:
MarkSpaceslotFree(resourceId);
break;

default:
Trace(ERROR, "Attempted to allocate invalid resource\n");
break;
}
}

};

Code Modified to Support Open Closed Principle
The problems with the above design is that it violates the Open Closed Principle. The following code presents a new design where the resource allocator is completely transparent to the actual resource types being supported. This is accomplished by adding a new abstraction, resource pool. The resource allocator directly interacts with the abstract class resource pool.
This has several advantages:
The resource allocator code need not be unit tested whenever a new resource type is added.
Adding a new resource type is fairly low risk as adding a new resource type does not involve changes to the resource allocator.
Developer adding a new resource type does not need understand the inner workings for the resource allocator
Further abstractions can be developed to group together resources that use similar algorithms to allocate resources. A few examples are:
FreeQueueResourcePool: Base class for all resource pools that are implemented with free/busy queue.
BookingResourcePool: Base class for all resource pools that are implemented as timebound bookings (similar to ticket booking in a movie theater).

Resource Allocator (Follows Open Closed Principle)
class ResourceAllocator
{
ResourcePool *m_pResourcePool[MAX_RESOURCE_POOLS];

public:
int Allocate(int resourceType)
{
int resourceId;
resourceId = m_pResourcePool[resourceType]->FindFree();
m_pResourcePool[resourceType]->MarkBusy(resourceId];
}

int Free(int resourceType, int resourceId)
{
m_pResourcePool[resourceType]->MarkBusy(resourceId];
}

void AddResourcePool(int resourceType, ResourcePool *pPool)
{
m_pResourcePool[resourceType] = pPool;
}

};
class ResourcePool
{
public:
virtual int FindFree() = 0;
virtual int MarkBusy() = 0;
virtual Free(int resourceId) = 0;
};
class TimeslotPool : public ResourcePool
{
public:
int FindFree();
int MarkBusy();
Free(int resourceId);
};
class SpaceslotPool : public ResourcePool
{
public:
int FindFree();
int MarkBusy();
Free(int resourceId);
};
Open and Closed
The above design follows the open and closed principle. The Resource Pool is open for extension as new resource pools can be added without much impact on the rest of the system. The Resource Allocator is closed for change, as no changes need to be made to it for enhancing the system.
As you can see the above has been achieved by using two techniques:
A base class was defined for Resource Pool. This base class captures the high level interfaces.
An array of Resource Pool pointers was defined in the Resource Allocator. This array is indexed by the resource id.
If this principle is followed during the design, most changes to the system would be in terms of adding new classes/methods. Changes to existing classes/methods would be minimized.