Powered By Blogger

Wednesday, December 11, 2013

Open Closed Principle

The Open Closed Principle ( OCP ) states:

Software Entities ( Classes, Methods, etc ) should be open for extension but closed for modification

A requirement of change in a class, many a times, cascades into spiraling changes across other classes and other dependent modules. Such a fragile, rigid and non-extinsible program puts a tremendous constraint on the incremental development as well as code maintenance.
Open closed principle, precisely , addresses this problem. It advocates that a program once tested and in production, should no longer be open to any further changes. A program is not open to any change, except on error conditions. Any change in the requirements should be incorporated only and only by extending the class through inheritance.

What does a program achieve on its confirmation to OCP

  • A low coupling between the program the clients or other dependent programs. This in turn promotes development that is flexible, extensible and resuable, leading to better development speed as well as low cost.

How to find if the given program violates OCP

Simply, When a change in requirement gives rise to change in a class.
But doesn't this sound outrageous. A change in requirement for a given class behavior must be incorporated in the class itself, isn't it ?

Well that's true, but why not use inheritance to  implement the new behavior by extending the base class. The benefit of extension is that the existing working code which is well tested, is not messed with.

But this brings forth few questions :
  • Are there any pitfalls of extending a class behavior ? 
  • How to take care that the new extended class do not override the Base class's behavior in a skewed way ? 
In other words, can the new extended class be a substitute for the original Base class ? 
It would be interesting to answer these questions, which shall be taken separately as Liskov Substitution Principle.

Let's take few program examples that violate OCP

Code Examples

Example # 1

This examples represents a Web Parsing system, capable of parsing different web pages on internet.

Class ParsingHandler  - This is responsible for parsing different kinds of Sources, like HTML , XML, etc.

enum DocType - Enumeration of Different kinds of available source.

HtmlSource - Class to encapsulate Html source content and its doc type.

XmlSource - Class to encapsulate Xml source and its doc type




/**
* Different types of Source that can be parsed.
*/
enum DocType {
 XML, HTML
}

/**
* Interface for the Source to be parsed.
*/
interface Source {
 DocType getType();

 String getSource();
}

/**



* HTML source class
*/
class HtmlSource implements Source {

 private String htmlSource;

 public HtmlSource(String htmlSource) {
  this.htmlSource = htmlSource;
 }

 public DocType getType() {
  return DocType.HTML;
 }

 public String getSource() {
  return this.htmlSource;
 }
}

/**
* XML source class
*/
class XmlSource implements Source {
 public String xmlSource;

 public XmlSource(String xmlSource) {
  this.xmlSource = xmlSource;
 }

 public DocType getType() {
  return DocType.XML;
 }

 public String getSource() {
  return this.xmlSource;
 }
}

/**
* Parses different sources
*/
public class ParsingHandler {

 public DomTree handle(Source source) {

  DocType type = source.getType();

  if (type == DocType.HTML) {
   return parseHtml(source.getSource());
  }

  else if (DocType.XML == type) {
   return parseXml(source.getSource());
  }
  return null;
 }

 private DomTree parseHtml(String src) {
  // logic for parsing an html content into a DOM structure
 }

 private DomTree parseXml(String src) {
  // logic for parsing an XML content into a DOM structure
 }

}

So far so good.
But how good this system fares with respect to changing requirements ?
Since this a web parsing system, no sooner this system is build than it is impounded by parsing all the different web pages. Soon it is reported that this system must also handle parsing of RSS feeds.

How will the system adapt to parsing RSS feeds.
  • enum DocType - Have to be modified to add a new type like RSS.
  • ParsingHandler - Since the handle() method must have the knowledge of the type of source it is parsing, hence this class needs a modification too. 

Both of the above classes are not closed for modification.
/**
* This class Violates Open Closed Principle. It is not closed for modifications.
* Needs to modified each time a new source is added to the system
*/
public class ParsingHandler {

 /**
  * This method behaviour depends upon the type of source. What happends if
  * the a new source type is added to the system ? This method have to be
  * modified to include the logic for parsing the new source type. So this
  * class is not closed and violates OCP
  */
 public DomTree parse(Source source) {

  DocType type = source.getType();

  if (type == DocType.HTML) {
   return parseHtml(source.getSource());
  }

  else if (DocType.XML == type) {
   return parseXml(source.getSource());
  }

  else if (DocType.RSS == type) {
   return parseRss(source.getSource());
  }
  return null;
 }

 private DomTree parseHtml(String src) {
  // logic for parsing an html content into a DOM structure
 }

 private DomTree parseXml(String src) {
  // logic for parsing an XML content into a DOM structure
 }

 private DomTree parseRss(String src) {
  // logic for parsing RSS feeds
 }

}

So what went wrong ?

How to avoid OCP violation

If we look at the Responsibility that the class ParsingHandler handles, it becomes clear that this class is cluttered with varied responsibilities, of parsing sources of different types. Clearly, the parsing logic needs to taken out from this class. But then, where should this logic be placed ?

Abstraction , Inheritance and Encapsulation

Encapsulation closely resembles the concept of Information hiding. Here information includes both the data and behavior. When data and behavior is combined ( encapsulated ) into one unit, the question arises is , how should this be exposed to other classes interacting with this unit.

A class should be designed so that it represents a well defined unit of responsibility, exposing only a limited, minimum information about itself to the outside interacting classes. This helps to attain high cohesion and low coupling. A changes in a given class should not produce a ripple effect. Classes must be build in a way so that a change in the class has no or minimal impact on other interacting classes. The more information a class keeps to itself, the lesser are the impact of changes on other dependent classes.

As a class is an encapsulation of data and methods, so both member variables and methods should be secured with a minimum public access.

Rule # 1 - Member variables, must all be declared private. Only the final constants and immutable objects
are immune to be declared as public

Rule # 2 - Public methods should have an abstract declaration. This can be achieved by abstraction and inheritance. Having an abstract declaration for each public method ensures that a change of requirement can be fulfilled by substituting a new implementation of the abstract method in a different class, without affecting an already existing class.

Design structure of example #1 represents an immature and mixed-up distribution of responsibility between classes. Immature because it does not take care of the fallouts of the changing requirements. Mixed-up because class ParsingHandler is handling parsing of too many varied sources.

Let's re-design example # 1:
Why not each Source class be themselves be responsible for parsing their content. Since each source class precisely know its type and has the knowledge of text that it needs to parse, hence the parsing logic for each doctype may be moved to the respective sources.

Interaction of parsing handler class and the different source classes must be via a layer of abstraction. With abstraction, the parsing handler class will no longer have to worry about the actual source. It can simply use the public abstract method, and thus be totally relieved of the changing requirements, which otherwise could have propagated from the source classes.

Let's see the re-designed classes.
HtmlSource class renamed to HtmlParser as it now handles responsibility of html parsing. Similarly class XmlSource renamed to XmlParser




/**
 * Represents layer of abstraction
 */
abstract class Parser {

 abstract DomTree parse(String source);

}

/**
 * Handles complete responsibility of parsing xml source
 */
class XmlParser extends Parser {

 DomTree parse(String source) {
  // logic for parsing XML content
 }
}

/**
 * Handles complete responsibility of pasing html source
 */
class HtmlParser extends Parser {

 DomTree parse(String source) {
  // logic for parsing HTML content
 }
}

/**
 * Does not violate Open Closed Principle
 */
class ParsingHandler {

 public DomTree handle(Parser parser, String src) {
  return parser.paser(src);
 }
}

New requirement for parsing RSS feeds can, now, be simply accomplished by adding a new class RssParser, without modifying any of the existing classes.
/**
 * Handles complete responsibility of parsing rss feed source
 */
class RssFeedParser extends Parser {

 DomTree parse(String source) {
  // logic for parsing RSS feed
 }
}


S          O          L             I           D


1 comment:

  1. Nice explanation!! Example, itself, explains everything!!

    ReplyDelete