Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

An Introduction to Design Pattern - Part 2

3.75/5 (4 votes)
22 May 2011CPOL12 min read 17.1K  
This article is the second part of a series of articles which will try to explain the basics of design patterns.

Prerequisites

Before reading this article, you should read the first part of this series. In the following article, I may have used class and object interchangeably. Unless otherwise stated, they both mean the same thing.

Abstract Class Vs Concrete Class

An abstract class is one which is generally declared using the keyword abstract and which has at least one method which doesn’t have a definition (or a body/implementation, whatever you say). Classes which aren’t declared using the abstract keyword are called Concrete Classes.

All the classes that I have given an example of in the previous article are concrete classes.

Let us see an example of an abstract class:

Java
public abstract class AnAbstractClass{
    protected string anAttribute;

    public void setAttribute(strint anAttribute){
        this.anAttribute = anAttribute;
    } 

    public string getAttribute(){
        return this.anAttribute;
    }

    public void anAbstractOperation();
}

The above class is an abstract class, since it is declared as abstract in its class definition. Abstract classes will defer some or all of its implementation to operations defined in subclasses; hence an abstract class cannot be instantiated. The operations that an abstract class declares but doesn’t implement are called Abstract Operations.

Now, why do we care?

We do care about abstract classes because they play an important part in design patterns. An abstract class is one whose main purpose is to define a common interface for its subclasses (“subclass” will be explained shortly). They are generally used to define a Base Type to be used in the class hierarchy of a system.

Inheritance

In OOP, new classes can be defined in terms of existing classes. This mechanism is known as inheritance. When a new class is created in this way, we say that it is a subclass of the existing class from which it is being inherited, and the existing class is called its parent class.

In the previous article of this series (link of which is given in the prerequisite section), I have shown an example of inheritance. Feel free to take a look at that example.

Objects that are instances of the subclass can contain all data defined by the subclass and its parent classes, and they’ll be able to perform all operations defined by this subclass and its parents.

But again, those who want to see another example, consider the following one:

Java
// Example 1
public class Account{
    protected string accountNumber;
    protected double balance;

    public Account(){}

    public void setAccountNumber(string accountNumber){
        this.accountNumber = accountNumber;
    }

    public string getAccountNumber(){
        return this.accountNumber;
    }
}

public class InventoryAccount extends Account{
    protected string inventoryItemNumber;

    public InventoryAccount(){}

    public void setItemNumber(string itemNumber){
        this.inventoryItemNumber = itemNumber;
    }

    public string getItemNumber(){
        return this.inventoryItemNumber;
    }
}

In this example, we are creating a new class called InventoryAccount by inheriting it from Account class. As a result, all of the operations that Account class defines will be available in the InventoryAccount class. Here, InventoryAccount is a subclass of Account class, and Account class is a superclass of InventoryAccount.

Class Inheritance VS Interface Inheritance

“What the heck!? There are two types of inheritance?!”

Yes, that’s right. We use both of them on a daily basis, we just aren’t aware of the distinction. Class inheritance defines an object’s implementation in terms of another object’s implementation. In short, it’s simply a mechanism for code and representation sharing. The example that I’ve provided just above is an example of this type of inheritance.

Interface inheritance, sometimes known as subtyping, ensures that an object has a specific type of object interface which it obtains either from a programming language’s interface (let’s call it method interface just to distinguish itself from the Object Interface for now) or from an object.

“Huh? What did you just say? Aren’t both of these the same thing then?”

Nope, they are not. Look at the example I have provided. Here, InventoryAccount is inherited from Account class. So it also inherits both the methods that Account class provides, along with their implementation. This is an example of class inheritance – InventoryAccount is defined in terms of Account class’s implementation (along with its other methods, of course).

But, if I inherit InventoryAccount in the following way:

Java
public class InventoryAccount extends Account{
    protected string inventoryItemNumber;

    public InventoryAccount(){}

    public void setAccountNumber(string accountNumber){        // Overriding Account's implementation
        this.accountNumber = accountNumber;
    }

    public string getAccountNumber(){                         // Overriding Account's implementation
        return this.accountNumber;
    }

    public void setItemNumber(string itemNumber){
        this.inventoryItemNumber = itemNumber;
    }

    public string getItemNumber(){
        return this.inventoryItemNumber;
    }
}

then I am only inheriting Account’s Object Interface, not their implementation. This is called interface inheritance.

Still not clear? Then comment back in!

Class inheritance is basically just a mechanism for extending an application’s functionality by reusing functionality in parent classes. It lets you define a new kind of object rapidly in terms of an old one. It lets you get new implementations almost for free, inhering most of what you need from existing classes. However, implementation reuse is only half the story. Inheritance’s ability to define families of objects with identical interfaces is also important.

Why? Because Dynamic Binding, and in turn, Polymorphism depends on it (“Huh? How?”).

When inheritance is used carefully (some will say properly), all classes derived from an abstract class will share its interface. This implies that a subclass merely adds or overrides operations and does not hide operations of the parent class. Consequently, all subclasses can then respond to the requests in the interface of this abstract class, making them all subtypes of the abstract class. As a result, all of these subclasses can be switched with one another in run-time, resulting in polymorphic behaviors.

There are two major benefits to manipulating objects solely in terms of the interface defined by abstract classes:

  • Clients (take a look at the definition of “client” from the previous article) remain unaware of the specific types of objects they use, as long as the objects adhere to the interface that clients expect.
  • Clients remain unaware of the classes that implement these objects. Clients only know about the abstract classes defining the interface.

Can you imagine the implication of the above two points ? You can’t? Ok, fine, then let me help you.

Consider our Accounting example. Suppose that you are developing an accounting application which only handles Inventory Accounts and you have created only InventoryAccount class. Your code looks somewhat like below:

Java
List<InventoryAccount> accounts = new List<InventoryAccount>();
accounts.Add(new InventoryAccount("Whatever initialization parameter is needed"));

.............. // Add another thousand class, do some manipulation, then save

ClassThatSavesAnAccountListToTheDatabase newClass = new ClassThatSavesAnAccountListToTheDatabase();
newClass.save(accounts);

Now after working hard for 3-4 months, your client (this client is a person ;-) ) comes to you and tells you, “Sooo sorry, I forgot to mention that this software will also manage our salary accounts. Could you do that for us, it would be a great help :D “, at that very moment, you will be thinking, “Darn it, I am dead :( “.

Why?

Because to accommodate this change, you will have to declare a new account class called SalaryAccount, code for its business logic, persistence, etc. You will also need to change the business logic in every place of your system, re-test it again thoroughly, and be sure that there will be hundreds of bugs this time in your system, all of which will be needed to be taken care of by you. In this scenario, we say that there is an implementation dependency between the subsystems of your application.

Instead of developing this way, if you would have declared an abstract class called Account and inherited InventoryAccount like the way I have shown in the inheritance example, and coded your application like this:

Java
List<Account> accounts = new List<Account>();
accounts.Add(new InventoryAccount("Whatever initialization parameter is needed"));

.............. // Add another thousand class, do some manipulation, then save
ClassThatSavesAnAccountListToTheDatabase newClass = new ClassThatSavesAnAccountListToTheDatabase();
newClass.save(accounts);

then you could have just declared another class called SalaryAccount by extending your Account abstract class and it would have fitted nicely with your existing code. You will still need to change some part of your code, but believe me, the amount of change will be very, very less in this later case, and we say that implementation dependency in this case is kept to minimal.

So the problem arose because you have coded your application using a reference to a concrete type, rather than using an abstract type. All the clients of your InventoryAccount class were aware of this fact, and hence fitting in the new class proved to be difficult.

So, I hope now you can realize the benefits to manipulating objects solely in terms of the interface defined by abstract classes. This way of coding so greatly reduces implementation dependency between subsystems that it leads to the following principle of Reusable Object-Oriented design:

OOP Principle 1: Program to an Interface, not to an Concrete Implementation.

What does this mean? In short, this principle says that you should not declare variables to be instances of particular concrete classes. Instead, commit only to an interface defined by an abstract class or method interface.

Does Design Pattern Help Us to Achieve OOP Principle 1

What do you know, it does! Almost all of the design patterns make use of this principle and show you a way to use it in your application. So, we can say:

Design patterns help us to program to an interface, not to a concrete implementation, thereby reducing implementation dependency as much as possible.

There you go. Another definition of what design patterns are :-) .

Reuse Mechanism in OOP

There are a lot of ways we can reuse existing code in an application, but the following two mechanisms are mostly used:

  • Class Inheritance
  • Object Composition

As we have learned before, class inheritance lets us define new implementation of one class in terms on another. Reuse by Subclassing in this way is often referred to as White-box reuse, because with this type of inheritance, the internals of parent classes are often visible to subclasses. Class inheritance is defined statically at compile-time and is straightforward to use, since it is supported directly by most of the OO languages. Class inheritance also makes it easier to modify the implementation being reused. When a subclass overrides some but not all of the operations, it can affect the operations it inherits as well, assuming the call the overridden operations.

But this type of reuse mechanism has some serious limitations. First, you cannot change the implementations inherited from parent classes at run-time, because inheritance is defined at compile-time, resulting in causing difficulties for polymorphism. Second, parent classes often define at least part of their subclasses’ physical representation. Because inheritance exposes a subclass to details of its parent’s implementation, it’s often said that Inheritance Breaks Encapsulation.

“What?! Did I hear you right?!”

Yes, you did. It may sound scary, but it’s true, because in this case the implementation of a subclass becomes so bound up with the implementation of its parent class that any change in the parent’s implementation will force the subclass to change. This is another form of Implementation Dependency.

Consider the following example:

Java
public class Display{
    private string dataToDisplay;

    public Display(){}

    private void setDisplayData(string dataToDisplay){
        this.dataToDisplay = dataToDisplay
    }

    private string getDisplayData(){
        return this.dataToDisplay;
    }

    public void displayDataVertically(){
        // Code to display the data vertically
        .......
    }
 }
public class LCDDisplay{

    public LCDDisplay(){}

    public void displayDataHorizontally(){
        // Code to display data horizontally
    }
}

In the above example, LCDDisplay is inheriting the displayDataVertically operation from its parent class, Display. If I change the logic of this operation in the Display class, it will also force the behavior of the LCDDisplay class to change, because this functionality of LCDDisplay class is defined in terms of the implementation that the Display class provides, resulting in the implementation dependency, and if the height of this inheritance tree keeps growing, you will really be in trouble if you ever need to change even a small part of your application.

This type of implementation dependencies can cause problems when you’re trying to reuse a subclass. Should any aspect of the inherited implementation not be appropriate for new problem domains, the parent class must be rewritten or replaced by something more appropriate. This dependency limits flexibility and ultimately reusability. One cure for this is to inherit from abstract classes, since they usually provide little or no implementation.

Object composition is an alternative to class inheritance. Here, new functionalities are obtained by assembling or composing objects to get more complex functionality. It requires that the objects being composed have well-defined object interfaces. This style of reuse is called Black-box reuse, because no internal details of objects are visible. Objects appear only as black boxes.

Let us look at the following example:

Java
public class Account{
    protected string accountNumber;
    protected double balance;
    protected InterestRateCalculatorInterface RateCalculator;
    protected final double rate = 0.5;            // Bad coding style, please don't follow it.
                                                  // I am using it just for the sake of this
                                                  // example.

    public Account(double initialBalance){
        this.balance = initialBalance;
    }

    public void setBalance(double initialBalance){
        this.balance = initialBalance;
    }

    public double getBalance(){
        return this.balance;
    }
    public void setAccountNumber(string accountNumber){
        this.accountNumber = accountNumber;
    }

    public string getAccountNumber(){
        return this.accountNumber;
    }

    public void setRateCalculator(InterestRateCalculatorInterface RateCalculator){
        this.RateCalculator= RateCalculator;
    } 

    public InterestRateCalculatorInterface getRateCalculator(){
        return this.RateCalculator;
    }

    public void calculateRateAndUpdateBalance(int numDays)
    {
        this.balance = this.RateCalculator.returnInterestRate(this.balance, numDays, this.rate);
    }
}

public interface InterestRateCalculatorInterface{    // Now that's what we've named method interface
                                                     // in this series of articles.

    public double returnInterestRate(double baseValue, int numDays, int rate);
}

public class InterestCalculator implements InterestRateCalculatorInterface{
    public double returnInterestRate(double baseValue, int numDays, double rate){
        double finalRate = (double) ( baseValue + (baseValue * numDays * rate) );

        return finalRate;
    }
}

class SomeCertainClass{
    public static  void main(String[] args){
        Account newAccount = new Account(0.0);
        InterestRateCalculatorInterface RateCalculator = new InterestCalculator();    // Remember our
                                                                                  // OOP Principle 1?
        newAccount.setRateCalculator(RateCalculator);

        newAccount.calculateRateAndUpdateBalance(30);
        System.out.Print("The new balance is: " + newAccount.getBalance());
    }
} 

In the above example, we are assigning an instance of an InterestCalculator object, which has the type InterestRateCalculator, to the member variable of the Account object at run-time, which adds a new functionality in the Account class and enables it to calculate interest rate and update the current balance. This is an example of object composition, and we say that the Account object is being composed by an object which has the type InterestRateCalculatorInterface at run-time.

Object composition is defined dynamically at run-time through objects acquiring references to other objects, thus being more Polymorphism-friendly. It requires objects to respect each others’ interfaces, which in turn requires carefully designed interfaces that don’t stop you from using one object with many others. But there is a pay-off. Because objects are accessed solely through their interfaces, we don’t break encapsulation. Any object can be replaced at run-time by another as long as it has the same type. Moreover, because an object’s implementation will be written in terms of object interfaces, there are substantially fewer implementation dependencies. Favoring object composition over class inheritance helps you keep each class encapsulated and focused on one task. Your classes and class hierarchies will remain small and will be less likely to grow into unmanageable monsters. On the other hand, a design based on object composition will have more objects (if fewer classes), and the system’s behavior will depend on their interrelationships instead of being defined in one class.

From the above discussion, we can see that object composition has very, very few bad effects compared to the class inheritance. This gives rise to our second OOP Principle:

OOP Principle 2 - Favor Object Composition over Class Inheritance.

Ideally, ideally, you shouldn’t have to create new components to achieve reuse. You should be able to get all the functionalities you need just by assembling existing components through object composition. But this is rarely the case, because the set of available components is never quite rich enough in practice. Reuse by inheritance makes it easier to make new components that can be composed with old ones. Inheritance and object composition thus work together. Nevertheless, statistics say that designers overuse inheritance as a reuse technique, and designs are made more reusable and simpler by depending more on object composition.

Does Design Pattern Help Us to Achieve OOP Principle 2

Yes it does. There is a pattern called strategy which teaches us how to compose objects at run-time to get different behaviors out of a particular class. Almost all of the design patterns favor composition over inheritance. So, we can say:

Design patterns enable us to reuse our code in the most efficient way by favoring object composition over class inheritance.

Enough for today. I am tired and hungry. I hope I can finish this introduction in the next article. Until then, good bye :-) .

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)