Pages

Google+

Tuesday, July 28, 2009

Secrets of Object Orientated Encapsulation

Something Between Just You and Me


There's a little secret I'd like to share... I think many object-oriented programmers use encapsulation incorrectly. For years, even I was guilty.


Yes, it's true.




Code Time


When I first began work as a developer, this was how I saw encapsulation:
public class House
{
public function createRoom()
{
var room= new Room();
}
// various house behaviors
}

public class Room
{
public function createDoor()
{
var door = new Door();
}
// various Room behaviors
}

public class Door
{
public function createDoorknob()
{
var doorKnob = new DoorKnob();
}
// various Door behaviors
}

Everything is nice and neat right? The house has rooms, but doesn't know anything about doors. Doors are the responsibility of the room, house is only concerned with managing rooms and the other house behaviors.

Generally encapsulation is taught and thought of in this hierarchical fashion - and it shouldn't be.  All the above is doing is taking a larger object and breaking it down into smaller bits.When this happens the smaller bits are only useful when used with the larger object. The only thing you're doing is creating lots of tightly coupled objects - which only ends up bloating your application. Consider this: suppose you wanted to create a french style house with french doors. Someone might think: 'no problem, I'll do something like this...'
public class FrenchHouse extends House
{
override public function createRoom()
{
var room= new FrenchRoom();
}
// various house behaviors
}

public class FrenchRoom extends Room
{
override public function createDoor()
{
var door = new FrenchDoor();
}
// various Room behaviors
}

public class FrenchDoor extends Door
{
public function createDoorknob()
{
var doorKnob = new DoorKnob();
}
// various French Door behaviors
}

Just to use different a door ( the FrenchDoor class ) I have to extend classes and override methods. Ouch. This is because creating a house sets in motion a sequence of events that I have no control over: The House creates a Room, the Room creates a Door.... In order to unwind it, I have to go to where the chain of creation starts and then my way down.

Encapsulation Is About Breaking Up Behavior


Of course the above is bit of an exaggeration, but it's a concentrated example of what happens across many programs I see.  In the above example all those classes have the fatal flaw of repeating the same behavior.  Can you guess what?  Here's a hint, say it out loud:

* The House creates a Room
* The Room creates a Door
* The Door creates a Doorknob

The keyword that is being repeated is 'creates'. These classes are all creating objects and are also responsible for it's own functionality. A class should only be responsible for it's behaviors and not responsible for any creation (within reason).  Instead, use a Creation Pattern and have each class require the objects it is going to act upon:
public class House
{
public function House(room:Room)
{
this.room = room;
}
// various house behaviors
}

public class Room
{
public function Room(door:Door)
{
this.door = door;
}
// various Room behaviors
}

public class Door
{
public function Door(doorKnob:DoorKnob)
{
this.doorKnob = doorKnob;
}
// various Door behaviors
}

public class Creator
{
pubic static function buildAndReturnHouse():House
{
var doorKnob = new DoorKnob;
var door = new Door(doorKnob);
var room = new Room(door);

return new House(room);
}
}

Think Voltron Not Robotech


Which TV show is more object-orientated? Voltron, of course. Which would someone guess as being more flexible and ultimately more powerful?  Again, Voltron. Sure Robotech pilots are skillfull, but they are really only good in a particular way and the pilot is powerless without his robowarrior counter part. Compare this to Voltron.  Each part of Voltron is formidable on it's own, combine them together and you have an awesome force.

To see our Voltron in action lets do the same thing as before - a house with French Doors.
public class FrenchDoor extends Door
{
public function FrenchDoor(doorKnob:DoorKnob)
{
this.doorKnob = doorKnob;
}
// various French Door behaviors
}

public class Creator
{
pubic static function buildAndReturnHouse(typeOfHouse:String):House
{
var doorKnob = new DoorKnob;

if(typeOfHouse == 'HouseWithFrenchDoors')
{
var door = new FrenchDoor(doorKnob);
}
else
{
var door = new Door(doorKnob);
}

var room = new Room(door);

return new House(room);
}
}

Ahhh much better. All I had to do was modify my creator class and create my French Door class. Compare that with the other example where I had to extend all the classes and override methods.

This post wasn't written to show how to use a Creation Pattern ( I probably wouldn't use a conditional and I avoid having parameters in my methods, especially in my constructors ) it's about recognizing responsibilities and why it's important to then separate and condense those responsibilities. That's using encapsulation.