Another Refactoring Exercise: Design Patterns Recommended!-) 50
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();
}
}
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)
Defined various Operators
Since we have 2 types of operator, I’ve created
and
If you expect so many operators, why not use reflection to implement OCP in full?
First, the operator interface and implementations: 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. Finally, the modified main class: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.
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…
Concrete operators:
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):
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).
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.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…
Concrete operators:
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):
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).
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.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.
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.
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.
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.
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:
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.
Anthony Williams wrote:
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: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.
I like the approach of building the contents of the factory from a properties file.
Java version:
The factory also contains an internal cache to store operators already loaded from the properties file.
Changed the internals of the calculator. Added a nested class to access the internals:
Then a class for each operation.Meanwhile, back at the ranch…
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.
Brett,
You said: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.
This method looks very useful for many applications
Very pleased that the article is what I want! Thank you
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!
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.
Very serious calculation, such research is important to keep to continue to develop
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.
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.
the article is what I want! Thank you
Your way of thinking is one of a kind. i find this to be excellent. Great work. roofing
nice code.
internette görüntülü olarak okey oyunu oyna, gerçek kisilerle tanis, turnuva heyecanini yasa.
Very serious calculation, such research is important to keep it continue to develop.
Hm great i like this and it is nice discussion here keep going on…
Your way of thinking is one of a kind. i find this to be excellent. Great work.
This method looks very useful for many applications
Your way of thinking is one of a kind. i find this to be excellent. Great work.
YES SERIOUSLY!!!!! SO MUCH SPARKLY TEXT!!!
This method looks very useful for many applications
blackhawk tactical gear
Designer women Juicy backpacks free shipping for wholesale
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
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!
some of days i searching in google some calculating method. it is great for me. thanks buddy
I was assigned to do a job by the attorney general, and that was to find out whether crimes were committed.
im codein this but i hav a problem …..but i wont fixed error????
der is nwe mid nyt im cant concentration .you hav to done grt job
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:
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.
Hi, Brilliant, just I have learnt, what I needed to know. thank you for writing such an excellent article.Please keep it up.
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
The problem back then was the need to separate personal and business guidelines on the same agenda, which is very much a persistent challenge.
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.
Very good to read your most informative article. Its very informative and well written also.
Another Refactoring Exercise: Design Patterns Recommended!-) 45 good post63
Another Refactoring Exercise: Design Patterns Recommended!-) 46 hoo,good article!!I like the post!144
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.
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?
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.