Projetar pergunta sobre como ocultar a implementação de usuários de uma biblioteca dinâmica

5

Estou criando uma DLL e, nos meus cabeçalhos públicos, tenho isto: (as definições estão em .cpp, mas para maior clareza, mostro-as em .hpp aqui)

ObjectTag.hpp:

class API_DLL ObjectTag {
public:
    ObjectTag() : mUUID(UIDGenerator::Generate()) {
    }

    uuid getUUID() {
        return mUUID;
    }

private:
    uuid mUUID;
};

Texture.hpp:

class API_DLL Texture : public ObjectTag {
public:
    Texture() : ObjectTag(){
    }

};

Eu tenho um editor de cena que cria objetos Texture, e os salva em um arquivo com seu uuid. Agora eu quero carregar os objetos Texture lidos do arquivo dentro da biblioteca e configurar seu UUID gerado e salvo anteriormente.

Minha primeira tentativa foi fazer com que essa função simples anule setUUID (uuid id):

class API_DLL ObjectTag {
public:
    ObjectTag() : mUUID(UIDGenerator::Generate()) {
    }

    uuid getUUID() const {
        return mUUID;
    }
    void setUUID(uuid id) {
        mUUID=id;
    }

private:
    uuid mUUID;
};

Mas isso permitirá que os usuários da biblioteca modifiquem a variável UUID. Como você projetaria isso de uma maneira que evitasse fazer isso dos usuários da biblioteca?

    
por Daniel Guzman 16.04.2016 / 15:46
fonte

2 respostas

3

How would you design this in a way that prevents doing that from the users of the library?

Interface de textura separada da implementação:

class API_DLL ObjectTag
{
    uuid mUUID;

public:
    uuid getUUID()
    {
        return mUUID;
    }
};

class API_DLL Texture: public ObjectTag // Texture interface visible in client code
{
public:
    virtual ~Texture() = 0;
    virtual void DoTextureThings() = 0;
};

template<typename T>
class EditableObjectTag: public T // EditableObjectTag not exposed to client code
                                  // defines save functionality
{
public:
    void setUUID(uuid id) { mUUID = id; }
};

class YourTexture: public EditableObjectTag<Texture> // YourTexture not exposed
                                                     // to client code

{
    void DoTextureThings() override { ... }
};

Sua função de carga:

API_DLL std::vector<std::unique_ptr<Texture>> LoadTextures()
{
    std::vector<std::unique_ptr<Texture>> result;

    result.emplace_back(new YourTexture{ bla, bla, bla });

    return result;
}

O código do cliente agora pode trabalhar com texturas, sem se importar que uma textura seja uma classe abstrata, e a interface Texture não menciona nada sobre a configuração de valores no objeto:

void ClientCode()
{
    auto textures = LoadTextures();
    textures[0]->DoTextureThings();

    // textures[0]->setUUID(someUUID); -> fails to compile
}

Nota : a classe EditableObjectTag é um design baseado em aspectos: adiciona uma interface editável no seu argumento de modelo.

    
por 18.04.2016 / 10:07
fonte
5

Se você deseja serializar e desserializar os objetos Texture, então você deve ter construtores, métodos ou interfaces explicitamente projetados para essa finalidade. Serialização é um dos poucos casos de uso que são especiais o suficiente para merecer isso.

Como este é o C ++, você tem muitas opções. Fora do topo da minha cabeça:

  1. Conceda os métodos Texture class serialize() e deserialize() . Simplicidade é uma virtude.
  2. Faça uma classe TextureSerializer separada que seja friend da classe Texture para que você possa tornar setUUID() private. Essa abordagem é geralmente uma boa idéia se você espera que a serialização seja complicada ou tenha dependências não-triviais ou a classe Texture já possui muitas coisas nela.
  3. Crie uma classe Serializable com os métodos virtual serialize() e deserialize() e nenhum estado, a.k.a., uma interface e torne Texture implementar essa interface. Se você espera que outros programadores possam escrever suas próprias classes serializáveis com as quais seu programa precisa lidar, essa pode ser a abordagem mais segura e genérica.
  4. Crie uma classe SerializableTaggedObject base com% virtualserialize() e deserialize() methods e o construtor uuid member e o gerador de uuid que você tem atualmente em ObjectTag . Se você tiver várias classes semelhantes a Texture , que precisam ser serializáveis e ter uuids, essa pode ser a melhor opção.

Todas essas opções protegerão você contra usuários não mal-intencionados que simplesmente não leram os comentários que você escreveu em cima de setUUID() . Como você pode ver nas minhas observações adicionais, qual delas você realmente escolhe deve depender de muitos outros fatores, mas qualquer um deles seria uma grande melhoria em relação à exposição de um setter público para um campo que deveria ser imutável.

Estou deliberadamente ignorando algumas questões de implementação, como se algum desses métodos deve ser estático. E eu estou supondo que você não está tentando se proteger contra os usuários mal-intencionados que gostariam de receber o Texture.deserialize("{ \"uuid\": \"bwahahahaha\" }") em vez do setUUID() , mas apenas impedir que os usuários disparem por engano comportamento indefinido em seu código.

P.S. Uma vez que você mencionou especificamente as DLLs, sinto que também devo mencionar o idioma pimpl , uma vez que oculta detalhes de implementação de uma forma que muitas vezes melhora a compatibilidade binária , o que tende a ser uma preocupação particularmente strong para as DLLs. Claro que você ainda tem que colocar um mecanismo de serialização em algum lugar na implementação real, então isso seria em adição a uma das opções que descrevi acima.

    
por 16.04.2016 / 17:42
fonte

Tags