C# Using The Proxy Pattern To Define Relationships

Introduction

I was recently working on a 2.0 Framework project where we had many types of related objects. However, we needed to ensure that there were not multiple instances of the same object in memory. Usually, the GOF Proxy pattern is used to hide or control access to an object, but we can also use it to define relationships between objects.

The structure of a family and the resulting relationships between family members roughly correlate with the types of relationships we were trying to build between objects. Let's say we have a person, Bill, who is father to Mary and brother to Peter. Peter is an uncle to Mary, but he is also a brother to Bill. The trick is how to represent this in our code.

First, let's look at the wrong way to tackle this so you can see why we needed the Proxy pattern.

Bad Design of Proxy pattern

Here's our Person class.

class BetterDesignPerson
{
    private string _Name;
    private int _Age;
    private Collection<BetterDesignRelative> _Relatives;
    public BetterDesignPerson(string name, int age)
    {
        _Name = name;
        _Age = age;
        _Relatives = new Collection<BetterDesignRelative>();
    }
    public int Age
    {
        get { return _Age; }
        set { _Age = value; }
    }
    public string Name
    {
        get { return _Name; }
        set { _Name = value; }
    }
    internal Collection<BetterDesignRelative> Relatives
    {
        get { return _Relatives; }
    }
}

And here's our BadDesignRelative class: Can you see what is wrong with the following implementation?

class BetterDesignRelative : BetterDesignPerson
{
    private string _Relation;
    public BetterDesignRelative(string name, int age, string relation)
        : base(name, age)
    {
        _Relation = relation;
    }
    public string Relation
    {
        get { return _Relation; }
        set { _Relation = value; }
    }
}

Even though Bill "is" a father to Mary and we set up the relationship appropriately through inheritance, we run into a problem when Bill has a birthday, and his age changes as below.

static public void Go()
{
    BetterDesignPerson mary = new BetterDesignPerson("Mary", 5);
    BetterDesignPerson bill = new BetterDesignPerson("Bill", 28);
    mary.Relatives.Add(new BetterDesignRelative("Bill", 28, "Father"));
    bill.Age = 29;
}

We actually have two "Bills" in memory.

Bill number 1: Bill.Age == 29
Bill number 2: Mary.Relatives[0].Age == 28

Mary's first and only relative is her father, Bill. Because Bill is not himself, this implementation is not correct, and so we have to start from scratch.

The Solution

Okay, so Bill is a Person first and foremost. Bill can also be a father, brother, uncle, nephew, or any other relation you can dream up. Obviously, we need to wrap Bill as the appropriate relation type depending on whose perspective we are looking at him from.

Step 1. Define the interface

We need an IPerson interface that our wrapper class and person class will implement. (We're calling our wrapper class PersonProxy). We also need an event we can fire off so if there is a change, everything on the UI will get updated.

public interface IPerson
{
    int Age { get; set; }
    string Name { get; set; }
    Collection<PersonProxy> Relatives { get; set; }
    event PersonChangeHandler Changed;
}

Step 2. Create the object class

Both our PersonProxy and Person classes will implement IPerson. Here's the Person class.

public class Person : IPerson, ICloneable
{
    #region Declarations
    private int _Age;
    private string _Name;
    private Collection<PersonProxy> _Relatives;
    #endregion
    #region Constructor
    public Person()
    {
        // ...
    }
    public Person(string pName, int pAge)
    {
        // ...
    }
    #endregion
    #region Properties
    public int Age
    {
        // ...
    }
    public string Name
    {
        // ...
    }
    public Collection<PersonProxy> Relatives
    {
        // ...
    }
    public event PersonChangeHandler Changed;
    #endregion
    #region Event Wireup
    // ...
    #endregion
}
public class PersonProxy : IPerson, ICloneable
{
    #region Declarations
    private IPerson _Person;
    private string _ProxyName;
    #endregion
    #region Constructor
    public PersonProxy(IPerson pPerson, string pProxyName)
    {
        // ...
    }

    #endregion
    #region Properties
    public string ProxyName
    {
        // ...
    }

    #endregion
    #region IPerson Members
    public int Age
    {
        // ...
    }
    public string Name
    {
        // ...
    }
    public Collection<PersonProxy> Relatives
    {
        // ...
    }
    public event PersonChangeHandler Changed;
    #endregion
    #region ICloneable Members
    public object Clone()
    {
        // ...
    }
    #endregion
    #region Event Wireup
    public void PersonChanged()
    {
        // ...
    }
    public void PersonChanged(IPerson pPerson)
    {
        // ...
    }
    #endregion
}

Step 3. Wire it up

Now, when we wire things up, we will only have one and only one Bill.

IPerson mary = new Person("Mary", 10);
IPerson bill = new Person("Bill", 45);
PersonProxy father = new PersonProxy(bill, "Father");
mary.Relatives.Add(father);
PersonProxy daughter = new PersonProxy(mary, "Daughter");
father.Relatives.Add(daughter);
IPerson peter = new Person("Peter", 50);
PersonProxy brother = new PersonProxy(peter, "Brother");
father.Relatives.Add(brother);
PersonProxy uncle = new PersonProxy(peter, "Uncle");
daughter.Relatives.Add(uncle);

Download and try out the attached project to see that Bill is now himself. The Person object is updated after you edit a textbox and leave it.

Until next time

Happy Coding


Similar Articles