Re: Does Liskov's principle hold also for struct?
Liskov's Substitution Principle says that public inheritance must always
model "is-a" or "works-like-a". But what about behaviorless structs?
I want to get away from global variables and create something I call
regional variables; something in between global and local. The regional
variables will be passed to objects as an "environment" struct.
For example:
struct BasicEnvironment
{
Logger* logger;
};
class MyClass
{
public:
MyClass( BasicEnvironment env ) { env.logger->log( "MyClass" ); }
};
Is there "&" missing here?
That is a very good question. I didn't miss it but I have had a lot of
anxiety about that little ampersand. My main idea was to force a copy
just like a unix shell does with its environment. However, I now realize
that the copy control must be outside receiving classes since a clumsy
programmer may just add the ampersand to a new class rendering the copy
control useless.
Now, if I want to extend the environment I have two options:
struct ExtendedEnvironment : public BasicEnvironment
{
char* homeDirectory;
};
or
struct ExtendedEnvironment
{
BasicEnvironment basicEnvironment;
char* homeDirectory;
};
Wherever I read it's recommended to use composition instead of
inheritance, but the discussions are always about classes and not about
behaviorless structs. If I use composition here I won't be able to add
something like
virtual void setEnvironment( Environment* env );
to MyClass in the future.
What is Environment? BasicEnvironment?
Consider this:
class Foo {
public:
virtual func(BasicEnvironment * env) = 0;
};
class Bar : public Foo {
public:
virtual func(BasicEnvironment * env) {
DerivedEnvironment * der_env = dynamic_cast ... bla bla
if (!der_env) throw ... bla bla
}
};
Although this would work, you cannot give the same environment to all
your objects. Each requires it's own type, but which one it is?
This can only be deduced by reading documentation, not by interface.
I agree with you. I'm not too keen on the dynamic_cast solution either.
I just tried to foresee the future with general objects taking different
kinds of environments, still the whole idea with the environment is that
it is essential for the class. Therefore, failing on a dynamic_cast you
describe above is a quite severe error and should not appear in a good
design at all.
Does the following design work for you? (in which case I'll send you
my bank account no. :-)
class Foo {
public:
void func() {
get_env().logger->log("func");
}
protected:
virtual const BasicEnvironment & get_env() const = 0;
};
class Bar_with_inherited_env : public Foo {
public:
void set_env(const DerivedEnvironment & env) { m_env = env; }
protected:
virtual const DerivedEnvironment & get_env() const {
return m_env;
}
private:
DerivedEnvironment m_env;
};
class Bar_with_composed_env : public Foo {
public:
void set_env(const DerivedEnvironment & env) { m_env = env; }
protected:
virtual const BasicEnvironment & get_env() const {
return m_env.basicEnvironment;
}
private:
DerivedEnvironment m_env;
};
Yes, maybe. I'm not so experienced with NVI yet but I will practice. ;)
See... both work. My choice would be composition. But I'd provide
two or more separate environments...
class BasicEnvironment;
class HomeEnvironment;
class BarEnvironment {
BasicEnvironment basic;
HomeEnvironment home;
};
Thanks for your input!
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]