Thursday, October 26, 2006

The Double If

This is a anti-pattern that I have started to see when people attempt to think in terms of objects, but don't think in terms of messages. The case is that you have one bit of code that will do a test and will return either a real value or null (in this case, that method is GetParentName). Then, anybody who uses GetParentName needs to either be OK with getting a null back, or needs to test to make sure that the value returned is not null. This code illustrates that anti-pattern:


class Item
{
//Return either a real string or null
string GetParentName()
{
Item parent = GetParent();
if (parent == null)
{
return null;
}
else
{
return parent.GetName();
}
}
}

void SetupTextBox(Item myItem, TextBox someTextBox)
{
string parentName = myItem.GetParentName();
if (parentName != null)
{
//TextBox::SetText(string) will throw an exception
//if it is passed a null
someTextBox.SetText(parentName);
}
}


A way to fix that is to change the class' interface. Instead of getting values from the class, let's tell the class to tell its information to somebody.


class Item
{
string TellParentNameTo(StringReceiver receiver)
{
Item parent = GetParent();
if (parent != null)
{
receiver.SetString(parent);
}
}
}

void SetupTextBox(Item myItem, TextBox someTextBox)
{
//myItem will do the best that he can do, even if he
//has a null
myItem.TellParentNameTo(someTextBox);
}


The biggest problem with the second example is that you need to have really, REALLY broad interfaces that are used everywhere. In this example, TextBox would need to be a StringReceiver. In fact, anybody that could conceivably get a string from an Item would need to be a StringReceiver. If TextBox were written so that it is a StringReceiver from the beginning, everything works great. However, suppose that StringReceiver was written later than TextBox. Either TextBox will need to get it retrofitted, or an adapter class will need to be written:


class Item
{
string TellParentNameTo(StringReceiver receiver)
{
Item parent = GetParent();
if (parent != null)
{
receiver.SetString(parent);
}
}
}

class TextBoxStringReceiverAdapter
{
TextBoxStringReceiverAdapter(TextBox textBox) {}

void SetString(string str)
{
textBox.SetText(str);
}
}

void SetupTextBox(Item myItem, TextBox someTextBox)
{
//myItem will do the best that he can do, even if he
//has a null
myItem.TellParentNameTo(
new TextBoxStringReceiverAdapter(
someTextBox)
);
}

No comments: