鸟类,蜜蜂和抽象类

时间:2015-09-24 16:25:08

标签: c++ design-patterns

新手我目前仍然坚持这个看似简单的问题。假设我想写一些关于一群快乐生育的动物的代码。显然,他们都需要一个mate()方法,所以我可以定义一个这样的抽象类:

class FemaleAnimal {
public:
    virtual FemaleAnimal mate(const MaleAnimal& a) const = 0;
    virtual MaleAnimal mate(const MaleAnimal& a) const = 0;
}

并衍生出所有不同的物种:

class FemaleBird : public FemaleAnimal {
public:
    FemaleBird mate (const MaleBird& b) const;
    MaleBird mate (const MaleBird& b) const;
}

class FemaleBee: public FemaleAnimal {
public:
    FemaleBee mate (const MaleBee& b) const;
    MaleBee mate (const MaleBee& b) const;
}

在main()中,我想要两个向量

males = vector<MaleAnimal>
females = vector<FemaleAnimal>

每个都可能包含鸟类和蜜蜂,并在运行时填充。每个指数的物种都匹配,所以 如果雄性[i]是蜜蜂,那么雌性[i]也是蜜蜂,所以我可以做到

vector<FemaleAnimal> femaleOffspring;
vector<MaleAnimal> maleOffspring;
for (int i=0; i<males.size(); ++i){
    femaleOffspring.push_back( females[i].mate(males[i]) );
    maleOffspring.push_back( females[i].mate(males[i]) );
}    

现在,显然我希望派生类中的mate()方法实现基类中的方法,但是我必须为动物定义mate(),例如

FemaleBee::mate(const MaleAnimal& a) const;

但蜜蜂不与鸟类交配。我如何实现这种专业化?这有什么特殊的设计模式吗?我试图研究协方差等问题,但这比帮助更令人困惑。

奖金问题:当男性[i]和女性[i]在运行时属于不同物种时,如何捕获案例?

编辑:假设男性和女性来自完全不同的类层次结构,并且不能明智地从公共基类派生。

编辑:为了完整起见,这是基于n.m.答案的最终解决方案。非常感谢大家!

#include <iostream>

using namespace std;

class GenericMale {
public:
    virtual void test() = 0;    // just to make the class abstract
};


template <typename Species>
class Male : public GenericMale
{
    void test() {};
};


class GenericFemale {
    virtual void tryMate (const GenericMale&) const = 0;
};


template <typename Species>
class Female : public GenericFemale
{
public:
    virtual void tryMate (const GenericMale& m) const
    {
        try {
            auto& p= dynamic_cast<const Male<Species>&>(m);    // will throw if Species does not match
            Species::doMate(p);
        } catch ( exception& e) {
            cerr << "[MATING ERROR] You filthy animals, stay within your own species!" << endl;
        }
    }
};


class Bee {
public:
    static void doMate(const Male<Bee>& p) {
        cout << "Buzz buzz buzz!" <<endl;
    }
};


class Bird {
public:
    static void doMate(const Male<Bird>& p) {
        cout << "Chirpy chirpy cheep cheep!" << endl;
    }
};



int main() {
    Female<Bee> queenBee;
    Male<Bee> drone;
    queenBee.tryMate(drone);

    Female<Bird> mamaBird;
    Male<Bird> papaBird;
    mamaBird.tryMate(papaBird);

    queenBee.tryMate(papaBird);
}

4 个答案:

答案 0 :(得分:3)

您基本上有两个选项:编译时检查和运行时检查。

编译时

您的MaleAnimalFemaleAnimal个对象没有使用您提供的签名的mate方法。事实上,由于任何女性不能交配任何男性,方法签名

FemaleAnimal::mate(const MaleAnimal& a) const

是一个过于慷慨的承诺。不要承诺你无法实现的目标。

FemaleAnimal应该能够与同一物种的MaleAnimal交配。我们怎么写下来的呢?我们编码动物类型的物种。在C ++中,这将是一个模板。

template <typename Species>
class Animal ...

template <typename Species>
class MaleAnimal : public Animal<Species> ...

template <typename Species>
class FemaleAnimal : public Animal<Species>
{ ...
   void mate (const MaleAnimal<Species>&) const ...
}

这种类型安全需要付出代价。您不能将不同物种的代表保留在同一容器中保留其物种信息。每个物种需要一个不同类型的独立容器。

 class AnimalContainer
 { ...
    virtual void mateAll() = 0;
 };

 template <typename Species>
 class SpecificContainer : public Container
 {
    ...
    std::vector<MaleAnimal<Species>> males;
    std::vector<FemaleAnimal<Species>> females;
    void mateAll()
    {
       for (...) females[i].mate(males[i]);
    }        
 }

现在你可以保留一个AnimalContainer个容器,每个容器可以保留一个单独的物种。

运行时

您的FemaleAnimal可以尝试与任何MaleAnimal交配。尝试可能会失败。这最好用dynamic_cast建模:如果检查通过,则返回正确的类型,如果没有,则失败(抛出异常)。

可以修改层次结构:

class Animal ...

class GenericMaleAnimal : public Animal ...
class GenericFemaleAnimal : public Animal 
{ ...
   virtual void tryMate (const GenericMaleAnimal&) const = 0;
};

template <typename Species>
class MaleAnimal : public GenericMaleAnimal ...

template <typename Species>
class FemaleAnimal : public GenericFemaleAnimal
{ ...
   virtual void tryMate (const GenericMaleAnimal& m) const
   {
      // will throw if species don't match
      auto& other = dynamic_cast<const MaleAnimal<Species>&>(m);
      // Specific mate procedure for given species
      Species::doMate(*this, other);
   }
};

现在,您可以将所有内容保存在同一容器中,并且您有责任使类型匹配。

答案 1 :(得分:2)

您的描述可能会指向答案:

  

每个指数的物种都匹配,所以如果雄性[i]是蜜蜂,那么雌性[i]也是蜜蜂

涵盖专业化

  但是,蜜蜂不会与鸟类交配。我如何实现这种专业化?

问:你是如何保证他们匹配的?答:您必须同时知道两个条目的类型!当您这样做时,只需设置命令模式,lambda等,以使用两种已知类型调用专用方法。

最后看看enable_if的这种用法 - 如果我对上述陈述不正确,可以用来解决你的问题。

答案 2 :(得分:1)

“当男性[i]和女性[i]在运行时属于不同物种时,如何捕捉案例?”:取决于是否错误交配错误。如果可以尝试但只是不起作用,您可以在mate()功能中执行一些检查,例如StillLearning建议对论证进行动态演示(是我的物种吗?)。如果您想要更灵活,可以使用可以提供的检查函数来配置该测试。在动物的构造函数中,或作为mate()的可选参数。如果测试是否定的,则无所事事。

如果错误的交配尝试是一个错误(因为你设置的向量严重并且无法继续),你可以抛出异常。

编辑:@Buddy指向双重调度模式的指针对于不同物种(它们各自基础的不同子类)的不同交配是有用和优雅的,即使只提供指针或对基础的引用。双重调度可以隐含地通过重载mate_callback()(或者其他任何 - 原始调用者在原始调用者中实现的函数)合理地为所需物种合理地包含“检查”,并且具有全部抛出或对所有其他物种没有任何作用。双重调度也符合道德标准,因为它在交配时需要达成共识。

答案 3 :(得分:0)

不要认为不同性别应该是不同的阶级。想想那些来自侏罗纪公园的恐龙会自发地改变它们的性别。还有一种情况是,性别不明确(细菌)或交配与性别(蜗牛)无关,更不用说无性繁殖了。

所以性别应该只是动物类的一个属性。

另一件事是,即使在不同的动物类型之间,有时也可能进行交配。想想马和驴。

所以,我就是这样建模的:

class Animal {
  // incomplete....
  bool likes(Animal * other) const;
};

class Mammal : public Animal {
    static AnimalList Mate(Mammal *a, Mammal *b);
};

AnimalList Mate(const AnimalList &participants);

现在交配的细节取决于动物类别。全局Mate()可以通过dynamic_cast进行广泛的区分(例如细菌,植物,哺乳动物之间等)并调用这些函数,然后(可能在更多dynamic_cast之后调用)参与者超负荷的交配功能。

未能交配应该是一个例外。

Animal::likes最重要的是兼容性;它可能会超出合作伙伴的健康状况或财务状况等偏好。