GOF State Pattern without fully shared interface

This is a discussion on GOF State Pattern without fully shared interface within the Theory and Concepts forums in category; Hello all, I have the GOF Design Patterns book (Gamma, et al.) and would appreciate if someone could help clarify an implementation detail when using the State Pattern. In the example in the book, all three states share the same methods: Open(), Close(), Acknowledge(). What happens if some states have other methods which are not common to all states? For example assume the following two additional methods: TCPEstablished::getRemoteHostAddress() TCPClosed::getIdleTime() How would the TCPConnection object call one of the above methods? For example how would we call "getIdleTime" which is applicable only for the TCPClosed state? ======================================== POSSIBILITY #1: One possibility ...

Go Back   Application Development Forum > Theory and Concepts

Object Mix

Register FAQ Calendar Search Today's Posts Mark Forums Read
  #1  
Old 04-26-2007, 03:27 AM
capnwhit@yahoo.com
Guest
 
Default GOF State Pattern without fully shared interface

Hello all,

I have the GOF Design Patterns book (Gamma, et al.) and would
appreciate if someone could help clarify an implementation detail when
using the State Pattern. In the example in the book, all three states
share the same methods: Open(), Close(), Acknowledge(). What happens
if some states have other methods which are not common to all states?
For example assume the following two additional methods:

TCPEstablished::getRemoteHostAddress()
TCPClosed::getIdleTime()

How would the TCPConnection object call one of the above methods? For
example how would we call "getIdleTime" which is applicable only for
the TCPClosed state?

========================================
POSSIBILITY #1:
One possibility is to test for the current state inside the
TCPConnection object. This is probably a bad choice because it seems
to defeat one of the motivations for the State Pattern (eliminating
conditional logic from the TCPConnection class). This possibility is
shown below just for discussion:

TCPConnection::method1()
{
int idleTime1 = 0;
if(_state->getCurrentState() == TCP_CLOSED)
{
TCPClosed* p = dynamic_cast<TCPClosed*>(_state);
if(p == 0)
{
throw BadCurrentState;
}
else
{
idleTime1 = p->getIdleTime();
}
}
}

========================================
POSSIBILITY #2:
Another possibility is to list all methods in the TCPState class
(whether shared by all derived states or not). For example the
TCPState class would need to implement "getIdleTime" even though this
method is only applicable for the TCPClosed state. This is shown
below:

int TCPState::getIdleTime()
{
throw BadCallToGetIdleTime; // default behavior for all derived
classes
}

The TCPClosed class is the only class which overrides the
"getIdleTime" method:

int TCPClosed::getIdleTime()
{
return m_IdleTime;
}

Now we can write the following:

TCPConnection::method2()
{
int idleTime2 = 0;
idleTime2 = _state->getIdleTime(); // this call may throw depending
on current state!
}

TCPConnection::method3()
{
int idleTime3 = 0;
if(dynamic_cast<TCPState*>(_state) != 0)
{
idleTime3 = _state->getIdleTime(); // guaranteed not to throw
}
}

I'm not too happy about the fact that setting "idleTime2" above may
throw an exception, and I'm not too happy that setting "idleTime3"
still required a conditional inside TCPConnection which the State
Pattern is supposed to eliminate... maybe the State Pattern can only
"alleviate" but not "eliminate" the state-related conditionals inside
the TCPConnection object?

Any comments would be appreciated.

Thanks,

Bob

Reply With Quote
  #2  
Old 04-26-2007, 06:44 AM
Daniel T.
Guest
 
Default Re: GOF State Pattern without fully shared interface

capnwhit{}yahoo.com wrote:

> I have the GOF Design Patterns book (Gamma, et al.) and would
> appreciate if someone could help clarify an implementation detail when
> using the State Pattern. In the example in the book, all three states
> share the same methods: Open(), Close(), Acknowledge(). What happens
> if some states have other methods which are not common to all states?
> For example assume the following two additional methods:
>
> TCPEstablished::getRemoteHostAddress()
> TCPClosed::getIdleTime()
>
> How would the TCPConnection object call one of the above methods? For
> example how would we call "getIdleTime" which is applicable only for
> the TCPClosed state?
>
> ========================================
> POSSIBILITY #1:
> One possibility is to test for the current state inside the
> TCPConnection object. This is probably a bad choice because it seems
> to defeat one of the motivations for the State Pattern (eliminating
> conditional logic from the TCPConnection class). This possibility is
> shown below just for discussion:
>
> TCPConnection::method1()
> {
> int idleTime1 = 0;
> if(_state->getCurrentState() == TCP_CLOSED)
> {
> TCPClosed* p = dynamic_cast<TCPClosed*>(_state);
> if(p == 0)
> {
> throw BadCurrentState;
> }
> else
> {
> idleTime1 = p->getIdleTime();
> }
> }
> }


Obviously messed up. Not only is this method trying to do two different
things, it means something different depending on what state the object
is in.

> ========================================
> POSSIBILITY #2:
> Another possibility is to list all methods in the TCPState class
> (whether shared by all derived states or not). For example the
> TCPState class would need to implement "getIdleTime" even though this
> method is only applicable for the TCPClosed state. This is shown
> below:
>
> int TCPState::getIdleTime()
> {
> throw BadCallToGetIdleTime; // default behavior for all derived
> classes
> }
>
> The TCPClosed class is the only class which overrides the
> "getIdleTime" method:
>
> int TCPClosed::getIdleTime()
> {
> return m_IdleTime;
> }
>
> Now we can write the following:
>
> TCPConnection::method2()
> {
> int idleTime2 = 0;
> idleTime2 = _state->getIdleTime(); // this call may throw depending
> on current state!
> }
>
> TCPConnection::method3()
> {
> int idleTime3 = 0;
> if(dynamic_cast<TCPState*>(_state) != 0)
> {
> idleTime3 = _state->getIdleTime(); // guaranteed not to throw
> }
> }


This isn't much better, 'method2' is missing the code to catch the
throw. I.E., in all three methods you show, there is still a
conditional. As you said, the state pattern is supposed to get rid of
the conditionals.

Your basic problem is the 'getter' in the state class. It practically
guarantees that the Subject class will contain a conditional even if all
of the states *do* implement the getter.

Try this:

TCPConnection::method1() // and 2 and 3
{
_state->method1(); // and 2 and 3
}

Now implement method1() for each state. Whatever commonality is in all
of them, move it up to the base class State.
Reply With Quote
  #3  
Old 04-26-2007, 11:47 AM
Mark Nicholls
Guest
 
Default Re: GOF State Pattern without fully shared interface

On 26 Apr, 08:27, capnw...{}yahoo.com wrote:
> Hello all,
>
> I have the GOF Design Patterns book (Gamma, et al.) and would
> appreciate if someone could help clarify an implementation detail when
> using the State Pattern. In the example in the book, all three states
> share the same methods: Open(), Close(), Acknowledge(). What happens
> if some states have other methods which are not common to all states?
> For example assume the following two additional methods:
>
> TCPEstablished::getRemoteHostAddress()
> TCPClosed::getIdleTime()
>
> How would the TCPConnection object call one of the above methods? For
> example how would we call "getIdleTime" which is applicable only for
> the TCPClosed state?
>
> ========================================
> POSSIBILITY #1:
> One possibility is to test for the current state inside the
> TCPConnection object. This is probably a bad choice because it seems
> to defeat one of the motivations for the State Pattern (eliminating
> conditional logic from the TCPConnection class). This possibility is
> shown below just for discussion:
>
> TCPConnection::method1()
> {
> int idleTime1 = 0;
> if(_state->getCurrentState() == TCP_CLOSED)
> {
> TCPClosed* p = dynamic_cast<TCPClosed*>(_state);
> if(p == 0)
> {
> throw BadCurrentState;
> }
> else
> {
> idleTime1 = p->getIdleTime();
> }
> }
>
> }
>
> ========================================
> POSSIBILITY #2:
> Another possibility is to list all methods in the TCPState class
> (whether shared by all derived states or not). For example the
> TCPState class would need to implement "getIdleTime" even though this
> method is only applicable for the TCPClosed state. This is shown
> below:
>
> int TCPState::getIdleTime()
> {
> throw BadCallToGetIdleTime; // default behavior for all derived
> classes
>
> }
>
> The TCPClosed class is the only class which overrides the
> "getIdleTime" method:
>
> int TCPClosed::getIdleTime()
> {
> return m_IdleTime;
>
> }
>
> Now we can write the following:
>
> TCPConnection::method2()
> {
> int idleTime2 = 0;
> idleTime2 = _state->getIdleTime(); // this call may throw depending
> on current state!
>
> }
>
> TCPConnection::method3()
> {
> int idleTime3 = 0;
> if(dynamic_cast<TCPState*>(_state) != 0)
> {
> idleTime3 = _state->getIdleTime(); // guaranteed not to throw
> }
>
> }
>
> I'm not too happy about the fact that setting "idleTime2" above may
> throw an exception, and I'm not too happy that setting "idleTime3"
> still required a conditional inside TCPConnection which the State
> Pattern is supposed to eliminate... maybe the State Pattern can only
> "alleviate" but not "eliminate" the state-related conditionals inside
> the TCPConnection object?
>
> Any comments would be appreciated.
>
> Thanks,
>
> Bob


Personally I don't like the state pattern....but I've been largely
attacked for my errancy of 'exposing' the underlying state objects.

Certainly if the 'context' (as labelled in GoF) is not shared between
different clients...then it seems much more sensible to only expose
those methods relavant to the 'state'.

e.g.

interface IClosedPortState
{
IOpenPortState OpenPort(...);
}

interface IOpenPortState
{
void SendData(...);
IClosedPortState Close();
}

clearly SendData makes no sense of a closed port....so don't have it.

The problem comes if the 'port' is shared....then the actual 'state'
of the port cannot be guarenteed....there are ways around this....one
of which is to use the state pattern as described in GoF.

Reply With Quote
  #4  
Old 04-26-2007, 12:36 PM
H. S. Lahman
Guest
 
Default Re: GOF State Pattern without fully shared interface

Responding to Capnwhit...

> I have the GOF Design Patterns book (Gamma, et al.) and would
> appreciate if someone could help clarify an implementation detail when
> using the State Pattern. In the example in the book, all three states
> share the same methods: Open(), Close(), Acknowledge(). What happens
> if some states have other methods which are not common to all states?


Then it isn't a State pattern. B-)

The point of State is that the Context would normally /always/ have the
State superclass responsibilities. The problem State resolves is that
they need to be implemented differently based on some dynamic context.
That problem is solved by delegating them to State and employing
polymorphic dispatch to access the different implementations. The
original problem that you want to solve is:

[Client]
| *
|
| R1
|
| 1
[Context]
+ open
+ close
+ getTime

But the getTime responsibility is only appropriate for /some/ [Client]
collaboration contexts. Even if open and close were implemented in
exactly the same way in all contexts, this would be a problem if getTIme
should be available to the same Client only in some dynamic contexts but
not in others.

Other dynamic complexity aside, the normal way one would get around that
would be via generalization of [Context]. Then any Client that just
needed open or close would access through the [Context] superclass and
any Client needing getTime would have a direct relationship to the right
subclass to navigate. Instantiating that relationship would explicitly
identify the dynamic context.

Clearly that sort of generalization in Context doesn't work if there are
dynamic implementation issues, so it needs to be applied to State once
it is delegated. Instead of

[Client]
| *
|
| R1
|
| 1 * R2 1
[Context] --------------- [State]
+ open + open
+ close + close
A
|
+---------+---------+
| |
[State1] [State2]
+ getTime

where Client could access State1:getTime erroneously through Context
because R2 is a superclass association, one should do:

* R2 1 1
[Context] --------------- [State] ---------------+
+ open |
+ close |
A |
| |
+---------+---------+ |
| | |
[State1] [State2] |
+ getTime | R1B
| 0..1 |
| |
| R1A |
| |
+----- [Client] --------------+
* *

where one instantiates R1A and R1B correctly depending on the context.
But the instantiation of R1A also depends on what specific services the
Client needs from the role for the role in that dynamic context. Now
Client can only invoke getTime if the R1A relationship has been
instantiated so we are enforcing the access rules with static structure.

<aside>
This is actually a good demonstration of why the way the GoF implement
delegation in their patterns is flawed. (In fairness to the GoF, they
make it pretty clear that their number one priority was explaining
patterns, not solving specific problems via OOP.) Once the open and
close responsibilities are delegated to [State], they no longer belong
to [Context] at all. [State] now represents an identifiable problem
space entity (role) that is abstracted on its own merits. The relational
model precludes any of its properties being dependent on the identity of
[Context] in the Class Model, so members of both [Context] and [State]
cannot own the same properties.

Therefore the Client should always navigate to [State] through [Context]
and collaborate with State in a peer-to-peer fashion rather than using
the [Context] interface as a middleman. Peer-to-peer navigation is also
fundamental to the OO paradigm because it is the main way one eliminates
the hierarchical dependencies of functional decomposition. IOW, the
interface of [Context] should not have any of the State responsibilities
after the delegation.

[You used a different interface, method1, for the [Context] interface,
but it had a different set of problems with cohesion, self-containment,
and logical indivisibility. Again, that is not the State pattern; in the
GoF pattern the [Context] interface just provides indirection, not
additional processing. From the Client perspective the specification of
method1 was a hybrid of the processing done in that method and the
processing the delegated [State] methods. Those problems were the
immediate cause of your difficulties.]

To see why that decoupling is important, suppose getTime was added
during maintenance due to a requirements change. Since getTime is a
specialization in the existing generalization the collaboration directly
with the [State] superclass would be patently incorrect. (The use of
dynamic_cast in your example is a clear indication that something is
wrong with the navigation since generalization relations are not
navigable, regardless of what C++ allows.) More to the point, one would
have to implement the R1A relationship and modify the [Context]
interface when the change is made.

In contrast, if the collaboration had been peer-to-peer with [State] in
the first place, then the change would be trivial (i.e., slightly
different rules for instantiating R1A applied in the same place that
already understands dynamic context) so that neither [Context] nor
[State] need to be touched at all (other than to add getTime).

In this particular example, the sort of checking you do in method1 is
exactly what must be done to instantiate the R1A association. One would
do that wherever one understands the dynamic context (e.g., probably
where R2 is instantiated). So all the Client needs to do is check if
that relationship is instantiated. Client throws an exception if it is
not and the Client thinks it needs to navigate it (e.g., to invoke
getTime). Then you don't need any code in method1, so you don't need
method1 at all.

Perhaps more important, since the R1A relationship is instantiated
explicitly for the dynamic context, checking the instantiation and
throwing an exception becomes a DbC correctness assertion in Client
rather than a flow of control decision. This is a direct result of
employing static structure to enforce the access rules.
</aside>


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl{}pathfindermda.com
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info{}pathfindermda.com for your copy.
Pathfinder is hiring:
http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



Reply With Quote
  #5  
Old 04-26-2007, 03:19 PM
capnwhit@yahoo.com
Guest
 
Default Re: GOF State Pattern without fully shared interface

On Apr 26, 5:43 am, "Daniel T." <danie...{}earthlink.net> wrote:
> > POSSIBILITY #1:

> Obviously messed up.
>

Yes... thus my question :-)

> > POSSIBILITY #2:

> This isn't much better
>

Bingo :-)

> Try this:
>
> TCPConnection::method1() // and 2 and 3
> {
> _state->method1(); // and 2 and 3
>
> }
>
> Now implement method1() for each state. Whatever commonality is in all
> of them, move it up to the base class State.- Hide quoted text -
>

OK, what I think you are saying is to start with the _base_ State
class and figure out your methods (in this case m1, m2, m3). Then all
you need to do is implement said methods in every derived state. So
far so good. My question is what do you do with functionality that is
only applicable to _one_ of the states, but not all? For example, when
I am in the TCPClosed state, I want to know how long the connection
has been idle. How do I fit that into the State Pattern?

Thanks!

Reply With Quote
  #6  
Old 04-26-2007, 03:47 PM
Tapio
Guest
 
Default Re: GOF State Pattern without fully shared interface

In article <1177615156.199161.279160{}t39g2000prd.googlegroup s.com>,
capnwhit{}yahoo.com says...
> On Apr 26, 5:43 am, "Daniel T." <danie...{}earthlink.net> wrote:
> > > POSSIBILITY #1:

> > Obviously messed up.
> >

> Yes... thus my question :-)
>
> > > POSSIBILITY #2:

> > This isn't much better
> >

> Bingo :-)
>
> > Try this:
> >
> > TCPConnection::method1() // and 2 and 3
> > {
> > _state->method1(); // and 2 and 3
> >
> > }
> >
> > Now implement method1() for each state. Whatever commonality is in all
> > of them, move it up to the base class State.- Hide quoted text -
> >

> OK, what I think you are saying is to start with the _base_ State
> class and figure out your methods (in this case m1, m2, m3). Then all
> you need to do is implement said methods in every derived state. So
> far so good. My question is what do you do with functionality that is
> only applicable to _one_ of the states, but not all? For example, when
> I am in the TCPClosed state, I want to know how long the connection
> has been idle. How do I fit that into the State Pattern?
>
> Thanks!
>
>

Make your base class abstract and add your real implemention into only
one concrete class. The abstract base class could throw an exception if
the concrete class does not have that functionality.

Reply With Quote
  #7  
Old 04-26-2007, 04:41 PM
Universe
Guest
 
Default Re: GOF State Pattern without fully shared interface

On Apr 26, 12:36 pm, "H. S. Lahman" <h.lah...{}verizon.net> wrote:

> Therefore the Client should always navigate to [State] through [Context]
> and collaborate with State in a peer-to-peer fashion rather than using
> the [Context] interface as a middleman. Peer-to-peer navigation is also
> fundamental to the OO paradigm because it is the main way one eliminates
> the hierarchical dependencies of functional decomposition. IOW, the
> interface of [Context] should not have any of the State responsibilities
> after the delegation.


Note, fundamental to Lahman's OO paradigm. The evaluation of using
peer to peer communication for roughly 4 or more nodes, each capable
of 4 or more message, by most programming and other system building
experts in nearly every paradigm, OO or not, is that relying upon it
often results in a nightmare, web of intractable complexity. Much
better to rely upon the intermediary node paradigm messaging
demonstrated by GoF in numerous patterns like State, Context,
Observer, Mediator, etc..

Elliott
---
The goal of OO is modelling a domain to simulate it as a machine to
reduce complexity.

Reply With Quote
  #8  
Old 04-26-2007, 10:09 PM
Daniel T.
Guest
 
Default Re: GOF State Pattern without fully shared interface

On Apr 26, 3:19 pm, capnw...{}yahoo.com wrote:
> On Apr 26, 5:43 am, "Daniel T." <danie...{}earthlink.net> wrote:> >


> OK, what I think you are saying is to start with the _base_ State
> class and figure out your methods (in this case m1, m2, m3). Then all
> you need to do is implement said methods in every derived state. So
> far so good.


No. First figure out all your methods in the Context class, then
delegate to the base State class.

> My question is what do you do with functionality that is
> only applicable to _one_ of the states, but not all? For example, when
> I am in the TCPClosed state, I want to know how long the connection
> has been idle. How do I fit that into the State Pattern?


Functionality that is only applicable to one of the states goes only
in that state. The TCPConnection doesn't have a function "getIdle"
because the client doesn't need to know that. Therefore it doesn't
need to call "getIdle" on its state sub-object.

As an example, if you didn't use the state pattern, then in some
method in TCPConnection you might have something like:

if ( state == CLOSED ) {
if ( idleTime() > limit )
doSomething();
}
else {
doSomethingElse();
}

With the state pattern, the above becomes:

state->foo();

void ClosedState::foo() {
if ( idleTime() > limit )
doSomething();
}

// default for other states
void State::foo() {
doSomethingElse();
}

You need to bury state spicific behavior into the spicific state. You
aren't doing that.

Reply With Quote
  #9  
Old 04-26-2007, 10:11 PM
Daniel T.
Guest
 
Default Re: GOF State Pattern without fully shared interface

On Apr 26, 3:46 pm, Tapio <nob...{}nowhere.com> wrote:

> Make your base class abstract and add your real implemention into only
> one concrete class. The abstract base class could throw an exception if
> the concrete class does not have that functionality.- Hide quoted text -


Probably a bad idea. A try { } catch() { } is just a conditional in OO
clothing.

Reply With Quote
  #10  
Old 04-26-2007, 10:52 PM
Universe
Guest
 
Default Re: GOF State Pattern without fully shared interface

On Apr 26, 10:11 pm, "Daniel T." <danie...{}earthlink.net> wrote:
> On Apr 26, 3:46 pm, Tapio <nob...{}nowhere.com> wrote:
>
> > Make your base class abstract and add your real implemention into only
> > one concrete class. The abstract base class could throw an exception if
> > the concrete class does not have that functionality.- Hide quoted text -

>
> Probably a bad idea. A try { } catch() { } is just a conditional in OO
> clothing.


Only for a failure and all good code shoud handle errors. If there is
no error then normal classes are used and possibly being swapped in
and out polymorphically to perform expected functionality. In
addition in many OOPs implement the "try/catch" is implemented using
classes and polymorphism. There is a need for error handling because
polymorphism doesn't magically handle any and all circumstances a
program may face.

Elliott
---
The goal of OO is to model a domain to simulate it as a machine to
reduce complexity.

Reply With Quote
Reply


Thread Tools
Display Modes


All times are GMT -5. The time now is 06:59 AM.


Powered by vBulletin® Version 3.7.2
Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.2.0
vB Ad Management by =RedTyger=

In an effort to better serve ads to our visitors, cookies are used on objectmix.com. For more information, check out our Privacy Policy.