我想实现Composite模式:
class Animal
{
public:
virtual void Run() = 0;
virtual void Eat(const std::string & food) = 0;
virtual ~Animal(){}
};
class Human : public Animal
{
public:
void Run(){ std::cout << "Hey Guys I'm Running!" << std::endl; }
void Eat(const std::string & food)
{
std::cout << "I am eating " << food << "; Yummy!" << std::endl;
}
};
class Horse : public Animal
{
public:
void Run(){ std::cout << "I am running real fast!" << std::endl; }
void Eat(const std::string & food)
{
std::cout << "Meah!! " << food << ", Meah!!" << std::endl;
}
};
class CompositeAnimal : public Animal
{
public:
void Run()
{
for(std::vector<Animal *>::iterator i = animals.begin();
i != animals.end(); ++i)
{
(*i)->Run();
}
}
// It's not DRY. yuck!
void Eat(const std::string & food)
{
for(std::vector<Animal *>::iterator i = animals.begin();
i != animals.end(); ++i)
{
(*i)->Eat(food);
}
}
void Add(Animal * animal)
{
animals.push_back(animal);
}
private:
std::vector<Animal *> animals;
};
你看,由于我对复合模式的简单要求,我最终编写了很多相同的重复代码,迭代在同一个数组上。
#define COMPOSITE_ANIMAL_DELEGATE(_methodName, _paramArgs, _callArgs)\
void _methodName _paramArgs \
{ \
for(std::vector<Animal *>::iterator i = animals.begin(); \
i != animals.end(); ++i) \
{ \
(*i)->_methodName _callArgs; \
} \
}
现在我可以像这样使用它:
class CompositeAnimal : public Animal
{
public:
// It "seems" DRY. Cool
COMPOSITE_ANIMAL_DELEGATE(Run, (), ())
COMPOSITE_ANIMAL_DELEGATE(Eat, (const std::string & food), (food))
void Add(Animal * animal)
{
animals.push_back(animal);
}
private:
std::vector<Animal *> animals
};
有没有办法用C ++元编程实现“清洁”?
std::for_each
已被建议作为解决方案。我认为我们的问题是更一般性问题的具体情况,让我们考虑一下我们的新宏:
#define LOGGED_COMPOSITE_ANIMAL_DELEGATE(_methodName, _paramArgs, _callArgs)\
void _methodName _paramArgs \
{ \
log << "Iterating over " << animals.size() << " animals"; \
for(std::vector<Animal *>::iterator i = animals.begin(); \
i != animals.end(); ++i) \
{ \
(*i)->_methodName _callArgs; \
} \
log << "Done" \
}
看起来这不能被for_each
看看GMan的优秀答案,C ++的这一部分绝对是不平凡的。就个人而言,如果我们只是想减少样板代码的数量,我认为宏可能是适合这种特殊情况的工具。
GMan建议std::mem_fun
和std::bind2nd
返回仿函数。不幸的是,这个API不支持3个参数(我不相信这样的东西被释放到STL中)。
为了便于说明,以下是使用boost::bind
的委托函数:
void Run()
{
for_each(boost::bind(&Animal::Run, _1));
}
void Eat(const std::string & food)
{
for_each(boost::bind(&Animal::Eat, _1, food));
}
答案 0 :(得分:10)
我不确定我是否真的看到了这个问题。为什么不能这样:
void Run()
{
std::for_each(animals.begin(), animals.end(),
std::mem_fun(&Animal::Run));
}
void Eat(const std::string & food)
{
std::for_each(animals.begin(), animals.end(),
std::bind2nd(std::mem_fun(&Animal::Eat), food));
}
还不错。
如果您真的想摆脱(小)样板代码,请添加:
template <typename Func>
void for_each(Func func)
{
std::for_each(animals.begin(), animals.end(), func);
}
作为私人实用程序成员,然后使用:
void Run()
{
for_each(std::mem_fun(&Animal::Run));
}
void Eat(const std::string & food)
{
for_each(std::bind2nd(std::mem_fun(&Animal::Eat), food));
}
更简洁一点。不需要元编程。
事实上,元编程最终会失败。您正在尝试生成以文本方式定义的函数。元编程无法生成文本,因此您不可避免地会在某处使用宏来生成文本。
在下一级,您将编写该函数,然后尝试取出样板代码。 std::for_each
做得很好。当然,正如已经证明的那样,如果你发现 过多的重复,那么也要考虑到这一点。
在回复评论中的LoggedCompositeAnimal
示例时,您最好的选择是制作类似于:
class log_action
{
public:
// could also take the stream to output to
log_action(const std::string& pMessage) :
mMessage(pMessage),
mTime(std::clock())
{
std::cout << "Ready to call " << pMessage << std::endl;
}
~log_action(void)
{
const std::clock_t endTime = std::clock();
std::cout << "Done calling " << pMessage << std::endl;
std::cout << "Spent time: " << ((endTime - mTime) / CLOCKS_PER_SEC)
<< " seconds." << std::endl;
}
private:
std::string mMessage;
std::clock_t mTime;
};
这主要是自动记录操作。然后:
class LoggedCompositeAnimal : public CompositeAnimal
{
public:
void Run()
{
log_action log(compose_message("Run"));
CompositeAnimal::Run();
}
void Eat(const std::string & food)
{
log_action log(compose_message("Eat"));
CompositeAnimal::Eat(food);
}
private:
const std::string compose_message(const std::string& pAction)
{
return pAction + " on " +
lexical_cast<std::string>(animals.size()) + " animals.";
}
};
像那样。 有关lexical_cast的信息。
答案 1 :(得分:2)
您可以使用仿函数而不是方法:
struct Run
{
void operator()(Animal * a)
{
a->Run();
}
};
struct Eat
{
std::string food;
Eat(const std::string& food) : food(food) {}
void operator()(Animal * a)
{
a->Eat(food);
}
};
并添加CompositeAnimal::apply
(#include <algorithm>
):
template <typename Func>
void apply(Func& f)
{
std::for_each(animals.begin(), animals.end(), f);
}
然后你的代码会像这样工作:
int main()
{
CompositeAnimal ca;
ca.Add(new Horse());
ca.Add(new Human());
Run r;
ca.apply(r);
Eat e("dinner");
ca.apply(e);
}
输出:
> ./x
I am running real fast!
Hey Guys I'm Running!
Meah!! dinner, Meah!!
I am eating dinner; Yummy!
为了保持界面的一致性,您可以更进一步。
将struct Run
重命名为Running
,将Eat
重命名为Eating
以防止方法/结构冲突。
然后CompositeAnimal::Run
使用apply
方法和struct Running
:
void Run()
{
Running r;
apply(r);
}
同样CompositeAnimal::Eat
:
void Eat(const std::string & food)
{
Eating e(food);
apply(e);
}
你现在可以打电话了:
ca.Run();
ca.Eat("dinner");
输出仍然相同:
I am running real fast!
Hey Guys I'm Running!
Meah!! dinner, Meah!!
I am eating dinner; Yummy!