3

我很难弄清楚如何设计无法在构造函数中初始化所有内部成员的类。我知道这应该是一些基本的东西,并在网上进行了讨论,但我不确定要寻找什么。因此,例如,请考虑以下代码:

#include <iostream>

class Workhorse
{
public:
    void SetData (const int &data)
    {
        this->data = data;
    }

    int GetData () const
    {
        return this->data;
    }

private:
    int data;
};

class Worker
{
public:
    Worker ()
    {
    }
    void Initialize (const int &data)
    {
        horse.SetData(data);
    }
    void Action () const
    {
        std::cout << horse.GetData() << std::endl;
    }

private:
    Workhorse horse;
};

int main ()
{
    Worker worker;
    worker.Initialize(3);
    worker.Action();
    return 0;
}

我想防止工人在没有先调用 Initialize() 的情况下调用任何方法。外行的实现是在 Worker 类中添加一个 isInitialized 标志,在 Initialize() 中将其设置为 true 并在每个公共方法的开头对其进行测试(如果我们引入一些继承,可能也在受保护/私有方法中?) . 不幸的是,这似乎有点麻烦且难以维护。此外,在所有方法中重复 if 语句也很糟糕。我什至还没有开始思考线程安全问题,但是,现在,我只是在实现一个单线程应用程序。有没有更聪明的方法来设计这个?


编辑:好的,我选择了一个愚蠢的设计作为例子,这确实是有缺陷的。让我试着更清楚地了解我所拥有的:

#include <iostream>

class PublicKeyCryptoProvider
{
public:
    struct PublicKey
    {
        int shared;
    };
    struct PrivateKey
    {
        int secret;
    };

    int Encrypt (const int &plaintext) const
    {
        int ciphertext;
        //apply encryption algorithm on plaintext
        ciphertext = plaintext * this->pk.shared;
        return ciphertext;
    }

    int Decrypt (const int &ciphertext) const
    {
        int plaintext;
        //apply decryption algorithm on ciphertext
        plaintext = ciphertext / this->sk.secret;

        return plaintext;
    }

    void GenerateKeys ()
    {
        this->pk.shared = 4;
        this->sk.secret = 4;
        //generate pk and sk
    }

    void SetPublicKey (const PublicKey &pk)
    {
        this->pk = pk;
    }

    const PublicKey &GetPublicKey () const
    {
        return this->pk;
    }

private:
    PublicKey pk;
    PrivateKey sk;
};

int main ()
{
    /* scenario 1: */
    PublicKeyCryptoProvider cryptoProvider;
    cryptoProvider.GenerateKeys();
    std::cout << cryptoProvider.Decrypt(cryptoProvider.Encrypt(3)) << std::endl;
    /* /scenario 1: */

    /* scenario 2: */
    PublicKeyCryptoProvider cryptoProvider1;
    cryptoProvider1.GenerateKeys();

    PublicKeyCryptoProvider cryptoProvider2;
    cryptoProvider2.SetPublicKey(cryptoProvider1.GetPublicKey());

    int ciphertext = cryptoProvider2.Encrypt(3);
    std::cout << cryptoProvider1.Decrypt(ciphertext) << std::endl;

    //now let's do something bad...
    std::cout << cryptoProvider2.Decrypt(ciphertext) << std::endl;
    /* /scenario 2: */

    return 0;
}

显然,您可以想象场景 2 完全有效的现实生活示例。鉴于上述情况,有没有比在 PublicKeyCryptoProvider 类中添加 canDecrypt 标志更好的选择,该标志在生成密钥时设置为 true,然后在解密方法的开头进行测试?我不得不提到这是一个非常简单的例子,因为在我的例子中,如果 PublicKeyCryptoProvider 是密钥的所有者并且它有更多的公共方法,它可以执行更快的加密,所以我注定要测试这个标志不止几次......另外,我有一个客户端 - 服务器模型场景,其中服务器为客户端公开了一堆公共方法,但客户端只能在调用 Initialize() 方法后调用这些方法服务器...

4

5 回答 5

2

我会做以下事情:

    class Worker
{
public:
    Worker (const int& data)
    {
        horse.SetData(data);
    }

    void Action () const
    {
        std::cout << horse.GetData() << std::endl;
    }

private:
    Workhorse horse;
};

由于您显然不希望 Worker 对象在没有被初始化的情况下存在,它的初始化应该是其构造的一部分,并且应该在没有这个初始化的情况下实例化它,因为没有它它就无法工作。

于 2012-06-27T10:25:33.267 回答
2

听起来您感兴趣的行为需要有一个充当管理器的类,决定是提供对 Workhorse 函数之一的访问,还是提供一个虚拟函数。一种可能性是创建一个抽象父类(Horse),指定 Workhorse 的接口,但不实现任何功能。从它派生出两个类,Workhorse 和 TrojanHorse。TrojanHorse 会将父类中的所有功能实现为 Shell,Workhorse 将与您已经创建的一样。

manager 类可以有你感兴趣的初始化函数,它可以存储一个 Horse 类型的对象。默认情况下,可以将 horse 对象分配给 TrojanHorse 对象,但 initialize 会将其分配给 Workhorse 对象。

该解决方案将避免 if 语句对速度的几乎所有影响,如果没有以适当的方式更改类,编译器会给出错误,并且它仍然可以被其他程序员理解,这将是可维护的。编码。

于 2012-06-30T19:08:57.933 回答
1

你提到你不认为继承是要走的路,但是有一种相当干净的方法可以用最少的继承来做到这一点。

这里有几个设计模式很有用。如果您将接口与实现分开,并将实现视为“总是返回错误”和“做一些有用的事情”,您可以将这两个实现视为策略,将接口视为代理。

代理总是将它的调用转发到一个实现,并且总是有一个实现(不需要检查标志)。

该接口使用默认实现进行初始化,该实现会导致某种错误(断言、抛出等)。这是一个例子

下面是一个使用 Clang 3.2 编译的示例:

#include <iostream>
#include <memory>
#include <cassert>
#include <stdexcept>

// Base class that defines the signatures of the functions to be forwarded.
// Another nice benefit is that each implementation can store whatever 
// specific data they need.
class Impl {
public:
    virtual void FuncA() = 0;
};


typedef std::unique_ptr<Impl> ImplPtr;


class ErrorImpl : public Impl {
public:
    virtual void FuncA() { 
        assert(!"Don't call this before calling InitializeImpl!");
        throw std::runtime_error("Don't call this before calling InitializeImpl!");  
    }     
};

class OtherImpl : public Impl {
public:
    void FuncA() { 
        std::cout << "Some other useful functionality here.\n";
    }         
};

// This is the class that user's will call.
class Proxy {
public:
    Proxy() : impl_( ImplPtr(new ErrorImpl) ) {}

    void InitializeImpl( ImplPtr ptr ) {
        // You must std::move std::unique_ptr's.
        impl_ = std::move( ptr );
    }
    void FuncA() { impl_->FuncA(); }

private:
    ImplPtr impl_;
};


int main( int, char**) {
    Proxy p;
    // p.FuncA(); // asserts & throws.

    p.InitializeImpl( ImplPtr(new OtherImpl) );
    p.FuncA();

    return 0;
}
于 2012-07-01T06:15:32.620 回答
1

好问题!制作一个难以错误使用的 API 总是好的,并且当您观察到未完全构造的类是危险的、难以正确使用且容易错误使用时。他们让我们自己和他人失败。我已经对您的第二个示例进行了一些重构,以提出一个更安全的设计,甚至不允许您的“做坏事”代码。

总的想法是PublicKeyCryptoProvider责任太多(违反SRP):

  • 密钥生成
  • 密钥存储
  • 加密
  • 解密

每一项职责都已下放。现在PublicKeyCryptoProvider更负责为您提供进行加密/解密和密钥管理所需的工具。

#include <iostream>
#include <utility>

struct PublicKey
{
    int shared;
};
struct PrivateKey
{
    int secret;
};

struct KeyPair
{
    PublicKey public_key;
    PrivateKey private_key;
};


struct Encryptor
{
    Encryptor( PublicKey shared_ )
     : shared( shared_ )
    {}

    int Encrypt (const int &plaintext) const
    {
        int ciphertext;
        //apply encryption algorithm on plaintext
        ciphertext = plaintext * shared.shared;
        return ciphertext;
    }

private:
    PublicKey shared;
};

struct Decryptor
{
    Decryptor( PrivateKey secret_ )
     : secret( secret_ )
    {}

    int Decrypt (const int &ciphertext) const
    {
        int plaintext;
        //apply decryption algorithm on ciphertext
        plaintext = ciphertext / secret.secret;

        return plaintext;
    }

private:
    PrivateKey secret;
};

class PublicKeyCryptoProvider
{
public:

    KeyPair GenerateKeys()
    {
        KeyPair keys;

        //generate pk and sk
        keys.public_key.shared = 4;
        keys.private_key.secret = 4;

        return keys;
    }


    Decryptor BuildDecryptor( PrivateKey key )
    {
        return Decryptor( key );
    }

    Encryptor BuildEncryptor( PublicKey key )
    {
        return Encryptor( key );
    }


/* These are replaced by directly building an Encryptor/Decryptor
 when you have a public or private key.

    void SetPublicKey (const PublicKey &pk)
    {
        this->pk = pk;
    }

    const PublicKey &GetPublicKey () const
    {
        return this->pk;
    }
*/

};

int main ()
{
    /* scenario 1: */
    PublicKeyCryptoProvider cryptoProvider;
    auto keys = cryptoProvider.GenerateKeys();
    auto decryptor = cryptoProvider.BuildDecryptor(keys.private_key);
    auto encryptor = cryptoProvider.BuildEncryptor(keys.public_key);

    std::cout << decryptor.Decrypt( encryptor.Encrypt(3) ) << std::endl;
    /* /scenario 1: */

    /* scenario 2: */
    PublicKeyCryptoProvider cryptoProvider1;
    auto keys1 = cryptoProvider1.GenerateKeys();

    PublicKeyCryptoProvider cryptoProvider2;
    auto encryptor2 = cryptoProvider2.BuildEncryptor(keys.public_key);

    int ciphertext = encryptor2.Encrypt(3);
    std::cout << decryptor.Decrypt(ciphertext) << std::endl;

    // I Can't do anything bad - the API has protected me from doing bad things! Yeah!
    //std::cout << cryptoProvider2.Decrypt(ciphertext) << std::endl;
    /* /scenario 2: */

    return 0;
}
于 2012-07-01T23:32:25.713 回答
0

如果您必须延迟对象初始化,我建议在代理未初始化的情况下使用带有访问运算符的代理。随时初始化代理。您不需要在每个方法中进行 if 检查,但检查是否已移至代理。一些智能指针会很方便。但是,据我所知,如果包含指针未初始化,它们不会抛出。因此,您可能需要自己的一个,如下所示。

#include <iostream>

class Workhorse
{
public:
    void SetData (const int &data)
    {
        this->data = data;
    }

    int GetData () const
    {
        return this->data;
    }

private:
    int data;
};

template <typename T> class Proxy
{
public:

    Proxy() : myObject(0)
    {
    }
    Proxy(T* anObj) : myObject(anObj)
    {
    }
    ~Proxy()
    {
        delete myObject;
        myObject = 0;
    }
    T* operator ->()const
    {
        if(NULL == myObject)
        {
            throw;  //  Bad object. Substitute an appropriate exception code.
        }
        return myObject;
    }

private:
    T* myObject;
};

class Worker
{
public:
    Worker ()
    {
    }
    ~Worker ()
    {
    }
    void Initialize (const int &data)
    {
        horse = new Workhorse;
        horse->SetData(data);
    }
    void Action () const
    {
            // Here no need to check if the horse is initialized.
        std::cout << horse->GetData() << std::endl;
    }

private:
    Proxy<Workhorse> horse;
};
于 2012-07-02T11:16:12.797 回答