The GoF (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides) book describes this pattern as a pattern that "Allows an object to alter its behaviour when its internal state changes. The object will appear to change its class."
As its name suggest, this pattern is focused on an object internal state. Basically, if we have to change the behavior of an object based on its internal state, we can keep a state variable in the object and use conditional code (e.g. if-else, switch) to take different actions based on the state.
For example,
let's suppose that we have a device which have two possible states: ON and OFF
(of course, you can have more states, but we keep the things as simple as
possible). From a JSF developer perspective this can be shaped like below:
<h:form>
<h:outputLabel for="deviceId"
value="Turn ON/OFF"/>
<h:selectBooleanCheckbox
id="deviceId" value="#{deviceControl.state}"/>
<h:commandButton value="Turn
ON/OFF" action="#{deviceControl.doOnOff()}"/>
</h:form>
Named
@RequestScoped
public class
DeviceControl {
private boolean state;
public boolean isState() {
return state;
}
public void setState(boolean state) {
this.state = state;
}
public void doOnOff() {
if
(state) {
System.out.println("Device is turned ON");
} else
{
System.out.println("Device is turned OFF");
}
}
}
Now, we will
use this example as the starting point for implementing the state pattern. Once we will re-write this code based on
state pattern, we will obtain a systematic and lose-coupled code, easily
maintainable and flexible, without if-else
or switch. This will be useful
because if the states number increases then the tight coupling between
implementation and the client code will be difficult to maintain. Before,
we proceed, you should know that the state pattern is based on two notions:
·
state
- the object internal state
·
context
- is the class that keeps a state reference to one of the concrete implementations
. It forwards the request to the state object for processing.
State Interface
We start
with the State
interface. This represents the contract that should be implemented by different
concrete states and context class:
public
interface State
{
public void doOnOff();
}
Concrete State Implementations
Each state
has its own implementation. Since we have two states, we write two
implementations, StartState and StopState:
import
javax.enterprise.context.RequestScoped;
import
javax.inject.Named;
@Named
@RequestScoped
public class
StartState
implements State
{
@Override
public void doOnOff() {
System.out.println("Device is turned
ON");
}
}
import
javax.enterprise.context.RequestScoped;
import
javax.inject.Named;
@Named
@RequestScoped
public class
StopState
implements State
{
@Override
public void doOnOff() {
System.out.println("Device is turned
OFF");
}
}
So far, so
good! At final step, we implement the context
object that will change it’s behavior based on its internal state:
import
javax.enterprise.context.RequestScoped;
import
javax.inject.Named;
@Named
@RequestScoped
public class
DeviceContext
implements State
{
private State state;
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
@Override
public void doOnOff() {
this.state.doOnOff();
}
}
Now, we
adjust the JSF page to pass the state
in the context. We can accomplish
this in several ways, depending on how we shape the interface. But, for the
moment, two buttons for invoking doOnOff() and <f:setPropertyActionListener/>
for passing the state will do the job:
<h:form>
<h:commandButton value="Start
Device" action="#{deviceContext.doOnOff()}">
<f:setPropertyActionListener
target="#{deviceContext.state}" value="#{startState}" />
</h:commandButton>
<h:commandButton value="Stop
Device" action="#{deviceContext.doOnOff()}">
<f:setPropertyActionListener
target="#{deviceContext.state}" value="#{stopState}" />
</h:commandButton>
</h:form>
Using role interfaces
Now let's
complicate a little bit the above example. Let's suppose that our device is a
cutting tool, and beside the ON/OFF action we need a CUT action. The issue
consist in the fact that a cutting tool can CUT only if it is ON state. When it
is in OFF state, it cannot CUT, which means that the CUT action doesn't belong
to both states, and we cannot simply re-write the State interface as:
public
interface State {
public void doOnOff();
public void doCut(); // in OFF state this
is not allowed
}
So, we are
in the case when some actions are not
allowed when the context object is in
a particular state. In order to shape this situation we use role interfaces.
First we add
a new interface for the CUT action:
public
interface CutAction {
public void doCut();
}
Next, while
the OFF state remains unchanged, the ON state will implement the CutAction
also:
import
javax.enterprise.context.RequestScoped;
import
javax.inject.Named;
@Named
@RequestScoped
public class
StartState implements State, CutAction {
@Override
public void doOnOff() {
System.out.println("Device is turned
ON");
}
@Override
public void doCut() {
System.out.println("Device is cutting ...");
}
}
Finally, our
cutting tool will cut only in ON state:
import
javax.enterprise.context.RequestScoped;
import
javax.inject.Named;
@Named
@RequestScoped
public class
DeviceContext implements State {
private State state;
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
@Override
public void doOnOff() {
this.state.doOnOff();
if (isCutState()) {
((StartState) state).doCut();
}
}
private boolean isCutState() {
return state instanceof StartState;
}
}
In the
second part of this article we will discuss about the JSF lifecycle and state
pattern.
Niciun comentariu :
Trimiteți un comentariu