To follow the discussion, download the refactored code from here.
In the refactored SwingCalculatorWithoutIfs, the conditional logic originally encountered in the JFrame is now replaced by giving to each button the responsibility for listening to itself. In general this is a good practice for GUI design. The frame or panel holding the components shouldn’t bear the responsibility for determining what is happening such as which button has been selected and hence, what method to invoke. These types of responsibilities are the domain of the objects themselves. Thus the buttons now have the responsibility for knowing when they’ve been selected and also what to do after they’ve been selected.
The second location where conditional logic was employed was in the CalculatorModel class itself. When numbers are entered into the calculator, it must be known whether the computation is in the pre-operator or post-operator mode. In the CalculatorModel class, the numberHit method is invoked whenever a number button has been hit by the user. Conditional logic is used to determine which mode the calculator is in, the pre-operator or the post-operator mode. A purist may argue that the use of any conditional logic statement violates a rule of object-oriented programming - that a method should only do one thing. Any conditional logic structure does at least two things: (1) determine the state of the Boolean conditional; and (2) do what needs to be done pending the result. Another way to think about this is to consider that whenever an object needs to ask itself, “What state am I in?” one must wonder why the object didn’t determine what it was to do when the state variable was changed. Why is it necessary to go back in time? Why is the object ignorant of both the state it is currently in and even worse, what to do about it? In the CalculatorModel class, the solution was to use a Strategy Design Pattern.
In the words of Shalloway (2005) we “find what varies and encapsulate it.” Although this sentiment appeared in the classic “Gang-of-Four” book (Gamma, 1990), it was Shalloway who stated the commonly used adage noted above. What’s varying here? The behavior of the numberHit method, depending on the mode the calculator is in. So, we create an interface named CalculatorStrategy that abstracts the numberHit method and we create two classes, PreOperatorStrategy and PostOperatorStrategy that provide concrete implementations for the method’s behavior in pre-operator and post-operator modes, respectively. In addition to instantiating these two strategies as CalculatorStrategy types, I also create a reference to a CalculatorStrategy type named currentStrategy. This will be set to whatever strategy is appropriate as determined by the changes in state of the CalculatorModel class. To complete the control of the strategy implementation, we include statements in the rememberOperator method, which is invoked when an operator button is hit, and the reset method, which is invoked when the equals button is hit, to assign the appropriate strategy to the currentStrategy. Now, we refactor the numberHit method in the CalculatorModel to invoke currentStrategy.numberHit and since the currentStrategy has already been adjusted to behave according to whatever state the calculator is in, there is no need to inquire as to its own state. The numberHit method is now loosely coupled to the implementation of the behaviors. The numberHit method doesn’t know how it is being implemented, it only knows that it is invoking a numberHit method on a CalculatorStrategy type class and therefore, the numberHit method must be implemented.
Lastly, one more ‘if’ to eliminate. When the equals button is hit, the operate method on the CalculatorModel is invoked which in turn invokes the reset method to prepare the calculator for a new computation. The reset method sets the value of the operator property to null. To avoid throwing a NullPointerException as a result of the user hitting the equals button before selecting an operator, we use conditional logic to check whether the value of operator property is null first before we try to invoke the operate method on it. The refactored SwingCalculator has eliminated this problem by creating an inner class named NullOperator that implements the OperatorButton interface but does nothing when the operate method is invoked. When the calculator is now reset, we set the operator property to NullOperator thus obviating the need to use conditional logic to check for a null value as described above.
Gamma, E., Helm,E., Johnson, R., and John Vlissides (1990) Design Patterns: Elements of Reusable Object-Oriented Software [ISBN 0201633612]
Shalloway, A., and Trott, J.R., (2005) Design Patterns Explained: A New Perspective on Object-Oriented Design [ISBN 0321247140]