Tuesday, May 14, 2013

Refactoring: Replace List With Object

I was reminded today of a refactoring which I have done several times, but which I hadn't specifically remember from the Refactoring book. So, I looked it up, and of course it is there.

Ah well, we can't all invent something new.

Anyway, the refactoring in question is "Replace Array With Object", though I've been seeing "Replace List With Object". I first encountered this in C#, and recently again in C++. The method for doing this refactoring I feel is more elegant than in Java or C (or Go for that matter), though primarily because Lists in C# and C++ have array-like syntax available, whereas in Java they do not.

Consider a naive data-structure which represents the "nuclear family":


enum Parents {
  DAD_INDEX = 0,
  MOM_INDEX = 1
};
list<Person> family = GetFamilyMembersParentsFirst();
Person dad = family[DAD_INDEX];
Person mom = family[MOM_INDEX];
int numChildren = family.size() - 2;


Then the client does this:

How will we handle families with more or less than one parent, or same-sex parents?

Replace List With Object to the rescue!! We need a class which represents the family as a unit. So, first off let's subclass list:


class Family: public list<Person>
{
}


Now, change GetFamilyMembersParentsFirst() to return a Family instead of a list<Person>:


Family GetFamilyMembersParentsFirst() {...}


Now, we can change the client to use Family:


enum Parents {
  DAD_INDEX = 0,
  MOM_INDEX = 1
};
Family family = GetFamilyMembersParentsFirst();
Person dad = family[DAD_INDEX];
Person mom = family[MOM_INDEX];
int numChildren = familiy.size() - 2;


Finally, we can start adding behaviors, like GetNumChildren(). This can be done by extracting the relevant behavior from the client code and moving it to the Family class:


class Family: public list<Person>
{
public:
    int GetNumChildren()
    {
        return size() - 2;
    }
}

...

Family family = GetFamilyMembersParentsFirst();
...
int numChildren = family.GetNumChildren();


Now Family can answer our questions about a given family unit. Hooray!

Now, let's take care of those pesky 'mom' and 'dad' variables. Since we're not changing how the class works yet, we'll just extract 'family[DAD_INDEX]' and 'family[MOM_INDEX]' to functions, and move them to the Family class:


class Family: public list<Person>{
public:
     Person Dad()
     {
         return (*this)[DAD_INDEX];
     }
     Person Mom()
     {
         return (*this)[MOM_INDEX];
     }
}
...
Family family = GetFamilyMembersParentsFirst();
Person dad = family.Dad();
Person mom = family.Mom();
int numChildren = family.GetNumChildren();


Hey, now we can add setters:


class Family: public list<erson>
{
private:
    Person dad;
    Person mom;
    list<Person> children;
public:
    void Dad(Person dad) { this->dad = dad; }
    void Mom(Person mom) { this->mom = mom; }
    void AddChild(Person child) { children.push_back(child); }
    ...
}


And use them within GetFamilyMembersParentsFirst():


Family GetFamilyMembersParentsFirst()
{
    Family family;
    ...
    family.Dad(GetMaleTaxPayer());
    family.Mom(GetFemaleTaxPayer());
    ...
}


Finally, we can change Family to not subclass list<Person>:


class Family
{
...
}


And everything still compiles and works! (Exactly what we would expect out of a good refactoring.)

No comments:

Post a Comment