Saturday, June 20, 2009

OOAD: Encapsulation

You would say that encapsulation and data hiding are almost essential while designing complex systems. You must hide your data and internal implementation. You must not let anyone know how dirty your laundry is.

But when you make this concept your guiding principal, you tend to write code which enforces strict client behavior. Invariably, you end up writing components that are almost unusable with changing requirements. When you get hell bent on using the component, either

1. you modify existing code breaking the very first principal of object oriented programming - entities must be closed to modification but open to extension. As one guy in my team puts it, your SVN revision must only have 'A's and almost zero 'M's.

2. or write infinitely complex super-generic code which is a nightmare to maintain and is uber-hard to understand

Here's a concrete example. Assume you need to maintain a list of animals. Your clients hold another animal. They need to check if the animal is dangerous or not. Religiously following the concept of encapsulation, you might come up with -

public enum Animal {
TIGER (Category.DANGEROUS);
LEOPARD (Category.DANGEROUS);
DOG (Category.FUN);
CAT (Category.DISGUSTING);
GODZILLA (Category.OH_MY_GOD);

private Category category;
public Animal (Category category) {
this.category = category;
}

// I will not allow people to see what category assignments
// I've made. It might offend them!
private Category getCategory() {
return this.category;
}

public static boolean isAnimalOfCategory(Category category) {
for (Animal a : EnumSet.allOf(Animal) {
if (a.getCategory().equals(category)) return true;
}
return false;
}
}
Clearly, this models the present but not the future. Another implementation could be -

public enum Animal {
TIGER (Category.DANGEROUS);
LEOPARD (Category.DANGEROUS);
DOG (Category.FUN);
CAT (Category.DISGUSTING);
GODZILLA (Category.OH_MY_GOD);

private Category category;
public Animal (Category category) {
this.category = category;
}

// But I'll let them know what all I have for a
// particular type and let them do whatever they want with it.
public static List<Animal> getAllDangerousOnes() {
return Arrays.asList(Animal.TIGER, Animal.LEOPARD);
}
}
I might also write a fancy method which given a Category would return a List of all animals I have for that category, but I'll pass. The second method enforces a logical grouping of all animals in the sample space which is also one of the objectives of the entity. The second class breaks encapsulation but lets you answer the following easily-

1. Is animal of category X?
2. Give me all animals of category Y.
3. Give me all animals but not of category Z.

A quirky tidbit but I it liked so much that I wanted to put it up here. Leave a comment if you think there are better solutions.

Khuda Hafiz,
Sultan of Samarkand