2006-12-02

Roles

What is a Customer? If you are some kind of business service provider then it will be a Company, if you are a window cleaner then it will be a Person, but if you are a travel agent it could be either. What about a Supplier? You could argue the same as for a Customer, but you could also argue that a Company/Person could be both a Supplier and a Customer.

This is where Roles come in. A Supplier isn't something you are, it is something you do, you supply something; just as a Customer consumes something. I find that it is much better to enable "Things" to perform more than one action, it is rare that life is simple enough for everyone to perform only a single role in life. The typical solution to this is the Party/Role pattern.

Party role pattern

A good point to make here is that this is a pattern, not a set of classes intend for inheriting from! This means that when you have a Company (alias "Party") you should create a CompanyRole class and associate them, descending CustomerRole and SupplierRole from CompanyRole. The problem with approaching this simply as a pattern is that we still cannot make both a Person and Company share roles. Another typical solution to this is to descend both Person and Company from a Party class. I personally only like to inherit classes for behaviour purposes, not in order to inherit properties/associations - inheritence is I am rather than I have.

So, how do I implement this kind of behaviour in my own applications? I still use a Party/Role pattern, but I use a bit more aggregation:
Aggregated party role pattern

This model allows for a collection of roles which are collected by being associated with a RoleHolder. Some roles may apply to any kind of object that performs roles, whereas some such as EmployeeRole may only be applied to a Person. To implement this functionality the RoleHolder should not be used directly, instead a concrete descendant should be used for each type of class; Person owns a PersonRoleHolder and Company owns a CompanyRoleHolder. A role may permit or deny itself being applied to a certain type of RoleHolder via its virtual MayApplyTo(roleHolder) method, likewise a concrete RoleHolder may reject a certain type of Role via its virtual MayAcceptRole(role) method.

This gives us a way to ensure that certain types of Roles cannot be applied to certain types of RoleHolders, there must be mutual agreement before an association may be made. Now we need a way of retrieving the true owner of the Role, this is achieved by overriding the abstract object GetOwner() of RoleHolder. employeeRole.RoleHolder.GetOwner() would return a Person, whereas customerRole.RoleHolder.GetOwner() could return either a Company or a Person.

Finally we need a uniformed way to get to the RoleHolder of an object, seeing as Person and Company do not have nor need a common ancestor class the most appropriate way to check if it holds Roles and to retrieve its RoleHolder is to use an interface, IRoleHolder.GetRoleHolder is implemented on the business entities in order to provide this ability. A could of nice methods to add to the RoleHolder class + IRoleHolder interface are:

bool HasRole(Type roleType);
Role GetRole(Type roleType);

This way we can write code like this:

IRoleHolder roleHolder = (Person as IRoleHolder);
if (roleHolder != null && roleHolder.HasRole(typeof(EmployeeRole)))
{
EmployeeRole employeeRole := RoleHolder.GetRole(typeof(EmployeeRole)) as EmployeeRole;
employeeRole.TerminateEmployment(DateTime.Now);
}

Conclusion:

This approach allows a business entity to perform multiple roles at the same time. It also allows the same role to be applied to business entities that do not share a common ancestor class.

3 comments:

darkadept said...

In the last part of your post you mention adding the HasRole and GetRole methods. Does this allow a Person to have multiple EmployeeRoles? It seems like this line:

EmployeeRole employeeRole := RoleHolder.GetRole(typeof(EmployeeRole)) as EmployeeRole;

does not allow that. Or does that line create a new role for that Person? I'm coming from the C++ world and Java syntax is a little foreign to me.

darkadept said...

ahh, except that's C# right. darn i'm an idiot, well my question still stands....

Peter Morris said...

The method you mention will return the first instance of that type. I'd say that if you want to record multiple employments for the same person you should do this.

EmployeeRole 1---* Employment

Where Employment has start/end dates, employee number etc.