is-a hierarchy
sir,
i would like to know that what is the difference between "is-a" relationship and "has-a" relationship.
&
i have read that if we dont have "has-a" relationship then there would be duplication of work, but i cant understand that what if there is no "is-a" relationship, what could be the disadvantages?
thanks.
[348 byte] By [
rabbia1] at [2007-9-27 18:50:14]

> sir,
>
> i would like to know that what is the difference
> between "is-a" relationship and "has-a" relationship.
Human "is-a" animal.
Human "has-a" head.
> &
> i have read that if we dont have "has-a" relationship
> then there would be duplication of work, but i cant
> understand that what if there is no "is-a"
> relationship, what could be the disadvantages?
>
I really don't know what you are talking about. I believe you are paraphrasing something and it isn't clear what you mean.
A common misconception for "is-a" relationships is to base it on common functionality. For instance the following code
class Animal
{
}
.
class Dog extends Animal
{
}
.
class Cat extends Animal
{
}
.
Now if the behavior 'walk' is added where should it be added? The simple idea is to add it to Animal. The reasoning is that both Dog and Cat walk and thus the common functionality can be added to Animal.
That reasoning is wrong.
The only reason walk should be added to Animal is because Animal (not Dog and Cat) needs the behavior walk. It might be that that is the case. But it might not be as well. For example what happens if a new class called "Whale" is added to the above. Now Animal has a method that can not be logically implemented for one of the children.
In the case where "Whale" will be added then the correct design decision would be that Dog and Cat both must have a walk method. And since both walk in a similar fashion then code will be duplicated for both.
Perhaps this is what you are referring to?
Keep in mind that this duplicate functionality need not be a problem. In the design phase the duplication is irrelevant. In the implementation phase it is easy to crete a "helper" class which implements the required functionality and thus removes the duplication.
Like this...
class Animal
{
}
.
class FourLeggedHelper
{
void walk() { MoveRightFrontFootForward();...}
}
.
class Dog extends Animal
{
void walk() { FourLeggedHelper.walk(); }
}
.
class Cat extends Animal
{
void walk() { FourLeggedHelper.walk(); }
}
.
A WalkingAnimal would be superior to the above design
> A WalkingAnimal would be superior to the above design
I suppose you mean an intermediate class in the hierarchy?
How so?
Since shared functionality is not a design reason for using a parent class, how do you justify adding such a class in the design.
Or are you referring to adding it as an implementation step? I am not sure I would be comfortable inserting a new hierarchy level, in the existing design hierarchy, purely for implementation reasons.
It's a good idea to edit the class hierarchy.There may be many WalkingAnimals, can you add the walk() method to every walkinganimal?
lvkai at 2007-7-6 20:16:48 >

It's a good idea to edit the class hierarchy.There may be many WalkingAnimals, can you add the walk() method to every walkinganimal?
lvkai at 2007-7-6 20:16:48 >

I've seen this example in another place and one (good) suggestion was to make the behaior walk as an interface that could be added to the subclasses:
public interface Walker
{
public void walk();
}
public abstract class Animal
{
}
public class Dog extends Animal implements Walker
{
public void walk();
}
This way, you can add a Whale to the Animal heirarchy and it isn't saddled with a walk() method that is nonsense to it. In this case, you define a new interface and have the Whale implement it:
public interface Swimmer
{
public void swim();
}
public class Whale extends Animal implements Swimmer
{
public void swim();
}
NOTE: It's late, so please be kind if I have made any typographical errors.
I agree, and combined with jschell's code would yield a very nice result:
class Animal
{
}
public interface Walker
{
public void walk();
}
class TwoLeggedWalker implements Walker
{
public void walk()
{
MoveRightFootForward();
...
}
}
class FourLeggedWalker implements Walker
{
public void walk()
{
MoveRightFrontFootForward();
...
}
}
public interface Swimmer
{
public void swim();
}
class TailSwimmer implements Swimmer
{
public void swim()
{
MoveTailUp();
...
}
}
class Dog extends Animal implements Walker
{
void walk()
{
FourLeggedWalker.walk();
}
}
class Chicken extends Animal implements Walker
{
void walk()
{
TwoLeggedWalker.walk();
}
}
public class Whale extends Animal implements Swimmer
{
public void swim()
{
TailSwimmer.swim();
}
}
But I would like a bit of elaboration on jschell's comment:
"Since shared functionality is not a design reason for using a parent class, how do you justify adding such a class in the design."
Given your comment, how do you ever justify refactoring the hierarchical structure of your design? Often times, increasing the fineness of granularity and specificity of object families comes after an initial design/implementation. Also how do you differentiate between "shared functionality" and refined levels of abstraction?
Though somewhat contrived, the following example illustrates my point:
class Light
{
private int _watts;
private boolean _isOn;
public Light(int watts)
{
_watts = watts;
}
public void turnOn()
{
_isOn = true;
}
public void turnOff()
{
_isOn = false;
}
public boolean isOn()
{
return _isOn;
}
public int getWatts()
{
return _watts;
}
}
class StrobeLight extends Light
{
private int _flashesPerSecond;
public StrobeLight(int watts, int flashesPerSecond)
{
super(watts);
_flashesPerSecond = flashesPerSecond;
}
public int getFlashesPerSecond()
{
return _flashesPerSecond;
}
}
class SpotLight extends Light
{
private int _color;
public SpotLight(int watts, int color)
{
super(watts);
_color = color;
}
public int getColor()
{
return _color;
}
public void setColor(int color)
{
_color = color;
}
}
Should the implementation of Light be considered improper because it encapsulates the common functionality of a light? Is this not the very nature of commonality which allows us to abstract from the concrete?
Now, WRT the refactoring issue, should the following modifications be considered improper?
class Light
{
private int _wattage;
public Light(int wattage)
{
_wattage = wattage;
}
public abstract void turnOn();
public abstract void turnOff();
public boolean isOn();
public int getWattage()
{
return _wattage;
}
}
class SwitchLight extends Light
{
private boolean _isOn;
public SwitchLight(int wattage)
{
super(wattage);
}
public void turnOn()
{
_isOn = true;
}
public void turnOff()
{
_isOn = false;
}
public boolean isOn()
{
return _isOn;
}
}
class DimmerLight extends Light
{
private int _brightness; //from 0 to 100
public SwitchLight(int wattage)
{
super(wattage);
}
public void turnOn()
{
_brightness = 100;
}
public void turnOff()
{
_brightness = 0;
}
public boolean isOn()
{
return (_brightness > 0);
}
public int setBrightness(int brightness)
{
_brightness = brightness;
}
}
class StrobeLight extends SwitchLight
{
private int _flashesPerSecond;
public StrobeLight(int watts, int flashesPerSecond)
{
super(watts);
_flashesPerSecond = flashesPerSecond;
}
public int getFlashesPerSecond()
{
return _flashesPerSecond;
}
}
class SpotLight extends DimmerLight
{
private int _color;
public SpotLight(int watts, int color)
{
super(watts);
_color = color;
}
public int getColor()
{
return _color;
}
public void setColor(int color)
{
_color = color;
}
}
> Since shared functionality is not a design reason for using a parent class, how do you justify adding such a class in the design.Taxonomy!
If I can elaborate, I agree with the interface Walkable, but
this shouldn't be implemented everywhere when it can be implemented
only when needed.The helpers are ok too, but it's not like we
want every animal to have to learn to walk again. By being part of
a certain group, the animal just KNOWS how to walk. A Dolphin and
a Whale swim similarly, so they can share the same implementation
of swimming depending on the sophistication of the model.
Animal
...Mammal extends Animal implements Furry, WarmBlooded
..........Canine extends Mammal implements Walkable, Carnivorous
..............Wolf extends Canine
..............Dog extends Canine
..........Cetacean extends Mammal implements Swimmable
..............Whale extends Cetacean
..................Orca extends Whale, implements Carnivorous
..............Dolphin extends Cetacean, implements Carnivorous
So there you have it, Genetics as science intended. A RightWhale is a Whale is a Cetacean is a Mammal is an Animal.
And all Cetaceans can swim. So we might as well implement Swim at the Cetacean level and only override as needed. Similarly, Whales aren't
Fish, but both implement Swimmable. Implementing Swimmable for
every fish is painful!
In the light example above, the first example is more elegant than the second. Override turnOn/turnOff if needed, but a light should have
certain basic properties.
For instance, a red light should not have to implement "TurnOn" and "TurnOff", it should just be a light that returns a different color. To have to implement "TurnOn" and "TurnOff" for every single light is not making things easy for the developer.
What about a CandleLight class?
In your case, the abstraction for Light seems to assume that all lights are electric. This is what jschell was referring to when he made his comments. Don't add functionality at the abstraction level unless it is true for ALL objects of that type. Otherwise, you will have to completely refactor your object heirarchy.
BTW, if Cetacean extends Mammal, how is it that a Dolphin or Whale is able to fulfill the contract of implementing Furry?
> Given your comment, how do you ever justify refactoring the
> hierarchical structure of your design? Often times, increasing the
> fineness of granularity and specificity of object families comes
> after an initial design/implementation. Also how do you differentiate
> between "shared functionality" and refined levels of abstraction?
The problem domain specifies the realities of the design.
A pet store is never going to need a class called "Whale". While Sea World will. But even Sea World isn't going to need to model amobea.
For a limited problem domain, say a Horse Ranch, the class "Animal" can include the behavior walk because all of the animals in the domain walk and always will. But walk is not being put in there because the children implement walk but rather because the parent should exhibit the behavior.
meadandale,
That's exactly my point. The only way to conceive of an abstraction is to identify what disparate entities have in common. And given how very rarely we identify abstractions perfectly the first time (if ever), an initial object hierarchy must be refactored.
Using your comment as an example, wouldn't the concept of CandleLight reveal that Light was not abstract enough, and should be ElectricLight.
What I do not buy is the idea that it invalidates the abstraction (and thus commonality) of Light out of StrobeLight and SpotLight. If your world consists only of those concretes, then the abstraction is valid... but now I feel I'm verging into epistemology.
Perhaps a better way to explain my position would be if, given the current Light example, we find that one of our concretes (say, SpotLight) itself is really an abstraction, and can be broken down into StageSpotLight, and SearchSpotLight. What would that mean for the functionality currently in SpotLight? If StageSpotLight and SearchSpotLight are truly SpotLights, then they should share some common funtionality, otherwise, why would you think they were concretes of the same abstraction in the first place?
Ceteceans aren't hair cells, since "furry". Constraint of problem not specified earlier implies I can model an Amoeba legally :)
Err, can have hair cells. Typo.
I agree with jc on common functionality, else we end up advocating all interface classes but leaf nodes...which is counterproductive.
I agree with jshells comments on how the domain (or Universe of Discourse, or UoD) affects what is still a reasonable abstraction, and what isn't.
I also agree with his comments that just because the currently implementing subclasses of a class share functionality, it shouldn't be move to the current superclass. But I most certainly don't agree with his design to use an external Helper class to implement common functionality. That's procedural programming, not OO!
I agree with the some of the other posters that the better way would be to insert an extra class/interface into the hierarchy (either the WalkingAnimal if your UoD contains only animals, or the Walker interface, if your UoD also contains other classes (Robots, perhaps) that may or may not be able to walk).
Common functionality is a reason to create a new superclass that contains that functionality, even though it is not necessarily a reason to add that functionality to an already existing superclass.
hhora at 2007-7-18 15:21:56 >

> But I most certainly don't agree with his
> design to use an external Helper class to implement
> common functionality. That's procedural programming,
> not OO!
>
That sounds idealistic.
Do you use 'int' rather than "Interger"? 'int' is not OO. Why do you use it then? Because by using it you make the application better.
> I agree with the some of the other posters that the
> better way would be to insert an extra class/interface
> into the hierarchy (either the WalkingAnimal if your
> UoD contains only animals, or the Walker interface, if
> your UoD also contains other classes (Robots, perhaps)
> that may or may not be able to walk).
>
Nope. Not in most cases.
Using a helper is not an ideal solution but it is more correct than inserting another layer. (Again in most cases.)
> Common functionality is a reason to
> create a new superclass that contains that
> functionality, even though it is not
> necessarily a reason to add that functionality to an
> already existing superclass.
No it isn't. It is an excuse.
It may sound idealistic, but aren't we discussing OO design here? I feel your design is not OO, that's what I was trying to point out.
I don't understand why having an external helper class is "more correct" than inserting a layer in the class hierarchy. More correct OO? Not by my definition of object-orientation.
I don't agree that common functionality is "an excuse" for creating a superclass that captures that functionality. Why have a class hierarchy, other than to capture common functionality in superclasses, and specific functionality in subclasses? Your initial remarks were about putting walk() in Animal, because the existing subclasses of Animal both happened to be walking animals. I fully agree that this is wrong, it's bad modelling, because not all animals have to be walking. But I believe your solution with the FourLeggedHelper is bad modelling too. By introducing a WalkingAnimal class (with possible subclasses of TwoLeggedWalkingAnimal and FourLeggedWalkingAnimal), you get a clean, clear class hierarchy that puts walking functionality where it belongs: with the animals-that-can-walk themselves, not in some otherwise nondescript helper class. An animal knows full well how to walk by itself, if it is a WalkingAnimal. It doesn't need some puppeteer to move its legs from the outside when someone is telling it to walk.
And even if you don't want to accept this from an OO point of view, please accept it from a coding efficiency point of view: in your design, every animal that wants to walk needs to implement its own version of walk() that calls the helper. By introducing a WalkingAnimal class, you have to implement walk() just once (or twice for the subclasses with Two and Four legs), and every subclass of WalkingAnimal can automatically walk.
hhora at 2007-7-18 15:21:56 >

I have next to nill experience in OO design, but I am a Zoologist (BSc Zoology Major) and the way I see it:
Animals like the wolf have inherited these things:
fur, a carnivorous habit, warm blood
Animals like the orca have inherted these things:
the ability to swim, a carnivorous habit
Therefore surely they should be inherited in OO design as well.
However something like catchable is not something that they inherit, but both a wolf and an orca can be caught so that should be an interface.
keep in mind that any design one does is meant to solve a specific problem. when we haggle over animal characteristics and what responsibilities should be assigned to "dog" vs. "whale" in some out-of-thin-air design, we're missing the point, since it's highly probable that we aren't writing some zoo simulation.
appropriate assignment of responsibility to classes in the solution space is probably the most important criterion for a good design. but it must be done in some context. i'm disappointed that the "animal" example is used so frequently to talk about the concepts of inheritance and polymorphism without it dissolving into arguments, because without a specific problem to solve, there is not good answer. even with a specific problem to solve, there is no "right" answer--just "better" and "worse" answers. as you become a better developer, you learn to detect the bad odors some designs emit as they evolve, and you refactor accordingly to eliminate the stink.
i learned the "100% is-a" rule to decide whether two concepts A and B belong in a general-specific relationship with each other:
1) it must make sense to say "B is-a A" or "B is-a-kind-of A"
2) 100% of the messages you send an instance of A must make sense when received by an instance of A.
both of these criteria must be satisfied. so while a Whale is-a-kind-of Animal, it in all likelihood cannot respond meaningfully to the walk() message.
> And even if you don't want to accept this from an OO point of view,
> please accept it from a coding efficiency point of view: in your
> design, every animal that wants to walk needs to implement its own
> version of walk() that calls the helper. By introducing a
> WalkingAnimal class, you have to implement walk() just once (or twice
> for the subclasses with Two and Four legs), and every subclass of
> WalkingAnimal can automatically walk.
ok, as long as we're on the animal example: i disagree that you have to implement walk() just once in your example, since different kinds of WalkingAnimals will no doubt walk at different rates of speed, with longer or shorter strides. even instances of the same kind of Animal will exhibit different walking characteristics and bigger or larger paw prints. so we're looking at a much bigger beast here, so to speak. you see, you can carry this example as far as you want, but now we're getting into over-design. do you want to completely model reality, or do you just want to solve a specific problem? don't go building a framework just yet, or solving every conceivable problem in a domain. solve a concrete problem first, solve a similar problem the same way, then abstract.
i agree with some other posters in that in all but the simplest problems you won't get it right the first time. make your design amenable to refactoring.
also, don't over-abstract. let your design evolve, and as more abstract concepts reveal themselves, build an inheritance hierarchy. but only if you must. a big trap is to over-design and build elaborate inheritance hierarchies that 1) ultimately aren't needed, and 2) are misapplied--some subclasses respond no-op to superclass messages, for example.
> It may sound idealistic, but aren't we discussing OO
> design here? I feel your design is not OO, that's what
> I was trying to point out.
>
> I don't understand why having an external helper class
> is "more correct" than inserting a layer in the class
> hierarchy. More correct OO? Not by my definition of
> object-orientation.
OO implementation - then yes it is.
OO design - then no it isn't correct. That is because adding a layer due to a implementation issue is always a compromise. A helper, by its nature, is more encapsulated than another layer. This means it is less likely to increase maintenance costs in the future.
>
> I don't agree that common functionality is "an excuse"
> for creating a superclass that captures that
> functionality. Why have a class hierarchy, other than
> to capture common functionality in superclasses, and
> specific functionality in subclasses?
The only reason (again in most cases) is because a "is-a" relationship exists in the problem domain (not in the implementation domain.)
> Your initial
> remarks were about putting walk() in Animal, because
> the existing subclasses of Animal both happened to be
> walking animals. I fully agree that this is wrong,
> it's bad modelling, because not all animals have to be
> walking. But I believe your solution with the
> FourLeggedHelper is bad modelling too. By introducing
> a WalkingAnimal class (with possible subclasses of
> TwoLeggedWalkingAnimal and FourLeggedWalkingAnimal),
> you get a clean, clear class hierarchy that puts
> walking functionality where it belongs: with the
> animals-that-can-walk themselves, not in some
> otherwise nondescript helper class.
Nope. That is still adding due to functionality rather than a "is-a" relationship. A layer should never be added (most cases again) due to functionality.
> An animal knows
> full well how to walk by itself, if it is a
> WalkingAnimal. It doesn't need some puppeteer to move
> its legs from the outside when someone is telling it
> to walk.
>
> And even if you don't want to accept this from an OO
> point of view, please accept it from a coding
> efficiency point of view: in your design, every animal
> that wants to walk needs to implement its own version
> of walk() that calls the helper. By introducing a
> WalkingAnimal class, you have to implement walk() just
> once (or twice for the subclasses with Two and Four
> legs), and every subclass of WalkingAnimal can
> automatically walk.
Exactly the reason that many people add parent classes. Or move functionality from children to parent.
The reasoning is wrong. It leads to more coupling and increased maintenance costs. And it breaks the model.
A parent should only every exhibit behavior that the parent should have.
Now you might find during implementation that you did not properly describe the parent during design. And thus some behavior needs to be in the parent. But that is because it belongs to the parent and not because all of the children have it.
A class should reflect behavior that specifies and is relevant to the object. It should not have behavior simply because the children of the class have it.
> Nope. That is still adding due to functionality
> rather than a "is-a" relationship. A layer should never
> be added (most cases again) due to functionality.
What then, is your reasoning for having an Animal class? Because when analysing a zoo (for example) you would initially only find subclasses: Giraffe, Elephant, etc. You then discover that these classes exhibit common behaviour, so you introduce the Animal class to capture common behaviour of Animals (like isAlive() or so). You take a few samples and find out that the animals you are modelling all happen to be walking, so you might be tempted to implement a walk() method in Animal. This might be wrong, as you correctly say, when you don't take the entire domain into account, because there might be animals that cannot walk. In the case of the Horseranch (dealing only with Horses, Pony's and perhaps Cats and Dogs), all animals will be walking, so it might be a correct decision.
In case of the zoo, you find out not all animals are walking, so clearly putting walk() in Animal is a wrong decision... What to do? I would say that the hierarchy is lacking. So you introduce an extra layer to capture functionality common to a significant set of subclasses of Animal: WalkingAnimals. I would say an "is-a" relationship exists between Animal and WalkingAnimal, and between WalkingAnimal and Giraffe (and Elephant), so how does this break the model? You notice behaviour in the problem domain that is common to a set of subclasses you already have. Why would it be better to have a class outside the hierarchy to capture this behaviour, than to introduce a class inside the hierarchy.
> A parent should only every exhibit behavior that
> the parent should have.
Unless the parent models an abstract class (like Animal), then it should exhibit behaviour common to ALL its children.
> Now you might find during implementation that you did
> not properly describe the parent during design. And
> thus some behavior needs to be in the parent. But that
> is because it belongs to the parent and not because all
> of the children have it.
Or you might find during implementation that you completely overlooked a more immediate parent, so you add it to capture the common functionality.
> A class should reflect behavior that specifies and
> is relevant to the object.
I would say walk() is relevant to a WalkingAnimal.
> It should not have behavior simply because the
> children of the class have it.
Unless it is common to ALL children.
(BTW, I am would be interested in a sample implementation of FourLeggedWalker.walk() (no arguments?), in terms of the subclass of Animal that is calling it, I don't quite see how you'd manage in the example you gave earlier in this topic)
hhora at 2007-7-18 15:21:56 >

BTW, you speak about "most cases". If you feel the Animal example doesn't adequately represent "most cases", feel free to switch to different example (hopefully still comprehensible to me and other visitors of this forum, so please no quantum mechanics ;).
hhora at 2007-7-18 15:21:56 >

Maybe a better way of explaining what I mean is this:
A class (any class) should describe behaviour common to all its instances (this means that all instances of its subclasses exhibit this behavious too, of course).
So when you go about modelling this zoo, you find classes like Giraffe, Elephant, Dolphin, Eagle, etc. You notice that all instances of these classes exhibit common behaviour: they can be alive or not, they can a specific skin-type (specifically: hairy, feathered, smooth), etc. You discover/know that the class of objects that exhibit this behaviour is called Animal. So you implement a class Animal that has the methods isAlive(), getSkinType(), etc. Then you discover that all instances of some subclasses of Animal (Giraffe, Elephant) exhibit the common behaviour of walking on four legs.
Why would you suddenly introduce an external helper in this case, instead of a subclass of Animal that is the superclass of these "Animals-that-walk-on-four-legs"?
And to specify my final question regarding the FourLeggedHelper: how would this external helper be able to modify the state of the four-legged-Animal-that-wants-to-walk, without knowing that it is indeed a WalkingAnimal (or FourLeggedWalkingAnimal)? What methods would it call? moveLeftFrontLeg(), moveRightFrontLeg()? What class has these methods and the state of what object do they modify?
hhora at 2007-7-18 15:21:56 >

jschell,
I have replicated and rearranged the order of some of your quotes so as to clarify my response. If by doing this I take something out of context, please let me know.
NOTE: I wish to use the terms abstract and concrete in place of parent and child, respectively.
Also, to remain consistent, I will use behavior in place of functionality since the latter, when used in a discussion of OO design, tends to be imply something about the actual method of implementation (which has no place in this discussion). The exception is when I am responding to a quotation.
[Responding to "Why have a class hierarchy, other than
to capture common functionality in superclasses, and
specific functionality in subclasses?"]
> The only reason (again in most cases) is because a
> "is-a" relationship exists in the problem domain (not
> in the implementation domain.)
What "problem domain" exists without concretes, from which one can abstract behaviors?
> Exactly the reason that many people add parent
> classes. Or move functionality from children to
> parent.
> The reasoning is wrong. It leads to more coupling and
> increased maintenance costs. And it breaks the
> model.
By their very nature is-a relationships are coupled. How is it possible for them to be more coupled?
WRT maintenance costs, is this not a moot point? If the implementation (whether it be direct code, or some helper class) of the defined behavior belongs in a class, it should be there, if not, not. Now I will grant, maintenance costs may play a part in deciding on the method of implementation.
Aside: my remark regarding behavior implementation does not cover how to make that determination.
> A parent should only every exhibit behavior that the
> parent should have.
In a problem domain of concretes, whereby you need to perform abstraction, how can you know what behavior the parent should have other than analyzing the concretes and finding commonality? Mammals are warm-blooded, etc.
Now I wish to analyze a set of responses:
1)
> Nope. That is still adding due to functionality
> rather than a "is-a" relationship. A layer should
> never be added (most cases again) due to
> functionality.
2)
> Exactly the reason that many people add parent
> classes. Or move functionality from children to
> parent.
> The reasoning is wrong.
3)
> Now you might find during implementation that you did
> not properly describe the parent during design. And
> thus some behavior needs to be in the parent. But
> that is because it belongs to the parent and not
> because all of the children have it.
4)
> A class should reflect behavior that specifies and is
> relevant to the object. It should not have behavior
> simply because the children of the class have it.
I infer from quotes 1 and 2 that: "functionality" should not be a factor in defining an is-a relationship, and that one should not use "functionality" of concretes as an indicator of potential abstraction.
Both 1 and 2 refer to "changing" the abstract. This presumes that the concretes exist prior to conception of the abstract. If we are not "changing", but rather "designing", does my inference (and your quotes) still apply? What if we substitute "behavior" for "functionality"?
I infer from quotes 3 and 4 that: behaviors of the abstraction are innate behaviors uninfluenced by their concretes, and they may be identified in the post-design phase.
Ignoring changes of requirements/definitions, how is it possible (according to #3) to know that something "belongs" in the abstract without contradicting 1 and 2?
Both 3 and 4 smack of Plato, and his perfect circle.
> What then, is your reasoning for having an Animal class? Because when
> analysing a zoo (for example) you would initially only find
> subclasses: Giraffe, Elephant, etc. You then discover that these
> classes exhibit common behaviour, so you introduce the Animal class
> to capture common behaviour of Animals (like isAlive() or so). You
> take a few samples and find out that the animals you are modelling
> all happen to be walking, so you might be tempted to implement a walk
> () method in Animal. This might be wrong, as you correctly say, when
> you don't take the entire domain into account, because there might be
> animals that cannot walk. In the case of the Horseranch
The problem domain is the domain that is required for a solution to the application. It is not the domain of the world or universe.
The zoo I go to has fish, so walk would not be in animal.
On the other hand if it was a small petting zoo then walk is an appropriate method for Animal. Unless you suspect that they will be adding fish.
Not it might be that when you end up implementing the solution for the zone (with fish) that they tell you that fish will never be included, maybe because a different application manages them. You might then make the decision that Animal does include walk. (Or you might choose to ignor them since they will want you to redo their fish software when they see how wonderful the rest is.)
Now notice that nothing in the above mentions zebras, or elephants or even "walking fish" (which the zoo I go to has.) I didn't choose to put walk into Animal because of the children but rather because of what it represents.
>So you introduce an extra layer to capture functionality common to a
>significant set of subclasses of Animal
Adding a layer might be a good idea but only if represents a type of something. But not because a convenient place is needed for walk.
For instance adding PlainsAnimal with a walk method would work. Or even PlainsPredator and PlainHerbivor (and maybe PlainsOmnivore.)
>Why would it be better to have a class outside the hierarchy to
>capture this behaviour, than to introduce a class inside the hierarchy.
Because you don't create new classes (design side) simply to represent behavior. An object is a collection of data and behavior. If one is lacking then it probably isn't an object.
> > A parent should only every exhibit behavior that
> > the parent should have.
>
> Unless the parent models an abstract class (like Animal), then it
> should exhibit behaviour common to ALL its children
A good point. However, again in most cases, one doesn't deal with parents but rather with children. An abstract method is still behavior associated with the parent but provides additional information that the implementation of that behavior will be different for each child. A non-abstract method says that the behavior might be different for each child.
>Or you might find during implementation that you completely overlooked
>a more immediate parent, so you add it to capture the common
>functionality.
Yes. But you do that because you find that you have missed a significant type (or actor, whatever.) Not because you have common functionality.
It might be case however that common functionality indicates a missing type. But common functionality by itself is not the determining factor.
> > It should not have behavior simply because the
> > children of the class have it.
>
> Unless it is common to ALL children.
No.
Here are the two sentences that summerize the concepts.
1 - All of the children have behavior A so put it into the parent.
2 - The parent has behavior A and thus all the children have it.
Those two sentences are not the same. If during implementation you determine that the second is true, then by all means put it in the parent.
But sentence 2 is not the same as sentence 1.
The first sentence is not justification for putting it into the parent. The concept is different.
> BTW, you speak about "most cases". If you feel the
> Animal example doesn't adequately represent "most
> cases", feel free to switch to different example
> (hopefully still comprehensible to me and other
> visitors of this forum, so please no quantum mechanics
> ;).
No I am just hyper-sensitive to that after having people jump on me in a couple other threads for not prefacing every sentence with something like that.
There are always exceptions to every rule. I just wanted to make clear that I was speaking about the general case.
>By their very nature is-a relationships are coupled. How is it
>possible for them to be more coupled?
The more points at which two discrete components touch the more tightly coupled they are. Which is more tightly coupled a parent/child that share one method or a parent/child that shares 20 methods?
>WRT maintenance costs, is this not a moot point?
No. Maintenance cost is the entire point. If it wasn't then no one would care if your application was "easily added to and expanded" because you could just re-write the entire thing the next time. No one would care how much it cost or how long it took. All they would care about is whether it works when you deliver it.
> In a problem domain of concretes, whereby you need to perform
> abstraction, how can you know what behavior the parent should have
> other than analyzing the concretes and finding commonality?
Of course. I don't believe I said that it didn't.
I will even note now that a common problem (which I also rail against) is adding general/abstract concepts into a design without having more than one concrete implementation. As far as I am concerned that is OO run amuck.
> I infer from quotes 1 and 2 that: "functionality" should not be a
> factor in defining an is-a relationship, and that one should not
> use "functionality" of concretes as an indicator of potential
> abstraction.
It is a factor, it is just not the sole factor.
> Both 1 and 2 refer to "changing" the abstract. This presumes that the
> concretes exist prior to conception of the abstract. If we are
> not "changing", but rather "designing", does my inference (and your
> quotes) still apply? What if we substitute "behavior"
> for "functionality"?
If I am following you...
It depends. Are you adding it solely because the concrete examples have it or because it belongs in the abstract model?
> I infer from quotes 3 and 4 that: behaviors of the abstraction are
> innate behaviors uninfluenced by their concretes, and they may be
> identified in the post-design phase.
Sorry, I don't know what you are talking about.
> Ignoring changes of requirements/definitions, how is it possible
> (according to #3) to know that something "belongs" in the abstract
> without contradicting 1 and 2?
I don't know about you, but my designs are never perfect. And I don't always deal with only my designs and I have seen one from anyone else that was perfect either (I have seen a couple that were close but they were also very high level.) The implementation phase always reveals flaws.
> For instance adding PlainsAnimal with a walk method
> would work. Or even PlainsPredator and PlainHerbivor
> (and maybe PlainsOmnivore.)
Why is PlainsAnimal an "allowed" subclass of Animal, when WalkingAnimal is not? I would say if you can model one characteristic of an Animal (habitat = plains) as a subclass, you can model another (method of movement = walk) as a subclass just as well?
> Because you don't create new classes (design side)
> simply to represent behavior. An object is a
> collection of data and behavior. If one is lacking
> then it probably isn't an object.
And what is your FourLeggedHelper other than a class to capture behaviour? There's no data in it, is there?
I would say that my WalkingAnimal would contain data on the limbs the animal has, to be able to move them when the walk() method is called.
BTW, just so we're clear: obviously, depending on the domain, there are choices to be made as to what to model in what way (e.g. model this characteristic as a field of the superclass, or as a separate subclass), but I object to you saying that modelling behaviour exhibited by instances of a class or several classes in an external helper class is better "in most cases" than creating a common subclass "just to capture that behaviour". I feel that if a set of instances of one superclass, no matter how varied otherwise, exhibits an interesting common behaviour, it is fine OO-design to capture that behaviour in a class that is a subclass of the original parent, and a superclass of the child-classes that exhibit the behaviour.
WRT coupling: you haven't answered my question about a sample implementation of your Helper: it seems to me that the helper and the classes that use it would have to be very tightly coupled indeed for the helper to be able to do something useful. The kind of coupling that "in most cases" (not mocking you, just in my experience) would be represented by a superclass-subclass relationship.
Your two sentences:
> 1 - All of the children have behavior A so put it into the parent.
This assumes an existing parent which may be too generic to capture the behaviour; unless "All" is literally all: if all childeren are guaranteed to have the behaviour, it must be a characteristic of the parent.
> 2 - The parent has behavior A and thus all the children have it.
This is true, of course.
But I think the 3rd case is equally valid:
3 - Many children exhibit behaviour A, so create a new parent to capture the behaviour.
hhora at 2007-7-18 15:22:02 >

Perhaps a real example (at least I think it is):
In previous versions of Java, String and StringBuffer did not have a common superclass (discounting Serializable). In Java 1.4, StringBuffer, String and CharBuffer share the common superclass (implemented, for convenience sake, as an interface) that exhibits the common behaviour of charAt(.), length(), and subSequence(..).
This common subclass was clearly added after observing common behaviour in several subclasses of Object. I fail to see how this could be implemented using a helper class that wouldn't be at least as tightly coupled as a superclass would be, or more importantly, why such a solution would be better.
hhora at 2007-7-18 15:22:02 >

>Why is PlainsAnimal an "allowed" subclass of Animal, when WalkingAnimal is not?
Because a zoo, at least the ones I go to, seperatet the animals like that. The fish are in one place and the plains animals are in another.
>And what is your FourLeggedHelper other than a class to capture
>behaviour? There's no data in it, is there?
It is an implementation compromise which does not pollute the hierarchy (obviously since it is not in the hierarchy.)
> I feel that if a set of instances of one superclass, no matter how
> varied otherwise, exhibits an interesting common behaviour, it is
> fine OO-design to capture that behaviour in a class that is a
> subclass of the original parent...
Different experiences. After having had to refactor several large models where the developers did exactly what you are describing I decided that there must be something wrong with. So far I haven't found anything wrong with the helper model.
> But I think the 3rd case is equally valid:
>3 - Many children exhibit behaviour A, so create a new parent to capture the behaviour.
Again it must be due to some of my previous experiences. I just don't like re-working large amounts of code.
> Because a zoo, at least the ones I go to,
> seperatet the animals like that. The fish are
> in one place and the plains animals are in another.
OK, so we're down to a domain decision: in your case, the habitat is the distinguishing factor, in my case it's the method of movement. Same difference: just a different model because of what the users are interested in: the zoo caretakers would probably be more interested in where the animal is, while someone studying how an animal moves about would be more interested in its method of movement.
I still have seen no explanation of why the external helper would be better, or how it would be implemented in a way that reduces coupling (assuming that that is a good thing, in this case). For example, the PlainsAnimal, why is it there, other than to capture the observed common behaviour/characteristic of the animal's habitat?
BTW, I would probably not have come up with the idea of modelling the animal's habitat/origin as a subclass, but rather as a property of the Animal class, since I would say all animals must have a habitat/origin. Of course, this is a decision the domain-experts could easily overrule.
Perhaps you can give us an example from your experience of a similar situation where your external helper approach helped to reduce complexity?
hhora at 2007-7-18 15:22:02 >

I think the is-a relationship attempts to focus too much on subclassing rather than behavior relationships.
I seriously doubt that we would all write classes named BlackWolf, RedWolf, YellowBird, etc.
I think blending a behavior and/or a property of a class as part of the class definition is incorrect.
If we change the example to a non-animate object, such as a Box. Would we create sub-classes of Box that are OpenableBox? RectangleBox? SkinnyBox? No. Our Box class would have properties such as "boolean open;" and "double height, width ;". We might subclass Box if there was additional functionality that needed to be implemented, that is not natural for a Box to do. Such as JackInTheBox.
> > Because a zoo, at least the ones I go to,
> > seperatet the animals like that. The fish are
> > in one place and the plains animals are in another.
>
> OK, so we're down to a domain decision: in your case,
> the habitat is the distinguishing factor, in my case
> it's the method of movement. Same difference: just a
> different model because of what the users are
> interested in: the zoo caretakers would probably be
> more interested in where the animal is, while someone
> studying how an animal moves about would be more
> interested in its method of movement.
I can't say that my choice is a great one. But I believe it is a better choice than yours because it models a 'type' rather than a 'behavior'.
>
> I still have seen no explanation of why the external
> helper would be better, or how it would be implemented
> in a way that reduces coupling (assuming that that is
> a good thing, in this case). For example, the
> PlainsAnimal, why is it there, other than to capture
> the observed common behaviour/characteristic of the
> animal's habitat?
Because it is entirely encapsulated. Inheritence is not.
Although I suppose that comes down to whether one considers composition to be the same as inheritence.
>
> BTW, I would probably not have come up with the idea
> of modelling the animal's habitat/origin as a
> subclass, but rather as a property of the Animal
> class, since I would say all animals must have a
> habitat/origin. Of course, this is a decision the
> domain-experts could easily overrule.
>
It was an example, not necessarily perfect, nor even useable. And not one that I gave a great deal of thought to.
> Perhaps you can give us an example from your
> experience of a similar situation where your external
> helper approach helped to reduce complexity?
You can see one in java - java.lang.Math.
But I also have one.
I need to convert a string representation of 'money' to a integer and go the other way as well.
This is used several classes.
I need to conver a string representation of 'date' to a date object and go the other way as well.
This also is used in several classes.
Some of the above classes overlap. Some use the date method. Some use the money method. Some use both.
None of the classes have a parent.
But that doesn't mean that I am going to add a MoneyAndDateConverter parent to each.
If these are your examples, then I'm surprised that you didn't notice a long time ago that this discussion is about something else entirely.
IMO, the examples you give here (notably java.lang.Math, I can't judge the your other example quite as well, since I haven't seen the code) are cases of functionality captured in a class because this behaviour would not normally be exhibited by a subclass or superclass, but rather be used by any class.
For example in the case of java.lang.Math, not many classes would exhibit the behaviour "abs(int i)", or "max(int i, int j)", but would rather use these functions to perform their normal duties.
I suspect your conversion methods are similar, since you keep talking about these methods being used by other classes (my emphasis).
Would the classes that use the "Date convertFrom(String s)" or "Money convertFrom(String s)" normally exhibit convertFrom behaviour, or do they just use this to perform other duties related to their purpose?
IMO, we were talking about something else entirely in this topic: we were talking about deciding whether to put behaviour exhibited by (NOT used by a number of subclasses in an existing parent, introduce a new parent, or put it in an external helper class. That is, whether or not to put the observed behaviour of movement on 4 legs, implemented in a walk() method in the parent class Animal (on which we agreed that this may or may not be right, depending on the domain), in a new class WalkingAnimal (which was the proposal I'm defending) or in an external FourLeggedWalker helper class (which was yours).
On a side note: why aren't these conversion methods part of your Date and Money classes, like Integer.parseInt(String s)?
hhora at 2007-7-18 15:22:02 >

Another side note: your statement that none of the classes have a parent is obviously a mistake, since all classes (except Object) have at least Object as their parent.
hhora at 2007-7-18 15:22:02 >

> IMO, ... exhibited by a subclass or superclass, but rather be used by any class.
Yes, that that is an opinion.
>On a side note: why aren't these conversion methods part of your Date
>and Money classes, like Integer.parseInt(String s)?
Because the data for money does not exhibit enough behavior to justify a class.
And of course a full fledged Date class already exists in the Java API (which is what I am using.) And quite properly the conversion functionality is not part of that class but is in a seperate class. I do note that the conversion class is neither a parent or a child of the date class.
It's not really an opinion, it's just a matter of proper modelling: would or wouldn't an instance of a class always exhibit a particular behaviour, or would it merely use certain functionality in the performance of its other "duties". If the former, it should be a method of that class, if the latter, a (static) method of some other class.
For example, I would say that a very small number (I can't think of any right now) of classes would normally exhibit the behaviour of java.lang.Math, (or java.util.Arrays, or java.util.Collections) but a lot of classes would use this functionality during the performance of behaviour they do exhibit.
I would also say that walking is a very common behaviour for some animals to exhibit. There may be some common functionality inside the specific implementation of walk() that may require the use of a helper, but that's no reason to not make walk() a part of a common superclass or interface.
I just noted that the Math, Arrays and Collections classes have something in common: they all capture behaviour that operates on certain other classes (or basic types, as the case may be). Is this a coincidence? I think not.
BTW, now that I've thought about it, you are probably right in saying that classes should not properly need to know how to convert Strings into instances of themselves. So all the Xxx.parseXxx(String s) methods (of Integer, Long, etc) should probably be moved to one or a few utility Converter classes, like your money and date converters.
But again: this is because the behaviour would not normally be exhibited by the classes that use this functionality (perhaps you can give an example of a class that uses the MoneyConverter).
However, walking is behaviour that is exhibited by some animals, so I feel it should properly be modelled in a WalkingAnimal class or Walker interface that relevant subclasses of Animal would extend/implement.
hhora at 2007-7-18 15:22:02 >

>
> I would also say that walking is a very common
> behaviour for some animals to exhibit. There may be
> some common functionality inside the specific
> implementation of walk() that may require the use of a
> helper, but that's no reason to not make walk() a part
> of a common superclass or interface.
>
I would expect that there are very few problem domains where that is useful however. For instance it wouldn't be useful either is zoo management nor in the veternary tracking.
The only use would be in customer kiosk exhibits. Those sorts of applications tend to be very specific, limited, and have very little functionality. Thus, if there was an Animal class at all it would be likely to have the walk() method because the demo only works for a couple of species.
Thus it demonstrates neither your point nor mine.
You seem to be suggesting that all designs and all implementations must be a perfect example of the principle.
I will leave it up to you to come up with a perfect example that demonstrates your point.
> I just noted that the Math, Arrays and Collections
> classes have something in common: they all capture
> behaviour that operates on certain other
> classes (or basic types, as the case may be). Is this
> a coincidence? I think not.
>
Nope. They are not the same sort of thing.
A collection/array is a discrete type. All concrete collection/arrays types have data and behavior. The concept in the real world might be abstract, but in the world of programming a collections/array are real entities.
The Math class is just a collection of functionality. It can't be the parent of anything because it is final. Is has no data (just constants.) It doesn't even really have behavior since all of the methods are static.
> BTW, now that I've thought about it, you are probably
> right in saying that classes should not properly need
> to know how to convert Strings into instances of
> themselves. So all the Xxx.parseXxx(String s) methods
> (of Integer, Long, etc) should probably be moved to
> one or a few utility Converter classes, like your
> money and date converters.
Probably.
However, extremism lends little to the practicalities of everyday programming.
DateFormat and SimpleDateFormat exists because of the complexities of dates in the real world.
Although NumberFormat can be used, it is used a lot less than DateFormat. Not to mention that the numeric fields exist with the real jave core where Date and all of the formatters exist somewhat outside of it.
>
> But again: this is because the behaviour would not
> normally be exhibited by the classes that use this
> functionality (perhaps you can give an example of a
> class that uses the MoneyConverter).
>
> However, walking is behaviour that is exhibited
> by some animals, so I feel it should properly be
> modelled in a WalkingAnimal class or Walker interface
> that relevant subclasses of Animal would
> extend/implement.
As you said. And as I said, behavior is not the sole determinate for deciding whether a type, abstract or not, should exist.
You can keep repeating the point if you like. And I will keep refuting it.
You could take a different tack and provide your own example that makes you point clearer.
> You seem to be suggesting that all designs and
> all implementations must be a perfect example of
> the principle.
No, I am suggesting is that the principle should be very clear so that if and when you deviate from it, it is clear that you do, and why. I'm making these points under the assumption that we're not discussing the solution to a specific real-life problem, but that we're having a theoretical discussion on OO modelling.
>> the Math, Arrays and Collections classes
>> [...] capture behaviour that operates on
>> certain other classes
> Nope. They are not the same sort of thing.
> A collection/array is a discrete type.
You seem to have misunderstood: I wasn't talking about collections and arrays, but about the java.util.Collections and java.util.Arrays classes, which capture only behaviour (like the Math class) that operates on other classes/basic types.
> You can keep repeating the point if you like.
> And I will keep refuting it.
The reason I keep repeating my point in different ways is that you don't seem to get it (at least, judging from the examples you give, I feel that this is the case). I feel we are comparing apples and oranges at the moment. I will try to make this clear using two examples.
[bExample 1:
Application example: Personal Information Manager.
Classes used: Date, DateFormat (as defined in the Java core libraries), and a two fictional classes Schedule, that maintains a set of appointments sorted by date; and TodoList, that also maintains a set of appointments, but sorted by priority (and then by date).
The interface for Appointment looks like this:public interface Appointment {
public Date getTime();
public int getPriority();
public String getDescription();
}
A part of Schedule might look like this:import java.util.Date;
import java.text.DateFormat;
public class Schedule { // extends neither Date nor DateFormat
private Map appointmentsByDate = new HashMap();
private DateFormat formatter = DateFormat.getDateTimeInstance();
...
public void printScheduleFor(Date date) {
SortedSet appointments = (SortedSet)appointmentsByDate.get(date);
Iterator it = appointments.iterator();
while(it.hasNext()) {
Appointment appointment = (Appointment)it.next();
System.out.println(formatter.format(appointment.getTime());
System.out.println(appointment.getDescription());
}
}
While TodoList looks like this:public class TodoList { // extends neither Date nor DateFormat
public static final int HIGHEST_PRIORITY = 0;
public static final int LOWEST_PRIORITY = 9;
private SortedSet[] appointmentsByPriority = new SortedSet[10]; // 10 priority levels
private DateFormat formatter = DateFormat.getDateTimeInstance();
...
public void print() {
for(int priority = 0; priority <= LOWEST_PRIORITY; priority++) {
System.out.println("Priority: " + priority);
SortedSet appointments = appointmentsByPriority[priority];
Iterator it = appointments.iterator();
while(it.hasNext()) {
Appointment appointment = (Appointment)it.next();
System.out.println(formatter.format(appointment.getTime());
System.out.println(appointment.getDescription());
}
}
}
}
Example 2:
Application example: online catalog of motorized vehicles.
Used classes: MotorizedVehicle, FordExplorer, Boeing747, DodgeViper.
MotorizedVehicle might look something like this:public abstract class MotorizedVehicle {
private Engine engine;
...
public abstract String getDescription();
public void start() {
engine.start();
...
}
public abstract void accellerate();
}
Class FordExplorer might look like this initially:public class FordExplorer extends MotorizedVehicle {
private Pedal gaspedal;
public String getDescription() {
return "Ford Explorer";
}
public void accellerate() {
gaspedal.press();
}
}
Class Boeing747 might look like this:public class Boeing747 extends MotorizedVehicle {
private ThrottleHandle handle;
public String getDescription() {
return "Boeing747";
}
public void accellerate() {
handle.moveForward();
}
}
And finally, DodgeViper looks like this:public class DodgeViper extends MotorizedVehicle {
private Pedal gaspedal;
public String getDescription() {
return "Dodge Viper";
}
public void accellerate() {
gaspedal.press();
}
}
Now it is discovered that DodgeViper and FordExplorer have common characteristics: they both contains GasPedals and they both accellerate using the same functionality: gaspedal.press(). After careful analysis, it is determined that all instances of DodgeViper and FordExplore may be described as Cars. This class is introduced and looks like this:public class Car extends MotorizedVehicle {
private GasPedal gaspedal;
public void accellerate() {
gaspedal.press();
}
}
FordExplorer and DodgeViper are changed like so:public class FordExplorer extends [b]Car[/b] {
public String getDescription() {
return "Ford Explorer";
}
}
public class DodgeViper extends [b]Car[/b] {
public String getDescription() {
return "Dodge Viper";
}
}
In example 1, why isn't DateFormat introduced into the hierarchy? Because Schedule and TodoList don't exhibit behaviour like "format(Date date)" or "parse(String s)", they use that behaviour. There is no "is-a" relation between DateFormat and Schedule, nor between DateFormat and TodoList! They're not subclasses of DateFormat, but clients.
In example 2, why was Car introduced into the hierarchy? Because it was observed that several subclasses of MotorizedVehicle share common behaviour. The class was introduced to capture this common behaviour. There is an "is-a" relation between Car and MotorizedVehicle, as well as between FordExplorer and Car, and DodgeViper and Car.
Example 2, alternative
I feel that implementation of this common functionality in a GasPedalPresser would only complicate things, unless you come up with something that's simpler than the above.
The best I could come up with was something like this (starting with the original FordExplorer and DodgeViper implementations):public class GasPedalPresser {
private static Map gaspedalsByCar;
...
public static pressGasPedalFor(Object obj) {
GasPedal gaspedal = (GasPedal)gaspedalsByCar.get(obj);
gaspedal.press();
}
}
FordExplorer and DodgeViper (and every future MotorizedVehicle that wants to press a gaspedal) would look like:public class FordExplorer extends MotorizedVehicle {
public String getDescription() {
return "Ford Explorer";
}
public void accellerate() {
GasPedalPresser.pressGasPedalFor(this);
}
}
public class DodgeViper extends MotorizedVehicle {
public String getDescription() {
return "Dodge Viper";
}
public void accellerate() {
GasPedalPresser.pressGasPedalFor(this);
}
}
Now you can convice me of a lot of things, but not that this solution is somehow better (from an OO standpoint) or easier to maintain (from a coding efficiency standpoint) than the solution initially chosen in example 2.
Hope this clarifies things.
hhora at 2007-7-18 15:22:02 >

BTW, in case you missed it, I agree with you in Example 1: capturing common functionality in a Helper class is a good idea in that case.
But I hope I have demonstrated that this case and what we've been discussing in this thread (started by your example of Animal, Cat and Dog) are two different things.
hhora at 2007-7-18 15:22:02 >

> >> the Math, Arrays and Collections classes
> >> [...] capture behaviour that operates on
> >> certain other classes
> > Nope. They are not the same sort of thing.
> > A collection/array is a discrete type.
>
> You seem to have misunderstood: I wasn't talking about collections
> and arrays, but about the java.util.Collections and java.util.Arrays
> classes, which capture only behaviour (like the Math class) that
> operates on other classes/basic types.
And you are saying that you would derive from those classes rather than using them by encapsulation?
>Example 2:
> Application example: online catalog of motorized vehicles.
> ...
> In example 2, why was Car introduced into the hierarchy? Because it
> was observed that several subclasses of MotorizedVehicle share common
> behaviour
My point is that parents should not be added to a hierarchy unless the problem domain clearly supports the "is-a" relationship. And using shared behavior between sub classes is never a reason for adding to the hierarchy. And in particular coding convenience is never a reason.
There is an implication from that where by a parent class which has nothing but behavior (no data) can be suspected of having been added for the wrong reason. But that is not assured.
You didn't provide the reasoning for why Car or MotorizedVehicle was added.
If it was added solely so a developer didn't have to write accellerate() twice then the addition of the new class was flawed.
> And you are saying that you would derive from those
> classes [Math, Collections, Arrays] rather than
> using them by encapsulation?
No, that's my entire point: there is almost no class that would derive from class Math, Collections or Arrays, because almost no class would exhibit that kind of behaviour. Most classes would only need to use the functionality.
> There is an implication from that where by a
> parent class which has nothing but behavior
> (no data) can be suspected of having been added
> for the wrong reason. But that is not assured.
I feel the distinction between data and functionality is an artificial one, in fact in some OO languages there is no syntactic difference between accessing a field and accessing a no-argument method. Would you call a method that calculated some value and returned it 'data' or 'functionality'? I would say everything a class exposes is part of its behaviour. Perhaps we should use 'interface' instead... Anyeway, that's not what this argument is about.
> You didn't provide the reasoning for why Car
> or MotorizedVehicle was added.
As I see it, superclasses exist for 3 reasons (assuming a bottom up design where you encounter specific instances and their classes first, and later seek to generalize):
1. So you can treat instances of its subclasses as being of the superclass (generalization to support polymorphism)
2. To capture common behaviour exhibited by all instances of its subclasses (generalization, inheritance of interface/behaviour)
3. To provide an initial, generic implementation of this behaviour that subclasses can inherit, extend or override (inheritance of implementation)
> If it was added solely so a developer didn't have
> to write accellerate() twice then the addition of
> the new class was flawed.
So you keep saying, but you don't provide me with any reasoning why this is so. If the behaviour is commonly exposed by the subclasses, and there is a possible superclass that would also naturally expose that behaviour, I don't see any reason not to put it in a superclass. Clearly, (given the analysis I provided) a Car would have a GasPedal, and would be able to accellerate(), so I feel this is a good generalization of DodgeViper and FordExplorer, which lets me implement accellerate() for all cars just once (perhaps I will need to override it once or twice for other car classes that may have hand-operated gas or buttons for increasing speed), but in my analysis, Cars will generally operate this way.
Why on earth would you want to use the GasPedalPresser solution, which adds nothing in the way of genericity, polymorphism, inheritance (or convenience), maintains GasPedals outside their natural containers (Cars), and forces me to code the same 3 lines for every type of car I may encounter in the future. BTW: do you notice it is hard to talk about classes like FordExplorer and DodgeViper without mentioning the word 'car'?
As an aside: any implementation that prevents code from being duplicated would have an immediate initial preference in my mind over one that does not.
As an other aside (not that it really proves anything): the Java core libraries are made up of extensive hierarchies, often in places where only more specific types existed in previous versions (most notably the Collections framework), while Helper classes are few and far between. I would say the Sun developers have a preference for capturing common functionality in superclasses rather than helper classes as well...
hhora at 2007-7-18 15:22:07 >

> > And you are saying that you would derive from those
> > classes [Math, Collections, Arrays] rather than
> > using them by encapsulation?
>
> No, that's my entire point: there is almost no class
> that would derive from class Math, Collections or
> Arrays, because almost no class would exhibit that
> kind of behaviour. Most classes would only need to use
> the functionality.
(After having read back over the posts where you brought these up and trying to figure out what you are talking about.)
Those objects implement functionality. They operate on passed in data (which seemed relevant to you in another post) because of course they have to operate on something - they have no data themselves.
The methods that they produce are not intended to be exposed but are used to implement another method.
Which is how I expect helper objects to work.
So what is your point?
> > You didn't provide the reasoning for why Car
> > or MotorizedVehicle was added.
>
> As I see it, superclasses exist for 3 reasons
> (assuming a bottom up design where you encounter
> specific instances and their classes first, and later
> seek to generalize):
>
> 1. So you can treat instances of its subclasses as
> being of the superclass (generalization to support
> polymorphism)
> 2. To capture common behaviour exhibited by all
> instances of its subclasses (generalization,
> inheritance of interface/behaviour)
> 3. To provide an initial, generic implementation of
> this behaviour that subclasses can inherit, extend or
> override (inheritance of implementation)
>
The first is correct. The other two are incorrect from the design point of view. Except, of course, to the extent that 2 and 3 are actually part of 1.
> > If it was added solely so a developer didn't have
> > to write accellerate() twice then the addition of
> > the new class was flawed.
>
> So you keep saying, but you don't provide me with any
> reasoning why this is so.
And as your rules above point this out, you consider this an acceptable practice.
Maybe the way you practice it it is.
But for most developers, when they do this, they put functionality in to the parent class solely because it is common. There is no thought given to what the parent class will do in the future, and sometimes not even to what it is doing now.
This leads to significantly higher maintenance costs due to the tighter coupling. This is especially evident over many generations of the code where more than one inexperience programmer has worked on the project and did it the same way as previous programmers because "everyone else does it that way."
And since there is no logical reason for this choice except convinence there is no reason to allow it in most cases.
> Why on earth would you want to use the GasPedalPresser
> solution, which adds nothing in the way of genericity,
> polymorphism, inheritance (or convenience), maintains
> GasPedals outside their natural containers (Cars), and
> forces me to code the same 3 lines for every type of
> car I may encounter in the future. BTW: do you notice
> it is hard to talk about classes like
> FordExplorer and DodgeViper without mentioning the
> word 'car'?
It wouldn't by chance have anything to do with the fact that Ford Explorer "is-a" Car would it?
>
> As an aside: any implementation that prevents code
> from being duplicated would have an immediate initial
> preference in my mind over one that does not.
>
Which both methodologies do.
> As an other aside (not that it really proves
> anything): the Java core libraries are made up of
> extensive hierarchies, often in places where only more
> specific types existed in previous versions (most
> notably the Collections framework), while Helper
> classes are few and far between. I would say the Sun
> developers have a preference for capturing common
> functionality in superclasses rather than helper
> classes as well...
I have no doubt about that.
The Sun developers also have coding standards which mandate the placement of curly brackets and how to comment code without any enforcement that code must be documented. The code base has both new and old code with NO comments at all. Entire files with nothing.
Perhaps that is how you like your code as well?
The Sun developers have also implemented the second GUI framework in less than seven years. Despite the fact, of which they must have known, that there were numerous other frameworks in existence (with formal critism of the flaws) which they could have studied in the first place.
Does that demonstrate their god-like powers?
And the String class has underone it third rework on the underlying structure of its basics during the same time period. The third one that I know of.
No one should use the Java API code base as an example of "good" programming. There are some good points. But there are some very definite flaws in it.
> The methods that they produce are not intended to
> be exposed but are used to implement another method.
> Which is how I expect helper objects to work.
> So what is your point?
My point is that what we've been talking about here is different from this use of helpers. We are talking about behaviour actually exposed (not merely used) by a number of classes derived from a common superclass (which can be Object in the absence of any other). The question is on whether to put this already exposed, but common behaviour in a new superclass/hierarchy layer or to implement it in a helper class.
My claim is that it's better to use a superclass to capture behaviour common to a number of subclasses, because if the behaviour is common to a number of subclasses, this actually defines a new superclass in and of itself (namely, the class of objects that expose this behaviour).
> put functionality in to the parent class solely because it is common
My claim is that it is very rare for subclasses of a common superclass to expose common behaviour, and still not logically be part of a new parent. Again, I'm not talking about functionality commonly used to implement a method, I'm talking about exposed behaviour of a class.
> This leads to significantly higher maintenance costs
> due to the tighter coupling.
Please demonstrate to me that this helper class leads to a coupling that is less tight, yet still maintains an acceptable model. I find it highly irregular that all gaspedals for all subclasses would be maintained in the GasPedalPresser (this falls outside my definition of an acceptable model), when a gaspedal would logically be part of the classes themselves (if not a common superclass of all subclasses that have a gaspedal). However, if you have a better example of the implementation of this helper, please show me as I can find no better implemention... unless I define a common interface (like Car) for the helper to operate upon.
> It wouldn't by chance have anything to do with
> the fact that Ford Explorer "is-a" Car would it?
Of course it has to do with the fact that FordExplorer "is-a" Car. The point is that because this is so, it is logical to put common behaviour for cars in a class Car, even though this behaviour was initially observed only in specific subclasses (FordExplorer, DodgeViper) of a more generic superclass (MotorizedVehicle).
>> As an aside: any implementation that prevents code
>> from being duplicated would have an immediate initial
>> preference in my mind over one that does not.
> Which both methodologies do.
But yours does it to a much lesser degree than mine: your methodology forces me to implement (in the same way all the time) the accellerate() method of any MotorizedVehicle that uses a GasPedal to accellerate. This leads to unneccessary duplication, IMO.
>> As an other aside (not that it really proves
>> anything):
> [rant against JavaSoft developers]
I think my initial remark already shows that I don't think the Sun programmers have god-like powers. And for the record no, I don't like my code without comments or laid out the way Sun's code is.
Having said that, I was specifically pointing to the Collections framework, which I find to be a good example of an OO hierarchy that came to be by designing like I'm proposing here: observing common behaviour in a number of subclasses, and creating a superclass (or even hierarchy of superclasses) to capture that behaviour (I use behaviour here in the sense of both interface and implementation).
hhora at 2007-7-18 15:22:07 >

>
> > put functionality in to the parent class solely
> because it is common
>
> My claim is that it is very rare for subclasses of a
> common superclass to expose common behaviour,
> and still not logically be part of a new parent.
> Again, I'm not talking about functionality commonly
> used to implement a method, I'm talking about
> exposed behaviour of a class.
So you are basically claiming that it indicates that a type does exist.
I don't dispute that.
But what I am saying is that I have seen developers create parent classes solely because there is duplicated code. And that is not correct.
>
> > This leads to significantly higher maintenance
> costs
> > due to the tighter coupling.
>
> Please demonstrate to me that this helper class leads
> to a coupling that is less tight, yet still maintains
> an acceptable model.
Sorry I don't know how to demonstrate that. If you are willing to provide the funds then there is probably a researcher somewhere that can demonstrate that.
A helper class is entirely encapsulated by the users. Same principle as composition.
A parent is not encapsulated. It can't be.
Encapsulation reduced coupling. If you want to dispute that then please provide a resource.
> > It wouldn't by chance have anything to do with
> > the fact that Ford Explorer "is-a" Car would it?
>
> Of course it has to do with the fact that FordExplorer
> "is-a" Car.
Let me repeat my point then.
Some developers and parent classes because of duplicate code and NOT because a "is-a" relationship exists.
Parent classes should ONLY be added when an "is-a" relationship exists. (Again pointing out that this holds for design not implementation and that there can be exceptions.)
Given that your example does have a "is-a" relationship then adding a parent is appropriate.
> The point is that because this is so, it
> is logical to put common behaviour for cars in a class
> Car, even though this behaviour was initially observed
> only in specific subclasses (FordExplorer, DodgeViper)
> of a more generic superclass (MotorizedVehicle).
>
Nope.
It logical to do so because the parent class, which has a "is-a" relationship to the children, exhibits that behavior.
> >> As an aside: any implementation that prevents code
> >> from being duplicated would have an immediate
> initial
> >> preference in my mind over one that does not.
>
> > Which both methodologies do.
>
> But yours does it to a much lesser degree than mine:
> your methodology forces me to implement (in the same
> way all the time) the accellerate() method of any
> MotorizedVehicle that uses a GasPedal to accellerate.
> This leads to unneccessary duplication, IMO.
>
> >> As an other aside (not that it really proves
> >> anything):
>
> > [rant against JavaSoft developers]
>
> I think my initial remark already shows that I don't
> think the Sun programmers have god-like powers. And
> for the record no, I don't like my code without
> comments or laid out the way Sun's code is.
>
> Having said that, I was specifically pointing to the
> Collections framework, which I find to be a good
> example of an OO hierarchy that came to be by
> designing like I'm proposing here: observing common
> behaviour in a number of subclasses, and creating a
> superclass (or even hierarchy of superclasses) to
> capture that behaviour (I use behaviour here in the
> sense of both interface and implementation).
Probably true. But that would be because the classes represent "is-a" relationships where appropriate.
Of course there must be an "is-a" relation between a parent and subclass (and there has been, in all my examples), it would be very bad if there wasn't. My point is that often observed common behaviour can provide the indication that a superclass exists.
To recap the examples I used:
Animal < is-a < WalkingAnimal < is-a < Giraffe/Elephant
Why class WalkingAnimal: because of observed behaviour (walking) in subclasses.
MotorizedVehicle < is-a < Car < is-a < FordExplorer/DodgeViper
Why class Car: because of observed behaviour (gaspedal, common implementation of accellerate())
However, your examples (except the first one with Animal, Cat and Dog that started this entire discussion) were about common functionality (not behaviour) that the subclasses would not expose, but merely use, and as a result, putting that functionality in a superclass would be a wrong design choice, I agree to that.
Maybe you'd agree to this conclusion:
1. Functionality commonly used by subclasses of a common ancestor is usually not a good reason to create a mid-level superclass that provides that functionality.
2. Functionality commonly exposed by subclasses of a common ancestor might very well be a reason to create a mid-level superclass that captures that behaviour.
As far as I'm concerned we've beaten this baby to death, I don't think I have any more to add, anyway.
hhora at 2007-7-18 15:22:07 >

> Of course there must be an "is-a" relation between a
> parent and subclass (and there has been, in all my
> examples), it would be very bad if there wasn't. My
> point is that often observed common behaviour can
> provide the indication that a superclass exists.
Ok...but again that was not my point.
My point is that common behavior is not the sole indicator. And thus creating a parent class solely because there is duplicated code is wrong.
>
> To recap the examples I used:
>
> Animal < is-a < WalkingAnimal < is-a <
> Giraffe/Elephant
> Why class WalkingAnimal: because of observed behaviour
> (walking) in subclasses.
>
> MotorizedVehicle < is-a < Car < is-a <
> FordExplorer/DodgeViper
> Why class Car: because of observed behaviour
> (gaspedal, common implementation of accellerate())
>
> However, your examples (except the first one with
> Animal, Cat and Dog that started this entire
> discussion) were about common functionality (not
> behaviour) that the subclasses would not expose, but
> merely use, and as a result, putting that
> functionality in a superclass would be a wrong design
> choice, I agree to that.
>
> Maybe you'd agree to this conclusion:
>
> 1. Functionality commonly used by subclasses of
> a common ancestor is usually not a good reason to
> create a mid-level superclass that provides that
> functionality.
Definitely.
>
> 2. Functionality commonly exposed by subclasses
> of a common ancestor might very well be a reason to
> create a mid-level superclass that captures that
> behaviour.
>
Yes. That is a partial indicator that a parent type could exist.
>
> As far as I'm concerned we've beaten this baby to
> death, I don't think I have any more to add, anyway.
Hmm...that probably actually occurred several days ago.
