我有fancyFunction
,其中包含一组实现接口A
的元素。该函数基于通过接口A
读取的属性对这些元素进行复杂的分析。在此分析过程中,它将调用Consumer c
的方法,这些方法将元素作为参数。
Consumer
旨在获取与A
完全无关的特定类型的参数。
你可以想象A
是图中边的抽象。该图表在fancyFunction
中进行分析,并且 - 例如 - 每次函数"越过"边缘,它会将该边缘发送到Consumer
,它会打印存储在边缘中的与边缘无关的附加信息。
下面给出的代码当然不能用类型语言(特别是C ++)编译,但是省略了类型(Matlab,Python),代码就可以了。
为了使它以类型语言(特别是C ++)工作,我看到两个选项:
将函数声明为
template <class CONSUMER>
void fancyFunction(A[] setOfAs, CONSUMER c){ ... }
声明operation1
和operation2
以获取最常规的对象,然后在实现中执行向下转发。
在这种情况下,您建议做什么?(据我所知,访客模式不是一种选择。)
完整的代码大纲(我暂时没有使用C ++,所以请原谅是否存在轻微的语法错误。):
void fancyFunction(A[] setOfAs, Consumer* c){
// do fancy analysis of setOfAs by properties
// read through interface A
double x = setOfAs[i]->getX();
// call functions in c with arguments of setOfAs[j]
...
c->operationX(setOfAs[i]);
...
c->operationY(setOfAs[j]);
...
}
class A{
virtual double getX();
}
class Consumer{
virtual void operationX(??? x); // whoops, what type do we expect?
virtual void operationY(??? y); // whoops, what type do we expect?
}
class Consumer1{
void operationX(Obj1 x){ ... } // whoops, override with different type
void operationY(Obj1 y){ ... } // whoops, override with different type
}
class Consumer2{
void operationX(Obj2 x){ ... } // whoops, override with different type
void operationY(Obj2 y){ ... } // whoops, override with different type
}
class Obj1 : public A {};
class Obj2 : public A {};
void test(){
Obj1 o1[];
Obj2 o2[];
Callback1 c1;
Callback2 c2;
fancyFunction(o1, &c1);
fancyFunction(o2, &c2);
}
答案 0 :(得分:2)
我相信您正在寻找的解决方案称为Visitor Pattern。
您不希望在您的花哨功能中手动转换对象A的每个实例,因为这是一个维护噩梦和清晰的代码嗅觉。
另一方面,如果每个对象自动处理自己的转换怎么办?这就是访客模式。
首先在基类(A)中定义一个新的“访问”函数,将您的消费者作为唯一参数:
class A
{
public:
virtual void Visit(Consumer& consumer) = 0;
}
然后为每个继承的类实现此函数,因此:
class B : public A
{
public:
void Visit(Consumer& consumer)
{
consumer.DoOperation(this); // 'this' utomatically resolves to type B*
}
}
每个派生类型现在通过将“this”指针传递给提供的Consumer实例来处理调用适当的操作重载。 'this'指针会自动解释为最具体的类型。
回顾一下原始的示例代码,看起来每个Consumer都提供多个操作,并且只处理一个类型。 此模式可能需要您稍微更改此范例:为每个操作创建单个使用者,其中每个使用者为每个可能的继承提供重载类型。
class ConsumerX
{
public:
void DoOperation(A* a) { /* ERROR! This is a base type. If this function is called, you probably need to implement another overload. */ }
void DoOperation(B* b) { /* Much better */ }
}
class ConsumerY
{
public:
void DoOperation(A* a) { /* ERROR! This is a base type. If this function is called, you probably need to implement another overload. */ }
void DoOperation(B* b) { /* Much better */ }
}
然后你的实现循环看起来像这样:
ConsumerX consumerX; // Does Operation X for every type
ConsumerY consumerY; // Does Operation Y for every type
for(int x = 0; x < numElements, x++)
{
auto element = setOfAs[x];
element.Visit(consumerX); //Do operation X
element.Visit(consumerY); //Do operation Y
}
答案 1 :(得分:0)
显然,模板是合适的。我甚至会问你为什么fancyFunction
坚持基类A
。它应该只是一个开始和结束迭代器。我也不打扰消费者。也要灵活,只需要任何功能。
事实上,我甚至不会写fancyFunction
。它已经存在:
std::for_each(o1.begin(), o1.end(),
[c1](Obj1 o) { double x = o.getX(); c1.operationX(o); c1.operationY(o); }
);