replace conditional with polymorphism
Posted on December 01, 2010 @ 13:09 oopExperienced OO practitioners know that switch statements are a design smell. Usually it’s an indication of missing polymorphic behaviour.
Take a look at the following snippet of code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
foreach (var permission in principal.Permissions)
{
if (newGroup != null)
{
var assignment = new SPRoleAssignment(newGroup);
if (assignment != null)
{
switch (permission.RoleDefinition)
{
case PermissionTypes.FullControl:
assignment.RoleDefinitionBindings.Add(fullControlRole);
break;
case PermissionTypes.Design:
assignment.RoleDefinitionBindings.Add(designRole);
break;
case PermissionTypes.Read:
assignment.RoleDefinitionBindings.Add(readRole);
break;
case PermissionTypes.Contribute:
assignment.RoleDefinitionBindings.Add(contributeRole);
break;
}
web.RoleAssignments.Add(assignment);
}
}
}
It looks simple enough, doesn’t it? But what happens if we add a new PermissionType? Are we switching on the PermissionType in other areas of the code base?
I think it’s safe to say that the above code violates the Open/Closed Principle as well as the Single Responsibility Principle.
When I look at this code, my mind immediately starts looking for the missing abstraction. In this case I believe it’s an abstraction over different Permission Types.
Here’s how my mind has re-factored the above code.
1
2
3
4
5
foreach (var permission in principal.Permissions)
{
var assignment = permission.CreateFor(newGroup);
web.AddRoleAssignments(assignment);
}
Instead of making RoleDefinition an enum, we can turn it into a class to represent each case in the switch statement. We then just delegate the polymorphic behavior of each RoleDefinition to decide which role should be added to the RoleDefinitionBinding.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FullControllRole : RoleDefinition
{
public void AddRolesTo(IList<RoleDefinitionBindings> roleDefinitionBindings)
{
roleDefinitionBindings.Add(fullControlRole /* or this */);
}
}
public class DesignRole : RoleDefinition
{
public void AddRolesTo(IList<RoleDefinitionBindings> roleDefinitionBindings)
{
roleDefinitionBindings.Add(designRole);
}
}
This also better satisfies the Law of Demeter.
The rest of my mental picture
Please note that the below code was written in a text editor and was not actually run against a compiler.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Permission
{
RoleDefinition role;
public Permission(RoleDefinition role)
{
this.role = role;
}
public SPRoleAssignment CreateFor(string group)
{
var assignment = new SPRoleAssignment(group);
role.AddRolesTo(assignment.RoleDefinitionBindings);
return assignment;
}
}
public class SPRoleAssignment
{
public SPRoleAssignment(string group){}
}
public class Web
{
IList<SPRoleAssignment> RoleAssignments;
public void AddRoleAssignments(SPRoleAssignment assignment)
{
RoleAssignments.Add(assignment);
}
}