Jeśli obiekt połączenia jest w rzeczywistości niedziałający, jeśli połączenie nie powiedzie się, nie ma sensu, aby obiekt istniał, jeśli wszystkie jego inne metody zawsze będą działały bezczynnie lub będą zgłaszać wyjątki. Z tego powodu chciałbym wykonać połączenie w konstruktorze i nie powiedzie się, wyrzucając wyjątek (pochodzący z std::exception
), jeśli ta metoda się nie powiedzie.
Jednak masz rację, że klienci klasy mogą potrzebować świadomości, że konstruktor może zablokować lub zawieść. Z tego powodu mógłbym uczynić konstruktor prywatnym i zastosować statyczną metodę fabryk (nazwany idiom konstruktora), aby klienci musieli wykonać jawne wywołanie MakeConnection
.
Klient nadal jest odpowiedzialny za sprawdzenie, czy połączenie nie jest dla niego śmiertelne lub czy może obsłużyć tryb offline. W pierwszym przypadku może posiadać połączenie według wartości i pozwolić, aby jakakolwiek awaria połączenia była przekazywana klientom; w tym ostatnim może być właścicielem obiektu za pomocą wskaźnika, najlepiej "inteligentnego". W tym ostatnim przypadku może podjąć próbę skonstruowania posiadanego połączenia w swoim konstruktorze lub może odroczyć go, dopóki nie będzie potrzebny.
E.g. (ostrzeżenie: kod całkowicie nietestowany)
class Connection
{
Connection(); // Actually make the connection, may throw
// ...
public:
static Connection MakeConnection() { return Connection(); }
// ...
};
Oto klasa, która wymaga działającego połączenia.
class MustHaveConnection
{
public:
// You can't create a MustHaveConnection if `MakeConnection` fails
MustHaveConnection()
: _connection(Connection::MakeConnection())
{
}
private:
Connection _connection;
};
Oto klasa, która może pracować bez niej.
class OptionalConnection
{
public:
// You can create a OptionalConnectionif `MakeConnection` fails
// 'offline' mode can be determined by whether _connection is NULL
OptionalConnection()
{
try
{
_connection.reset(new Connection(Connection::MakeConnection()));
}
catch (const std::exception&)
{
// Failure *is* an option, it would be better to capture a more
// specific exception if possible.
}
}
OptionalConnection(const OptionalConnection&);
OptionalConnection& operator=(const OptionalConnection&);
private:
std::auto_ptr<Connection> _connection;
}
I wreszcie taki, który tworzy jeden na żądanie, i proponuje wyjątki dla dzwoniącego.
class OnDemandConnection
{
public:
OnDemandConnection()
{
}
OnDemandConnection(const OnDemandConnection&);
OnDemandConnection& operator=(const OnDemandConnection&);
// Propgates exceptions to caller
void UseConnection()
{
if (_connection.get() == NULL)
_connection.reset(new Connection(Connection::MakeConnection()));
// do something with _connection
}
private:
std::auto_ptr<Connection> _connection;
}
Istnieją oczywiście wyjątki, gdy konstruktorzy blokujący mają sens, na przykład blokady typu RAII. Oczywiście ważna jest dokumentacja i odpowiednie nazewnictwo. –
+1 Wykonując operacje blokowania w oddzielnej metodzie łączenia, pozostawiasz otwarte drzwi dla metody "anuluj", aby anulować tę operację. –