Is there any problem with customizing a new interface out of otherinterfaces?

D

DeMarcus

Hi,

Let's say I have three interfaces

class IWorker
{
public:
virtual ~IWorker() {}

virtual void work() = 0;
};

class IEnergyConsumer
{
public:
virtual ~IEnergyConsumer() {}

virtual void refuel( int energy ) = 0;
};

class ICloner
{
public:
virtual ~ICloner() {}

virtual ICloner* clone() = 0;
};


Would it be problematic or immoral to create a new pure interface out of
those, like this?

class ICell : public IWorker, public IEnergyConsumer, public ICloner
{
public:
virtual ~ICell() {}
};

Or is it just fine to customize new interfaces out of existing interfaces?


Thanks,
Daniel


PS. I haven't seen it before, that's why I'm asking. If you have any
references to such thing, please let me know.
 
G

Goran Pusic

Hi,

Let's say I have three interfaces

class IWorker
{
public:
    virtual ~IWorker() {}

    virtual void work() = 0;

};

class IEnergyConsumer
{
public:
    virtual ~IEnergyConsumer() {}

    virtual void refuel( int energy ) = 0;

};

class ICloner
{
public:
    virtual ~ICloner() {}

    virtual ICloner* clone() = 0;

};

Would it be problematic or immoral to create a new pure interface out of
those, like this?

class ICell : public IWorker, public IEnergyConsumer, public ICloner
{
public:
    virtual ~ICell() {}

};

Or is it just fine to customize new interfaces out of existing interfaces?

Thanks,
Daniel

PS. I haven't seen it before, that's why I'm asking. If you have any
references to such thing, please let me know.

I'd say what you want goes against interface segregation principle of
SOLID (http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod). I
mean, if you're making interfaces, and even if you need two+
interfaces on one object in a given piece of code, there's nothing
wrong in passing two interface references in, or "dynamic_casting"
from one to another. What you seem to want to do is to separate your
interfaces (for e.g. clarity of design), but also mix them back again
(for e.g. convenience^^^). Sure, you can do it, but it's kinda sour...

^^^ but which brings you back to "massive base class" design smell.

Goran.

P.S. shouldn't ICloner::Clone return something else (some common base
class), not the ICloner? (That's what non-C++ frameworks that
implement "cloneable" interface are doing...)
 
R

red floyd

P.S. shouldn't ICloner::Clone return something else (some common base
class), not the ICloner? (That's what non-C++ frameworks that
implement "cloneable" interface are doing...)

Such as what? There is *no* common base class. And by using
covariant
return, ICloner is fine.

e.g. the following is perfectly cromulent:

class Cloneable : public ICloner {
public
Cloneable* clone() { return new Cloneable(*this); }
};
 
D

DeMarcus

I'd say what you want goes against interface segregation principle of
SOLID (http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod). I
mean, if you're making interfaces, and even if you need two+
interfaces on one object in a given piece of code, there's nothing
wrong in passing two interface references in, or "dynamic_casting"
from one to another. What you seem to want to do is to separate your
interfaces (for e.g. clarity of design), but also mix them back again
(for e.g. convenience^^^). Sure, you can do it, but it's kinda sour...

^^^ but which brings you back to "massive base class" design smell.

Ok, I agree with you, you have a good point. However, I don't want to
mix them back "for convenience". The reason is rather to put constraints
on entities.

Let's say I want to create a function that can take all kinds of
objects, but with the constraint that they must at least be clonable and
be able to do some work. Is the following still a bad idea?

class IConstrainedObject : public ICloner, public IWorker
{
public:
virtual ~IConstrainedObject() {}
};

void someFunction( IConstrainedObject* object ) { /* ... */ }

(As I write this I see that it will force objects to be clonable and do
some work, which is good, but it will also block objects not inheriting
from IConstrainedObject that actually may implement both ICloner and
IWorker.)

I still agree with your "massive base class design smell", but I also
want some tool to tailor-make interfaces.

There are patterns, like mix-ins and policies to tailor-make
implementations, but aren't there any nice pattern to tailor-make
interfaces as well?
 
P

Pavel

Goran said:
I'd say what you want goes against interface segregation principle of
SOLID (http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod). I
mean, if you're making interfaces, and even if you need two+
interfaces on one object in a given piece of code, there's nothing
wrong in passing two interface references in, or "dynamic_casting"
from one to another. What you seem to want to do is to separate your
interfaces (for e.g. clarity of design), but also mix them back again
(for e.g. convenience^^^). Sure, you can do it, but it's kinda sour..
Hmm. Following the very first link from the page you referenced, to
http://www.objectmentor.com/resources/articles/srp.pdf, we can see the
author suggests "fine-grained" interfaces and uses the modem as an
example (converted interfaces to C++):

class DataChannel {
public:
virtual void send(char) = 0;
virtual char recv() = 0;
virtual ~DataChannel() {}
};

class Connection {
public:
virtual void dial(const std::string &) = 0;
virtual void hangup() = 0;
virtual ~Connection() {}
};

Then, he introduces the concrete class (not an interface)


class Modem: public DataChannel, public Connection {
public:
void send(char);
char recv();
void dial(const std::string &);
void hangup();
~Modem();
};

as a necessary evil ("this is not desirable but maybe necessary").

As every OOD tutorial, seems perfectly logical until the real life
intervenes:

At some point, a couple of months or years down the road, it may very
well become desirable to segregate between sinks and source channels
(for example, to support simplex data connections or provide a base
class for a device that can't send in principle, like a keyboard or
cannot receive in principle like a printer):

class DataSink {
virtual void send(char) = 0;
~DataSink() {}
};

class DataSource {
virtual char recv() = 0;
~DataSource() {}
};

So, where does it leave DataChannel? Are we to change only one class,
DataChannel interface, and live with "sour" composite interface:

class DataChannel : public DataSink, public DataSource {
};

or all existing derived classes created in these 2 months or years, in
our and other organizations, for the sake of SOLIDity?

The lessons:

1. General: Take all principles with grain of salt (a rude analogy of
never say never)
2. Specific: the granularity of an OO-model inevitably gets finer as we
learn more about the domain area and need to satisfy more requirements.
It is impractical to get a complete model in the first release. Even if
we get it, it will become incomplete after the first round of the new
requirements (if our app is success, that is. If it is dead-born and
have no users, it may keep enjoying its OO-purity). As soon as we admit
the granularity will be getting finer as a result of *later changes*, we
thereby admit we may have to live with composite interfaces. Thus, there
is no reason to shy composite interfaces to start with -- instead we
should concentrate on less restrictive policies allowing us to maintain
composite interfaces in sustainable manner.

Just my 2c
Pavel

..
 
G

Goran Pusic

Hmm. Following the very first link from the page you referenced,to
http://www.objectmentor.com/resources/articles/srp.pdf, we can see the
author suggests "fine-grained" interfaces and uses the modem as an
example (converted interfaces to C++):

class DataChannel {
public:
        virtual void send(char) = 0;
        virtual char recv() = 0;
        virtual ~DataChannel() {}

};

class Connection {
public:
        virtual void dial(const std::string &) = 0;
        virtual void hangup() = 0;
        virtual ~Connection() {}

};

Then, he introduces the concrete class (not an interface)

class Modem: public DataChannel, public Connection {
public:
        void send(char);
        char recv();
        void dial(const std::string &);
        void hangup();
        ~Modem();

};

as a necessary evil ("this is not desirable but maybe necessary").

Frankly, I don't see why the author put in "not desirable" part. It's
all about the coherence of any given part of the code. Code that deals
with data exchange it oblivious to "connection" aspect. Conversely,
code that deals with connectivity aspect is oblivious to data exchange
aspect. (Or at least, they are/could be in the example). But a modem
is a beast that unifies the two. So what? That does not change the
abstract "data exchange" setup, nor "connectivity" setup. There is in
fact nothing "undesirable".
As every OOD tutorial, seems perfectly logical until the real life
intervenes:

At some point, a couple of months or years down the road, it may very
well become desirable to segregate between sinks and source channels
(for example, to support simplex data connections or provide a base
class for a device that can't send in principle, like a keyboard or
cannot receive in principle like a printer):

class DataSink {
        virtual void send(char) = 0;
        ~DataSink() {}

};

class DataSource {
        virtual char recv() = 0;
        ~DataSource() {}

};

So, where does it leave DataChannel? Are we to change only one class,
DataChannel interface, and live with "sour" composite interface:

class DataChannel : public DataSink, public DataSource {

};

or all existing derived classes created in these 2 months or years, in
our and other organizations, for the sake of SOLIDity?

The lessons:

1. General: Take all principles with grain of salt (a rude analogy of
never say never)
2. Specific: the granularity of an OO-model inevitably gets finer as we
learn more about the domain area and need to satisfy more requirements.
It is impractical to get a complete model in the first release. Even if
we get it, it will become incomplete after the first round of the new
requirements (if our app is success, that is. If it is dead-born and
have no users, it may keep enjoying its OO-purity). As soon as we admit
the granularity will be getting finer as a result of *later changes*, we
thereby admit we may have to live with composite interfaces. Thus, there
is no reason to shy composite interfaces to start with -- instead we
should concentrate on less restrictive policies allowing us to maintain
composite interfaces in sustainable manner.

I __think__ I understand your meaning, and if so, I agree with
you :). The hard cold truth is that __change__ is a bitch. In a
situation you describe, going from DataChanel to DataSink/Source pair
seems very reasonable. But the thing is, calling code will change,
too. Code that deals with keyboard (and other data sources) has no
business even knowing "sinks".

So the change is the external force that influences the design.
There's IMO absolutely no way around that.

Goran.
 
G

Goran Pusic

Let's say I want to create a function that can take all kinds of
objects, but with the constraint that they must at least be clonable and
be able to do some work. Is the following still a bad idea?

class IConstrainedObject : public ICloner, public IWorker
{
public:
    virtual ~IConstrainedObject() {}

};

void someFunction( IConstrainedObject* object ) { /* ... */ }

I guess the deciding factor clincher is how _important_ is this
constraint in the overall design.

Note, however, that with interfaces, you don't normally have a
destructor at all, let alone a virtual one. You're using mix-in
inheritance anyhow, so the deriving concrete object's destructor is
what matters. (In fact, is there any reason/situation where what I am
saying is wrong? Anyone?)

Also, should someFunction check for null object? I guess it should
not, and so you should have IConstrainedObject& (not*) there. ;-)

Goran.
 
A

Alf P. Steinbach /Usenet

* Vladimir Jovic, on 02.09.2010 09:34:
Shouldn't that be like this :

class Cloneable : public ICloner {
public
virtual ICloner* clone() { return new Cloneable( *this ); }
};

?

No.

On the other hand, the method should be 'const'.

And on the third hand, an interface like ICloner serves no practical purpose.


Cheers & hth.,

- Alf
 
D

DeMarcus

I guess the deciding factor clincher is how _important_ is this
constraint in the overall design.

Hm, let me try another approach. Let's instead call it IGuideline, or
why not just return to call it ICell.

Imagine you have a function that you want to operate on cells, like this.

void someFunction( ICell& cell ) { /* ... */ }

Then you know at compile time that all cells inheriting from ICell will
work with this function since ICell ensures you have ICloner and
IWorker. If we don't use ICell, then we have to check for ICloner and
IWorker in run-time with dynamic_cast. I would prefer to do that check
in compile time.

Another question is; if you don't have ICell, IConstrainedObject or
equivalent, what type would the argument type in someFunction() be?

void someFunction( ??? & object );

Would you prefer to do like this?

class ICell /* NOTE! No inheritance */
{
public:
virtual ~ICell() {}

/* maybe some virtual functions here */
};

class HumanCell : public ICell, public ICloner, public IWorker
{
/* Implement a concrete class */
/* ... */
};


void someFunction( ICell& cell )
{
dynamic_cast<IWorker&>(cell).work();
}

int main()
{
HumanCell hc;

someFunction( hc );
}
 
P

Pavel

Goran said:
Frankly, I don't see why the author put in "not desirable" part. It's
all about the coherence of any given part of the code. Code that deals
with data exchange it oblivious to "connection" aspect. Conversely,
code that deals with connectivity aspect is oblivious to data exchange
aspect. (Or at least, they are/could be in the example). But a modem
is a beast that unifies the two. So what? That does not change the
abstract "data exchange" setup, nor "connectivity" setup. There is in
fact nothing "undesirable".


I __think__ I understand your meaning, and if so, I agree with
you :). The hard cold truth is that __change__ is a bitch. In a
situation you describe, going from DataChanel to DataSink/Source pair
seems very reasonable. But the thing is, calling code will change,
too. Code that deals with keyboard (and other data sources) has no
business even knowing "sinks".
I think of the code that deals with keyboard is kinda a new code (the
keyboard was one of the new artifacts to support that caused separating
to Sink and Source at the first place). That is, it has to be more like
written anew, not really channged.

The old code, the one that used DataChannel, will not have to change
unless one decides to deprecate/retire the composite interface
"DataChannel".

So the change is the external force that influences the design.
There's IMO absolutely no way around that.
Yes, this seems to be our common point. My take out of it is that if one
can foresee with significant probability that the model will contain a
particular structure (composite interface in our case) as a result of a
change, it may be wise to accept the inevitable from the start and
"embrace" that structure instead of "rejecting" (the quotes are for the
wording borrowed from old Microsoft's rhetoric about Java).


-Pavel
 
P

Pavel

Goran said:
I guess the deciding factor clincher is how _important_ is this
constraint in the overall design.

Note, however, that with interfaces, you don't normally have a
destructor at all, let alone a virtual one. You're using mix-in
inheritance anyhow, so the deriving concrete object's destructor is
what matters. (In fact, is there any reason/situation where what I am
saying is wrong? Anyone?)
Yes, and more often than not. If your client programs to the interfaces
(else why would you define them), s/he will need to delete an object by
the pointer to the interface (mixin) at some point. Without virtual
destructor in the interface, not only won't such deletion call the
destructor of the most derived object as it supposed to; it won't even
call the correct operator delete.
 

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,770
Messages
2,569,584
Members
45,078
Latest member
MakersCBDBlood

Latest Threads

Top