Decoupling classes

A

Alan Woodland

Hi, I've looked through the FAQ, and I can't seem to find an answer to
this one. Can anyone point me to a design pattern that will produce the
desired behaviour illustrated below please? I know why it doesn't print
"W visiting B", and I've read about double dispatch now too. What I'd
really like though is to keep A and V completely unaware of the
existence of B and W and still end up with "W visiting B" getting
printed. Ideally too I'd like to avoid putting a dynamic_cast in
W::visit(A&).

Is there a nice design pattern for doing this? Or an I searching for the
impossible.

Thanks for any advice,
Alan

class A {
};


class V {
public:
virtual void visit(A& a) = 0;
};

class B : public A {
};

class W : public V {
public:
virtual void visit(A& a) {
std::cout << "W visiting A" << std::endl;
}

virtual void visit(B& b) {
std::cout << "W visiting B" << std::endl;
}
};

int main(void) {
B b;
A a;
A *t = &a;

W *v = new W();

v->visit(*t);
t = &b;
v->visit(*t);

return 0;
}
 
V

Victor Bazarov

Alan said:
Hi, I've looked through the FAQ, and I can't seem to find an answer to
this one. Can anyone point me to a design pattern that will produce
the desired behaviour illustrated below please? I know why it doesn't
print "W visiting B", and I've read about double dispatch now too.
What I'd really like though is to keep A and V completely unaware of
the existence of B and W and still end up with "W visiting B" getting
printed. Ideally too I'd like to avoid putting a dynamic_cast in
W::visit(A&).

Is there a nice design pattern for doing this? Or an I searching for
the impossible.

Thanks for any advice,
Alan

class A {
};


class V {
public:
virtual void visit(A& a) = 0;
};

class B : public A {
};

class W : public V {
public:
virtual void visit(A& a) {
std::cout << "W visiting A" << std::endl;
}

virtual void visit(B& b) {
std::cout << "W visiting B" << std::endl;
}
};

int main(void) {
B b;
A a;
A *t = &a;

W *v = new W();

Did you mean

V *v = new W();

? You would probably need a virtual destructor in 'V', of course.
It doesn't really matter, though. What you're trying to do _is_
impossible.
v->visit(*t);
t = &b;
v->visit(*t);

No matter how you slice it, the type of 't' is A*. There is no way
for the program to learn where 't' came from. If 'A' or 'B' _were_
polymorphic types, one could try using 'dynamic_cast', yet it might
still fail (say, if 't' is part of another class deriving from 'A').

Double dispatch would help, if you allow that 'B' could know about 'W'.
return 0;
}

What problem are you trying to solve? Perhaps it's possible to do
using templates?

V
 
M

mlimber

Alan said:
Hi, I've looked through the FAQ, and I can't seem to find an answer to
this one. Can anyone point me to a design pattern that will produce the
desired behaviour illustrated below please? I know why it doesn't print
"W visiting B", and I've read about double dispatch now too. What I'd
really like though is to keep A and V completely unaware of the
existence of B and W and still end up with "W visiting B" getting
printed. Ideally too I'd like to avoid putting a dynamic_cast in
W::visit(A&).

Is there a nice design pattern for doing this? Or an I searching for the
impossible.

Thanks for any advice,
Alan

class A {
};


class V {
public:
virtual void visit(A& a) = 0;
};

class B : public A {
};

class W : public V {
public:
virtual void visit(A& a) {
std::cout << "W visiting A" << std::endl;
}

virtual void visit(B& b) {
std::cout << "W visiting B" << std::endl;
}
};

int main(void) {
B b;
A a;
A *t = &a;

W *v = new W();

v->visit(*t);
t = &b;
v->visit(*t);

return 0;
}

The "problem" is that you have used a polymorphic reference to treat
your B object as an A object. Thus, the best function overload for
W::visit() is the one that accepts an A-reference. If you instead
passed a B-reference directly, it would happily use the other overload.
If you can't make such a change, you'll have to either downcast using
dynamic_cast to regain the lost type information or redesign. The
dynamic_cast, BTW, would not need to be present in V since W can
override the virtual function that accepts an A-reference. (The
B-reference overload obviously cannot be called with a V object except
where the B object is treated as an A, so V would still be ignorant of
B and W types.)

Cheers! --M
 
T

Tom Widmer

Alan said:
Hi, I've looked through the FAQ, and I can't seem to find an answer to
this one. Can anyone point me to a design pattern that will produce the
desired behaviour illustrated below please? I know why it doesn't print
"W visiting B", and I've read about double dispatch now too. What I'd
really like though is to keep A and V completely unaware of the
existence of B and W and still end up with "W visiting B" getting
printed. Ideally too I'd like to avoid putting a dynamic_cast in
W::visit(A&).

I don't think those two requirements are compatible - either you need V
to know about both A and B (in which case you have the normal, cyclic
visitor pattern), or you need to use dynamic_cast (in which case you
have the acyclic visitor pattern).

Google for "cyclic visitor" and "acyclic visitor". A good implementation
of both is available as part of the Loki library. See
http://sourceforge.net/projects/loki-lib/

Here's your code in acyclic form:

struct V {
virtual ~V(){}
};

struct Base {
virtual ~Base(){}
virtual void accept(V&) = 0;
};

struct A;

struct AV: public virtual V {
virtual void visit(A& a) = 0;
};

struct A: public Base {
virtual void accept(V& v)
{
if (AV* av = dynamic_cast<AV*>(&v))
av->visit(*this);
}
};

struct B;

struct BV: public virtual V {
virtual void visit(B& b) = 0;
};

struct B : public A {
virtual void accept(V& v)
{
if (BV* bv = dynamic_cast<BV*>(&v))
bv->visit(*this);
}
};

#include <iostream>

struct W : public AV, public BV {
virtual void visit(A& a) {
std::cout << "W visiting A" << std::endl;
}

virtual void visit(B& b) {
std::cout << "W visiting B" << std::endl;
}
};

int main(void) {
B b;
A a;
A *t = &a;

W *v = new W();

t->accept(*v);
t = &b;
t->accept(*v);
std::cin.get();
return 0;
}


Tom
 
A

Alan Woodland

Victor said:
Alan Woodland wrote:
snip


Did you mean

V *v = new W();
Yes, sorry
? You would probably need a virtual destructor in 'V', of course.
It doesn't really matter, though. What you're trying to do _is_
impossible.
It's as I feared then :( I was just trying to keep the example shorter.
No matter how you slice it, the type of 't' is A*. There is no way
for the program to learn where 't' came from.
Yes, I was hoping there was a gem of wisom that could make it appear
like to not be.
If 'A' or 'B' _were_
polymorphic types, one could try using 'dynamic_cast', yet it might
still fail (say, if 't' is part of another class deriving from 'A').
Yes, thats not too much of a problem though, I can handle that.
Double dispatch would help, if you allow that 'B' could know about 'W'.
I thought double dispatch required A knowing about W too? It's fine for
B to know about W I think - they'll both be being compiled into the same
shared object. (See below)
What problem are you trying to solve? Perhaps it's possible to do
using templates?

I've got a group of Data structures that extend one base class. I've
written all the code so far to be very modular, and load a series of
plugins using shared objects (which works beautifuly). The problem now
though is that I want there to be output plugins that can do various
different types of output (e.g, rendering to OpenGL, saving to XML file
etc.) without ending up tying the Data structures to the output type.
The problem is though that the data structures themselves get extended
by some plugins, so adding a 'virtual void render(DataStructure& ds) =
0' pure virtual method in the base class for the OutputPlugin will only
result in the output plugin being aware of the basic information, not
the fact that it may (or may not) have been subclassed. So to sumarise
the code doesn't really feel like it belongs in a 'virtual void
render()' method in the DataStructure class, and putting it in the
OutputPlugin misses the sub classing.

Alan
 

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,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top