My struggles in understanding and learning about Object Oriented design, and the tools and knowledge I've taken from them.

Thursday, January 29, 2009

Shifting Responsibility in OOP

One of the things I've struggled with in my learning of OOP is who should own responsibility for things. Coming from mainly procedural PHP, I basically followed the model of: Page calls method, which may call another method, and eventually work gets done because the page initiated the work.

For instance, consider the following code:


$booleanVar = getBooleanResult($someInput);
if($booleanVar == true)
{
callMethodA($someOtherInput);
}
else
{
callMethodB($yetEvenDifferentInput);
}


The above code works just fine, but it doesn't take advantage of what object oriented programming provides: ENCAPSULATION. As mentioned earlier, encapsulation is basically the ability to hide behavior (ie methods/functions) within a class, and never having to expose that method to the outside world. The implication is that you can let your class do the work of assembling the "guts" of an object, and not have to manipulate it through each iteration of code.

Let me use an example. Below is code that I have to manage, because I'm not taking full advantage of what OOP provides:


public class Human
{
///Here I create some private variables,
///and expose them publicly below
private int _numberOfArms, _numberOfEyes;
private string _eyeColor;

///My Public Properties
public int NumberOfArms
{
get { return _numberOfArms; }
set { _numberOfArms = value; }
}

public int NumberOfEyes
{
get { return _numberOfEyes;}
set { _numberOfEyes = value; }
}

public string EyeColor
{
get { return _eyeColor;
set { _eyeColor = value; }
}

public string Talk(string wordsToSay)
{
return "I am going to say: " + wordsToSay;
}

}

///...Then in some other object
Human Tim = new Human();
Tim.NumberOfArms = 2;
Tim.NumberOfEyes = 2;
Tim.EyeColor = "Green";
Tim.Talk("Hey man. I have " + Tim.NumberOfArms + " arms" );
///Output: I am going to say: Hey man. I have 2 arms


The above example follows the same model as my first example. It's all nice and fine that the Human class has some attributes (NumberOfArms, NumberOfEyes, and EyeColor) along with a behavior (Talk()), but the class in general is pretty dumb. One of the things I've come to realize in the time I've been programming is that if you can transfer responsibility from the caller of the object to the object itself, that is really much better.

That doesn't always work (for instance consider .Net controls, such as textboxes, labels, etc). Sometimes you need to manipulate those properties outside of the class; however, there are usually plenty of opportunities to transfer responsibility to the class.

Consider the revised example:


public class Human
{
///I'm adding a constructor here. The class knows how
///many arms, eyes, etc
///it has. It's asinine to assume otherwise in
///this case
public Human(string myFirstWords, string theirFirstWords)
{
///Notice I use keyword "this" here.
this.NumberOfArms = 2;
this.NumberOfEyes = 2;
this.Talk(myFirstWords);
this.Listen(theirFirstWords);
}

///Here I create some private variables,
///and expose them publicly below
private int _numberOfArms, _numberOfEyes;
private string _eyeColor;
private bool _endOfDiscussion = false;

///My Public Properties
public int NumberOfArms
{
get { return _numberOfArms; }
set { _numberOfArms = value; }
}

public int NumberOfEyes
{
get { return _numberOfEyes;}
set { _numberOfEyes = value; }
}

public string EyeColor
{
get { return _eyeColor;
set { _eyeColor = value; }
}

public string Talk(string wordsToSay)
{
return "I am going to say: " + wordsToSay;
}

///I listen here. And I continue to listen and talk
///until the end of discussion occurs.
public void Listen(string input)
{
while(!_endOfDiscussion)
{
if(this.needToRespond(input))
this.Talk(this.thinkUpResponse(input));

}
}

///Assume that input may be dead air. If the person
///I'm talking to
///actually says something, then I know I need
///to respond
private bool needToRespond(string input)
{
if(input != "")
{
return true;
}
return false;
}

///Here I use some encapsulation to do stuff that
///needn't be exposed to the real world.
///It sort of follows, if you think about it,
///because no one really sees a human think up a response
///A human's thoughts are conveyed when humans Talk()
private string thinkUpResponse(string input)
{
if(input.Contains("?"))
{
_endOfDiscussion = true;
return this.answerToQuestion(input);
}
else
{
return this.wittyBanter(input);
}
}

///This is a response to a question
private string answerToQuestion(string question)
{
///Do stuff here to figure out how
///to answer question
string answer = ///...
return answer;
}

///Figure out how to think up something funny
private string wittyBanter(string statement)
{
///Do stuff here to come up with
///a witty response
string wittyResponse = ///...
return wittyResponse;
}



}


In the above example, the constructor handles much of the initialization of the object; however, it doesn't need to be the constructor. It could just as easily be a method called Load(), or something like that. This is particularly useful if you can't populate an object until required information has been set, which would have to be done outside of the class. Just for fun, I added some extra behavior in the above class, which will help the Human class to have a conversation, and I use some private methods to demonstrate appropriate implementation of private vs. public methods (ie encapsulation). The above class probably won't get you very far in getting a class to actually have a decent conversation, because one would probably need to implement threads, or maybe some clever design pattern to make that work properly, but I think this is a decent example of shifting some of the responsibility away from the user of the class to the class itself.
Post a Comment

Followers

Search This Blog

Powered by Blogger.