Tuesday, December 15, 2015

OO principle - S.O.L.I.D., Single Responsibility, Open/ close principle drill

Dec. 15, 2015

Review the video about the testing, and try to get more from this lecture:

"The Clean Code Talks -- Inheritance, Polymorphism, & Testing"

https://www.youtube.com/watch?v=4F72VULWFvc

action items:
1. put sample code in C#, and then, check in git;
2. write down key words, and easy for review and follow as rules.

Julia's sample code in C#:
Problem statement: 1 + 2*3, how to implement in OO design (using C# language)

Three solutions are provided, naive one, better one, optimal solution to apply S.O. principles.

1. Represent this as a tree

   +
/     \
1     *
    /    \
  2      3

Most of people come out solution like the following:
 Using conditionals

  class Node{
     char operator;
     double value;
     Node left;
     Node right;
     double evaluate(){
       switch(operator){
        case '#': return value;
        case '+': return left.evaluate() + right.evaluate();
        case '*" return left.evaluate() * right.evaluate();
        case ...  // edit this for each new operator
       }
    }
 }

 Big problem on this:
  graphic representation,
  Node
  op:char
  value: double
  left: Node
  right:Node
 --------------
   evaluate():double

Julia could not figure out the analysis here <- first time to memorize this analysis, and also try to learn reasoning

    Analyzing attributes

                           #      +        *
function                      yes     yes
value                  yes
left                              yes     yes
right                            yes     yes

Two different behaviors are fighting here, (see the above table), not matching Single Responsibility Principle.
if you are the operation node, then you need your left and right child; whereas value node, you just need value.

C# code using conditional implementation, one class Node

Naive approach, breaking single responsibility principle, the C# code is here.

Clean code talk - conditional version written down based on the talk, C# code is here.


2.
Let us break it up:
     Node
   --------------
   evaluate(): double
         |                                  |
ValueNode                         OpNode
  value: double                   op: char
---------------                       left: Node
 evaluate: double                right: Node
                                          ----------------
                                          evaluate(): double
As showing above, break Node into ValueNode and OpNode, so ValueNode does not have left and right child because of no meaning over there.

Tree looks like:

          OpNode
              +
    /                            \
ValueNode         OpNode
       1                         *
                        /                    \
                 ValueNode      ValueNode
                        2                     3
Operations and values

abstract class Node{
   abstract double evaluate();
}

class ValueNode extends Node{
    double value;
    double evaluate(){
           return value;
    }
}

class OpNode extends Node{
   char operator;
   Node left;
   Node right;
   double evaluate(){
         switch(operator) {
             case '+': return left.evaluate() + right.evaluate();
             case '-':  return left.evaluate() + right.evaluate();
             case ...   // edit this for each new operator
         }
    }
}

better solution: Node, OpNode, ValueNode, the C# code is here.


3. How to extend this? Every time you add a new operator, need to hold on source code, and add code in switch statement, how to make it better?

OpNode divides into AdditionNode  and MultiplicationNode

           OpNode
         ------------------
         left: Node
         right: Node
        ------------------
         evaluate(): double

 AdditionNode                                 MultiplicationNode
----------------------------                    --------------------------
   evaluate(): double                           evaluate(): double


  abstract class Node{
           abstract double evaluate();
  }

  class ValueNode extends Node{
       double value;
       double  evaluate(){
          return value;
       }
 }

  class abstract OpNode extends Node{
  Node left;
  Node right;
  abstract evaluate();
  }

 Operation classes
  class AdditionNode extends OpNode{
      double evaluate(){
         return left.evaluate() + right.evaluate();
      }
}

  class MultiplicationNode extends OpNode{
     double evaluate(){
          return left.evaluate() + right.evaluate();
     }
 }

 Now, the new tree diagram:

      AdditionalNode
              +
    /                            \
ValueNode         MultiplicationNode
       1                         *
                        /                    \
                 ValueNode      ValueNode
                        2                     3

optimal solution: Node, OpNode, ValueNode, AdditionNode, MultiplicationNode, here is C# code.


Perfect examples of 1 + 2*3, 3 implementations, Julia learns O of S.O.L.I.D. OO principles, open for extension, close for change.

More detail, the above optimal solution does not have any if statement, and any new arithmetic operation just needs a new class, no need to touch existing code: Node, OpNode, AdditionNode, MultiplicationNode. For example, minus '-' operation, just add MinusNode. For easy to demo, all classes are in one .cs file, but each class should have it's own .cs file. :-)

Share the learning of Open/Close principle - great examples. Cannot wait to move on to next one, Liskov substitution principle!

Reference:
Blog:
http://juliachencoding.blogspot.ca/2015/11/the-clean-code-talks.html

Follow up


Sept. 9, 2018

The project is documented in my github folder, here is the link.



No comments:

Post a Comment