Another Refactoring Exercise: Design Patterns Recommended!-) 50

Posted by Brett Schuchert Wed, 10 Jun 2009 01:57:00 GMT

Well the previous exercise was fun so here’s another one. The following code is taken from the same problem, an RPN calculator. Originally, the interface of the calculator was “wide”; there was a method for each operator. E.g., plus(), minus(), factorial(). In an effort to fix this, a new method, perform(String operatorName) was added and ultimately the interface was fixed gradually to remove those methods.

Changing he calculator API in this way is an example of applying the open/closed principle. However, the resulting code is just a touch ugly (I made it a little extra ugly just for the hack [sic] of it). This code as written does pass all of my unit tests.

Before the code, however, let me give you a little additional information:
  • I changed the calculator to use BigDecimal instead of int
  • Right now the calculator has three operators, +, – !
  • Eventually, there will be many operators (50 ish)
  • Right now there are only binary and unary operators, however there will be other kinds: ternary, quaternary, and others such as sum the stack and replace just the sum on the stack or calculate the prime factors of the top of the stack so take one value but push many values

So have a look at the following code and then either suggest changes or provide something better. There’s a lot that can be done to this code to make it clearer and make the system easier to extend.

The Perform Method

   public void perform(String operatorName) {
      BigDecimal op1 = stack.pop();

      if ("+".equals(operatorName)) {
         BigDecimal op2 = stack.pop();
         stack.push(op1.add(op2));
         currentMode = Mode.inserting;
      } else if ("-".equals(operatorName)) {
         BigDecimal op2 = stack.pop();
         stack.push(op2.subtract(op1));
         currentMode = Mode.inserting;
      } else if ("!".equals(operatorName)) {
         op1 = op1.round(MathContext.UNLIMITED);
         BigDecimal result = BigDecimal.ONE;
         while (op1.compareTo(BigDecimal.ONE) > 0) {
            result = result.multiply(op1);
            op1 = op1.subtract(BigDecimal.ONE);
         }
         stack.push(result);
      } else {
         throw new MathOperatorNotFoundException();
      }
   }

Unlike the last example, I’ll provide the entire class. Feel free to make changes to this class as well. However, for now focus on the perform(...) method.

One note, Philip Schwarz recommended a change to what I proposed to avoid the command/query separation violation. I applied his recommendation before posting this updated version.

The Whole Class

package com.scrippsnetworks.calculator;

import java.math.BigDecimal;
import java.math.MathContext;

public class RpnCalculator {
   private OperandStack stack = new OperandStack();
   private Mode currentMode = Mode.accumulating;

   enum Mode {
      accumulating, replacing, inserting
   };

   public RpnCalculator() {
   }

   public void take(BigDecimal value) {
      if (currentMode == Mode.accumulating)
         value = determineNewTop(stack.pop(), value);

      if (currentMode == Mode.replacing)
         stack.pop();

      stack.push(value);
      currentMode = Mode.accumulating;
   }

   private BigDecimal determineNewTop(BigDecimal currentTop, BigDecimal value) {
      BigDecimal newTopValue = currentTop;
      String digits = value.toString();
      while (digits.length() > 0) {
         newTopValue = newTopValue.multiply(BigDecimal.TEN);
         newTopValue = newTopValue.add(new BigDecimal(Integer.parseInt(digits
               .substring(0, 1))));
         digits = digits.substring(1);
      }

      return newTopValue;
   }

   public void enter() {
      stack.dup();
      currentMode = Mode.replacing;
   }

   public void perform(String operatorName) {
      BigDecimal op1 = stack.pop();

      if ("+".equals(operatorName)) {
         BigDecimal op2 = stack.pop();
         stack.push(op1.add(op2));
         currentMode = Mode.inserting;
      } else if ("-".equals(operatorName)) {
         BigDecimal op2 = stack.pop();
         stack.push(op2.subtract(op1));
         currentMode = Mode.inserting;
      } else if ("!".equals(operatorName)) {
         op1 = op1.round(MathContext.UNLIMITED);

         BigDecimal result = BigDecimal.ONE;
         while (op1.compareTo(BigDecimal.ONE) > 0) {
            result = result.multiply(op1);
            op1 = op1.subtract(BigDecimal.ONE);
         }
         stack.push(result);
      } else {
         throw new MathOperatorNotFoundException();
      }
   }

   public BigDecimal getX() {
      return stack.x();
   }

   public BigDecimal getY() {
      return stack.y();
   }

   public BigDecimal getZ() {
      return stack.z();
   }

   public BigDecimal getT() {
      return stack.t();
   }
}
Comments

Leave a response

  1. Avatar
    Naresh Jain about 11 hours later:
    Updated the perform method to
    
        @Deprecated
        public void perform(final String operatorName) {
            if (!operator.containsKey(operatorName)) {
                throw new MathOperatorNotFoundException();
            }
            perform(operators.get(operatorName));
        }
    
        public void perform(final Operator operator) {
            operator.eval(stack);
            currentMode = Mode.inserting;
        }
    

    Had to temporarily add the following map (this should go away once our deprecated method is knocked off. That would really kill the primitive obsession smell)

    
        private static Map<String, Operator> operators = new HashMap<String, Operator>() {
            {
                put("+", Operator.ADD);
                put("-", Operator.SUBTRACT);
                put("!", Operator.FACTORIAL);
            }
        };
    

    Defined various Operators

    
       private static abstract class Operator {
            private static final Operator ADD = new BinaryOperator() {
                @Override
                protected int eval(final int op1, final int op2) {
                    return op1 + op2;
                }
            };
    
            private static final Operator SUBTRACT = new BinaryOperator() {
                @Override
                protected int eval(final int op1, final int op2) {
                    return op2 - op1;
                }
            };
    
            private static final Operator FACTORIAL = new UnarayOperator() {
                @Override
                protected int eval(final int op1) {
                    int result = 1;
                    int currentOperandValue = op1;
                    while (currentOperandValue > 1) {
                        result *= currentOperandValue;
                        --currentOperandValue;
                    }
                    return result;
                }
            };
    
            public abstract void eval(final OperandStack stack);
        }
    

    Since we have 2 types of operator, I’ve created

    
        public static abstract class UnarayOperator extends Operator {
    
            @Override
            public void eval(final OperandStack s) {
                s.push(eval(s.pop()));
            }
    
            protected abstract int eval(int op1);
    
        }
    

    and

    
        public static abstract class BinaryOperator extends Operator {
    
            @Override
            public void eval(final OperandStack s) {
                s.push(eval(s.pop(), s.pop()));
            }
    
            protected abstract int eval(int op1, int op2);
    
        }
    
  2. Avatar
    Sebastian Kübeck about 16 hours later:

    If you expect so many operators, why not use reflection to implement OCP in full?

    First, the operator interface and implementations:
    public interface Operator {
        public String getName();
        RpnCalculator.Mode perform(OperandStack stack,
                RpnCalculator.Mode mode);
    }
    
    public class Add implements Operator {
        @Override
        public String getName() {
            return "+";
        }
    
        @Override
        public RpnCalculator.Mode perform(OperandStack stack, 
                RpnCalculator.Mode mode) {
            BigDecimal op1 = stack.pop();
            BigDecimal op2 = stack.pop();
            stack.push(op1.add(op2));
            return RpnCalculator.Mode.inserting;
        }
    }
    
    public class Subtract implements Operator {
        @Override
        public String getName() {
            return "-";
        }
    
        @Override
        public RpnCalculator.Mode perform(OperandStack stack, 
                RpnCalculator.Mode mode) {
            BigDecimal op1 = stack.pop();
            BigDecimal op2 = stack.pop();
            stack.push(op2.subtract(op1));
            return RpnCalculator.Mode.inserting;
        }
    }
    
    public class Factorial implements Operator {
        @Override
        public String getName() {
            return "!";
        }
    
        @Override
        public RpnCalculator.Mode perform(OperandStack stack, 
                RpnCalculator.Mode mode) {
            BigDecimal op1 = stack.pop();
            op1 = op1.round(MathContext.UNLIMITED);
    
            BigDecimal result = BigDecimal.ONE;
            while (op1.compareTo(BigDecimal.ONE) > 0) {
                result = result.multiply(op1);
                op1 = op1.subtract(BigDecimal.ONE);
            }
            stack.push(result);
            return mode;
        }
    }
    
    Next, I use reflection to look up all implementations of the Operator interface, create an instance and pack them into a HashMap. The perform method finds and executes the operator. Now every time I write a new operator, it is automatically used by the calculator.
    import java.io.*;
    import java.lang.reflect.Modifier;
    import java.net.URL;
    import java.util.*;
    
    import org.skuebeck.refactoring.RpnCalculator.Mode;
    
    public class Operators {
        private static Operators instance = new Operators();
        private final Map<String, Operator> operators = 
            new LinkedHashMap<String, Operator>();
    
        public Operators() {
            try {
                String pkg = Operator.class.getPackage()
                        .getName().replace('.', '/');
                Enumeration<URL> urls = Operator.class
                        .getClassLoader().getResources(pkg);
                addOperators(urls);
            } catch (IOException e) {
                throw new IOError(e);
            }
        }
    
        private void addOperators(Enumeration<URL> urls) {
            while (urls.hasMoreElements()) {
                File[] classFiles = getClassFiles(
                    urls.nextElement());
                for (File file : classFiles) {
                    String className = 
                        className(file.getName());
                    try {
                        Class<?> operatorClass = 
                            Class.forName(className);
                        if (isOperatorClass(operatorClass)) {
                            addOperator(operatorClass);
                        }
                    } catch (Exception e) {
                    }
                }
            }
        }
    
        private File[] getClassFiles(URL url) {
            File directory = new File(url.getPath());
            File classFiles[] = directory
                    .listFiles(new FilenameFilter() {
                        @Override
                        public boolean accept(File dir,
                                String name) {
                            return !name.contains("$")
                                    && name.endsWith(".class");
                        }
                    });
            return classFiles;
        }
    
        private String className(String className) {
            String pkg = Operator.class.getPackage().getName();
            return  (pkg != null ? pkg + "." : "") +
                className.substring(0, className.length() - 6);
        }
    
        private boolean isOperatorClass(Class<?> operatorClass) {
            return Operator.class.isAssignableFrom(operatorClass) &&
                    Modifier.isPublic(
                        operatorClass.getModifiers()) &&
                    !Modifier.isAbstract(
                        operatorClass.getModifiers());
        }
    
        private void addOperator(Class<?> operatorClass)
                throws InstantiationException,
                IllegalAccessException {
            Operator operator = 
                (Operator) operatorClass.newInstance();
            operators.put(operator.getName(), operator);
        }
    
        public Operator findOne(String operatorName)
                throws MathOperatorNotFoundException {
            if (!operators.containsKey(operatorName)) {
                throw new MathOperatorNotFoundException();
            }
            return operators.get(operatorName);
        }
    
        public static Mode perform(String operatorName,
                OperandStack stack, Mode mode)
                throws MathOperatorNotFoundException {
            return instance.findOne(operatorName).perform(
                stack, mode);
        }
    }
    
    Finally, the modified main class:
    public class RpnCalculator {
        ...
        public void perform(String operatorName) {
            currentMode = Operators.perform(operatorName, 
                stack, 
                currentMode);
        }
        ...
    }
    
  3. Avatar
    Brett L. Schuchert 1 day later:
    As already pointed out, there seems to be a strategy in there. Here’s a first pass to expose that:
       public void perform(String operatorName) {
          if ("+".equals(operatorName)) {
             new Plus().execute(stack);
          } else if ("-".equals(operatorName)) {
             new Minus().execute(stack);
          } else if ("!".equals(operatorName)) {
             new Factorial().execute(stack);
          } else {
             throw new MathOperatorNotFoundException();
          }
          currentMode = Mode.inserting;
       }
    But what next? How about:
    • Split loop. While this is not a loop, the idea of separating selecting what to do from doing the work will make it clear what’s next.
    • Extract selection into a separate method (factory method)
    • Extract factory method into factory class
    • Allow Dependency injection of Factory

    Oh, and while you’re at it, if you look at what you’d come up for Minus and Plus, you might notice some duplication. That duplication will exist across all binary operators.

    So what next? Remove that DRY violation and while you’re at it, develop unit tests to confirm the fix before applying it to Plus and Minus.

  4. Avatar
    Thomas Vidic 1 day later:

    Here is my atempt: First, introduce hirarchy of operator classes, including abstract BinaryOperator to remove DRY violation from Plus / Minus. Did not created UnaryOperator, there is only one yet so YAGNI…

    public interface Operator {
        public void perform(OperandStack stack);
    }
    public abstract class BinaryOperator implements
        Operator {
        public void perform(OperandStack stack) {
            final BigDecimal op1 = stack.pop();
            final BigDecimal op2 = stack.pop();
            stack.push(computeResult(op1, op2));
        }
        protected abstract BigDecimal computeResult
           (BigDecimal op1, BigDecimal op2);
    }

    Concrete operators:

    public class Plus extends BinaryOperator {
        protected BigDecimal computeResult(BigDecimal op1, 
            BigDecimal op2) {
            return op1.add(op2);
        }
    }
    public class Minus extends BinaryOperator {
        protected BigDecimal computeResult(BigDecimal op1, 
            BigDecimal op2) {
            return op1.substract(op2);
        }
    }
    public class Factorial implements Operator {
        public void perform(OperandStack stack) {
            BigDecimal op1 = stack.pop();
            op1 = op1.round(MathContext.UNLIMITED);
            BigDecimal result = BigDecimal.ONE;
            while (op1.compareTo(BigDecimal.ONE) > 0) {
                result = result.multiply(op1);
                op1 = op1.subtract(BigDecimal.ONE);
            }
            stack.push(result);
        }
    }

    Factory for operators (Note the TODO, i was to lazy to create this factory implementation now, and i think everybody can imagine how it would look like):

    public interface OperatorFactory {
        public Operator getOperator(String operatorName);
        // TODO: move this into it's own class, use Map to 
        // store operator implementations
        public static final OperatorFactory default = new 
            OperatorFactory() {
                public Operator getOperator(String 
                    operatorName) {
                    if ("+".equals(operatorName) {
                        return new Plus();
                    } else if ("-".equals(operatorName) {
                        return new Minus();
                    } else if( "!".equals(operatorName) {
                        return new Factorial();
                    }
                    throw new MathOperatorNotFoundException();
                }
            }
        }

    Finally, changes to the RpnCalculator (omitted unchanged stuff). I use constructor-injection for the factory because i think, it would not make much sense to change the factory during a calculation (which would be possible with setter-injection).

    public class RpnCalculator {
        private OperatorFactory operatorFactory =    
            OperatorFactory.default;
        ...
        public RpnCalculator() {
        }
        public RpnCalculator(OperatorFactory factory) {
            this.operatorFactory = factory;
        }
        ...
        public void perform(String operatorName) {
            Operator operator = 
                 operatorFactory.getOperator(operatorName);     
            operator.perform(stack);
            currentMode = Mode.inserting;
        }
        ...
    }

    One final thought: I really don’t like this currentMode = Mode.something stuff. This looks like some kind of state pattern; there should be only one place responsible for “mode-management”, but currently i have no idea how to achive this.

  5. Avatar
    Thomas Vidic 1 day later:

    Here is my atempt: First, introduce hirarchy of operator classes, including abstract BinaryOperator to remove DRY violation from Plus / Minus. Did not created UnaryOperator, there is only one yet so YAGNI…

    public interface Operator {
        public void perform(OperandStack stack);
    }
    public abstract class BinaryOperator implements
        Operator {
        public void perform(OperandStack stack) {
            final BigDecimal op1 = stack.pop();
            final BigDecimal op2 = stack.pop();
            stack.push(computeResult(op1, op2));
        }
        protected abstract BigDecimal computeResult
           (BigDecimal op1, BigDecimal op2);
    }

    Concrete operators:

    public class Plus extends BinaryOperator {
        protected BigDecimal computeResult(BigDecimal op1, 
            BigDecimal op2) {
            return op1.add(op2);
        }
    }
    public class Minus extends BinaryOperator {
        protected BigDecimal computeResult(BigDecimal op1, 
            BigDecimal op2) {
            return op1.substract(op2);
        }
    }
    public class Factorial implements Operator {
        public void perform(OperandStack stack) {
            BigDecimal op1 = stack.pop();
            op1 = op1.round(MathContext.UNLIMITED);
            BigDecimal result = BigDecimal.ONE;
            while (op1.compareTo(BigDecimal.ONE) > 0) {
                result = result.multiply(op1);
                op1 = op1.subtract(BigDecimal.ONE);
            }
            stack.push(result);
        }
    }

    Factory for operators (Note the TODO, i was to lazy to create this factory implementation now, and i think everybody can imagine how it would look like):

    public interface OperatorFactory {
        public Operator getOperator(String operatorName);
        // TODO: move this into it's own class, use Map to 
        // store operator implementations
        public static final OperatorFactory default = new 
            OperatorFactory() {
                public Operator getOperator(String 
                    operatorName) {
                    if ("+".equals(operatorName) {
                        return new Plus();
                    } else if ("-".equals(operatorName) {
                        return new Minus();
                    } else if( "!".equals(operatorName) {
                        return new Factorial();
                    }
                    throw new MathOperatorNotFoundException();
                }
            }
        }

    Finally, changes to the RpnCalculator (omitted unchanged stuff). I use constructor-injection for the factory because i think, it would not make much sense to change the factory during a calculation (which would be possible with setter-injection).

    public class RpnCalculator {
        private OperatorFactory operatorFactory =    
            OperatorFactory.default;
        ...
        public RpnCalculator() {
        }
        public RpnCalculator(OperatorFactory factory) {
            this.operatorFactory = factory;
        }
        ...
        public void perform(String operatorName) {
            Operator operator = 
                 operatorFactory.getOperator(operatorName);     
            operator.perform(stack);
            currentMode = Mode.inserting;
        }
        ...
    }

    One final thought: I really don’t like this currentMode = Mode.something stuff. This looks like some kind of state pattern; there should be only one place responsible for “mode-management”, but currently i have no idea how to achive this.

  6. Avatar
    Markus Gärtner 1 day later:
    Basically I pretty much ended up with similar code as Sebastian Kübeck. Influenced by Replace Type Code with Class I refactored to the following Strategy while changing the perform method to a different signature using an Operator instead of the String. I deprecated the former method while doing so to indicate that it’s become obsolete:
    
        @Deprecated
        public void perform(String operator) {
            if (Operator.ADD.getName().equals(operator)) {
                perform(Operator.ADD);
            } else if (Operator.SUB.getName().equals(operator)) {
                perform(Operator.SUB);
            } else if (Operator.FACULTY.getName().equals(operator)) {
                perform(Operator.FACULTY);
            } else {
                throw new MathOperatorNotFoundException();
            }
        }
    
        public void perform(Operator operator) {
            currentMode = operator.perform(stack, currentMode);
        }
    
    

    The new Operator class has the three different operations currently supported as a public static final field with some inner subclasses for the moment. For the 50 operations I would like to make these top level classes in the future.

    
    package com.scrippsnetworks.calculator;
    
    import java.math.BigDecimal;
    import java.math.MathContext;
    
    import com.scrippsnetworks.calculator.RpnCalculator.Mode;
    
    import static com.scrippsnetworks.calculator.RpnCalculator.Mode;
    
    public abstract class Operator {
    
        private String name;
    
        public static final Operator ADD = new AddOperator();
        public static final Operator SUB = new SubOperator();
        public static final Operator FACULTY = new FacultyOperator();
    
        private Operator(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public abstract Mode perform(OperandStack stack, Mode previousMode);
    
        static class AddOperator extends Operator {
    
            public AddOperator() {
                super("+");
            }
    
            @Override
            public Mode perform(OperandStack stack, Mode previousMode) {
                BigDecimal op1 = stack.pop();
                BigDecimal op2 = stack.pop();
                stack.push(op1.add(op2));
                return Mode.inserting;
            }
    
        }
    
        static class SubOperator extends Operator {
    
            public SubOperator() {
                super("-");
            }
    
            @Override
            public Mode perform(OperandStack stack, Mode previousMode) {
                BigDecimal op1 = stack.pop();
                BigDecimal op2 = stack.pop();
                stack.push(op2.subtract(op1));
                return Mode.inserting;
            }
    
        }
    
        static class FacultyOperator extends Operator {
    
            public FacultyOperator() {
                super("!");
            }
    
            @Override
            public Mode perform(OperandStack stack, Mode previousMode) {
                BigDecimal op1 = stack.pop();
                op1 = op1.round(MathContext.UNLIMITED);
    
                BigDecimal result = BigDecimal.ONE;
                while (op1.compareTo(BigDecimal.ONE) > 0) {
                    result = result.multiply(op1);
                    op1 = op1.subtract(BigDecimal.ONE);
                }
                stack.push(result);
                return previousMode;
            }
        }
    }
    

    Operator seems to be a Strategy pattern. I choose not to pass in the whole RpnCalculator to the perform method (Command pattern) here, since I would have needed to expost internal representation of the RpnCalculator to the Operator classes by then.

    Passing the current RpnCalculator Mode makes the usage a bit awkward from my point of view. I think the design needs a separate State object here for the operation mode. By then I would make the RpnCalculator perform the operation and have a state field where I would pass in the operation in order to make the state transition to the next state.

  7. Avatar
    Markus Gärtner 1 day later:

    There is one thing I missed. This morning I was thinking whether or not I should try to solve the problem in Haskell or Elan. Unfortunately I decided that my expertise with both languages have gotten a bit too rusty to do so.

  8. Avatar
    Christian Vest Hansen 1 day later:

    Haskell version!

    This time I pretty much decided to skip reading the original code entirely, and just start from scratch based on the listed requirements.

    The typing was a bit hard to get right on this one because of the many number of numeric types in Haskell, and the anal-retentive type system.

    This one can be directly executed with the “runhaskell” program.

    
    module Rpn
      where
    
    import qualified Data.Map as Map
    
    data Operator a = Unary (a -> a)
                    | Binary (a -> a -> a)
                    | Ternary (a -> a -> a -> a)
                    | Quaternary (a -> a -> a -> a -> a)
                    | Variadic ([a] -> a)
    
    calculate :: (Num a) => Operator a -> [a] -> [a]
    calculate (Unary op) (a:ns) = (op a):ns
    calculate (Binary op) (a:b:ns) = (a `op` b):ns
    calculate (Ternary op) (a:b:c:ns) = (op a b c):ns
    calculate (Quaternary op) (a:b:c:d:ns) = (op a b c d):ns
    calculate (Variadic op) ns = [op ns]
    
    operators = Map.fromList [
        ("neg", Unary negate),
        ("abs", Unary abs),
        ("sig", Unary signum),
        ("recip", Unary recip),
        ("exp", Unary exp),
        ("sqrt", Unary sqrt),
        ("log", Unary log),
        ("log10", Unary (logBase 10)),
        ("+", Binary (+)),
        ("-", Binary (-)),
        ("*", Binary (*)),
        ("/", Binary (/)),
        ("max", Binary max),
        ("min", Binary min),
        ("^", Binary (\a b -> a ^^ (floor b))),
        ("sum", Variadic sum),
        ("product", Variadic product)
      ]
    
    perform stack input = apply operator
      where apply Nothing = (read input):stack
            apply (Just op) = calculate op stack
            operator = Map.lookup input operators
    
    performRpn input =
      show $ head $ foldl (perform) [] $ words input
    
    main = interact (unlines . (map performRpn) . lines)
    
    
  9. Avatar
    Anthony Williams 3 days later:

    Hi Brett,

    My first thought is to extract the code for selecting the operation into a factory. This actually follows on from your suggestion in the comments, thought I didn’t look at the comments until just now. Anyway, perform should look like:

    
    public void perform(String operatorName) {
            getOperation(operatorName).perform(stack);
            currentMode = Mode.inserting;
        }
    
    

    This leaves us with how to get the operation. The simplest case is an if/else chain, as we have to start with, but that’s not ideal if we’re going to extend it. I would probably go with a HashMap, as Naresh did. I would also likely go with abstract UnaryOperator and BinaryOperator implementations of the CalculatorOperator interface as base classes for the various operators.

    In your comment you also suggest extracting a factory class and using DI. I think this is overkill in this case, and not necessary for implementation or testing (it should be trivial to mock the getOperation factory method). However, it depends on how the class is to be used - if the set of operations need to be configurable it might make sense to use DI with a factory class for the operations.

  10. Avatar
    Brett L. Schuchert 3 days later:

    Anthony Williams wrote:

    In your comment you also suggest extracting a factory class and using DI. I think this is overkill in this case, ...

    I generally agree, though realize I’m talking about manual DI, not using a container. However, consider one of the comments about building the contents of the factory by looking for implementors of the top-level interface.

    I’ve done this in C# and it certainly can be done in Java.

    I use this problem in a course and I do that for two reasons:
    • Test Isolation – I actually create a saboteur test-double to verify proper exception propagation.
    • Open/Closed Principle – I can add operators, that are auto registered, and then the RPN Calculator picks up new abilities without any change.

    Even so, your comment is still on the mark. I think it’s good to do so for demonstration purposes, but it’s probably overkill for this simple problem.

  11. Avatar
    Rafael Naufal 5 days later:

    I like the approach of building the contents of the factory from a properties file.

    Java version:

    package com.scrippsnetworks.calculator;
    
    public interface OperatorFactory {
    
        Operator getOperator(String operatorName);
    }
    
    package com.scrippsnetworks.calculator;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    public class OperatorFactoryImpl implements OperatorFactory {
    
        private Map<String, Operator> operators = new HashMap<String, Operator>();
        private static Properties operatorClasses = new Properties();
        static {
            InputStream is = OperatorFactoryImpl.class.getClassLoader()
                    .getResourceAsStream("operators.properties");
            try {
                operatorClasses.load(is);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public Operator getOperator(String operatorName) {
            if (operators.containsKey(operatorName)) {
                return operators.get(operatorName);
            }
            String operatorClassName = operatorClasses.getProperty(operatorName);
            try {
                Operator operator = (Operator) Class.forName(operatorClassName)
                        .newInstance();
                operators.put(operatorName, operator);
                return operator;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    The factory also contains an internal cache to store operators already loaded from the properties file.

  12. Avatar
    Thomas Eyde 6 days later:

    Changed the internals of the calculator. Added a nested class to access the internals:

    
        public class RpnCalculator
        {
            private readonly Dictionary<string, Operation> operations = new Dictionary<string, Operation>();
    
            public RpnCalculator()
            {
                RegisterOperation(new Plus());
                RegisterOperation(new Subtract());
                RegisterOperation(new Factorial());
            }
    
            private void RegisterOperation(Operation operation)
            {
                operations.Add(operation.Name, operation);
            }
    
            public void Perform(String operatorName)
            {
                Operation operation;
                if (operations.TryGetValue(operatorName, out operation))
                {
                    operation.Perform(this);
                }
                else
                {
                    throw new MathOperatorNotFoundException();
                }
            }
    
            public abstract class Operation
            {
                private RpnCalculator current;
    
                public string Name
                {
                    get { return GetName(); }
                }
    
                protected abstract string GetName();
    
                public void Perform(RpnCalculator calculator)
                {
                    current = calculator;
                    PerformOn(current.stack);
                    UpdateMode();
                }
    
                protected abstract void PerformOn(OperandStack stack);
    
                protected virtual void UpdateMode()
                {
                    //no op.
                }
    
                protected void UseInsertMode()
                {
                    current.currentMode = Mode.Inserting;
                }
            }
        }
    
    
    Then a class for each operation.
    
        public abstract class InsertOperation : RpnCalculator.Operation
        {
            protected override void UpdateMode()
            {
                UseInsertMode();
            }
        }
    
        public class Factorial : RpnCalculator.Operation
        {
            protected override string GetName()
            {
                return "!";
            }
    
            protected override void PerformOn(OperandStack stack)
            {
                var op1 = stack.Pop();
                op1 = op1.Round();
    
                var result = BigDecimal.One;
                while (op1.CompareTo(BigDecimal.One) > 0)
                {
                    result = result.Multiply(op1);
                    op1 = op1.Subtract(BigDecimal.One);
                }
                stack.Push(result);
            }
        }
    
        public class Subtract : InsertOperation
        {
            protected override string GetName()
            {
                return "-";
            }
    
            protected override void PerformOn(OperandStack stack)
            {
                var op1 = stack.Pop();
                var op2 = stack.Pop();
                stack.Push(op2.Subtract(op1));
            }
        }
    
        public class Plus : InsertOperation
        {
            protected override string GetName()
            {
                return "+";
            }
    
            protected override void PerformOn(OperandStack stack)
            {
                var op1 = stack.Pop();
                var op2 = stack.Pop();
                stack.Push(op1.Add(op2));
            }
        }
    
  13. Avatar
    Philip Schwarz 8 days later:
    public enum Operator
    {    
        ADDITION("+") 
        {                
            public Mode execute(OperandStack stack, Mode mode)
            {
                stack.push(stack.pop().add(stack.pop())) ;
                return Mode.accumulating;
            }    
        },
    
        SUBTRACTION("-") 
        {
            public Mode execute(OperandStack stack, Mode mode)
            {
                stack.push(stack.pop().subtract(stack.pop())) ;
                return Mode.accumulating;
            }    
        },
    
        FACTORIAL("!") 
        {
            public Mode execute(OperandStack stack, Mode mode)
            {
                BigDecimal op1 = stack.pop();    
                op1 = op1.round(MathContext.UNLIMITED);
                BigDecimal result = BigDecimal.ONE;
                while (op1.compareTo(BigDecimal.ONE) > 0)
                {
                    result = result.multiply(op1);
                    op1 = op1.subtract(BigDecimal.ONE);
                }
                stack.push(result);
                return mode;
            }
        };
    
        public abstract Mode execute(OperandStack stack, Mode mode);    
    
        @Override public String toString() { return name; } 
    
        public static Operator withName(String name) 
        {
            Operator op = OPERATORS.get(name);
            if (op == null) throw new MathOperatorNotFoundException();
            return op;
        }
    
        private static final Map<String,Operator> OPERATORS  = 
            new HashMap<String, Operator>();
    
        static
        {
            for (Operator op : values()  )
            {
                OPERATORS.put(op.toString(), op);
            }            
        }
    
        private String name;
        private Operator(String name) { this.name = name; }
    }
    

    Meanwhile, back at the ranch…

    
    public class RpnCalculator
    {
        ...
        public void perform(String operatorName)
        {
            mode = Operator.withName(operatorName).execute(stack, mode);
        }
        ...
    }    
    
  14. Avatar
    Brett L. Schuchert 8 days later:
    Use of enum

    Why/why not use an enum?

    It certainly is convenient. What is it buying you here? You are not switching on it. There’s some short-hand notation for construction, as the system grows, I’d probably go with a property file or an IoC container. Why? You’re going to end up with a lot of code in one place. None of those individual enums are technically closed.

    Also, an enum can lead to a compile-time dependency. If we are trying to allow for extension without change (open/closed principle), enums cause a problem. You did not do that, so that’s not a problem as you’ve written it. Still, that’s a lot of code in one place.

  15. Avatar
    Philip Schwarz 9 days later:

    Brett,

    You said:
    as the system grows, I’d probably go with a property file or an IoC container.

    I have a simple question, but first some context (bear with me for stating the obvious in several places)...

    Often, when people say that a program is open to extension and closed to modification, they don’t mean total closure, they mean that change is confined to a factory. i.e. they don’t mean that extending the behaviour of the program can be achieved without changing any code whatsoever, purely by adding a new code module; what they mean is that some change is required, but it is negligible, since it is limited to simply changing a factory by hard-coding in it the identity of the new module, so that the factory can use the module.

    Often, when people say that closure is total, they mean that not even that small change to the factory is required, because the factory uses metaprogramming to obtain the identities of the modules that it must use (e.g. in Java, Class.forName(operatorName)), and to instantiate the modules (e.g. in Java, Class.newInstance() ).

    When you say that you’d go with a property file, I understand that: you mean you’d achieve total closure by externalising module identities in a property file, and then use metaprogramming to get the operator class names and instantiate them, like Rafael Naufal did.

    But when you say that you’d go for an IoC container, I am not sure that I fully understand what you mean…Do you by any chance mean that you’d inject the factory, and that doing so allows you to achieve total closure because when you want to modify or extend the factory, instead of changing the factory (which would prevent you from claiming total closure), you introduce a new factory (i.e. purely add a new module), and inject that one instead?

    Can you elaborate?

    Thanks.

  16. Avatar
    xex 27 days later:

    This method looks very useful for many applications

  17. Avatar
    chanel store about 1 year later:

    Very pleased that the article is what I want! Thank you

  18. Avatar
    http://www.whiteiphone4transformer.com about 1 year later:

    People love fashion will always love white iphone 4. Now you can get the advance chance to take the hottest iphone 4 conversion kit. Here we go!

  19. Avatar
    Silicone Molding about 1 year later:

    Mold making is the core business of Intertech (Taiwan). With world level technology, Intertech enjoys a very good reputation for making Injection Mold and Plastic Moldsfor their worldwide customers.

  20. Avatar
    skirunride about 1 year later:

    Very serious calculation, such research is important to keep to continue to develop

  21. Avatar
    iPhone SMS to Mac Backup about 1 year later:

    Would you like to banckup iphone SMS to mac, macBook, macbookPro as .txt files? Now a software iphone SMS to Mac Backup can help you to realize it.

  22. Avatar
    centrifugal fan about 1 year later:

    Haile Electric Industrial Co. Ltd. is the leading manufacturer and exporter of cold room,axial fan,condensing unit,centrifugal fan,shaded pole motor and refrigeration products in China.

  23. Avatar
    clothing manufacturer about 1 year later:

    the article is what I want! Thank you

  24. Avatar
    Willie Trevizo about 1 year later:

    Your way of thinking is one of a kind. i find this to be excellent. Great work. roofing

  25. Avatar
    okey oyunu oyna about 1 year later:

    nice code.

    internette görüntülü olarak okey oyunu oyna, gerçek kisilerle tanis, turnuva heyecanini yasa.

  26. Avatar
    For Sale In Hayward about 1 year later:

    Very serious calculation, such research is important to keep it continue to develop.

  27. Avatar
    EDDM Retail about 1 year later:

    Hm great i like this and it is nice discussion here keep going on…

  28. Avatar
    divorce settlement agreement about 1 year later:

    Your way of thinking is one of a kind. i find this to be excellent. Great work.

  29. Avatar
    Santa Monica Homes For Sale about 1 year later:

    This method looks very useful for many applications

  30. Avatar
    mediation about 1 year later:

    Your way of thinking is one of a kind. i find this to be excellent. Great work.

  31. Avatar
    mbt shoes sales about 1 year later:

    YES SERIOUSLY!!!!! SO MUCH SPARKLY TEXT!!!

  32. Avatar
    blackhawk tactical gear about 1 year later:

    This method looks very useful for many applications

    blackhawk tactical gear

  33. Avatar
    bagsupplyer over 2 years later:

    Designer women Juicy backpacks free shipping for wholesale

  34. Avatar
    Diablo3 over 2 years later:

    hmm ,i’m not sure if this is what i’m looking for but anyway this is interresting and could be useful some day,thanks for taking time to write such cool stuff

  35. Avatar
    canada goose coat over 2 years later:

    When it comes to feather dress, what appears in your mind? Which kind brand of down jacket do you like prefer? Though there are many down jackets for you to choose from, on the word, which one you really enjoy? I want to say that canada goose coats is really your best choice. I believe you can’t agree with me any more. When you take the quality into consideration, you will find that it is superior to any other kind of coat. Besides, discount canada goose jackets is a world well-known brand, which has gained high reputation in the world, which has accepted by our customers and some organization. Because of its high quality, some of our loyal customers have promoted it to the people around them. In their opinion, it is good to informing others to know it. Recently, Canada Goose Trillium Parka is on hot sale. What I have to inform you is that all the products there are made by hand, so they are elaborative and elegant enough. It is really beautiful once you dress in. So, if you are a lovely girl or woman, go to the store to buy one for you. You will appreciate it that you have such a coat.In addition, they also have any other products like canada goose Gloves and canada goose jacket supplier.Hope your will like its!

  36. Avatar
    car colour service over 2 years later:

    some of days i searching in google some calculating method. it is great for me. thanks buddy

  37. Avatar
    Tips For Bowling over 2 years later:

    I was assigned to do a job by the attorney general, and that was to find out whether crimes were committed.

  38. Avatar
    website design over 2 years later:

    im codein this but i hav a problem …..but i wont fixed error????

  39. Avatar
    full colour leaflet over 2 years later:

    der is nwe mid nyt im cant concentration .you hav to done grt job

  40. Avatar
    christian louboutin over 2 years later:

    The professional design make you foot more comfortable. Even more tantalizing,this pattern make your legs look as long as you can,it will make you looked more attractive.Moveover,it has reasonable price.If you are a popular woman,do not miss it.

    Technical details of Christian Louboutin Velours Scrunch Suede Boots Coffee:

    Color: Coffee
    Material: Suede
    4(100mm) heel
    Signature red sole x

    Fashion, delicate, luxurious Christian louboutins shoes on sale, one of its series is Christian Louboutin Tall Boots, is urbanism collocation. This Christian louboutins shoes design makes people new and refreshing. Red soles shoes is personality, your charm will be wonderful performance.

  41. Avatar
    Polo Ralph Lauren Pas Cher over 3 years later:

    Hi, Brilliant, just I have learnt, what I needed to know. thank you for writing such an excellent article.Please keep it up.

  42. Avatar
    mbtshoe over 3 years later:

    Australia Beats By Dre Studio dr dre beats headphones beats studio beats pro beats solo hd pro headphones music Official store Monster Beats By Dre Pro

  43. Avatar
    rental genset over 3 years later:

    The problem back then was the need to separate personal and business guidelines on the same agenda, which is very much a persistent challenge.

  44. Avatar
    music production over 3 years later:

    It is one of the most impressive blog that I have ever read. It is really good to see such an amazing site online. I would say that there is no other site in the internet with so much of information.

  45. Avatar
    Coach Factory over 3 years later:

    Very good to read your most informative article. Its very informative and well written also.

  46. Avatar
    bladeless fans over 3 years later:

    Another Refactoring Exercise: Design Patterns Recommended!-) 45 good post63

  47. Avatar
    louboutin sales over 3 years later:

    Another Refactoring Exercise: Design Patterns Recommended!-) 46 hoo,good article!!I like the post!144

  48. Avatar
    <a href="http://www.lorenziboots.com/">Gianmarco Lorenzi Shoes</a> over 3 years later:

    Our life is modifying promptly with the ralph lauren sale of the society. The modification is mostly come seal the fashionable goods according to the modification in fashion Pas Cher Women’s Ralph Lauren sale Vuitton Sacs. Well, otherwise, it Men’s Ralph Lauren shirts differs in many aspects due to ones gravy style. The gravy style broadly articulating demonstrates the personality and individuality of people.

  49. Avatar
    is fuschia pink over 3 years later:

    It has been several years since the men’s messenger bag has taken over as one of the most popular ways for men to carry their belongings and it is easy to see why?

  50. Avatar
    Rubber Molds over 3 years later:

    With more than 20 years of experience, Intertech provides an extensive integrated operational ability from design to production of molds 100% made in Taiwan. Additional to our own mold making factory, we also cooperate with our team vendors to form a very strong working force in Taiwan.

    For the overseas market, we work very closely with local representatives in order to take care of the technical communication and after-sales service to our customers. We also participate in the EUROMOLD & FAKUMA exhibitions and meet our customers every year in Europe. By concentrating on mold “niche markets”, we play a very useful mold maker role from the Far East whenever customers want to develop their new projects. We provide services from A to Z to our customers on a very economic cost and effect basis.

Comments