Re: Short Refactoring Tip: let the object do it for you
by dws (Chancellor) on May 19, 2003 at 21:46 UTC
|
If we need to change those names, this code will be break pretty quickly.
Not only that, but forcing a client to write
if ($po->status->name eq 'Pending') { ...
requires them to know that a purchase order has a status, and that the status has a name. Both of those details are probably inappropriate for client code that deals with purchase orders to have to know. It also makes purchase orders harder to unit test, since you then have to either unit test a Status at the same time, or rig up mock objects to use in places of a Status. By structuring the API like
if ($po->can_send) { ...
you reduce the complexity of testing.
There's a design principle--whose name I'm blanking on (Demeter's Law -- thanks, lachoy)--that says that if you find yourself writing expressions that navigate more than one level into an object structure, the top-level object needs a different protocol. In the case above, can_send is that new protocol, and status can be made private (e.g., by renaming it to _status).
| [reply] [d/l] [select] |
|
| [reply] |
Re: Short Refactoring Tip: let the object do it for you
by demerphq (Chancellor) on May 19, 2003 at 22:49 UTC
|
Id like to point out that this doesn't scale very well. Sure you have a valid point in a limited case, such as when there are only a few relevent states. But what happens when there are lots? Or the system is dynamic? Or if you want to say things like
if ($po->status->name =~ /^(sent|pending|waiting|procratinating|delaye
+d)$/i) {
I'd rather write the above than call a bunch of methods, let alone write the methods in the first place. :-)
I thinks its unlikely I would write an object that require on a regular basis the double indirection like that. And if I did face a situation like my example above I'd probably do something more like
my $status=$pos->status;
if ($status->is('Pending')) {}
if ($status->in(qw(sent pending waitning procrastinating delayed)) { }
The issue here for me is that the use of 'Pending' isn't wrong, its the use of 'Pending' in such a way that I can't intercept that usage that is wrong. The subs above allow use to redefine the back end behaviour just as yours does, but its a lot more flexible an approach, while still providing us a way to block inapropriate checks. For instance $status->is('Pneding') should produce an error. Albeit a run time one. Oh well you cant have everything can you? :-)
Anyway, I use both styles as seems appropriate for the task at hand, often both at the same time even. :-)
---
demerphq
<Elian> And I do take a kind of perverse pleasure in having an OO assembly language...
| [reply] [d/l] [select] |
|
my %STATES = (
sent => sub { . . . },
pending => sub { . . . },
waiting => sub { . . . },
procrastinating => sub { . . . },
delayed => sub { . . . },
);
# Make 'pending' and 'waiting' the same thing
$STATES{pending} = $STATES{waiting};
# Later
$STATES{$status->state->name}->();
It's faster than a speeding regex. And I personally think dispatch tables are sexy.
---- I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
-- Schemer
Note: All code is untested, unless otherwise stated
| [reply] [d/l] |
|
| [reply] [d/l] |
|
| [reply] [d/l] |
Re: Short Refactoring Tip: let the object do it for you
by castaway (Parson) on May 20, 2003 at 06:36 UTC
|
| [reply] |
|
Ah, but it isn't common sense. In old-time C, for example, you wouldn't write something like that. Instead, you would write something like:
#define STATUS1 (1 << 0)
#define STATUS2 (1 << 1)
#define STATUS3 (1 << 2)
#define STATUS4 (1 << 3)
...
typedef int Status;
...
Status product_status = 0;
...
if (product_status & (STATUS1 | STATUS2))
{
// Do stuff here
}
It's imperative programming, man! You gotta be in control or you lose your job! (Think about it - if you couldn't point to incomprehensible code, your PHB would find someone who could. I mean, if anyone could write code that works, then how do you justify your 6-figure consulting fee?)
Also, writing good class interfaces requires both time and understanding of the business domain. Maybe your world allows for these, but no application I've ever had the (mis)fortune to work on has ever been in that most blessed of states. (No, that isn't Texas, either.)
------ We are the carpenters and bricklayers of the Information Age. Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement. Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified. | [reply] [d/l] |
|
I have no idea what common sense has to do with C.. (Or rather why something shouldn't be, just because someone did it in C differently).
(For the record, I avoid C/C++ like the plague, or try to..)
Not everyone *can* write code that works, much less code that is maintainable, usable etc. Obfuscating for the sake of it never appealed to me.
We do take time here and define our interfaces (between products+components) - it saves lots of hassle later.
C.
| [reply] |
Re: Short Refactoring Tip: let the object do it for you
by mvc (Scribe) on May 21, 2003 at 23:06 UTC
|
You are on the right road, but it seems as if nobody has mentioned your solution is not good OO design:
BAD: if ($po->status->name eq 'Sent') ...
ALMOST AS BAD: if ($po->can_send) ...
GOOD: $po->send
You are breaking encapsulation in all but the last case. Getters are a design smell. They are just a way to make private data public.
Instead of asking objects questions, tell them to do things. The object will figure out how to do it.
Allen Holub says about this:
"Never ask an object for the information you need to do
something; rather, ask the object that has the information
to do the work for you."
| [reply] |
|
[% IF item.can_receive %]
<input type="text" name="received" value="[% item.received %]" cla
+ss="input2"/>
[% ELSE;
item.received;
END %]
In the above case, I am querying whether or not an item can accept a value for given attribute (those are Template Toolkit directives). If so, an input box is presented to the end user, with the current value filled in. If not, they only see the value. As this data can be represented in many other ways (and not just on HTML forms), it doesn't seem to make sense to have the object present HTML code to the end user. I like the idea of moving the conditional logic into the object, but I don't see a clean way of doing that.
Cheers,
Ovid
New address of my CGI Course.
Silence is Evil (feel free to copy and distribute widely - note copyright text) | [reply] [d/l] |
|
Here we have a classic conflict of design forces:
- Encapsulation
- Seperation of concerns (cohesion)
If you put the UI code in the object, you are encapsulated but your object is now doing its regular work + UI work (low cohesion). If you keep it as is, you get clean seperation of concerns, but break encapsulation.
There are other solutions that could balance these forces better. The balance of forces you want depends on your requirements.
You could, for example, have the template code look like this:
<% item.getReceivedUI("<input ...>") %>
The item object getReceivedUI() will check if receive is possible, if so interpolate the parameter as a TT template with item.received in the stash. If not, it returns item.received. Then you remove the UI code from the item object by delegating the actual interpolation to some strategy object, that may use TT or anything. Thus the item knows not about UI, just about presentation logic. The template is still in charge of the UI. Is this extra work worth it? Depends on your requirements.
| [reply] [d/l] [select] |
|
| [reply] [d/l] [select] |
|
Do you define "predicate function" here as "method that returns a boolean value"? If so, then it reveals something about the object state- information that should be encapsulated.
What if you decide that "you can always send!". Then you have to remove the conditionals from all clients of your object. Or what if there are now 3 states for sending, each requiring a different send method? We want the clients to know as little as possible about the object. Knowing only one method, is better than knowing a method, its return type, and another method.
"...The key question is: How much of one module must be
known in order to understand another module?..."
Yourdon, Ed and Larry L. Constantine. Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design. (1979) On Coupling, p.85
Breaking encapsulation here increases coupling between the clients of the object and the object itself. Note I am not saying: never use boolean methods. There may be other design forces besides "encapsulate hermetically", so we do not know which solution is best here.
Code like: "if an object can do this, tell it to do it" smells.
| [reply] |
|
|
|