Avoid duplicate code with delegates
2008-09-10 | Filed Under Software development |
This post looks at a common system architecture (using business objects, proxy objects and command objects) and describes how code duplication can be avoided in certain situations. While all example code is in C#, this approach can be applied to most other languages with just minor tweaks.
Let’s assume that we have the following class that represents a business object:
public interface IPerson {
string FirstName { get; set; }
string LastName { get; set; }
}
public class Person : IPerson, ICloneable {
private string _FirstName;
private string _LastName;
public string FirstName {
get { return _FirstName; }
set { _FirstName = value; }
}
public string LastName {
get { return _LastName; }
set { _LastName = value; }
}
public object Clone() {
var ret = new Person();
ret.FirstName = _FirstName.Clone() as string;
ret.LastName = _LastName.Clone() as string;
return ret;
}
}
Then let’s assume that we want to expose objects of this class to an untrusted party, e.g. an MsScriptControl macro API or a plugin interface. Naturally, it would be a seriously bad idea to grant any such code direct access to our actual business objects, and so a proxy object is needed. Normally, such a proxy object would make sure that no invalid data is passed to the actual business objects, that object references are not broken, etc. For simplicity’s sake, our proxy does none of this.
public class PersonProxy : IPerson {
private Person _Person;
public PersonProxy(Person WrappedPerson) {
_Person = WrappedPerson;
}
public string FirstName {
get { return _Person.FirstName; }
set { _Person.FirstName = value; }
}
public string LastName {
get { return _Person.LastName; }
set { _Person.LastName = value; }
}
}
As a final assumption, let’s suppose that these business objects are never changed directly, but that any actions performed upon them are wrapped in command objects. In our case, we’ll be using a simplified command object that assigns one Person object’s state to another. Normally, such a command would store the before/after states to make such an action capable of undo/redo, but again, this has been omitted as it is not relevant for the point I am trying to make.
public class CopyStateCommand {
private Person _Source;
private Person _Dest;
public CopyStateCommand(Person Source, Person Dest) {
_Source = Source;
_Dest = Dest;
}
public void Execute() {
_Dest.FirstName = _Source.FirstName;
_Dest.LastName = _Source.LastName;
}
}
We can now extend our proxy class to make use of this command:
public class PersonProxy : IPerson {
private Person _Person;
public PersonProxy(Person WrappedPerson) {
_Person = WrappedPerson;
}
public string FirstName {
get {
return _Person.FirstName;
}
set {
var newState = _Person.Clone() as Person;
newState.FirstName = value;
var cmd = new CopyStateCommand(_Person, newState);
cmd.Execute();
}
}
public string LastName {
get {
return _Person.LastName;
}
set {
var newState = _Person.Clone() as Person;
newState.LastName = value;
var cmd = new CopyStateCommand(_Person, newState);
cmd.Execute();
}
}
}
However, as the above code clearly shows, this approach involves a lot of code duplication. The property setters all follow the same pattern: they create a copy of the current state, update a single property, then apply the new state using a command.
This is where delegate methods come in. Let’s say we add the following code to the PersonProxy class:
private delegate void UpdatePersonMethod(Person NewState);
private void Update(UpdatePersonMethod M) {
var newState = _Person.Clone() as Person;
M(newState);
var cmd = new CopyStateCommand(_Person, newState);
cmd.Execute();
}
Using this approach, the boilerplate code is defined just once in a dedicated method. Calling the user-supplied delegate method (with the new state object as its argument) allows property-specific changes to be made. For example, the entire code for FirstName’s setter is boiled down to the following single line of code:
Update(delegate(Person NewState) {
NewState.FirstName = value;
});
This leaves us with the following, updated PersonProxy class:
public class PersonProxy : IPerson {
private Person _Person;
public PersonProxy(Person WrappedPerson) {
_Person = WrappedPerson;
}
public string FirstName {
get { return _Person.FirstName; }
set {
Update(delegate(Person NewState) {
NewState.FirstName = value;
});
}
}
public string LastName {
get { return _Person.LastName; }
set {
Update(delegate(Person NewState) {
NewState.LastName = value;
});
}
}
private delegate void UpdatePersonMethod(Person NewState);
private void Update(UpdatePersonMethod M) {
var newState = _Person.Clone() as Person;
M(newState);
var cmd = new CopyStateCommand(_Person, newState);
cmd.Execute();
}
}
I call this approach the Delegated State Setter pattern.
While it might be considered overkill for small and simple classes, this pattern can make large classes with a wide range of properties much easier to both develop and maintain when working with various layers of proxies and command objects.
Post Linx
Permalink | Trackback |
|
Print This Page |
Comments
Leave a Reply