CSC/ECE 517 Fall 2012/ch1 1w24 nr

From Expertiza_Wiki
Jump to navigation Jump to search

Decomposition, message forwarding, and delegation versus inheritance in OOP

Introduction

Inheritance plays an important role in designing reusable object oriented software. In this article we will discuss some alternatives to inheritance to achieve object oriented code reusability. Design techniques like composition, message passing and delegation can be used instead of inheritance in some cases to achieve the same goals with more cohesion and less coupling. The basic difference in these approaches lies in the separation of interface and implementation. We shall explain and compare these techniques with examples.

Definitions

Inheritance

Inheritance is an object oriented programming concept for code reuse. It says that we can define a subclass on a parent class and that will inherit its parent’s attributes and behavior <ref name="inheritance">Inheritance</ref>. Consider the following example:

class A {
  protected int attribute1, attribute2;
  public void method1() {
    ...
    //method definition
  }
  public void method2() {
    ...
    //method definition
  }
}

class B extends A {
  public void method1() {
    ...
    //method overriding
  }
  public void method3() {
    ...
    //new method definition
  }
}

Here, class B is a subclass that inherits the attributes and methods defined by its parent A. The subclass inherits the method interface as well as the implementation from its parent. The subclass may define its own attributes and methods or it may provide its own implementation for its parent’s methods. This is called method overriding.

Composition

In some cases object composition is an alternative to class inheritance to achieve code re-usability. Here, new complex functionality is achieved by assembling or composing objects <ref name="objectcomposition">Object Composition</ref>. Unlike inheritance, composition relies on reuse of interface rather than implementation. Consider the new object as a large container <ref name="Design Principles">Design Principles</ref>. Complex functionality is achieved in this larger container by placing smaller closed containers inside it. Every smaller object has its own independent implementation hidden from the outer container. Thus composition provides encapsulation in the true sense. This is why composition is also called the black box approach for code reuse. As an example consider a chair class <ref name="compositionaggregation">Composition and Aggregation</ref>. A chair is made up of a seat, backrest and four legs.

public class Seat { 
   …
}
public class Backrest { 
   …
}
public class Leg { 
   …
}
public class Chair {
   protected Seat seat;
   protected Backrest backrest;
   protected Leg leg;
  
   Chair() {
      Seat s = new Seat();
      Backrest b = new Backrest();
      Leg l = new Leg();
      …
   }
}

In this example since seat, backrest and legs are components of a chair it is better to use composition instead of inheritance. The seat, backrest and legs class have their own implementation which is hidden from the chair class. These components can be used in the chair class with the help of their defined interface. The chair class can communicate with its component objects through message passing. Thus, code reusability is obtained without caring for the implementation of the component objects.

Aggregation

Another concept related to object composition is that of Aggregation. Suppose we also use a class Person in the Chair class to represent the person occupying the chair <ref name="compositionaggregation">Composition and Aggregation</ref>. Then this person is not initialized in the Chair class as a person has an existence irrespective of the chair, whereas the seat, backrest and legs do not have any existence without the chair.

Delegation

Delegation is a way of making composition as powerful for reuse as inheritance <ref name="Delegation">Design Patterns</ref>. Delegation involved two objects: a delegator who delegates a operation to another object, the delegatee. This can be compared to inheritance where a subclass "defers" request to the parent class for a method which is not overridden in the subclass <ref name="Delegation">Design Patterns</ref>. The advantage of delegation over inheritance is that delegation allows composition behavior to occur dynamically at run-time, whereas inheritance causes the relationship to be established statically at compile-time. Although, overuse or delegation may make the code hard to understand and may invite runtime inefficiencies.

Let us convince ourselves that Inheritance is bad at certain places

Below are the cases<ref name="Limitations">Limitations and Alternatives</ref>:

Problem 1: Multiple Inheritance introduces lot more confusion<ref name="confusions">Diamond Problem</ref>

One such is Diamond Problem. Two classes B and C inherit from the same base class A and class D inherits from B and C. If D calls a method defined in class A and implemented very differently in classes B and C.

Diamond Problem with Inheritance example.

Class A {
  virtual print() {
     cout << “This is class A”;
  }
}
class B : A {
  override print() {
     cout << “This is class B”;
  }
}
class C : A {
  override print() {
     cout << “This is class C”;
  }
}
class D : B, C {
  A a = new A();
  a.print();
}

Inheritance is link time

Types are fixed during compile time and super classes cannot be changed dynamically during program execution. Another problem which arises because of static linking is, the changes to super class automatically propagates to the sub-class. In essence inheritance creates a strong coupling. This can be addressed with help of Composition, which allows to change behaviour at runtime.

Inheritance:
class Fruit {
}
class Vegetable {
}
class Tomato : Fruit {
}
or
class Tomato : Vegetable{
}

Composition:
class Fruit {
}
class Vegetable {
}
class Tomato {
  if(some-criteria) {
     Fruit f = new Fruit();
  } else {
     Vegetable v = new Vegetable();
  }
}

When inheritance is used class Tomato can either inherit from Fruit or Vegetable, this has to be decide while implementation and cannot be decided during runtime. But when composition is used, based on some condition at run time, Tomato can get behaviour of fruit or vegetable.

Inheritance is white box

Inheritance is <ref name="whiteboxwiki">white box</ref> and not <ref name="blackboxwiki">black box</ref>, since it exposes lot more details than necessary. This violates the OO concept of encapsulation and data hiding. Composition acts as black box and hence solves this problem <ref name="whitebox">Inheritance breaks Encapsulation</ref>

Huge Explosion in the number of Types

There will be a huge explosion if a new type is created for every possible combination of behaviours, this can be avoided with delegation.

Base classes:
class fly {
}
class eat {
}
class talk {
}
class dance {
}

Sub classes:
class human : eat, talk {
}
class birds : fly, eat {
}
class robot : dance, fly {
}

In the above example for every possible pair of behaviours there might be class like human can eat and talk and not fly, birds can eat and fly but not dance and robots can dance and fly but not eat. This list can keep growing and might become difficult to manage.

When to use What

When to use Inheritance instead of Composition/Aggregation

Composition or Aggregation can always be good instead of Inheritance because it is flexible and behaviour can be changed on the fly. But there are situations where Inheritance might make more sense.

If type B wants to expose the entire interface of type A and type B can be substituted in place of type A - use Inheritance definitely.

class Phone {
  public dialcall();
  public receivecall();
  public savecontacts();
}
class SmartPhone : Phone {
}

For example, SmartPhone will require and expose all the functionalities of a Phone like dialing a call, receiving a call and saving contacts in phone. Above these basic functionalities might add more functionality. So SmartPhone should be derived from Phone and must not contain Phone.

When to use Composition/Aggregation instead of Inheritance

If type B wants to expose only part of the interface of type A - use Composition/Aggregation.

class Dog {
  eat();
  play();
  bark();
}
class Cat : Dog {
  bark() {
     do nothing
  }
}

In the above example, class Dog exposes some behaviour and class Cat inherits from Dog. But a cat cannot bark and hence bark method has to be overriden and left empty. Since Cat needs only a part of the behaviour exposed by Dog, it is good to extract out the common functionality and make it a separate interface or class and include in both the classes.<ref name="compositionoverinheritance">Composition over Inheritance</ref>

When to use Delegation instead of Inheritance

Only when it is needed to copy the functionality and exposing the base class API is not needed then Delegation is the right thing to do.

Inheritance:
class List {
  public:
     insert_at_front();
     insert_at_back();
     delete_from_front();
     delete_from_back();
     insert_at_position();
     find_loop();
}
class Stack : List {
}

Delegation
class List {
  public:
     insert_at_front();
     insert_at_back();
     delete_from_front();
     delete_from_back();
     insert_at_position();
     find_loop();
}
class Stack {
  List newlist = new List();
  public Push() {
     newlist.insert_at_back();
  }
  public Pop() {
     newlist.delete_from_back();
  }
}

In the above example, Stack is implemented with List, if the Stack class inherits from List class, it gets all the List behaviour like find_loop, insert_at_front, delete_at_front, which is not defined by the Stack ADT and so Stack should not expose these methods. Instead Stack can contain an instance of List and expose just two method needed for a Stack, push and pop. Internally the Push call and Pop call can be redirected to the List class with the help of the List instance.


UML Representation

The Unified Modeling Language standard specifies graphical notations for object oriented models. Inheritance is represented using a solid line with a closed, unfilled triangle pointing from the child class to the parent class. For example, following UML represents the inheritance relationship when Dog class subclasses from Pets class.

Composition relationship is represented in UML using a solid line from the parent class to the part class with an unfilled diamond shape on the parent class end. Aggregation has a similar notation with the only difference being that the diamond shape is filled.

References

<references/>

External Links

  1. Liskov substitution principle
  2. Composition over Inheritance
  3. Deciding between Composition vs Inheritance
  4. Composition vs Inheritance
  5. Composition over Inheritance
  6. Delegation instead of Inheritance
  7. Delegation vs Inheritance
  8. Delegation
  9. Object Oriented Development
  10. Decomposition/generalization methodology for object-oriented programming
  11. Two object oriented decomposition methods
  12. Design Patterns