On 8 mar, 13:48, "Leigh Johnston" <le...@i42.co.uk> wrote:
"Michael Doubez" <michael.dou...@free.fr> wrote in message
news:bffc3535-8ad3-4816-b3cf-83f72c78e556@j27g2000yqn.googlegroups.com...
On 8 mar, 12:01, "Leigh Johnston" <le...@i42.co.uk> wrote:
"Michael Doubez" <michael.dou...@free.fr> wrote in message
news:d962d8ed-d69e-4b5f-a224-bebd8dcafb83@e7g2000yqf.googlegroups.com...
On 5 mar, 11:25, "Leigh Johnston" <le...@i42.co.uk> wrote:
"Michael Doubez" <michael.dou...@free.fr> wrote in message
news:cdb20f88-8668-4092-972e-85a119dff724@j27g2000yqn.googlegroups.com...
On 4 mar, 19:37, James Kanze <james.ka...@gmail.com> wrote:
[snip]
Deriving from classes which weren't designed to be bases (such
as the standard containers) is generally a bad idea.
That's because, IMO the standard containers usually have a
complete
interface and there is no need to.
The examples I gave where for the case where an interface needs to
be
augmented but you use the word "usually" which is fine: "usually"
is
not
"always".
But the corollary is that is seldom useful or a good design decision
to inherit from them. A complete interface means that functions or
composition should be used; AFAIS there are three cases:
- the extended part is stateless: a function should be used
- the extended part keeps associated state:
* the extended class should be somewhat notified of modification
but the base class is not designed that way and composition should
be
used (I exclude an external notify modification system)
* the extended class doesn't need to be notified, both
information should be composed as a pair in another structure
* the extended structure exploits the internal of the container:
this is not portable, even across the same version of the compiler.
Garbage.
Really ?
A minimal complete interface is IMO an essential value for general
purpose class design (I am not talking about classes implementing
specific use cases).
The "is-a" relationship is well defined
I am curious to hear your definition.
and perfectly fine.
I find it ambiguous: it depends on what you mean by "is".
In fact, I find it useful only in defining what is-not (i.e. what
should be composed and not inherited).
Example of "is-a":
struct widget
{
colour background;
virtual void draw(device_context& dc) const
{
dc.fill_rect(client_rect(), background);
}
colour get_background() const { return background; }
};
struct label : widget
{
virtual void draw(device_context& dc) const
{
widget::draw(dc); // erases background in background colour
dc.draw_text(0, 0, get_background() ^ -1, label_text()); // draw text
in
inverted colour
}
};
label "is-a" widget, i.e. it inherits a widget's ability to fill in its
background and this behaviour is optional (label does not have to call
widget::draw()). label also inherits widget's background colour and can
query it for use in its own drawing code.
If I have a Spacer widget that doesn't draw anything, the background
member is useless.
And the name widget (WIndow gaDGET) is not really a thing, it is more
a base class for elements contained within a window. To me, it looks
like something you are forced into by strong typing rather than a
design decision.
"is-a" is related to LSP:
struct window
{
std::vector<widget*> widgets;
device_context dc;
void draw()
{
for (auto i = widgets.begin(); i != widgets.end(); ++i)
i->draw(dc);
}
}
i.e. if LSP is adhered to then a label can be passed to any function
which
accepts a widget reference/pointer. This is the essence of the "is-a"
relationship.
Well, is-a yields a correct program only if it preserves LSP. Although
there are some cases where LSP is not preserved: if I make Integer and
String subtypes of AdditiveType, Integer addition is symmetric but
String addition/concatenation is not although math tells us that '+'
is reserved for symmetric operations.
From the examples you gave, I see that, for you, is-a is thought in
terms of interface and polymorphism, not in terms of subtyping.
Now, if I have a class Mammal:
class Mammal
{
public:
Mammal(unsigned nb_breast):nb_breast(nb_breast){}
unsigned nbBreast()const{ return nb_breast; };
protected:
unsigned nb_breast;
};
If I define cat is-a mammal:
class Cat: public Mammal
{
public:
enum Type{ /* type of cat */};
Cat(Type type): Mammal(catType2NbBreast(type)){}
void mastectomy(unsigned nb_breast_removed)
{
assert( nb_breast_removed <= nb_breast);
nb_breast -= nb_breast_removed;
}
};
Here, I have a is-a relationship without talking about LSP or
polymorphism.
As I said elsewhere, the fact that C++ implements (dynamic)
polymorphism in terms of inheritance doesn't help. Well, it couldn't
do it another way, now, could it ?
--
Michael
are special cases. So you are incorrect to say that I do not use "is-a" for
subtyping.