Self-managing objects – To support Agile development processes

Posted on July 8, 2011

0


The main issue: avoiding entanglement of dependencies

When you build software, at a certain point you will start building (managed) lists of objects.

You might even have one or more managers taking care of this.

For instance:

  1. Person – Is a Value Object (VO) containing a person.
  2. PeopleManager – Is a manager containing a List of people (List<People> in C#)

So far nothing is wrong as there are two clearly paired Classes.

But what if you create a third object called “Department” with a manager?

  1. Department – Contains a list of people (List<People>)
  2. DepartmentManager – Contains methods to find out which person is in what department
So far, again nothing seems really wrong.

Here comes the catch

What if you remove/delete a person?

Since you now have two lists with people (in PeopleManager and Department) you will have to remove that person from two places. And this is only a simple example.

What we can learn from data models

When you create a database, the database data model deals in a different way with dependencies than we are used in Object Oriented Programming (OOP).

The main reason is to keep data management simple and prevent data from becoming inconsistent.

If there is a One to Many relationship between Department and Person, the link is created in Person, by adding a departmentID. Department will not have a list of people, but will query Person to see who is referring to its ID. (query=”SELECT * FROM person WHERE departmentID = “+ this.departmentID)

In OO (Object Oriented) environments, it might be tempting to insert the Department Object into a person, but again management of data consistancy might become troublesome. My preference is to refer to a Department via a String or Integer value.

Review

  1. We add a department of type Department to the person: person.department
  2. We add a (static) method to PeopleManager: List<People> getPeopleInDepartment(Department department)
    1. This (static) method will go through the People list and extract all people in the Department.

All management regarding people will be done in PeopleManager.

This might seem counter-intuitive. But once you start refactoring, distributed dependencies to specific data and objects will start to break you up.

The goal: simplifying your project

I have noticed that the most common models for events only work for small projects. The moment you start something big, these models become a problem.

  1. Boilerplate code – They force you to create a lot of boiler plate code, just to make it work within that (limited and usually rigid) model
  2. Loss of control – When your project grows, it becomes less and less clear what happens where and why specific updates you KNOW happened in some object are not reflected elsewhere.

Events

Another issue with the classic approach are Events.

When I update an object somewhere, and this information is required elsewhere, I usually shoutcast an Event: “My object has been updated!”. Attached to the event is the object or object list I changed.

With persistant Objects I can simplify the Event Model thoroughly.

Take this example:

// Some code on Class "A"
Person myPerson = Person.getPerson(12);
// Add observer that will take action when this specific person 
// is updated. For instance: when presented in a list
myPerson.addUpdateObserver(myUpdateHandler)

// Some code in class "B"
Person myPerson = Person.getPerson(12);
// Change some values
myPerson.firstName="Joe";
myPerson.dispatchUpdateEvent();

All observers on Person with ID 12 will now automatically receive a dispatched event and will be able to take action.

As you also might want to observe global events on Person, it is not too hard to create a global, static Event Dispatcher on Person. So when a global observer is attached to the Person Class, that observer will also be informed of any change.

// Using a global event listener
Person.addGlobalUpdateObserver(myGlobalPersonUpdateHandler)

All events are created behind the scenes and can be default events, dispatched over local Dispatchers. This way, the dispatched events never leave the scope of the Object or Class. Naturally all observers are registered against the local Dispatcher as well.

Moving it one step further: cutting all dependencies

You probably know the concept of multitons and Object Pools.

Multitons and Objects in Object Pools are objects with a specific ID or state, addressed through a static method.

For instance on ID:

string personID=12;
// Get the person
Person person = Person.getPerson(personID)

Or state:

List<Person> people = Person.getDeletedPeople(); 
// Where getDeletedPeople will return the objects in the pool flagged as "deleted".

What we do is get an instance of a specific Person object and put it in a temporary variable.

Regardless where we are in our project, when we call for a person with ID 12, we will always get the same object.

Lookup tables

The main trick is to register objects against a Lookup Table. For a Person that Lookup table might be indexed against the Person ID. For a Deaprtment, that might be a Department ID.

// A C# lookup table
Dictionary<int, Person> peopleList = new Dictionary<int, Person>();
// Getting a person
public Person getPerson(int personID)
{
     // For simplicity sake we left out the check if the person is really there
     return peopleList[personID]
}

If you want to find a list of people in a Department, you can do the following:

// Getting people in a department
public List<People> getPeopleInDepartment(Department department)
{
      List<People> peopleInDepartment = new List<People>();
      // Loop through all people
      foreach (Person person in peopleList)
      {
           if(person.department == department)
           {
                   peopleInDepartment.Add(person);
            }
    }
    return peopleInDepartment;
}

Benefits

  1. Dependencies to objects are temporary – You are not maintaining persistent references to multiple objects on multiple places.
  2. Garbage collection is more reliable – As your persistant references are only on one place: the Class or Class Manager, releasing references only have to be done at one place.
  3. Simplified code – I found that at a certain point I was shipping shitloads of object references through all kind of chains of objects and method calls, to have it end up at the right place. Currently the only thing I ship are String and Int values representing the ID of an object. And in most cases If a store and object in class “A” and need to retrieve it in class “C”, I simply use a specific static method on the Class itself: “someClass.getObjectListBasedOnSomeParameters(parameterA, parameterB)
  4. Increase in application stability – As your maintenance of objects is done centrally and managed in one object, strange stuff and lost objects are less likely to happen
  5. Increase in code stability when refactoring – Another reason for me to drop the classic approach was that my code tended to break a lot when refactoring. While the compiler had all flags on green, parts of my application simply did not work anymore due to some broken dependency.

Disadvantages

  1. Performance – The main disadvantage of lookup tables is performance. Each time you loop, you pay.

Concluding

From OOP principles it might seem counter-intuitive to manage Objects in the Objects themselves (or in a specific and paired Object Manager) and only use temporary references.

The advantages of lookup tables and centralized/localized object management become clear and increasingly bigger when:

  1. You do a lot of refactoring – As you will take apart your code, move parts elsewhere and so on.
  2. Your project becomes increasingly complex – Querying objects where you need them becomes more beneficial than sending lists of objects from somewhere and hoping that the right lists arrive at the right place though chains of objects and method calls.
  3. You use persistent data and persistent objects – As any and multiple objects can contain “Joe Bloggs, ID =2”, using centralized lookup lists and object management assures that when you update “Joe Bloggs, ID=2” ALL objects representing “Joe Bbloggs, ID = 2” are updated.
  4. Your event model starts to work against you – Events are usually linked to objects. The more granular your event model becomers, the more event types you will have to maintain to keep things up to date and the more bloated your code becomes. The event: PersonEvent(“A person has been updated”) with the specific object attached is less clear than person.dispatchUpdatedEvent(); where only all specific listeners to that object and that class will receive that event dispatch.
Advertisements
Posted in: Design patterns