| Register | FAQ | Calendar | Search | Today's Posts | Mark Forums Read |
|
#1
| |||
| |||
| 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 |
|
#2
| |||
| |||
| 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. |
|
#3
| |||
| |||
| 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. |
|
#4
| |||
| |||
| 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 |
|
#5
| |||
| |||
| 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! |
|
#6
| |||
| |||
| 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. |
|
#7
| |||
| |||
| 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. |
|
#8
| |||
| |||
| 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. |
|
#9
| |||
| |||
| 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. |
|
#10
| |||
| |||
| 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. |
![]() |
| Thread Tools | |
| Display Modes | |
In an effort to better serve ads to our visitors, cookies are used on objectmix.com. For more information, check out our Privacy Policy.