This question might solicit debate and/or argument, so if it get's closed I think the answer to this question will be: it depends. But hopefully this question can be answered with a good reason to choose one design decision over the other.
When I was viewing this answer, to a question about OOP, it occurred to me that I was meaning to ask this question sometime.
Let's assume, that the requirements and technicalities of the project permit you to choose either of the following options:
option a:
$service = new SendService();
$recipient = new Recipient( 'John', 'Doe', 'john.doe@example.com' );
$message = new Message( $service );
$message->setContent( 'Lorem ipsum...' );
$message->send( $recipient );
In other words: Let the message communicate internally with the service, and let it pass the recipient and itself to the service.
option b:
$service = new SendService();
$recipient = new Recipient( 'John', 'Doe', 'john.doe@example.com' );
$message = new Message();
$message->setContent( 'Lorem ipsum...' );
$service->send( $message, $recipient );
In other words: Let client code pass the message (and recipient) to the service.
Is there any particular (design) reason to choose one over the other, or is this simply a matter of preference?
In this particular cas开发者_如何学编程e I would think the latter option more closely resembles a real live example of sending a postal card to the postal office for instance, in stead of cramming a postal office into a postal card, if you get my drift. And that it therefor makes more sense to choose that option. Or do real life metaphors not justify design choices?
As I see this, the use case can be resumed to:
I have a
message
, and amessage service
. I need to send themessage
somehow, using themessage service
The alternatives are:
A - The
message
knows themessage service
and knows how to send itself.
And:
B - The
message
is not aware of themessage service
. Themessage service
receives themessage
and sends it.
As I see it, in the first case, something like this is happening sonner or later (pseudocode):
Message {
send(MessageService) = {
// Do lot's of stuff.
MessageService.send(this)
}
}
So, why not just go with option B
in the first place?
$message = new Message( $service );
That makes litte sense. You're forcing your user to declare up front that a given message will be used by a particular service. In general, the message doesn't need to know anything about the service it's being sent through. However, the service -does- need to know about the message.
You're effectively limiting your flexibility for little to no gain. What if you want to send it via email, but if it fails, you want to be able to spool it to a service which will retry. Do you want to write:
$message = new Message($emailService);
if (!$message->send()) {
$message2 = new Message($spoolService);
$message2->clone($message);
$message2->send();
}
Or:
$message = new Message();
if (!$email->send($message)) {
$spooler->send($message);
}
What if you're writing that spooler - and you want to persist those messages and read them back. Now your reader needs to know about your service too, to be able to create the messages in the first place, etc. In general, a message is a self-contained object - keep it that way.
I can't personally envision a single solid reason to do option a.
The one that needs the least freedom relative to this contract should receive the other one. Usually, in a case like this with a data object and a service, the service should receive the data object. But not always. In particular, in applications where operations are chained together dynamically, e.g., as Function objects, you'll want the Message to accept the operations to be performed.
As noted earlier, passing the service in the constructor of the message makes little sense. But I fear that this is not what you meant to ask about - in other words, I think you probably "should" have written
$message->send( $service, $recipient );
to keep it apples and apples.
A key degree of freedom to think about is testability.
Sometimes the answer is "neither" - where a third class is needed to encapsulate the particular instance of a send operation.
Well, I do think this is probably an open question unless you had a specific circumstance in mind; but it's not totally open. Personally, I think that unless you had a specific technical reason for doing so, there's no particular reason to break the metaphor of the service taking an object; however, there is no hard and fast rule that you can't do things like having the message send itself. (In fact it reminds me of some of the earlier examples I read when I was just picking up OO.)
See, though, perhaps there you have a technical reason to do so. Say, your application has several possible Service
s that a Message
might be sent through, depending on a configuration that maps a particular client system to a particular service. The clients have no particular reason to need to know anything about the specifics of the service, the only thing that the clients really need to do is compose a message and send it off. Perhaps this was even already mostly implemented only the messages were simply logged by each component, instead of being sent through a service, and it's a production system and you need to make the absolute minimum of changes to the system. Or perhaps this was just decided as a better system metaphor by the team.
So, a client system requests a Message
from some sort of MessagingFacade
object, which instantiates the Message
, gives it a reference to the Service
, and passes it to the client. The client then can do whatever it needs to with it, say, passing it around to multiple different parts of the system to collate status information, with any particular part of the system possibly being the terminal point for writing out the Message
depending on the system status and logging level, and you want the terminal entity to be able to send it without requiring (or allowing) all the classes that might be the sender to know about the MessagingFacade
.
In a situation like this, it seems reasonable to me that the Message itself implements the Send verb so that the message composers can just complete the message and call theMessage.Send
.
So, despite the fact that there may be 'rules of thumb' about things like this, part of writing real life systems is recognizing that it's okay to break these rules, especially when following the 'normal' rules end up breaking metaphors elsewhere in your system. The answer to this question, as with many of the 'which is better'-type questions, is always: what makes sense in my particular situation? Does one or the other simplify other parts of my system? Does one or the other break any assumptions in the already implemented parts of my system? And so forth. :)
So:
"Is there any particular (design) reason to choose one over the other, or is this simply a matter of preference?"
I think the answer to this is: we don't have enough information to decide. Any design reasons would depend on the particulars of your system. If we are assuming, as in your exposition, that there truly are no technical differences at all between the two choices, then I think I would go with the traditional view of a service sending a message, BUT I also think there is an argument to be made in the other direction. If we let a message send itself, we have reduced the coupling in the system; originally everything (possibly many classes) that was going to send a message had to know about both Message
and Service
, whereas we've only increased coupling between Message
and Service
(just those classes).
"In this particular case I would think the latter option more closely resembles a real live example of sending a postal card to the postal office for instance, in stead of cramming a postal office into a postal card, if you get my drift. And that it therefor makes more sense to choose that option. Or do real life metaphors not justify design choices?"
I would say that real-life metaphors do not in and of themselves justify design choices. They only justify in the case where the model of the system does closely model real life. It's hard to give a general answer here, because there are so many real-life metaphors, and so many possible ways to implement a system. In any particular case, you can argue for whether or not modeling it against the real-world is the right decision, but in the abstract, there are just far too many exceptions to give a firm answer.
精彩评论