Luke said:
More to the point, this is a rather spurious counterexample. If happy
can be appropriately specified by a bool, it's rather questionable to
store it as a string.
I was just giving an example to show that there could be a lot more
going on.
For one thing it makes more sense. The specifics of what
setHappy(true) vs. setHappy(false) are not entirely clear: is
setHappy(false) just not being happy or being unhappy? There is no
doubt what the intention of the above is.
class Person {
public:
enum Mood { happy, sad, ambivalent, angry, content };
void setMood(Mood m) { mood_ = m; }
// deprecated in documentation.
// kept, with implementation changed, for compatibility as needed.
void setHappy(bool flag) { setMood(happy); }
private:
Mood mood_;
};
This is a clean, extensible generalization.
You have changed the behavior of setHappy!! Now there is absolutely no
use in the parameter at all!! You are implementing my "solution" even
as you argue against it only you are just implementing half of it,
leaving the rest undone.
Yes, it is a silly mistake and not what you meant...but look how easily
that happened. When you see "setHappy" you are not considering the
alternative. It happened because it is more natural to think of
setHappy as having the effect of making the object happy, not in making
it not so. This is actually a rather important part of the discussion
and a fundamental issue.
Were we to take the
approach you suggest, we'd wind up with something more like:
class Person {
public:
void setHappy();
void setUnhappy();
void setAmbivalent();
void setAngry();
void setContent();
private:
// implementation details
};
Incorrect. First as I mentioned before when there is a "flag" variable
you usually have room to improve your design. Second none of those
functions should exist. Just like you can't reach into someone's brain
and make them happy, angry, whatever...you shouldn't be able to do so
with an object. "setter" functions are another indication of bad
design; by what right does one object have to touch the privates of
another? Instead, actions that Person takes or that are performed on
Person should create this effect. In other words there should be no
interface for *setting* a person's mood although there may be reason
enough to "interpretMood()" (just in case you might behave differently
with an angry person vs. a happy one) which would likely return some
sort of mood object (that might have query functions like
"mightBeSnappy()" better yet you might want that query in Person itself
and avoid returning a mood at all) for unlike you are expressing, moods
are not mutually exclusive and can be very complex.
Also, why shouldn't there be setX() for certain common sets of moods?
For instance, setContent() might also make a person happy, no?
Operations that are commonly done in a set might well be implemented in
a function instead of leaving it all to the client every time.
Finally you are unfairly misrepresenting the possiblities. Just as you
can "adjust" your interface with the addition of an extra function so
can I:
setMood(Mood m) { mood_ |= m; }
unsetMood(Mood m) { mood_ &= ~m; }
makeHappy() { setMood(happy); }
makeUnhappy() { unsetMood(happy); }
I have just performed the same refactoring algorithm you did and the
result is very much the same. There is no /comparitive/ scaling issues
here. In fact as you can see, my code can represent a vast count more
moods and combination of moods than yours can. Your way would require
that I build my mood mask and pass it in even though I probably only
want to change a particular aspect of the mood.
So while setHappy() and setUnhappy() can seem like a good idea because
it "separates concerns" or "hides implementation details," really all
you've done is treat two particular data values (true and false) as
special cases and written separate interfaces for each. It's easy to
do this for bool because there are only two values in the domain. With
an enumerated type, it's irritating but possible. With pretty much
anything else, it's completely impractical. It doesn't separate
concerns, it tries to represent one thing as two. It produces tighter
coupling, in fact, because clients are tied to the number of methods in
your interface.
Such drastic growth indicates to me that an object is needed to
represent mood. Neither of our "solutions" scale. The problem
actually stems from the very fundamental issue of having setter
functions and state flags.
I'd rather have functions that made sense and dealt with cohesive,
discrete responsibilities. I'd rather not have two functions modifying
the same state needlessly.
Well that depends. What is a person likely to do upon becomming happy
vs. unhappy? Such things could easily grow beyond the feasability of
being done in a single function. At this point you have a function
performing two responsibilities...doing a bunch of stuff to make a
person happy as well as doing a bunch of stuff to make a person not
happy. Is a person likely to do the same thing when becomming happy as
they would when becomming not happy? I doubt it. Now you have a
single function performing two *unrelated* tasks that have the sole
relation of an input flag.
Imagine:
setHappy(bool flag)
{
if (flag)
{ laugh(); smile(); beNice(); throwParty(); }
else
{ cry(); drownInBooze(); beAntisocial(); }
happy = flag;
}
And that is a very simple case but notice how very little is actually
related at all. Becomming happy vs. unhappy have drastically different
effects on the Person in question.
Consider now that you have people with different cultures that have
different moods based on different events. Obviously the very concept
of having a setter for happyness is flawed as this should be done by
the object itself in response to what is done with/to it.
There is nothing wrong with the introduction of a conditional
statement, if it is a clear and effective representation of the logic
being implemented. A conditional statement within a single function
body is less complexity to manage than two separate, but semantically
interwove, functions. If, as in my example above, the requirements
require you to flex in a direction you didn't plan for, such that your
scheme is not extensible, you wind up having to scrap it and start over
with a totally new interface.
You do anyway. My functionality is almost exactly as "extensible"
(actually should say non-extensible) as yours.
The best protection against future change is not to introduce
additional complexity in the name of being flexible, but to represent
concepts in a clear and expressive manner, use good judgement about
appropriate levels of abstraction, and assume as little as possible
about the nature of future changes.
You're being unreasonable.
So you say but the whole thing fell appart, didn't it. Think about how
much of the behavior of a Person would be altered by a mood. Would
"performGreeting()" be the same for example? ( happy ? say("Nice to see
you"):say("Go to hell.") ) I think you are looking at a state and when
you look at a state there is a pattern that can be used, even in simple
cases, that is VERY extensible and clean.
Consider also how much of a person's mood is based on how they
interpret what happens to them and how little is actually based on the
events themselves. No two people feel the same way about the same
thing. Public setter functions take responsibility from where it
belongs, the object itself, and place it in objects that have no
business making these decisions.
There is a difference between thinking ahead a little, or just using
common tools available to you that are known to scale, and "analysis
paralysis". Just looking at something like a public setter and state
flag warned me that there were issues...and there are. Counter and
switch variables have their place but usually limited to a small scope
like a single function or less. If there IS a good reason to have a
flag variable embedded in an object then it is almost never a good
thing to have public setter functions for that flag...why do external
objects need to set your internal state?? Truely there is almost never
a good reason and such constructs almost always indicade poor design
decisions.
In other words your argument for using "flag" in a variable name under
such circumstances is flawed by the fact that such circumstances are
flawed.
Now, occasionally you may run into a reason why it is necessary to do
all of those things that indicate bad design but I have yet to run into
one that wasn't, "because it is already like that and I'm not allowed
(or don't have time) to change it." Occasionally there is, "I couldn't
think of nothing better," but I don't think of that as a *good* reason.