抽象基类或派生类中的类变量?

3

我有一个抽象类,其中有一个变量 owner_ 是一个字符串。每个派生类都声明这个变量的名称。更好的实践是将变量放在抽象基类中,还是在派生类中多次实现它?

#include <string>
#include <iostream>

class Pet
{
    public:
        Pet(const std::string& owner) : owner_(owner) {}
        virtual ~Pet() = 0;
        virtual void print_status() = 0;
    protected:
        const std::string owner_;
};

Pet::~Pet() {}

class Dog : public Pet
{
    public:
        Dog(const std::string& owner) : Pet(owner) {}
        ~Dog() {};
        void print_status()
        {
            std::string s = "Woof! My owner is ";
            s += owner_;
            std::cout << s << std::endl;
        }
    // Or better here?
    // private:
    //     const std::string owner_;
};

class Cat : public Pet
{
    public:
        Cat(const std::string& owner) : Pet(owner) {}
        ~Cat() {};
        void print_status()
        {
            std::string s = "Meow! My owner is ";
            s += owner_;
            std::cout << s << std::endl;
        }
    // Or better here?
    // private:
    //     const std::string owner_;
};

int main()
{
    Dog dog("Mario");
    dog.print_status();

    Cat cat("Luigi");
    cat.print_status();

    return 0;
}

完全取决于你想要什么。每个“Pet”都真的有一个主人吗? - 463035818_is_not_a_number
它不可能,每只宠物都有主人。 - Chiel
那么,在基类中拥有所有者名称肯定是更好的选择。 - kocica
有第三个选项:在 Pet 中将其设为私有,并提供一个 getOwner()。我不明白为什么派生类应该直接访问成员。 - 463035818_is_not_a_number
如果您的构造函数与基类完全相同,您可以编写using Pet::Pet; - kocica
2个回答

4

我认为这正是抽象基类的用途:在继承层次结构中提供接口的通用实现。
我甚至会更进一步,将接口与抽象基类分开:

struct IPet {
    virtual ~IPet() = {}
    virtual void print_status() = 0;
    virtual const std::string& get_owner() const = 0;
};

class Pet : public IPet
{
public:
    Pet(const std::string& owner) : owner_(owner) {}
    virtual const std::string& get_owner() const { return owner_; }
    virtual ~Pet() {} // = 0; Don't declare the destructor as pure virtual function
    virtual void print_status() = 0;
protected:
    std::string owner_;
};

谢谢,但是我从这个设计中能得到什么好处? - Chiel
@Chiel 接口和实现的清晰分离。 - user0042
@Chiel 如果你有一个protected成员,你需要检查至少两个地方,以确保不违反不变量。想象一下,如果你想禁止空字符串作为所有者,那么更容易让单个类负责确保owner_ == ""永远不为真。如果你将其设置为受保护的,则每个派生类都必须注意到这一点。 - 463035818_is_not_a_number
就像上面所说的那样,这种分离也会影响到实现的粘性。在基类中提供了关于这个类设计的重要“线索”,以及这个类的派生类必须包含哪些最重要的组件等。 - Guillotine
1
我建议避免在基类中使用静态成员,有常见的模式可以以不同的方式解决这种方法,例如GoF单例模式。这个问题已经在这里得到了回答SO static constant variables in abstract base classCan i have static member in an abstract base class - Guillotine
显示剩余5条评论

1
你可能希望使用抽象方法来强制子类实现方法,但不一定在其中定义任何内容。如果你有意使用它们,则将所有者放置在基类中,但在各自的方法中具有不同的内容是正确的。
例如,如果你想要所有子类至少在其自己的类中声明函数,那么就使用抽象方法,这有时需要用于各自子类的不同行为。
class Pet
{
public:
    Pet(const std::string& owner) :
        owner_(owner) {}
    virtual ~Pet() = 0;
    virtual void print_status() = 0;
protected:
    const std::string owner_;
};

Pet::~Pet() {}

class Dog : public Pet
{
private:
    int age;
public:
    Dog(const std::string& owner, int age) :
        Pet(owner), age(age) {}
    ~Dog() {};
    void print_status(){
        std::cout << "Woof! My owner is " << this->owner_ << 
            " and my age is " << this->age << "\n\n";
    }
};

class Cat : public Pet
{
public:
    Cat(const std::string& owner) :
        Pet(owner) {}
    ~Cat() {};
    void print_status() {
        std::cout << "Miaw, my owner is " << this->owner_ << '\n';
    }
};

int main()
{
    Dog dog("Mario", 25);
    dog.print_status();

    Cat cat("Luigi");
    cat.print_status();

    system("pause");
    return 0;
}

谢谢@Chiel澄清情况。我想知道这个解决方案是否对您有所帮助。 - knoxgon
为什么要使用->this?这对于非模板类来说不是必需的,是吗?至于其余部分,你的例子与我的例子并没有太大的区别,或者我错过了什么? - Chiel
“@VG_this实际上是一个好的使用习惯。”不是的。在Java中可能这样。 - user0042
在这种情况下,我觉得有些混淆。您正在使用“this”来区分本地的“private”变量和从基类继承的“protected”变量。如果我要使用“this”,我会用它来区分本地范围和类范围内的变量。 - Chiel
1
@Chiel 啊,对不起,我可能错过了那个 :) 当我写那个的时候,我实际上很匆忙。没有太注意到那个。是的,我也会在私有成员上使用它。更正即将到来! - knoxgon
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接