The Open Closed 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.
|