Specialization, Generalization, and Abstraction

A question was raised on LinkedIn (you can follow me there: https://www.linkedin.com/in/hector-fontanez) that got me thinking and I decided to blog about it. I am going to start by defining what generalization means in Object-Oriented Programming (OOP) and Design (OOD). Generalization in OOD implies that a specialized (child) class is based on a general (parent) class. This, in turn, implies a "is-a" relationship.

When you analyze the object types and their relationships as depicted in the image above, from left to right, it goes from very general to more specific. You could say that an Animal "is a" more general form of Dog, of Canine, and even than Mammal. Likewise, a Canine "is a" more general form of Dog.

If you analyze it from right to left, the opposite happens: it goes from more specific to more general. Put in other terms, the type gets broadened. Therefore, you can still refer to dogs as canines, mammals, or even animals. The problem with generalization is that broadening the type opens the door to type-casting problems (I will not be covering Generics on this blog). For example, we already established that dogs are animals, but birds, fish, and frogs, are also animals; although not canines or even mammals.

Specialization (the opposite of generalization) is achieved in OOD and OOP through inheritance. When you "read" this relationship as depicted using UML class diagrams, you say that a subclass "is-a" type of the superclass. In our example, a Dog "is-a" Canine, and a Canine "is-a" Mammal, and a Mammal "is-an" Animal. You can go from the bottom class to the top class and say that a Dog "is-an" animal and that statement will still be correct. In the book Effective Java by Joshua Bloch (my favorite book on Java), he establishes to favor composition over inheritance. I have heard many people say that inheritance was a mistake. The fact that people abuse inheritance it does not make it a mistake. The example illustrated here is a perfect case for inheritance. In fact, Joshua put it brilliantly in his book: "inheritance is appropriate only in circumstances where the subclass really is a subtype of the superclass. In other words, a class B should extend a class A only if a is-a relationship exists between two classes." An this reality must hold true for all derived subtypes of the immediate child. For example, are all types of canines mammals? The answer to that question is yes. Therefore, it is OK for Canine to extend Mammal. The question then must be answer for Mammal as well: Are all mammals animals? The answer is also yes. By default, all canines are animals because we already determined that all canines are mammals.

The problem is that often inexperienced developers find certain attributes and behaviors in a class and extend the class to get access to these attributes without really thinking about the true relationship between a superclass and a subclass. Remember, just because an airplane flies and has wings it does not make it a bird (even though it is often referred as such by aviators). A plane IS NOT an animal. Likewise, a car and a house both have doors and windows, but they are not the same thing; one is a dwelling and the other one a vehicle. However, they both have some similar attributes which I am going to use to explain abstraction.

Abstraction (in OOD) is emphasis on an idea, or a concept, rather than broadening or generalizing a type. In my opinion, abstraction and generalization are not synonyms in OOD; although they are in the English lexicon. Let's examine my statement to see if my opinion holds true. I already establish that a House "is-a" form of Dwelling, and that a Car "is-a" form of Vehicle. I also stated that both have windows and doors (points of entry, maybe?). Doors and windows are opened, closed, locked, unlocked, etc. The point here is that these two totally incompatible data types (Car and House) do share some commonalities when it comes to these objects; doors and windows. So how do we include these similar behaviors in disparate classes such as Car and House? The answer is through the use of interfaces. In OOD, an interface is a structure that allows the enforcement of certain properties (or concepts) on an object. In this case, the concept common between Car and House is that both are "Lockable." In fact, a Safe is neither a dwelling or a vehicle, but it is also "Lockable."

In this context, "Lockable" is a concept, an idea. It is a certain property that is exhibited by certain types of objects that do not necessarily have to be directly related (by inheritance) in any way, shape, or form. Moreover, not all cars are necessarily lockable just because they have doors and windows. A golf cart could be lockable by other means. Because the concept of being locked or unlocked could (and most likely will) change between types, these methods will be undefined in the interface and it will be up to each implementing class what this concept of "lockable" means to each one. In other words, the methods in the interface will be abstract (undefined) and the implementing class has the responsibility to define this behavior.

Java and C# define this structure by using the keyword "interface." In C++, the concept of interface is realized by creating classes that contain nothing but pure virtual methods. In Java, a interface could look as simple as the one below:


public interface Lockable {
    public void lock();
    public void unlock();
}

Implementing classes must define what actions need to take place. For example, locking a car might require a difference action or sequence of events than locking a golf cart.

There is another construct, not as abstracted as interfaces. There are cases when you have different behaviors of related objects. For example, certain domestic animals serve us as pets. You could say that a pet is an animal that has certain characteristics; a certain behavior that separates them from other, wilder animals of the same type. Using dogs for example, a house dog may present certain characteristics that separate them from wild (stray) dogs. Specifically, we often teach our pets to do tricks. That characteristics has nothing to do with being a Dog; but has everything to do with being a Pet. It is possible to make Pet an interface with a method doTrick(). We could also make use of abstract classes to achieve the same. If we were to make Dog an abstract class, we could include an abstract method doTrick() and allow subclasses of Dog decide what that means for them.


public abstract class Dog {
    public final void bark() {
        System.out.println("woof!");
    }

    public abstract void doTrick();
}

Then, we could have subclasses define what trick they'll perform:


public class MyDog extends Dog {
    @Override
    public void doTrick() {
        System.out.println("Rolling over for a treat!");
    }
}

My dog knows how to roll over. But your dog can perform a different trick:


public class YourDog extends Dog {
    @Override
    public void doTrick() {
        System.out.println("I am sitting still!");
    }
}

And some dogs just don't know any tricks, and this is OK...


public class DumbDog extends Dog {
    @Override
    public void doTrick() {
        // Do nothing. A dumb dog doesn't know any tricks
    }
}

Comments

Popular posts from this blog

Combining State and Singleton Patterns to Create a State-Machine

The Beauty of the Null Object Pattern

Exception Handling: File CRUD Operations Example