ADT and inheritance

M

maverik

Hi all.

I have some class that contains only pure virtual functions (class-
interface):

class IButton {
// Contents of the class
};

So when I actually implement some type of button I do it like this

class ImageButton : public IButton {
// Contents of the class
};

It allows me to create a container of buttons without knowledge about
button types that this container would hold:

std::vector<IButton *> m_buttons;

The problem is: when I add some specific function to the inherited
button class then I can call it only in this way:

((ImageButton *)m_buttons)->SetImage(...); // I know that i-th
button is typeof ImageButton

Looks ugly a little bit, isn't it?

Of course I can add this specific function to the base class (IButton)

class IButton {
void SetImage(...) = 0;
};

but in this case IButton becomes just a bunch of methods from
different classes.

Is there a way to make it better?

Thanks.
 
V

Vladimir Jovic

maverik said:
Hi all.

I have some class that contains only pure virtual functions (class-
interface):

class IButton {
// Contents of the class
};

So when I actually implement some type of button I do it like this

class ImageButton : public IButton {
// Contents of the class
};

It allows me to create a container of buttons without knowledge about
button types that this container would hold:

std::vector<IButton *> m_buttons;

The problem is: when I add some specific function to the inherited
button class then I can call it only in this way:

((ImageButton *)m_buttons)->SetImage(...); // I know that i-th
button is typeof ImageButton

Looks ugly a little bit, isn't it?


Yes, c-type casts are always bad.
Of course I can add this specific function to the base class (IButton)

class IButton {
void SetImage(...) = 0;
};

but in this case IButton becomes just a bunch of methods from
different classes.

What is wrong with that?
 
M

maverik

((ImageButton *)m_buttons)->SetImage(...); // I know that i-th
Yes, c-type casts are always bad.

Ok, I actually use static_cast, so the code looks like
static_cast said:
What is wrong with that?

Hmmm... one thing is

class IButton {
// 100 methods from AButton goes here
// ...
// 100 methods from BButton goes here
// ...
// 100 methods from CButton goes here
// ...
};

and the programmer who add new methods always should update the base
class. The other thing is that the purpose of IButton is to include
common operation for all types of Buttons not every operation from
every type. I think it's rather good idea, isn't it? If not, please,
take a minute to explain why.
 
D

Daniel Pitts

maverik said:
Hi all.

I have some class that contains only pure virtual functions (class-
interface):

class IButton {
// Contents of the class
};

So when I actually implement some type of button I do it like this

class ImageButton : public IButton {
// Contents of the class
};

It allows me to create a container of buttons without knowledge about
button types that this container would hold:

std::vector<IButton *> m_buttons;

The problem is: when I add some specific function to the inherited
button class then I can call it only in this way:

((ImageButton *)m_buttons)->SetImage(...); // I know that i-th
button is typeof ImageButton

Looks ugly a little bit, isn't it?

Of course I can add this specific function to the base class (IButton)

class IButton {
void SetImage(...) = 0;
};

but in this case IButton becomes just a bunch of methods from
different classes.

Is there a way to make it better?

Thanks.

Usually, the container values aren't configured after the fact, so
you'll never call SetImage on an object reference you got from m_buttons.

std::vector<IButton *> m_buttons;

ImageButton * myButton = new ImageButton();
myButton->SetImage(...);
m_buttons.add(myButton);

IButton should contain methods that you need to support any IButton (in
Java Swing, for example, JButton has a paintComponent method, which can
be overridden by subclasses)

If you are going to cast frequently at runtime, then you've probably got
the wrong static type.
 
S

Stuart Redmann

maverik said:
I have some class that contains only pure virtual functions (class-
interface):

class IButton {
// Contents of the class
};

So when I actually implement some type of button I do it like this

class ImageButton : public IButton {
// Contents of the class
};

It allows me to create a container of buttons without knowledge about
button types that this container would hold:

std::vector<IButton *> m_buttons;

The problem is: when I add some specific function to the inherited
button class then I can call it only in this way:

((ImageButton *)m_buttons)->SetImage(...); // I know that i-th
button is typeof ImageButton

Looks ugly a little bit, isn't it?

Of course I can add this specific function to the base class (IButton)

class IButton {
void SetImage(...) = 0;
};

but in this case IButton becomes just a bunch of methods from
different classes.

Is there a way to make it better?


I'd advise to use several containers for the derived class pointers:
one container contains all objects with their base interface IButton,
one
contains pointers to all ImageButton objects, and one contains all
objects of
type SomeOtherButton. This means that registering objects of type
ImageButton includes
registering the object in the list of IButtons as well. This makes the
code
much more readable and maintainable than using dynamic_casts, IMHO.

Regards,
Stuart
 
J

James Kanze

maverik wrote:
I have some class that contains only pure virtual functions
(class- interface):
class IButton {
// Contents of the class
};
So when I actually implement some type of button I do it
like this
class ImageButton : public IButton {
// Contents of the class
};
It allows me to create a container of buttons without
knowledge about button types that this container would hold:
std::vector<IButton *> m_buttons;
The problem is: when I add some specific function to the
inherited button class then I can call it only in this way:
((ImageButton *)m_buttons)->SetImage(...); // I know that i-th
button is typeof ImageButton


That really should be:

Usually, the container values aren't configured after the
fact, so you'll never call SetImage on an object reference you
got from m_buttons.
std::vector<IButton *> m_buttons;
ImageButton * myButton = new ImageButton();
myButton->SetImage(...);
m_buttons.add(myButton);
IButton should contain methods that you need to support any
IButton (in Java Swing, for example, JButton has a
paintComponent method, which can be overridden by subclasses)
If you are going to cast frequently at runtime, then you've
probably got the wrong static type.

Yes. The question is: what the vector is supposed to contain.
If it's just ImageButton, then it should be
std::vector< ImageButton* >. Otherwise, users of the vector
*generally* shouldn't use parts of the interface that are unique
to ImageButton.

The one exception I can think of is if you do define an extended
button interface, e.g.:

class IExtendedButton : public IButton
{
// More pure virtual functions with additional
// functionality.
};

It's perfectly reasonable to imagine cases where you want to use
some additional features of ExtendedButton, if they are present,
and fall back to some default handling if they aren't. In such
a case, something like:

for ( std::vector< IButton* >::const_iterator
iter = m_buttons.begin() ;
iter != m_buttons.end();
++ iter ) {
IExtendedButton* extended
= dynamic_cast< IExtendedButton* >( *iter );
if ( extended != NULL ) {
// use added features...
} else {
// use default handling...
}
}

is reasonable. Extending this to a chain of else if... for
each possible type isn't, but for one, or possibly two
extensions, OK.
 
V

Vladimir Jovic

maverik said:
((ImageButton *)m_buttons)->SetImage(...); // I know that i-th
button is typeof ImageButton
Looks ugly a little bit, isn't it?

Yes, c-type casts are always bad.


Ok, I actually use static_cast, so the code looks like
static_cast<ImageButton *>(m_buttons)->SetImage(...);


As James Kanze said, it should have been dynamic_cast
Hmmm... one thing is

class IButton {
// 100 methods from AButton goes here
// ...
// 100 methods from BButton goes here
// ...
// 100 methods from CButton goes here
// ...
};

and the programmer who add new methods always should update the base
class. The other thing is that the purpose of IButton is to include
common operation for all types of Buttons not every operation from
every type. I think it's rather good idea, isn't it? If not, please,
take a minute to explain why.


1) it seams very wrong to have so many methods in one class.

2) can you do what Stuart Redmann suggested? To have different base classes.

From what you wrote, you have a bad design problem.
 
D

Daniel Pitts

James said:
Yes. The question is: what the vector is supposed to contain.
If it's just ImageButton, then it should be
std::vector< ImageButton* >. Otherwise, users of the vector
*generally* shouldn't use parts of the interface that are unique
to ImageButton.

The one exception I can think of is if you do define an extended
button interface, e.g.:

class IExtendedButton : public IButton
{
// More pure virtual functions with additional
// functionality.
};

It's perfectly reasonable to imagine cases where you want to use
some additional features of ExtendedButton, if they are present,
and fall back to some default handling if they aren't. In such
a case, something like:

for ( std::vector< IButton* >::const_iterator
iter = m_buttons.begin() ;
iter != m_buttons.end();
++ iter ) {
IExtendedButton* extended
= dynamic_cast< IExtendedButton* >( *iter );
if ( extended != NULL ) {
// use added features...
} else {
// use default handling...
}
}

is reasonable. Extending this to a chain of else if... for
each possible type isn't, but for one, or possibly two
extensions, OK.

That is one approach. The other is to have IButton have a method which
returns IExtendedButton. The default implementation returns NULL, but
subclasses can return any object they wish (including themselves if they
are extended buttons).

This removes the need for dynamic cast, and makes the concept of
"extended button" more concrete.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top