Design question: alternative to inheritance.

G

gas

Hi

So, I have the following problem:

I have to store several types of Cells, (call them A, B, ...), where the
number of Cell types is going to increase as we continue development.

Among other things, I have to compute inductances between all
combinations of these cells. How the inductance is computed depends
entirely on the type of cell.

It seems like there must be an elegant solution to this, probably a
design pattern. The naive solutions I think of have big disadvantages:
If I store a pointer of base classes, calculating the inductances gets
messy and complicated (especially as the number of Cell types
increases). If I store the different cell types seperately, the routines
where I calculate over the different combinations of cells gets ugly, and
scales badly.

So can someone give me a good idea, point me at a good pattern, whatever?

Thanks,

Glen
 
G

gas

On Wed, 16 Jul 2008 13:57:37 -0400, Victor Bazarov wrote:

Thanks for the reply.
gas said:
I have to store several types of Cells, (call them A, B, ...), where
the number of Cell types is going to increase as we continue
development.

Are they different types or different "types"? What's the actual
difference? Do you model the difference in behaviour using virtual
functions (polymorphism) or using some kind of property data and an
internal switch statement?


I was trying to avoid details and boil the question down to just the
relevant information, but maybe I boiled it too much. To give the
context, I've been brought in to work on a project that has been
developed, until now, primarily by students. The application models
electromagnetic behavior of circuit elements. Thus far, inductances have
been calculated solely by approximating the elements of Bars (Bars is an
eponymous class), or series of Bars . Now we would like to extend our
approximations to contain also Cylinders, hollow cylinders, etc. To save
on typing I shortened these to A,B,C, etc... So they are different
classes.

Polymorphism is useful to our program design, but not in the context I am
discussing here. Vis:
So, the computation mechanism will have to either know what type of cell
(the true type) it is (by querying the property data) or require the
cell itself to perform part of the computation thus relying on cells'
polymorphic behaviour, right?

It cannot rely on the cells polymorphic behavior. If it did, that would
certainly make the issue simple, and I wouldn't be posting here. The
integrals which have to be performed depend on the combination of
elements. So there is totally different behavior for calc_ind(A,A),
calc_ind(A,B), calc_ind(B,B), and so on. Querying types and using
switches is certainly a solution but, as I mentioned in the original
post, has obvious disadvantages in scaling.

Ideally, I would use overloaded functions to implement the routines, but
then the code where I iterate over all combinations of elements gets
pretty ugly, and hard to extend.
Looks like a Visitor pattern...

I have to admit a low familiarity with design patterns. I thought the
visitor class was relevant to visiting single classes, not pairs of
classes. Thanks for the tip, I'll go have a read now.
You seem to be in the OOD land here and yours is not really a C++
language problem. Try posting your inquiry to 'comp.object'. There is
also 'comp.software.patterns' newsgroup which might be worth visiting.

Well, the code is in C++. I don't care if the best solution stems from
an OO paradigm, a generic paradigm, a functional programming paradigm, or
a combination thereof, as long as I can implement in C++.

Thanks for your time.
 
S

Stefan Ram

gas said:
So there is totally different behavior for calc_ind(A,A),
calc_ind(A,B), calc_ind(B,B), and so on.

Such behavior is written using so-called »multimethods«, here
»bimethods« (my term for multimethods with two parameters).

The visitor pattern mentioned is a means to emulate bimethods
in languages without multimethods.

http://google.to/search?q=multimethods+C++

This is needed only, if the types are only known at run-time.
If the types are known at compile time, you just can overload
calc_ind.
 
S

Stefan Ram

The visitor pattern mentioned is a means to emulate bimethods
in languages without multimethods.

To explain the visitor pattern, I have made up a simplified
version: The dynamic-static adaptor.

There might be a type hierarchy as follows:

struct X { ... };
struct A : public X { ... };
struct B : public X { ... };

One might be given two function definitions with /static/
dispatch:

struct S
{
static void static_process( A const * a )
{ ::std::cout << "S::static_process( A )\n"; }

static void static_process( B const * b )
{ ::std::cout << "S::static_process( B )\n"; }};

But one might want to call »static_process« via /dynamic/ dispatch:

int main()
{ A a;
B b;
{ X * x = &a; x->static_process(); }
{ X * x = &b; x->static_process(); }}

S::static_process( A )
S::static_process( B )

(The final two lines are the output wanted.)

This won't work directly as shown above, because of the
dynamic-static mismatch.

But one can write a dynamic-static adaptor to
convert the dynamic calls to static calls:

struct X { virtual void process() = 0; };
struct A : public X { void process(){ S::static_process( this ); }};
struct B : public X { void process(){ S::static_process( this ); }};

The key to this is that the type of »this« is known statically
at the two places above. Therefore, it can be used for static
dispatch. Still, calls to X#process() are dispatched
dynamically. So, a /dynamically/ dispatched call is converted to
a /statically/ dispatched call. The complete source code:

#include <iostream>
#include <ostream>

struct A;
struct B;

struct S
{ static void static_process( A const * a )
{ ::std::cout << "S::static_process( A )\n"; }
static void static_process( B const * b )
{ ::std::cout << "S::static_process( B )\n"; }};

struct X { virtual void process() = 0; };
struct A : public X { void process(){ S::static_process( this ); }};
struct B : public X { void process(){ S::static_process( this ); }};

int main()
{ A a;
B b;
{ X * x = &a; x->process(); }
{ X * x = &b; x->process(); }}

S::static_process( A )
S::static_process( B )

(The final two lines are the output.)

By a combination of two dynamic-static adaptors, one gets the
visitor pattern. This might be elaborated in another post.
 
S

Stefan Ram

By a combination of two dynamic-static adaptors, one gets the
visitor pattern. This might be elaborated in another post.

The following example for the visitor pattern might deviate
from the visitor pattern published elsewhere, because I have
only used two types, »A« and »B«, and I have »reinvented« it
from my recollection, without verification with the literature.

But at least it shows a combination of two dynamic-static
adaptors to get a »bidynamic-bistatic adaptor«:

#include <iostream>
#include <ostream>

struct A;
struct B;

struct S
{ static void static_process( ::A const * const a, ::A const * const a_ )
{ ::std::cout << "::S::static_process( A, A )\n"; }
static void static_process( ::A const * const a, ::B const * const b )
{ ::std::cout << "::S::static_process( A, B )\n"; }
static void static_process( ::B const * const b, ::A const * const a )
{ ::std::cout << "::S::static_process( B, A )\n"; }
static void static_process( ::B const * const b, ::B const * const b_ )
{ ::std::cout << "::S::static_process( B, B )\n"; }};

struct X
{ virtual void process( ::A const * const a )const = 0;
virtual void process( ::B const * const b )const = 0;
virtual void process( ::X const * const x )const = 0; };

struct A : public ::X
{ void process( ::A const * const a )const{ ::S::static_process( a, this ); }
void process( ::B const * const b )const{ ::S::static_process( b, this ); }
void process( ::X const * const x1 )const{ x1->process( this ); }};

struct B : public ::X
{ void process( ::A const * const a )const{ ::S::static_process( a, this ); }
void process( ::B const * const b )const{ ::S::static_process( b, this ); }
void process( ::X const * const x1 )const{ x1->process( this ); }};

int main()
{ ::A const a;
::B const b;
::X const * x0;
::X const * x1;
x0 = &a; x1 = &a; x0->process( x1 );
x0 = &a; x1 = &b; x0->process( x1 );
x0 = &b; x1 = &a; x0->process( x1 );
x0 = &b; x1 = &b; x0->process( x1 ); }

::S::static_process( A, A )
::S::static_process( A, B )
::S::static_process( B, A )
::S::static_process( B, B )

(The last four lines are the output.)
 
S

Stefan Ram

Supersedes: <[email protected]>

By a combination of two dynamic-static adaptors, one gets the
visitor pattern. This might be elaborated in another post.

The following example for the visitor pattern might deviate
from the visitor pattern published elsewhere, because I have
only used two types, »A« and »B«, and I have »reinvented« it
from my recollection, without verification with the literature.

But at least it shows a combination of two dynamic-static
adaptors to get a »bidynamic-bistatic adaptor«:

#include <iostream>
#include <ostream>

struct A;
struct B;

struct S
{ static void static_process( ::A const * const a, ::A const * const a_ )
{ ::std::cout << "::S::static_process( A, A )\n"; }
static void static_process( ::A const * const a, ::B const * const b )
{ ::std::cout << "::S::static_process( A, B )\n"; }
static void static_process( ::B const * const b, ::A const * const a )
{ ::std::cout << "::S::static_process( B, A )\n"; }
static void static_process( ::B const * const b, ::B const * const b_ )
{ ::std::cout << "::S::static_process( B, B )\n"; }};

struct X
{ virtual void process( ::A const * const a )const = 0;
virtual void process( ::B const * const b )const = 0;
virtual void process( ::X const * const x )const = 0; };

struct A : public ::X
{ void process( ::A const * const a )const{ ::S::static_process( a, this ); }
void process( ::B const * const b )const{ ::S::static_process( b, this ); }
void process( ::X const * const x1 )const{ x1->process( this ); }};

struct B : public ::X
{ void process( ::A const * const a )const{ ::S::static_process( a, this ); }
void process( ::B const * const b )const{ ::S::static_process( b, this ); }
void process( ::X const * const x1 )const{ x1->process( this ); }};

void process( ::X const * const x0, ::X const * const x1 )
{ x0->process( x1 ); }

int main()
{ ::A const a;
::B const b;
::X const * x0;
::X const * x1;
x0 = &a; x1 = &a; process( x0, x1 );
x0 = &a; x1 = &b; process( x0, x1 );
x0 = &b; x1 = &a; process( x0, x1 );
x0 = &b; x1 = &b; process( x0, x1 ); }

::S::static_process( A, A )
::S::static_process( A, B )
::S::static_process( B, A )
::S::static_process( B, B )

(The last four lines are the output.)
 

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

Forum statistics

Threads
474,173
Messages
2,570,938
Members
47,474
Latest member
VivianStuk

Latest Threads

Top