Tuesday, October 11, 2011

A few SE principles for OO developement

Open-Closed Principle (OCP)
The intention with this principle is that software entities(classes, modules, functions, etc) should be open for extension but closed for modifications. Entities should be written so that they can be extended without requiring them to be modified. So when requirements change, we extend the behavior of such entities by adding new code, not by changing old code that already works.

The problem with changeability is that if we need to create an new shape, such as a Triangle, we must modify the "drawShape()" method. In a complex application the switch/case statement above is repeated over and over again for every kind of operation that can be performed on a shape. Even worse is the fact that the switch/case statement retains a dependency upon every possible shape that can be drawn, thus, whenever one of the shapes is modified in any way, the modules need recompilation, and possibly modification. 

When the majority of entities in an application confirm to the open-closed principle, then new features can be added to an application by adding new code rather than by changing the existing working code. In this way the working code is not exposed to breakage. Adding a new Triangle shape becomes a matter of extending the Shape interface. Inheritance is what the Open-Closed Principle advocates to build clean extendable code.




Liskov Substitution Principle (LSP)
This principle states that "Functions that use pointers or references to Base classes must be able to use objects of derived classes without knowing it".  So simply put it this way, a subclass object must be substitutable for an object of its superclass.  For example, say we have a Class B and a subclass C which extends class B.  In other words  C "is-a" B, which means that C is a specialization of B, and  in OOP  the "is-a" relationship arises in the context of inheritance.
  class B { ... }
  class C extends B { .. }
Now say we have class X with some method g that takes an argument of class B.
  class X {  public void g(B b){ ... } }
What the LSP principle tells us is that if method g behaves correctly when given a B object, it should also behave correctly when given a C object without g knowing about the existence of C. The problem arises when the "is-a" relationship doesn't work as expected. The classic example is the Square and Rectangle. In mathematics a Square is a  Rectangle, and since the "is-a" relationship holds it makes sense to model this with inheritance.

    public class Rectangle {
      
        protected int width;
        protected int height;
      
        public int getWidth() {
            return width;
        }
      
        public void setWidth(int width) {
            this.width = width;
        }
      
        public int getHeight() {
            return height;
        }
      
        public void setHeight(int height) {
            this.height = height;
        }
    }
  
    public class Square extends Rectangle {
      
        public int getHeight() {
            return super.height;
        }
      
        public int getWidth() {
            return super.width;
        }
      
        public void setWidth(int width) {
            super.setWidth(width);
            super.setHeight(width);
        }
      
        public void setHeight(int height) {
            super.setHeight(height);
            super.setWidth(height);
        }
    }
If we look at the classes above, it appears that these classes would work just fine.  They seem consistent and valid - and they do when they are view in isolation.  However, what would happen when a client's  function do something like below.

  

 public void g(Rectangle r) {
  r.setHeight(4);
  r.setWidth(3);
  
  assert(r.getHeight() * r.getWidth() == 12);
 }

The assertion will pass when a Rectangle is passed to this function, however it will failed when a Square is passed.  These function exposes a violation of the LSP, since it clearly cannot operate properly when a subtype object is passed to it.  So the "is-a" relationship holds for a Square and Rectangle in the mathematics world, but here it fails in the OO world because the behavior of the Square object does not seem to be consistent with the behavior of the Rectangle object. The main point of the LSP is that we should think about the expected behavior of a class when subclassing new ones.



Dependency Inversion Principle (DIP)
This principle states that Abstractions should not depend on details, but rather details should depend on abstractions. Dependency Inversion is then the strategy of depending upon interfaces or abstract classes, and not on concrete implementations. Every dependency in the design should target an interface or abstract class and no dependency should ever target a concrete class. In other words, program to an interface and not to a concrete implementation. Abstract classes and interfaces tend to change much less frequently and are easier to extend (OCP)


That is it, three very important principles that are closely related, lets keep them in mind to get the most out of our OO development.

No comments:

Post a Comment