Sunday, August 9, 2009

The Open Closed Principle

Introduction

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.

The Open Close Principle Violation Example

The code below shows a resource allocator. The resource allocator currently handles timeslot and spaceslot resource allocation:



public class ResourceAllocator
{
public enum ResourceType
{
Time,
Space
}
public int Allocate(ResourceType resourceType)
{
int resourceId = default(int);
switch (resourceType)
{
case ResourceType.Time:
resourceId = FindFreeTimeSlot();
MakeTimeSlotBusy(resourceId);
break;
case ResourceType.Space:
resourceId = FindFreeSpaceSlot();
MakeSpaceSlotBusy(resourceId);
break;
default:
throw new InvalidOperationException ("Attempted to allocate invalid resource");
break;
}
return resourceId;
}
}



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.

Modified Code to Support 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:




public enum ResourceType
{
Time,
Space
}
public class ResourceAllocator
{
Dictionary resourcePools = new Dictionary();

public void AddResourcePool(ResourceType resourceType, ResourcePool pool)
{
if (!resourcePools.ContainsKey(resourceType))
{
resourcePools.Add(resourceType, pool);
}
}
public int Allocate(ResourceType resourceType)
{
int resourceId = default(int);
if (resourcePools.ContainsKey(resourceType))
{
resourceId = resourcePools[resourceType].FindFree();
resourcePools[resourceType].MarkBusy(resourceId);
}
else
{
throw new InvalidOperationException("Attempted to allocate invalid resource");
}
}
public int Free(ResourceType resourceType, int resourceId)
{
if (resourcePools.ContainsKey(resourceType))
{
resourcePools[resourceType].Free(resourceId);
}
else
{
throw new InvalidOperationException("Attempted to free invalid resource\n");
}
}
}

public abstract class ResourcePool
{
public abstract int FindFree();
public abstract void MarkBusy(int resourceId);
public abstract int Free(int resourceId);
}

public class TimeSlotPool : ResourcePool
{
public override int FindFree()
{ /*finds free time slot */ }
public override void MarkBusy(int resourceId)
{ /*marks slot as busy */ }
public override int Free(int resourceId)
{ /*releases slot */}
}

public class SpaceSlotPool : ResourcePool
{
public override int FindFree()
{ /*finds free space slot */ }
public override void MarkBusy(int resourceId)
{ /*marks slot as busy */ }
public override int Free(int resourceId)
{ /*releases slot */}
}

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.



No comments:

Post a Comment