Wednesday, September 11, 2013

Composition vs Inheritance choice - the true story.


As an inexperienced developer keeps diving into new ideas of software design, a time will come when the contradiction between composition and inheritance became the question.
Composition over Inheritance is a popular principle in Object Oriented design. Below we will try to make things clear about the choice from between those two along the development process.


Before we even move forward we assume that the terms of a basic software engineering terms such as aggregation, association and the UML basics are clear to the reader [see our article].
In object oriented design it is a fundamental activity to establish class relations. The functionality two discussed methods - composition and inheritance - are

Let us first consider the nature of both terms.
One of the fundamental activities of an object-oriented design is establishing relationships between classes. Two fundamental ways to relate classes are inheritance and composition. It is worth mentioning that you can get at the functionality of inheritance when you use composition. This article will compare these two approaches to relating classes and will provide guidelines on their use.

Composition

As the name itself tell the composition is about  building something out of smaller parts. The key to understand composition in a right way is to understand aggregation and association. In this article we will focus around composition as a instance variables referencing to other objects.

public class A{
    private B b = new B();
}

Inheritance

Along this article inheritance would mean a single inheritance through class extension.
public class A{...}
public class B extends A{...}

Benefits of inheritance:
  • advantage of dynamic binding.
    Dynamic binding is a JVM mechanism that is responsible for choosing which method
    implementation will be invoked at runtime based on the class of the object.
  • Polymorphism -
    In general means to use a variable of a superclass type to hold a reference to an object whose class is the superclass or any of its subclasses
Both of the above can help make code easier to change. This is nice as long as you want to add a new type of a subclass.
However, this is not the case when the change involves class structure.

Composition vs Inheritance


The superclass in inheritance is often referred to as a fragile. It is because one change in superclass structure will force changes in many subclasses. The fragile part of superclass is its interface. In case of a well-designed superclass with a clean separation of interface and implementation in the object-oriented style, any changes to the superclass's implementation shouldn't cause problems at all. However, if we try to change the interface even the best design will not help breaking subclasses code.

Example:

public class A {
    //interface method:
    public int m1(){...} // change int to Integer return value 
}

The change of the type from int to Integer can cause errors in subclasses that invokes that method on any reference of type A. What is more even if subclasses override this method this would also cause braking code. The only way to solve this problem is to move along the entire A class inheritance hierarchy and modify every interface part that need to conform the new look of the superclass.
What is more, if you have code directly using subclass of a A class that the code will also suffer from superclass interface modification. That is the reason to state the inheritance provide “weak encapsulation”.
Inheritance allows subclass to reuse code from superclass if it is not overriding it. However, the subclass of A only “weakly encapsulates” the A class code it is reusing, as the changes to A’s interface can break code that directly uses such subclass.


In such situation let us move to alternate solution provided by composition. In terms of code reuse, composition turns out to be more code changing friendly.

  • Inherited code reuse

Let  us consider some code using
inheritance:

class A {
     // Return int value  resulted from the method activity.
    public int method1() {
      System.out.println("Method 1. from class A");
      return 1;
    }
}

class B extends A {}

class MainClass {

  public static void main(String[] args) {
    B b = new B();
    int v = B.method1();
  }
}

The application would result in printout of: “Method 1. from class A”, because B inherits (therefore, reuses) A’s implementation of method1(). However, if in the future one would want to change the construction of method1() in A class, then the entire application would broke. This would happen even if the use of A class is indirect through the B class.
  • Composed code reuse
The same example can be handled with composition.

class A {
    // Return int value  resulted from the method activity.
   public int method1() {
      System.out.println("Method 1. from class A.");
      return 1;
   }
}


class B {

    private A aVar = new A();

   public int method1() {
      return aVar.method1();
   }
}

class MainClass2 {

public static void main(String[] args) {

    B b = new B();
    int v = b.method1();
  }
}

In this approach the B class due to composition construct, becomes front-end class while the superclass A plays a role of back-end class. In contrast to inherited version we have an overridden implementation or pure code reuse from superclass in other case. With composition we have an explicit method call to the method of the back-end superclass. This is sometimes called forwarding or delegation.
What is more composition brings stronger encapsulation than inheritance. It is because each time we change a method from superclass the code relying on the subclass is not broken. So e.g. changing the method1() return value from A class would not force change in the interface of class B and thus no problems caused in the main application using the B class only. The problem might arise in class B method1() but not in the main app.
This way the main project is not affected. 

Compare & Contrast: composition & inheritance


    1. As shown in previous section it is much less complicated to propagate changes from back-end class down the hierarchy with composition that it is superclass down the inheritance hierarchy.
      In
      composition, change to the back-end class interface would force possible changes in front-end class implementation but not its interface. Therefore the change does not have to propagate. The code depending on front-end class interface still can work if it interface does not change with the back-end class interface change.
      In case of
      inheritance any change to superclass’s interface would not only force changes down the inheritance hierarchy of classes but will cause problems with the code using those classes.
    2. The problem however arises also when we try to modify the second classes: front-class in case of composition or subclass in case of inheritance.
      In
      inheritance it is impossible to change the subclass’s interface in a way that is not confirming the compatibility with the superclass interface. E.g. it is not possible to add to the subclass a new method with the same signature but with different return type as a method inherited from superclass.Composition allows the change of interface of front-end class without affecting back-end classes.
    3. The aspect of object creation. Composition is capable of lazy creation of back-end objects until they are really needed and what is more, the back-end objects can be changed during the lifetime of the front-object.
      Inheritance on the other hand creates superclass part as soon as subclass is being created. It is part of a subclass object to the end of its lifetime.
    4. Polymorphism of inheritance comes with ease of creation of new subclasses. A superclass interface code will work with any new subclass created.Composition without use of interfaces does not bring such feature. However adding interface to composition can overcome this limitation. [see article]
    5. In most cases the performance of explicit method invocation forwarding (delegation) from composition is less efficient comparing to single invocation of an inherited superclass method implementation. This is however mostly JVM related.

When Compose when Inherit?


    The A,B,C rules of thumb.
       
    1. Inherit only when is-a relationship exist. Use inheritance only when the  subclass IS-A superclass:
      e.g. Car is-a Vehicle, Horse is-a Mammal etc.
      What is more this relation must be true along the lifetime of an application using speciffic domain model
      e.g. Employee is-a Person up to a point when an Employee becomes unemployed or a Supervisor. So Employee is not a Person but a role a person plays during the lifetime of application. In this case we should consider composition
    2. The code reuse can not determine the use of inheritance. Only the is-a relationship is valid determinant. Otherwise use composition.
    3. The polymorphism can not determine the use of inheritance. Without IS-A relationship it is enough to use composition with interfaces instead of inheritance. [see article]

    so[L]id 

    Let us mention here one of the most important principles in object-oriented design :
    SOLID (Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion). 

    Actually we will focus here only on the L (LSP) part of this acronym.
    objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
    Typical violation of this principle is with a specific class that inherits from more general class. E.g. Square inherits from Rectangle. Let us assume we would have a method called calculateField() resulting in field value as a product. Now if we would consider a Square as a Rectangle then it's sides length still must stay the same.
    Now changing the side lengthto the Square i.e. :
    rectangle.setWidth(10);
    rectangle.setHeight(20);
    

    would cause some unexpected behavior because there should be a mechanism assuring that both sides are even in case of Square and in Rectangle there is none. 
    What is more the result of the field would be 100 for square instead expected 200 for rectangle. Therefore this voilates the Liskov substitution as it is not working as expected.

    No comments :

    Post a Comment

    Social Buttons End POst