具有可变返回类型的纯虚拟函数的功能-解决方法/设计

Functionality of a pure virtual function with variable return type - workaround/design?

本文关键字:功能 解决 方法 设计 函数 虚拟 返回类型      更新时间:2023-10-16

我正在开发一个非常非常简单的数据访问层(DAL),它具有两个类:DataTransferObject(DTO)和DataAccessObject(DAO)。这两个类都是抽象基类,需要针对特定用例进行继承和修改。

class DataTransferObject {
protected:
    //protected constructor to prevent initialization
};
class DataAccessObject {
public:
    virtual bool save(DataTransferObject o) = 0;
    virtual DataTransferObject* load(int id) = 0;
};

在来自业务逻辑层的House类的情况下,DAL类的实现将读取以下内容:

class Dto_House : public DataTransferObject {
public:
    int stories;
    string address;   //...which are all members of the House class...
    Dto_House(House h);
};
class Dao_House : public DataAccessObject {
public:
    bool save(Dto_House h) { /*...implement database access, etc...*/ }
    Dto_House* load(int id) {/*...implement database access, etc...*/ }
};

EDIT:当然,派生类知道House类的结构和数据存储。简单,漂亮,okidoke。现在我想在DTO类中提供一个方法toObject(),以便将Dto_House快速转换为House对象。然后我阅读了C++14中的自动返回类型推导,并尝试了:

class DataTransferObject {
public:
    virtual auto toObject() = 0;
};

但我不得不发现:虚拟函数没有自动返回类型推导(


对于这种特定情况,您对实现"推导返回类型的虚拟函数"有什么想法我想在我的DTO"界面"中有一个通用的toObject()函数。

我唯一想到的是这样的事情:

template <typename T>
class DataTransferObject {
    virtual T toObject() = 0;
};
class Dto_House : public DataTransferObject<House> {
public:
    int stories;
    string address;
    House toObject() {return House(stories, address);}
};

编辑:一个可能的用例是:

House h(3, "231 This Street");
h.doHouseStuff();
//save it
Dto_House dtoSave(h);
Dao_House dao;
dao.save(dtoSave);    //even shorter: dao.save(Dto_House(h));
//now load some other house
Dto_House dtoLoad = dao.load(id 2);
h = dtoLoad.toObject();
h.doOtherHouseStuff();

但这所房子不知道它是可以保存和装载的。

当然,可以派生抽象的DAO类来进一步完善它,以便与例如Sqlite、XML文件或其他文件一起使用。。。我只是介绍了一个非常基本的概念。

设置一个空的抽象类——实际上是一个接口,然后让两种类型都实现它,并将其设置为toObject返回引用类型,怎么样?

class Transferable 
{
    virtual ~Transferable() = 0;
}

然后:

class DataTransferObject {
public:
    //Return a reference of the object.
    virtual Transferable& toObject() = 0;
};
Dto_House : public DataTransferObject, Transferable { /*...*/ }
House     : public DataTransferObject, Transferable { /*...*/ }

上面的例子就是为了说明我的观点。更好的是,您可以使用DataTransferObject作为返回的引用类型,而不使用其他抽象类:

class DataTransferObject {
public:
    virtual DataTransferObject& toObject() = 0;
};
Dto_House : public DataTransferObject { /*...*/ }
House     : public DataTransferObject { /*...*/ }

更新:如果你想把类分开,按照约定分离数据和操作之间的任何关联,你可以在表示数据的东西上设置基类的名称,即:Building、Constructionetc,然后将其用于toObject中的引用类型。

您还可以让类在数据操作的API上操作这些操作。

通常,virtual函数不能在不同的子类中返回不同的类型,因为这违反了静态类型语言的整个概念:如果调用DataTransferObject::toObject(),编译器直到运行时才知道它将返回什么类型。

这突出了设计的主要问题:为什么你需要一个基类?你打算如何使用它?调用DataTransferObject::toObject(),即使你使用了一些魔法(或使用动态类型语言),听起来也不是一个好主意,因为你不能确定返回类型是什么。无论如何,你都需要一些类型转换或ifs,等等,要使其正常工作,或者您将只使用所有此类对象(HouseRoad等)的公共功能,但您只需要为所有这些对象提供一个公共基类。

事实上,同一返回类型规则有一个例外:如果您返回一个指向类的指针,则可以使用协变返回类型概念:子类可以重写virtual函数以返回原始返回类型的子类。如果你所有的"对象"都有一个公共基类,你可以使用类似的东西

struct DataTransferObject {
    virtual BaseObject* toObject() = 0;
};
struct Dto_House : public DataTransferObject {
    virtual House* toObject() { /*...*/ } // assumes that House subclasses BaseObject
};

然而,这仍然会留下同样的问题:如果你的代码中只有DataTransferObject,即使你(而不是编译器)知道它是Dto_House,你也需要一些类型转换,这可能是不可靠的。

另一方面,您的模板解决方案似乎很好,只是您将无法显式调用DataTransferObject::toObject()(除非您知道对象的类型),但正如我所解释的,这是一个坏主意。

因此,我建议您考虑如何实际使用基类(甚至编写一些示例代码),并在此基础上做出选择。