我很难想象如何设计无法在构造函数中初始化所有内部成员的类。我知道这应该是基本的,并在网上讨论,但我不知道该寻找什么。因此,例如,请考虑以下代码:
#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调用任何方法。外行人的实现是在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()方法之后调用这些方法服务器......
答案 0 :(得分:2)
我会做以下事情:
class Worker
{
public:
Worker (const int& data)
{
horse.SetData(data);
}
void Action () const
{
std::cout << horse.GetData() << std::endl;
}
private:
Workhorse horse;
};
由于你显然不希望一个Worker对象在没有被初始化的情况下存在,它的初始化应该是它构造的一部分,它应该在没有这个初始化的情况下进行实例化,因为没有它就无法工作。
答案 1 :(得分:2)
听起来你感兴趣的行为需要有一个充当经理的类,决定是否提供对Workhorse功能之一的访问,或者是虚拟功能。一种可能性是创建一个抽象父类(Horse),指定Workhorse的接口,但不实现任何功能。从中派生出两个类,Workhorse和TrojanHorse。 TrojanHorse会实现父类中的所有函数,因为Shells,Workhorse就像你已经创建它一样。
经理类可以拥有你感兴趣的初始化函数,它可以存储一个类型为Horse的对象。默认情况下,可以将horse对象分配给TrojanHorse对象,但初始化会将其分配给Workhorse对象。
这个解决方案几乎可以避免来自if语句的所有速度影响,如果类没有以正确的方式改变,编译器会给出错误,它将是可维护的,并且它仍然可以理解为另一个程序员看代码。
答案 2 :(得分: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;
}
答案 3 :(得分: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;
}
答案 4 :(得分:0)
如果你必须延迟对象初始化,我建议如果未初始化代理,则使用带有访问运算符的代理。随时初始化代理。您不需要检查每个方法,但检查是否移动到代理。 一些智能指针会很方便。但是,据我所知,如果包含指针未初始化,它们不会抛出。因此,您可能需要自己的一个,如下所示。
#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;
};