65.9K
CodeProject is changing. Read more.
Home

Integrate Design Patterns into Your Code

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3 votes)

Apr 12, 2016

CPOL

7 min read

viewsIcon

11130

This article shows pieces of code that can be rewritten using design patterns. At the same time, some object oriented principles behind design pattern are also shown.

Introduction

A design pattern is a solution to a common problem in object oriented programming. The most famous design patterns are probably the 23 ones contained in the Gang of Four book, de facto the first book on the topic.

The usage of design patterns usually starts with recognizing a problem and finding an appropriate design pattern to apply. However if you are relatively new to the topic of design patterns, it can take a while before you can master where and when to use a design pattern.

This tip shows design patterns from a different angle: it shows how to use some design pattern starting from existing pieces of code. This will also allow us to explore some basic ideas that are behind a number of design patterns.

The pieces of code are written in C#. Notice that the explanation of each mentioned design patterns is beyond the scope of this tip since it would be necessary to write an article for each design pattern.

Background

Knowledge of the main concepts of object oriented programming is expected.

Code That Can Be Rewritten With Design Patterns

The following elements of code can be rewritten with design patterns:

  1. if - else blocks
  2. Inheritance
  3. the new keyword

These elements can potentially lead to inflexible code. The advantage of using design patterns can be that the code is then easier to maintain and/or is more flexible. On the other hand, the use of design patterns often involves writing more code, and in turn, the development time is increased. More details about pros and cons of design patterns are discussed at the end of this article.

The peculiarity of each element of code is detailed in the following.

if - else blocks

In almost all programs, there are if - else blocks like the following:

if (condition1)
  { // ... code 1 }
else if(condition2)
  { // ... code 2; }
...
else if(conditionN)
  { // ... code N }

There is nothing wrong with if-else blocks. The problem is that they can potentially degenerate to large blocks of code as the program keeps changing (change is one constant in software development). Large blocks of code are obviously hard to maintain.

Depending on the if-else block, this can be rewritten using a particular design patterns. At the end of the day, design patterns will encourage you to split your if-else block into multiple classes that are (or should be) easier to maintain since each class has a well defined role. Which design pattern to use is explained in the following:

  • if the conditional variable is a state and you have to handle state changes, or in other words you are modeling a machine state, like in this example:
    	if (state == STATE_1) 
           { state = STATE_2; } 
        else if(state == STATE_2) 
           { state = STATE_3; } 
        ... 
    then you may use the state pattern. The state pattern allows an object to change its behavior depending on its internal state.
  • if the conditional variable influences an action to be executed like in this example:
    abstract class Animal; 
    class Dog: Animal; 
    class Fish: Animal; 
    class Bird: Animal;
    ...
    if (myAnimal is Dog)
      { (myAnimal as Dog).walk(); }
    else if (myAnimal is Fish)
      { (myAnimal as Fish).swim(); }
    else if(myAnimal is Bird)
      { (myAnimal as Bird)fly(); }
    you may use the command pattern. The command pattern encapsulates an action to be executed into an extra-class, opening new scenarios like command queuing, logging and undo actions.
  • if the conditional variable influences the type of an algorithm to be executed like in this example:
    if (myVar== "A")
      { execAlgorithmA(par); }
    else if(myVar == "B")
      { execAlgorithmB(par); }
    ...
    else if(myVar == "N")
      { execAlgorithmN(par); }
    then you may use the strategy pattern. The strategy pattern encapsulates a family of related algorithms, each in a different class, and makes them interchangeable.

In all the design patterns above, conditionals are replaced with a hierarchy of classes, one class for each pair of bracket parenthesis ({}). This is the application of the object oriented principle of encapsulating (= putting in a class), what changes.

if (condition1) 
{ // ... code 1 }       // put code in a class1 :  ICommonInterface

else if(condition2) 
{ // ... code 2; }  // put code in a class2 : ICommonInterface

... 

else if(conditionN) // put code in a classN : ICommonInterface
{ // ... code N }

Notice that the classes share a common interface (called ICommonInterface in the example above) so that you can replace the usage of one class with one another.

Inheritance

Inheritance allows for code reuse which is a good thing but, on the other hand, can also lead to rigid solutions. Indeed, inheritance is a strong relationship in the sense that all that a child class inherits from a parent class cannot be removed nor can be changed at runtime. For example, if a parent class has the public methods getValue(), getText(), getInput(), then a child class will have such methods as well and it will not be possible to hide them. Of course, if a method should not be implemented, it can throw a NotImplementedException but this is probably not the best solution.

We can apply the object oriented principle that says to favor composition over inheritance. Composition is in many cases a more flexible solution because composition can be changed at runtime.

// code with inheritance
class Parent {
     public void Method1(){};
}
class Child : Parent {
     // Child inherits Method1 from class Parent
}

// a more flexible solution with composition

// interface to be implemented by the father class
interface MyInterface {
   void Method1();
}

class Parent : MyInterface {
  public void Method1(){};
}

class Child {
    MyInterface component;

    // component can be set to an instance of Parent
    // so you can call parent.Method1() but component
    // can also be set to the instance of another class
    // implementing MyInterface
}

Examples of design patterns that favor composition over inheritance are the strategy pattern and the
decorator pattern.

The New Keyword

The new keyword is used to create new class instances. When you instantiate a class instance like that in your code:

class Class1 {
     Class2 cls2;

     public Class1() {
        cls2 = new Class2();
        cls2.setup();
     }
}

you have that Class1 is dependent on Class2. The potential problem with this code is:

  • Class1 references the concrete class Class2. It would be more flexible to reference an interface (or also an abstract class) implemented by Class2 (if existing, of course) instead. This would allow you to replace Class2 with another class implementing the same interface. This is the application of the object oriented principle that says to reference interfaces (or abstract classes) instead of concrete classes.
  • The instantiation of Class2 may also require to call some methods like setup in the example. Perhaps the sequence of the methods is important. In any case, it could be a good idea to have an extra class whose only purpose is to create instances of Class2 ready to be used.

These two problems are handled in the following:

class Class1 {
    // now Class1 references an interface implemented by Class2 
    InterfaceClass2 cls2;

    public Class1 {
       // instance of Class2 created the hard way was 
       // cls2 = new Class2();
       // cls2.setup();
   
       // or better a solution with a factory class
       // where cl2 is now ready to be used 
       // (no need to call the setup method
       cls2 = FactoryClass2.getInstance();
    }
}

// factory class
static Class FactoryClass2 {
    // getInstace returns an instance of Class2 that is ready to be used
    // since it is properly initialize
    static Class2 getInstance() {
       Class2 c = new Class2();
       c.setup();
       return c;
    }
}

This is a simplified version of the factory method pattern which can be applied whenever you have to create a class instance. Notice that the factory method could also be applied to replace if-else blocks, for example when which class must be instantiated depends on a particular condition.

In case the creation of an object requires the creation of a number of components that make up that object, you could have a look at the abstract factory pattern. For example:

class MyClass1 {
   public MyClass1 {
      c1 = new ComponentClass1();
      c2 = new ComponentClass2();
      ...
      cn = new ComponentClassN();
   }
   
   ComponentClass1 c1;
   ComponentClass2 c2;
   ...
   ComponentClassN cn;
}

you have that the creation of an instance of MyClass1 involves the creation of n objects c1, c2, .. cn.

Discussion

The usage of design patterns allows to rewrite pieces of code that are potentially dangerous, in the sense that could lead to hard to maintain code. On the other hand, after you apply design patterns, you will have your code split up in a bunch of classes and in the end, you will have to write more code. So you could end up with a blown up code. The code split up in classes is potentially easier to maintain because each class has a defined role.

Design patterns have the cost of a longer development time. Their use makes sense when the code maintainability is more important that the development time.

On the other hand, it can be hard to know up front which pieces of code could benefit from design patterns in terms of costs/benefits. So to start with a good solution could be to apply as few patterns as possible (or if any at all) and refactor the code later. Actually, this is quite opinionable and each developer will have his own good solution.

Conclusions

In this article, we have seen some elements of code that can be rewritten using design patterns. This will involve writing more code but at the same time, the code will be more maintainable.

We have mentioned some design patterns and some common principles behind command patterns. The common principles mentioned in this article are:

  • Encapsulate what changes, which is equivalent to rewriting conditional statements with a set of classes. Each class has a defined role so it should be easier to find where a bug is. Examples of design patterns using this principle are the command pattern, the strategy pattern and the state pattern.
  • Favor composition over inheritance. Composition is more flexible as it can be changed at runtime. Examples of design patterns using this principle are the strategy pattern and the decorator pattern.
  • Reference interfaces (or abstract classes) instead of concrete classes in your code. This will decouple your code from a certain concrete class type. Examples of design patterns using this principle are the factory pattern and abstract factory pattern.

History

  • April 2016: First version