factory design question..

M

ma740988

Consider:

#include "handyfactory.h"
#include <iostream>

struct Shape
{
virtual void print() const=0;
};


struct Square: public Shape
{
void print() const
{
cout<<"I am square, hear me roar!" <<endl;
}
};


struct Triangle: public Shape
{
void print() const
{
cout<<"I am triangle, hear me roar!" <<endl;
}
};


int main()
{
Factory::reg<Shape, Square>("square");
Factory::reg<Shape, Triangle>("triangle");
Factory::make<Shape>("triangle")->print();

}


-----handyfactory.h-----
#include <map>
#include <string>
using namespace std;
struct Factory
{
template<typename T>
static map<string, T *> &getMap()
{
static map<string, T *> mp;
return mp;
}

template <typename T>
struct Creator
{
virtual T * make()=0;
};


template <typename T1, typename T2>
struct CreatorSub: public Creator<T1>
{
virtual T1 * make()
{
return new T2;
}
};


template <typename T1, typename T2>
static void reg(string const &str)
{
(getMap<Creator<T1> >())[str]=new CreatorSub<T1, T2>;
}


template <typename T>
static T* make(const string &str)
{
return getMap<Creator<T> >()[str]->make();
}
};

Now the factory appears to be ideal considering the print functions are
pure vitual. What if the print functions differed in the number of
parameters? For instance, assume the print methods for square and
triangle took 1 and 2 arguments respectively.

void print( int idx ) {} // square
void print ( int idx, int jdx ) // triangle..
 
I

I V

ma740988 said:
Now the factory appears to be ideal considering the print functions are
pure vitual. What if the print functions differed in the number of
parameters? For instance, assume the print methods for square and
triangle took 1 and 2 arguments respectively.

void print( int idx ) {} // square
void print ( int idx, int jdx ) // triangle..
From the point of view of the factory, I don't think this would make a
difference. The problem is, what do you do with the objects created by
the factory if they don't have a common interface? You could use
dynamic_cast, as long as Shape has one or more virtual functions:

Shape* shape = Factory::make<Shape>("triangle");
Triange* triangle = dynamic_cast<Triangle*>(shape);
if( !triangle ) {
std::cerr << "I thought this was a Triangle, but it's not" <<
std::endl;
std::exit(1);
}
triangle->print(2, 3);

However, dynamic_cast is generally considered to be a less than ideal
solution, for various reasons, for one, that it defers type checking
from compile time to run time, which may mean bugs go undetected. If
Square and Triangle don't have a common interface (as they won't, if
they only have a "print" method which takes a different number of
arguments), then you probably shouldn't use them in a factory of the
sort you have above - the factory claims to create objects supporting a
given interface, and if there is no appropriate common interface, the
factory cannot behave properly (note that if there is no common
interface which Triangle and Square share, there shouldn't be a common
base type that they both derive from).

What exactly is the problem you are trying to solve? If you're just
asking a theoretical question, then the theoretical answer is simple:
"don't do that" (that is, don't create a factory which creates classes
with no common interface). If you have a particular use in mind, then
people may be able to suggest alternative designs if you give us more
detail.
 
Y

yuvalif

If some interfaces are common, and other are common in functionality
(print) and not in prototype (different number of arguments) you may
use a function object for print, base class for the function object
will be the same (in the base class Shape) and the factory could create
the derived "Print" object.

Yuval.
 
K

kasiyil

You can define a more general API. If one of two prints takes one
argument and the other takes 2, then you can generalize the API of
print to take 2 arguments and you dont use the second argument in the
print that takes only one argument. As a result, the API fits both
prints.

A more gentle solution is define a parameter interface class and derive
2 subclasses from this interface.
Like:

class PrintParameter {
};

class OneArgumentPrintParameter {
int x;
};

class TwoArgumentPrintParameter {
int x;
int y;
};

then print methods look like ;

void print (PrintArgument *arg);

if this mehod is the one that needs one argument :

void print(PrintArgument *arg)
{
use ((OneArgumentPrintParameter *)arg)->x;
}

else

void print(PrintArgument *arg)
{
use TwoArgumentPrintParameter *)arg)->x;
use TwoArgumentPrintParameter *)arg)->y;
}


I hope it helps!
 
K

kasiyil

You can define a more general API. If one of two prints takes one
argument and the other takes 2, then you can generalize the API of
print to take 2 arguments and you dont use the second argument in the
print that takes only one argument. As a result, the API fits both
prints.

A more gentle solution is define a parameter interface class and derive
2 subclasses from this interface.
Like:

class PrintParameter {
};

class OneArgumentPrintParameter {
int x;
};

class TwoArgumentPrintParameter {
int x;
int y;
};

then print methods look like ;

void print (PrintArgument *arg);

if this mehod is the one that needs one argument :

void print(PrintArgument *arg)
{
use ((OneArgumentPrintParameter *)arg)->x;
}

else

void print(PrintArgument *arg)
{
use (TwoArgumentPrintParameter *)arg)->x;
use (TwoArgumentPrintParameter *)arg)->y;
}


I hope it helps!
 
M

ma740988

I said:
ma740988 wrote: [.......]

What exactly is the problem you are trying to solve? If you're just
asking a theoretical question, then the theoretical answer is simple:
"don't do that" (that is, don't create a factory which creates classes
with no common interface). If you have a particular use in mind, then
people may be able to suggest alternative designs if you give us more
detail.

You're right . I'm not so sure the factory is a good idea here.
Here's my initail attempt. Two classes wanting to run different
algorithms...

#include <iostream>
#include <vector>
#include <map>
#include <numeric>
#include <deque>

#define MEAN1 1
#define MEAN2 2
#define MEAN3 3
#define MEAN4 4
#define MEAN5 5
#define MEAN6 6

using namespace std;

typedef deque <double> DOUBLE_QUEUE;

class AbstractAlgorithm{
public:
virtual double run(const DOUBLE_QUEUE& queue) = 0;
};


class Mean1: public AbstractAlgorithm{
public:
double run(const DOUBLE_QUEUE& queue){
double val = accumulate ( queue.begin(), queue.end(), 0. ) /
queue.size();
cout << val << " ";
return val;
}
};

class Mean2: public AbstractAlgorithm{
public:
double run(const DOUBLE_QUEUE& queue){
double val = accumulate ( queue.begin(), queue.end(), 0. ) /
queue.size();
cout << val << " ";
return val;
}
};

class Mean3: public AbstractAlgorithm{
public:
double run(const DOUBLE_QUEUE& queue){
double val = accumulate ( queue.begin(), queue.end(), 0. ) /
queue.size();
cout << val << " ";
return val;
}
};

class Mean4: public AbstractAlgorithm{
public:
double run(const DOUBLE_QUEUE& queue){
double val = accumulate ( queue.begin(), queue.end(), 0. ) /
queue.size();
cout << val << " ";
return val;
}
};

class Mean5: public AbstractAlgorithm{
public:
double run(const DOUBLE_QUEUE& queue){
double val = accumulate ( queue.begin(), queue.end(), 0. ) /
queue.size();
cout << val << " ";
return val;
}
};

class Mean6: public AbstractAlgorithm{
public:
double run(const DOUBLE_QUEUE& queue){
double val = accumulate ( queue.begin(), queue.end(), 0. ) /
queue.size();
cout << val << " ";
return val;
}
};

class Algorithms{
private:
map<int,AbstractAlgorithm*> _registeredAlgorithms;

public:
void registerAlgorithm(const int name, AbstractAlgorithm* alg){
_registeredAlgorithms[name]=alg;
}

double run(const int name, const DOUBLE_QUEUE& queue){
_registeredAlgorithms[name]->run(queue);
}
};

class MultipleCallAlgorithm{
friend ostream &operator<<(ostream &str, MultipleCallAlgorithm c);
private:
vector<int> _v;
Algorithms _a;

public:
MultipleCallAlgorithm(const Algorithms a){
this->_a = a;
}

void registerAlgorithm(const int s){
_v.push_back(s);
}

void runRegistered(const DOUBLE_QUEUE& queue){
vector<int>::iterator iter;

for(iter = _v.begin() ; iter != _v.end() ; iter++)
_a.run(*iter,queue);
}

double run(const int name, const DOUBLE_QUEUE &queue ){
_a.run(name,queue);
}
};

class YingYang{
private:
MultipleCallAlgorithm* _a;
public:
YingYang(MultipleCallAlgorithm* a){
this->_a = a;
}

void registerAlgorithm(const int name){
_a->registerAlgorithm(name);
}

void runRegistered(const DOUBLE_QUEUE& queue){
_a->runRegistered(queue);
}
};

main(){

Algorithms a;

// Algorithm registration
a.registerAlgorithm(MEAN1, new Mean1());
a.registerAlgorithm(MEAN2, new Mean2());
a.registerAlgorithm(MEAN3, new Mean3());
a.registerAlgorithm(MEAN4, new Mean4());
a.registerAlgorithm(MEAN5, new Mean5());
a.registerAlgorithm(MEAN6, new Mean6());

YingYang ying(new MultipleCallAlgorithm(a));
ying.registerAlgorithm(MEAN1);
ying.registerAlgorithm(MEAN3);
ying.registerAlgorithm(MEAN6);

YingYang yang(new MultipleCallAlgorithm(a));
yang.registerAlgorithm(MEAN1);
yang.registerAlgorithm(MEAN4);
yang.registerAlgorithm(MEAN5);

// Example data
DOUBLE_QUEUE q;
q.push_back(3);
q.push_back(4);
q.push_back(5);
q.push_back(6);
q.push_back(7);

ying.runRegistered(q);
yang.runRegistered(q);

}

So that was my initial attempt to determine how the factory could help
me here. That's fine except in reality each MEAN function differs in
the number of arguments. Well the factory _blew_ up, so now i'm
thinking ... - but first NOTE: MEAN is 'for demostration purpose'..
My real algorithms is FFT's IFFTs, BEM, MEAN etc. Both classes Ying
and Yang will run a subset of each .. meaning I know in advacen what to
run. An incoming message from the 'outside' world is what tells me the
sequence to run them. That's irrelevant though. Based on the code
above assume Yang should run MEAN1, MEAN4 and MEAN5. Assume Ying will
run MEAN1, MEAN3 and MEAN6. Assume MEAN1 takes two arguments, MEAN3
takes 5 arguments, MEAN6 takes 4 arguments.

That aside, I'm thinking I'm down to two options:
1. - One "Algorithms" class per client-class
- Algorithm registration will occur BEFORE class creation, but each
client class will have a copy of each algorithm (duplicates).
- As all the Algorithms classes must have the same interface, there are
two options:
a) fixed number of registerable functions (example: Algorithms
interface will be: alg1, alg2 and alg3, executing mean1, mean 3 and
mean 5 for Ying and mean1,mean 4 and mean 5 for Yang).

2. A generic interface where client classes refer to algorithms by
name. This means a
separate "MultipleCall algorithm" for each client class. This bothers
me though, so maybe I can try mixing both options, and use
"Centralized algorithm list" and "Algoritm registration". This way I
have flexibility and a single AlgorithmHelper class. The only drawback
here is a little more complicated interface - which I'm not sure how to
put together.

So ultimately I could have
class Ying:
algorithms.run(MEAN1,data1);
algorithms.run(MEAN3,different_data);
algorithms.run(MEAN6,different_data);

class Yang:
algorithms.run(MEAN1,data1);
algorithms.run(MEAN4,different_data);
algorithms.run(MEAN5,different_data);

Design 'decisions' (alot more involved than putting together a foo
class - if you follow what I'm saying) tend to be a little over my head
right now so I'm struggling with putting together both options.
 

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,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top