替代设计以避免dynamic_cast?

时间:2015-06-19 08:54:25

标签: c++ oop dynamic-cast

假设我有Archive界面和File界面。

  • 每个File保证至少有std::string name
  • 每个Archive可以std::vector<File*> Archive::list() const其文件。
  • 每个Archive都可以Archive::extract(std::vector<File*> files)

然后我有ZipArchiveZipFileZipFile包含档案文件中的偏移量和其他实现细节。然后是TarArchive / TarFile等等。其中每个填充std::vector<File*> list() const的实例为ZipFileTarFile等。

list()旨在让用户有机会选择要解压缩的文件。他们从该向量中选择元素,然后将此向量传递给extract()

此时,ZipArchive需要假设它已传递正确的类型并执行dynamic_cast<ZipFile*>(file)以访问实现细节。

这感觉很糟糕。这可以接受吗?还有其他选择吗?

4 个答案:

答案 0 :(得分:2)

根据评论中的建议,您可以将提取界面从Archive移至File。归档将返回std::vector<File*>,但实际上每个对象将是例如ZipFile,并且将知道它属于哪个归档以及它的类型,并且将能够调用适当的提取方法。

因此,您可以拥有代码而无需检查存档类型:

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_) {}
};

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

如果你想通过一次调用提取很多文件,那可能会更加困难,但你可以让档案extractFile只记住那个文件,然后在Archive类中使用一个特殊的方法来提取所有文件记住的文件一下子。我认为这甚至可以隐藏在一个相当简单的界面下。

答案 1 :(得分:1)

您的ZipArchive可以在其文件列表中搜索传递的指针。如果它在那里,它可以使用存储的指针(已经是类型ZipFile)或static_cast传递指向ZipFile的指针(因为你已经证明它的类型)。如果传递的指针不在列表中,那么它显然不是该存档所拥有的文件,因此您可以继续进行错误处理。

您还可以为每个Archive*添加File类型的后向指针。具体的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
        }
    }
}

答案 2 :(得分:0)

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;
};

然后在ZipArchive实现中,如果extract返回的int是archiveType方法,则可以执行 static_cast 而不是动态的 static_cast 。与为Zip档案类型注册的相同。

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
        }
    }
}

答案 3 :(得分:0)

你需要分析你对Archive的处理方式:你是否需要在某些地方因某种不确定的类型而有一些共同的行为,或者你不是吗?这将带来两种不同的设计选择,因此请谨慎选择。

正如评论中所说,你似乎不需要前者 让File代表文件句柄,ZipFileTarFile是其派生。然后,对于每种类型的文件,让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;
};

TarArchive也一样。不再需要处理所有权,指针等等;你也获得了强大的类型安全性。