|  | design problem |  | |
| | | Nick Keighley |  |
| Posted: Wed Jun 04, 2008 6:58 am Post subject: design problem |  |
| |  | |
Hi,
This is a rather "thinking out loud" post as I suspect if I could ask a clear question I'd be able to ask it myself.
Suppose the system handles Calls between Parties. Calls process Messages that arrive from some comms infrastructure (say Channels). So a Channel creates a Messages when the socket or whatever signals that there is data to process. Messages fall into two catagories those that belong to existing Calls and those that create new Calls (I'm ignoring errors at the moment).
So my first thought was a CallManager. (code is Not Quite C++)
CallManager::process_message (Message& msg) { Call* call;
if (is_new_call(msg)) call = create_call (msg); else call = lookup_call (msg.call_id());
call->process_msg (msg); }
It's the name that put me off. CallManager looks a bit GodClass like. I could change the name to MessageDispatcher, but that is only a name change. Or make the Channel class create and lookup calls (that sounds deeply wrong). Or should the message process itself?
class Message { public: virtual received_action() = 0; };
class NewCallMessage: public Message { public: received_action(); }
similarly ExistingCallMessage
NewCallMessage::received_action() { call = create_call(); }
no... that isn't going to work.
Ok, try this
class Message { public: virtual Call* get_call() = 0; private: received_action() { Call* call = get_call(); // call needs to be stored somewhere... call->process_call(); } };
Call* NewCallMessage::get_call() { return create_call (*this); }
Call* ExistingCallMessage::get_call() { return lookup_call (*this); }
I think I need some sort of CurrentCallCollection. New calls will be added to it existing calls will be looked up in it and dead calls removed.
So is the CallManager a good idea or should calls "process themselves">
-- Nick Keighley |
| |
| | | S Perryman |  |
| Posted: Wed Jun 04, 2008 6:58 am Post subject: Re: design problem |  |
| |  | |
Nick Keighley wrote:
| Quote: | This is a rather "thinking out loud" post as I suspect if I could ask a clear question I'd be able to ask it myself. Suppose the system handles Calls between Parties. Calls process Messages that arrive from some comms infrastructure (say Channels). So a Channel creates a Messages when the socket or whatever signals that there is data to process. Messages fall into two catagories those that belong to existing Calls and those that create new Calls (I'm ignoring errors at the moment).
|
[ musings snipped - but read and acknowledged ]
| Quote: | So is the CallManager a good idea or should calls "process themselves" .
|
Communications systems have a canonical model for services :
The service (telephony, Internet pipe etc) .
The user (U) plane, which provides the resources needed to support the service (connections, codecs etc) .
The control (C) plane, which provides the means to make service requests, and configures the U-plane in order to satisfy the request.
Your "CallManager" is the C-plane component. In the comms world, the C-plane *is* God.
The "God class" problem is effectively one of s/w granularity. You have a C-plane function, but there is nothing inherent in the subject domain that demands the implementation of the function as one distinct component (a class etc) .
A common OO implementation is that each service becomes an object in its own right, encapsulating the "work flow" for each service request. The C-plane service receiver becomes a factory that creates an appropriate service object for the C-plane message received. The service object is then invoked and the request is processed.
Regards, Steven Perryman |
| |
| | | Daniel T. |  |
| Posted: Wed Jun 04, 2008 9:37 am Post subject: Re: design problem |  |
| |  | |
Nick Keighley <nick_keighley_nospam@hotmail.com> wrote:
| Quote: | This is a rather "thinking out loud" post as I suspect if I could ask a clear question I'd be able to ask it myself.
Suppose the system handles Calls between Parties. Calls process Messages that arrive from some comms infrastructure (say Channels). So a Channel creates a Messages when the socket or whatever signals that there is data to process. Messages fall into two catagories those that belong to existing Calls and those that create new Calls (I'm ignoring errors at the moment).
So my first thought was a CallManager. (code is Not Quite C++)
CallManager::process_message (Message& msg) { Call* call;
if (is_new_call(msg)) call = create_call (msg); else call = lookup_call (msg.call_id());
call->process_msg (msg); }
|
Note: the above isn't much different than the FlyweightFactory defined in GoF (in the Flyweight pattern.)
if (flyweight[key] exists) { return existing flyweight; } else { create new flyweight; add it to pool of flyweights; return the new flyweight; }
What you have above sounds like a relatively standard factory pattern to me. Factories aren't gods... :-)
That said, if an object is constantly having to get the call from this factory then maybe it should simply hold on to it after the first lookup instead. |
| |
| | | Nick Keighley |  |
| Posted: Wed Jun 04, 2008 10:13 am Post subject: Re: design problem |  |
| |  | |
On Jun 4, 9:39 am, S Perryman <q...@q.com> wrote:
| Quote: | Nick Keighley wrote:
This is a rather "thinking out loud" post as I suspect if I could ask a clear question I'd be able to ask it myself. Suppose the system handles Calls between Parties. Calls process Messages that arrive from some comms infrastructure (say Channels). So a Channel creates a Messages when the socket or whatever signals that there is data to process. Messages fall into two catagories those that belong to existing Calls and those that create new Calls (I'm ignoring errors at the moment).
So is the CallManager a good idea or should calls "process themselves" .
Communications systems have a canonical model for services :
The service (telephony, Internet pipe etc) .
The user (U) plane, which provides the resources needed to support the service (connections, codecs etc) .
The control (C) plane, which provides the means to make service requests, and configures the U-plane in order to satisfy the request.
Your "CallManager" is the C-plane component. In the comms world, the C-plane *is* God.
The "God class" problem is effectively one of s/w granularity. You have a C-plane function, but there is nothing inherent in the subject domain that demands the implementation of the function as one distinct component (a class etc) .
A common OO implementation is that each service becomes an object in its own right, encapsulating the "work flow" for each service request. The C-plane service receiver becomes a factory that creates an appropriate service object for the C-plane message received. The service object is then invoked and the request is processed.
|
ok, thanks!
-- Nick Keighley |
| |
| | | H. S. Lahman |  |
| Posted: Wed Jun 04, 2008 3:05 pm Post subject: Re: design problem |  |
| |  | |
Responding to Keighley...
| Quote: | Suppose the system handles Calls between Parties. Calls process Messages that arrive from some comms infrastructure (say Channels). So a Channel creates a Messages when the socket or whatever signals that there is data to process. Messages fall into two catagories those that belong to existing Calls and those that create new Calls (I'm ignoring errors at the moment).
|
So we have something like:
0..1 [Party] ----------+ | 0..1 | | talks to | R1 | | +--------------+ | | [Call] | 1 | | R2 | | processes | * [Message] | * | | R3 | | created by | 1 [Channel] | * | triggers | | R4 | | 1 [Socket]
Allocating responsibilities here for processing calls and messages is fairly straight forward. So you don't need a Call Manager to do that.
As I understand your concern the issue is with who instantiates all these objects and relationships. I assume [Party], [Socket], and [Channel] are handled specially outside the scope of message processing and we don't need to worry about that. That leaves [Call] and [message].
[Message] seems like something [Channel] should create since [Channel] is a surrogate for external handling of messages. So the tricky part devolves down to instantiating the R2 association when a Message is created. That, in turn, may require that a Call be created and the R1 association be instantiated.
One possibility is to let [Channel] do all that. The rationale is that it already is essentially a factory object by nature of its relationship with [Message]. When one instantiates things, ideally one wants to keep all the relevant rules and policies under a single scope to make it easier to ensure referential integrity. IOW, when one instantiates objects with unconditional associations to other objects, referential integrity demands that the other objects be created at the same time (relative to the solution flow of control).
However, cohesion is another concern. The reason one employs factory objects is to encapsulate those rules and policies because they are usually different than the rules and policies that govern collaborations. So one wants one's factory objects to be dedicated to just that subject matter, especially if those rules and policies are somewhat complicated as they seem to be here.
Here [Channel] may have some other responsibilities related to things like converting formats from the [Channel] into a more useful form for message processing. If those are at all complicated, one might want to encapsulate them separately from the instantiation rules and policies. In that case, [Channel] has the responsibility for converting formats (or whatever) while some other object would act as factory to create [Message] and, if necessary, [Call]. That can easily be accommodated by modifying the bottom part of the example diagram:
* creates [Message] ------------+ R5 | * | | formats for | | | 1 | R3 [MessageFactory] | | 1 | | invokes | 1 1 | [Channel] ------------+ R6 | * | triggers | | R4 | | 1 [Socket]
Now [MessageFactory] is essentially your [CallManager]; it just has a name with less emotional baggage and it is dedicated exclusively to instantiation. To do that it may need to collaborate with [Channel] to get the proper formating (or whatever), which it would pass through to [Message] attributes.
Note that the R3 association really isn't necessary any more since [MessageFactory] is a middleman. The formatting collaboration is via R6 during the initialization of [Message] by [MessageFactory]. This segues to an interesting point: how does the Message actually get processed? Presumably it would be processed as soon as it is instantiated and the relevant associations have been instantiated. [MessageFactory] knows when that is complete, so it is the logical one to send the kick-off message to Call to announce that a Message is available for processing. Thus the flow of control is just daisy-chained through the [MessageFactory] middleman and all we have done is separate and encapsulate the concerns of instantiation from those of extracting initialization data from [Channel].
-- There is nothing wrong with me that could not be cured by a capful of Drano.
H. S. Lahman hsl@pathfindermda.com Pathfinder Solutions LINK blog: LINK "Model-Based Translation: The Next Step in Agile Development". Email info@pathfindermda.com for your copy. Pathfinder is hiring: LINK (888)OOA-PATH |
| |
| | | Nick Keighley |  |
| Posted: Thu Jun 05, 2008 7:59 am Post subject: Re: design problem |  |
| |  | |
On 4 Jun, 12:37, "Daniel T." <danie...@earthlink.net> wrote:
| Quote: | Nick Keighley <nick_keighley_nos...@hotmail.com> wrote: This is a rather "thinking out loud" post as I suspect if I could ask a clear question I'd be able to ask it myself.
Suppose the system handles Calls between Parties. Calls process Messages that arrive from some comms infrastructure (say Channels). So a Channel creates a Messages when the socket or whatever signals that there is data to process. Messages fall into two catagories those that belong to existing Calls and those that create new Calls (I'm ignoring errors at the moment).
So my first thought was a CallManager. (code is Not Quite C++)
CallManager::process_message (Message& msg) { Call* call;
if (is_new_call(msg)) call = create_call (msg); else call = lookup_call (msg.call_id());
call->process_msg (msg); }
Note: the above isn't much different than the FlyweightFactory defined in GoF (in the Flyweight pattern.)
if (flyweight[key] exists) { return existing flyweight; } else { create new flyweight; add it to pool of flyweights; return the new flyweight; }
What you have above sounds like a relatively standard factory pattern to me. Factories aren't gods... 
|
well it seemed a little more than a factory to me in that the [Message] has to be dispatched to the appropriate [Call]
| Quote: | That said, if an object is constantly having to get the call from this factory then maybe it should simply hold on to it after the first lookup instead
|
yes , but the "hanging on to" has to involve keeping it somewhere. In your code above the pool of flyweights. I was assuming CallManager had a collection-of-active-calls or some such. There may be many calls and the messages to different calls can interleave arbitarily.
-- Nick Keighley |
| |
| | | Daniel T. |  |
| Posted: Thu Jun 05, 2008 11:19 pm Post subject: Re: design problem |  |
| |  | |
In article Nick Keighley <nick_keighley_nospam@hotmail.com> wrote:
| Quote: | "Daniel T." <danie...@earthlink.net> wrote: Nick Keighley <nick_keighley_nos...@hotmail.com> wrote:
CallManager::process_message (Message& msg) { Call* call;
if (is_new_call(msg)) call = create_call (msg); else call = lookup_call (msg.call_id());
call->process_msg (msg); }
Note: the above isn't much different than the FlyweightFactory defined in GoF (in the Flyweight pattern.)
if (flyweight[key] exists) { return existing flyweight; } else { create new flyweight; add it to pool of flyweights; return the new flyweight; }
What you have above sounds like a relatively standard factory pattern to me. Factories aren't gods... :-)
well it seemed a little more than a factory to me in that the [Message] has to be dispatched to the appropriate [Call]
|
Why is that the case? What if we did this?
Call* CallManager::getCall(/*whatever msg.call_id() returns*/) { Call* result = 0; if (is_new_call(id)) result = lookup_call(id); else result = create_call(id); return result; }
The client would then do:
callManagaer.getCall(msg.call_id())->process(msg); Now the callManager is nothing more than a container that holds calls. It no longer associates with "Message", it doesn't even need to associate with Call! In C++, a std::map will do. No one could call it a god then. :-)
| Quote: | That said, if an object is constantly having to get the call from this factory then maybe it should simply hold on to it after the first lookup instead
yes, but the "hanging on to" has to involve keeping it somewhere. In your code above the pool of flyweights.
|
No, that wasn't my point. In order for your code to make sense, (at least to me,) it must be the case that there is something in a message that identifies what call should use it. Why not make that association explicit in the code? |
| |
| | | Nick Keighley |  |
| Posted: Fri Jun 06, 2008 9:23 am Post subject: Re: design problem |  |
| |  | |
On 6 Jun, 02:19, "Daniel T." <danie...@earthlink.net> wrote:
| Quote: | In article Nick Keighley <nick_keighley_nos...@hotmail.com> wrote: "Daniel T." <danie...@earthlink.net> wrote: Nick Keighley <nick_keighley_nos...@hotmail.com> wrote:
|
I'm probably making mountains out of molehills...
| Quote: | CallManager::process_message (Message& msg) { Call* call;
if (is_new_call(msg)) call = create_call (msg); else call = lookup_call (msg.call_id());
call->process_msg (msg); }
Note: the above isn't much different than the FlyweightFactory defined in GoF (in the Flyweight pattern.)
if (flyweight[key] exists) { return existing flyweight; } else { create new flyweight; add it to pool of flyweights; return the new flyweight; }
What you have above sounds like a relatively standard factory pattern to me. Factories aren't gods... :-)
well it seemed a little more than a factory to me in that the [Message] has to be dispatched to the appropriate [Call]
Why is that the case?
|
well *someone* has to associate [Messages] with [Calls]
| Quote: | What if we did this?
Call* CallManager::getCall(/*whatever msg.call_id() returns*/)
|
call_id is some unique identifier assigned to the call when it's created. It probably just an integer.
| Quote: | { Call* result = 0; if (is_new_call(id)) result = lookup_call(id); else result = create_call(id); return result;
}
The client would then do:
callManagaer.getCall(msg.call_id())->process(msg);
|
yes but. "who is the client" is the question I'm trying to ask!
class Client { msg_received(Msg&); };
// called by Channel Client::msg_received (Msg& msg) { callManagaer.getCall(msg.call_id())->process(msg); }
now [Client] *could* be [Channel], but that seems to be loading too much on [Channel]. I'd been trying to make [Client] be [CallManager].
| Quote: | Now the callManager is nothing more than a container that holds calls. It no longer associates with "Message", it doesn't even need to associate with Call!
|
well it contains [Calls]
| Quote: | In C++, a std::map will do. No one could call it a god then. :-)
That said, if an object is constantly having to get the call from this factory then maybe it should simply hold on to it after the first lookup instead
yes, but the "hanging on to" has to involve keeping it somewhere. In your code above[,] the pool of flyweights.
No, that wasn't my point. In order for your code to make sense, (at least to me,) it must be the case that there is something in a message that identifies what call should use it.
|
that's the call_id thing
| Quote: | Why not make that association explicit in the code
|
how? Somehow there's a mapping from call_id to [Call] and I agree a map seems like a good way to do this.
I'm beginning toi think the only god-like about my class was the name.
-- Nick Keighley |
| |
| | | Daniel T. |  |
| Posted: Fri Jun 06, 2008 7:24 pm Post subject: Re: design problem |  |
| |  | |
On Jun 6, 5:23 am, Nick Keighley <nick_keighley_nos...@hotmail.com> wrote:
| Quote: | On 6 Jun, 02:19, "Daniel T." <danie...@earthlink.net> wrote:
In article Nick Keighley <nick_keighley_nos...@hotmail.com> wrote: "Daniel T." <danie...@earthlink.net> wrote: Nick Keighley <nick_keighley_nos...@hotmail.com> wrote:
I'm probably making mountains out of molehills...
CallManager::process_message (Message& msg) { Call* call;
if (is_new_call(msg)) call = create_call (msg); else call = lookup_call (msg.call_id());
call->process_msg (msg); }
Note: the above isn't much different than the FlyweightFactory defined in GoF (in the Flyweight pattern.)
if (flyweight[key] exists) { return existing flyweight; } else { create new flyweight; add it to pool of flyweights; return the new flyweight; }
What you have above sounds like a relatively standard factory pattern to me. Factories aren't gods... :-)
well it seemed a little more than a factory to me in that the [Message] has to be dispatched to the appropriate [Call]
Why is that the case?
well *someone* has to associate [Messages] with [Calls]
What if we did this?
Call* CallManager::getCall(/*whatever msg.call_id() returns*/)
call_id is some unique identifier assigned to the call when it's created. It probably just an integer.
{ Call* result = 0; if (is_new_call(id)) result = lookup_call(id); else result = create_call(id); return result;
}
The client would then do:
callManagaer.getCall(msg.call_id())->process(msg);
yes but. "who is the client" is the question I'm trying to ask!
|
How long does a Message object live? Is it just a data packet that needs to be passed to some Call object processed and then dumped?
| Quote: | class Client { msg_received(Msg&);
};
// called by Channel Client::msg_received (Msg& msg) { callManagaer.getCall(msg.call_id())->process(msg);
}
now [Client] *could* be [Channel], but that seems to be loading too much on [Channel]. I'd been trying to make [Client] be [CallManager].
|
Not at all. Channel makes Message objects right? And Message objects know what Call they should be associated with.
What would happen if you put each Call on its own thread? Then the Channel would just pass the message to the correct Call and let Calls do what they will. Even if each Call isn't on a seperate thread, that's what Channel should be doing. Is there more than one Channel? If so, can messages bound to one Call come in from different channels? |
| |
| | | Nick Keighley |  |
| Posted: Mon Jun 09, 2008 3:43 pm Post subject: Re: design problem |  |
| |  | |
On 6 Jun, 20:24, "Daniel T." <danie...@earthlink.net> wrote:
| Quote: | On Jun 6, 5:23 am, Nick Keighley <nick_keighley_nos...@hotmail.com wrote: On 6 Jun, 02:19, "Daniel T." <danie...@earthlink.net> wrote: In article Nick Keighley <nick_keighley_nos...@hotmail.com> wrote: "Daniel T." <danie...@earthlink.net> wrote: Nick Keighley <nick_keighley_nos...@hotmail.com> wrote:
I'm probably making mountains out of molehills...
CallManager::process_message (Message& msg) { Call* call;
if (is_new_call(msg)) call = create_call (msg); else call = lookup_call (msg.call_id());
call->process_msg (msg); }
Note: the above isn't much different than the FlyweightFactory defined in GoF (in the Flyweight pattern.)
if (flyweight[key] exists) { return existing flyweight; } else { create new flyweight; add it to pool of flyweights; return the new flyweight; }
What you have above sounds like a relatively standard factory pattern to me. Factories aren't gods... :-)
well it seemed a little more than a factory to me in that the [Message] has to be dispatched to the appropriate [Call]
Why is that the case?
well *someone* has to associate [Messages] with [Calls]
What if we did this?
Call* CallManager::getCall(/*whatever msg.call_id() returns*/)
call_id is some unique identifier assigned to the call when it's created. It probably just an integer.
{ Call* result = 0; if (is_new_call(id)) result = lookup_call(id); else result = create_call(id); return result;
}
The client would then do:
callManagaer.getCall(msg.call_id())->process(msg);
yes but. "who is the client" is the question I'm trying to ask!
How long does a Message object live? Is it just a data packet that needs to be passed to some Call object processed and then dumped?
class Client { msg_received(Msg&);
};
// called by Channel Client::msg_received (Msg& msg) { callManagaer.getCall(msg.call_id())->process(msg);
}
now [Client] *could* be [Channel], but that seems to be loading too much on [Channel]. I'd been trying to make [Client] be [CallManager].
Not at all. Channel makes Message objects right?
|
yes
| Quote: | And Message objects know what Call they should be associated with.
|
usually. NewCall messages also cause the call to be created.
| Quote: | What would happen if you put each Call on its own thread?
|
I don't see a problem with that
| Quote: | Then the Channel would just pass the message to the correct Call and let Calls do what they will.
|
ok... but that means Channels will also create calls
| Quote: | Even if each Call isn't on a seperate thread, that's what Channel should be doing. Is there more than one Channel?
|
yes
| Quote: | If so, can messages bound to one Call come in from different channels?-
|
yes
-- Nick Keighley |
| |
| Page 1 of 2 .:. Goto page 1, 2 Next | |
|
|