简体   繁体   English

替代设计以避免dynamic_cast?

[英]Alternative design to avoid dynamic_cast?

Say I have Archive interface and File interface. 说我有Archive界面和File界面。

  • Each File is guaranteed to have at least std::string name . 保证每个File至少具有std::string name
  • Each Archive can std::vector<File*> Archive::list() const its files. 每个Archive都可以std::vector<File*> Archive::list() const它的文件。
  • Each Archive can Archive::extract(std::vector<File*> files) . 每个Archive都可以Archive::extract(std::vector<File*> files)

Then I have ZipArchive and ZipFile , ZipFile contains offset in the archive file and other implementation details. 然后我有ZipArchiveZipFileZipFile包含归档文件中的偏移量和其他实现细节。 Then there's TarArchive / TarFile and so on. 然后是TarArchive / TarFile等等。 Each of these fills std::vector<File*> list() const with instances of ZipFile , TarFile etc. 这些中的每一个都ZipFileTarFile等实例填充std::vector<File*> list() const

list() is meant to give users a chance to select which files to unpack. list()旨在让用户有机会选择要解压缩的文件。 They select elements from that vector, then they pass this vector to extract() . 他们从该向量中选择元素,然后将这个向量传递给extract()

At this point, the ZipArchive needs to assume it was passed the right type and do dynamic_cast<ZipFile*>(file) to access implementation details. 此时, ZipArchive需要假设它已传递正确的类型并执行dynamic_cast<ZipFile*>(file)以访问实现细节。

This feels bad. 这感觉很糟糕。 Is this acceptable? 这可以接受吗? Are there alternatives? 还有替代品吗?

As suggested in the comments, you can move the extracting interface from Archive to File . 如评论中所建议,您可以将提取界面从Archive移动到File The archive will return std::vector<File*> , but in fact each object will be, eg, ZipFile , and will know which archive it belongs to and what is its type, and will be able to call proper extract method. 归档文件将返回std::vector<File*> ,但事实上每个对象将是例如ZipFile ,并且将知道它属于哪个归档文件以及它的类型,并且能够调用适当的提取方法。

As a result, you can have code without any checking of archive type: 因此,您可以在不检查存档类型的情况下获得代码:

struct File;
struct Archive {
    virtual std::vector<File*> fileList() = 0;
};

struct File {
    File(std::string name_) : name(name_) {}
    virtual void extract() = 0;
    std::string name;
};

struct ZipFile;
struct ZipArchive: public Archive {
    void extractFile(ZipFile& file);
    virtual std::vector<File*> fileList();
};

struct ZipFile: public File {
    ZipArchive* archive;
    virtual void extract() { archive->extractFile(*this); }
    ZipFile(std::string name_, ZipArchive* archive_) : File(name_), archive(archive_) {}
};

Full example: http://ideone.com/kAs5Jc 完整示例: http//ideone.com/kAs5Jc

It can be more diffucult if you want to extract many files with one call, but you can have archive's extractFile just remember that file, and then a special method in the Archive class to extract all the remembered files at once. 如果你想通过一次调用提取许多文件,可能会更加困难,但是你可以让archive的extractFile只记住那个文件,然后在Archive类中使用一个特殊的方法来一次提取所有记忆的文件。 I think this can even be hidden under a rather simple interface. 我认为这甚至可以隐藏在一个相当简单的界面下。

Your ZipArchive could search in its list of files for the passed pointer. 您的ZipArchive可以在其文件列表中搜索传递的指针。 If it's in there, it could either use the stored pointer (which already is of type ZipFile ) or static_cast the passed pointer to ZipFile (because you have proven its type). 如果它在那里,它可以使用存储的指针(已经是ZipFile类型)或static_cast传递给ZipFile指针(因为你已经证明了它的类型)。 If the passed pointer is not in the list, it's obviously not a file owned by this archive, so you can go on with error handling. 如果传递的指针不在列表中,那么它显然不是该存档所拥有的文件,因此您可以继续进行错误处理。

You could also add a backpointer of type Archive* to every File . 您还可以为每个File添加Archive*类型的backpointer。 The concrete ZipArchive implementation can than check if its one of its files by a simple pointer comparsion. 具体的ZipArchive实现可以通过简单的指针比较来检查它的一个文件。

void ZipArchive::extract(std::vector<File*> files) 
{
    for (auto file : files)
    {
        if (file->archive() == this) 
        {
            // one of my files
            auto zipFile = static_cast<ZipFile*>(file);
            // do something with zipFile
        }
        else
        {
            // file is owned by some other archive
        }
    }
}
class Archive { 
public:
    static int registerArchiveType(const std::string &name) {
        // generate a unique int for the requested name archive type
        // and store it in a map or whatever
        return uniqueInt;
    }

    int archiveType() const;

protected: 
    Archive(int type) : _type(type) {}
private: 
    int _type;

public:
     virtual extract(std::vector<File*> files);

    // your implementation details
};

class File {
public:
    int archiveType() { return _archType; }
protected:

    // force implementations to pass the same type
    // they received from the call to Archive::registerArchiveType
    File() {}
    void setArchiveType(const std::string &archiveType) {
        // multiple calls to registerArchiveType return the
        // same identifier if passed the same string
        _archiveType = Archive::registerArchiveType(archiveType);
    }

private:
    int _archiveType;
};

Then in your ZipArchive implementation, in the extract method you can perform a static_cast rathern than a dynamic one if the int returned by archiveType is the same as the one registered for the Zip archive type. 然后在ZipArchive实现中,如果archiveType返回的int与为Zip归档类型注册的int相同,则在extract方法中可以执行static_cast而不是动态。

static const char* ZIP_TYPE = "zip";

// specialize your ZipFile making sure
// you pass the correct archive type identifier
// in the constructor
class ZipFile {
public:
    ZipFile() : File() {
        setArchiveType(ZIP_TYPE);
    }
    // bla bla
};

void ZipArchive::extract(std::vector<File*> files) {
    for (int i = 0; i < files.count(); i++) {
        if (files[i]->archiveType() == Archive::registerArchiveType(ZIP_TYPE)) {
            ZipFile *zipFile = static_cast<ZipFile*>(files[i]);
            // do something with zipFile
        }
    }
}

You need to analyze the way you will treat Archive s: do you need to have in some places some common behavior due to undetermined types or do you not? 你需要分析你对待Archive的方式:你是否需要在某些地方因某种不确定的类型而有一些共同的行为,或者你不是吗? This will bring to two different design choices, so choose carefully. 这将带来两种不同的设计选择,因此请谨慎选择。


As said in the comments, you don't seem to need the former. 正如评论中所说,你似乎不需要前者。
Let File represent a file handle and ZipFile , TarFile be derivations thereof. File表示文件句柄和ZipFileTarFile是其派生。 Then, for each type of file let an Archive handle it. 然后,对于每种类型的文件,让Archive处理它。

struct ZipFile 
{        
     File handle; 
     // zip-specific implementation details 
};

struct TarFile
{ 
     File handle;
     // tar-specific implementation details
};

class ZipArchive
{
     public:
            std::vector<ZipFile> list() const; 
            void extract(std::vector<ZipFile>);         

     private:
             std::vector<ZipFile> archive;
};

And the same for TarArchive . TarArchive There is no more need to handle ownership, pointers and so on; 不再需要处理所有权,指针等等; you also get strong type safety. 你也获得了强大的类型安全性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM