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:
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:
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:
public class InventoryAccount extends Account{
protected string inventoryItemNumber;
public InventoryAccount(){}
public void setAccountNumber(string accountNumber){
this.accountNumber = accountNumber;
}
public string getAccountNumber(){
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:
List<InventoryAccount> accounts = new List<InventoryAccount>();
accounts.Add(new InventoryAccount("Whatever initialization parameter is needed"));
..............
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 “, 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:
List<Account> accounts = new List<Account>();
accounts.Add(new InventoryAccount("Whatever initialization parameter is needed"));
..............
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:
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(){
.......
}
}
public class LCDDisplay{
public LCDDisplay(){}
public void displayDataHorizontally(){
}
}
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:
public class Account{
protected string accountNumber;
protected double balance;
protected InterestRateCalculatorInterface RateCalculator;
protected final double rate = 0.5;
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{
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();
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 .