Feeding Your Monkey
Your Feed
method in VirtualPet
was an example of encapsulation. It probably resembled this:
public void Feed() {
Hunger -= 5;
}
So we fed our pets like this:
myMonkey.Feed();
Here, the VirtualPet
class is responsible for updating hunger when a pet feeds. A benefit of this is that the VirtualPet
class encapsulates the effect of a feeding. In this case, it decreases hunger by five.
If we were to do this in a procedural fashion without encapsulation, we might do this from our Main
method in our app:
myMonkey.Hunger -= 5; // feed myMonkey
Hide Your Hunger
An example of violating encapsulation is the way we've handled accessing our VirtualPet
's hunger so far. You may have been doing something like this:
Console.WriteLine("My pet's hunger is " + myMonkey.Hunger);
That's all well and good while only our app class is using VirtualPet
, but what if other developers start using our code, and someone (let's assume innocently) does something like the following?
thatPet.Hunger = -5;
That would wreck your assumptions about hunger a bit, wouldn't it? We avoid this situation by giving our Hunger
instance variable a private
accessibility modifier.
public class VirtualPet
{
public int Hunger { get; private set; }
// ...
}
How do I get at it?
Once our Hunger setter is now private, meaning Hunger
can no longer be set anywhere outside of our class. We can still get
the value from outside the class, however:
int petsHunger = myMonkey.Hunger;
Even though we can access Hunger externally, we should usually let our VirtualPet
class make any decisions that involve its Hunger
.
For example, we may need to figure out if our pet is "Hungry" or not.
public class VirtualPet
{
public int Hunger { get; set; }
public bool IsHungry()
{
return Hunger > 50;
}
}
Why is the above code so much better than writing the code below?
static void Main(string[] args)
{
VirtualPet myPet = new VirtualPet();
bool isHungry = myMonkey.Hunger > 50;
}
Keep Your Hands to Yourself
Encapsulation is the practice of hiding information about an object from other objects. We do this so that the responsibility for manipulating state is limited to one object.
A classic example of encapsulation is the cashier collecting the price of a purchase. The cashier asks you for money to cover your purchase. You withdraw the money from your wallet/purse/pocket, then he returns your change. The cashier does not reach into your pocket, withdraw your wallet, remove money, then put change into your wallet.
Shotgun surgery, a design smell from Fowler's Refactoring, can be an indicator of poor encapsulation. This analogy references the small pellets resulting from a shotgun blast, the removal of which requires surgery at several places. Poor encapsulation means that a small change can require us to change several classes.
I spy, with my little eye…
Visibility modifiers allow us to specify the visibility of an element (who can see it). From most visible to least visible, these are:
modifier | visibility |
---|---|
public |
everyone can see it |
protected |
this class and subclasses can see it |
none (default) | things in the same assembly can see it |
private |
only visible inside this class, not even subclasses can see it |
The instance variables (fields) that define the state of your objects should have less than public
visibility.
In practice, most things will be public
or private
. Sometimes, you will see protected
. You will almost never see default visibility in practice. (There's nothing wrong with default visibility. This is more a statement about practices in the industry and the level of craftsmanship.)
In Practice
In reading your book, it may feel like publicly "settable" properties violate encapsulation. For example, when our monkey's Hunger property was:
public int Hunger {get; set;}
myMonkey.Hunger = 35;
How is this any better than directly manipulating a field:
public int hunger;
myMonkey.hunger = 35; // direct manipulation
The answer is, it's not much better. We could do some validation of the value passed into the setter, but this isn't much different. Setter methods violate encapsulation by definition. We introduce them because you will see them in the wild, where sometimes they are used to do great damage.
In Practice...keep setters private when you can
Luckily, we can usually avoid using public setters. Avoid them when you can.
- Prefer constructor parameters to initialize state.
- Use methods to encapsulate interactions with your state.
- Never make your fields public. At a minimum, wrap them in a property.