問題の概要
この質問には 2 つの競合する懸念があります。
- のライフサイクル管理
Subsystem
。適切なタイミングでの削除を可能にします。
- のクライアントは、使用している が有効
Subsystem
であることを知る必要がSubsystem
あります。
取り扱い#1
System
を所有しており、Subsystem
独自のスコープでライフサイクルを管理する必要があります。これに s を使用shared_ptr
すると、破棄が簡単になるので特に便利ですが、解放に関して求めている決定論が失われるため、それらを配布しないでください。
取り扱い#2
これは、取り組むべきより興味深い懸念事項です。Subsystem
問題をより詳細に説明すると、 a のように動作するオブジェクト (およびそのSubsystem
親) が存在するが、 aが破棄されたSystem
後に適切に動作するオブジェクトをクライアントが受け取る必要があります。Subsystem
これは、Proxy パターン、State パターン、およびNull オブジェクト パターンを組み合わせることで簡単に解決できます。これは解決策としては少し複雑に思えるかもしれませんが、「複雑さの反対側にしかないシンプルさがあります。」ライブラリ/API 開発者として、システムを堅牢にするためにさらに努力する必要があります。さらに、システムがユーザーの期待どおりに直感的に動作し、悪用しようとすると適切に減衰することを望んでいます。この問題には多くの解決策がありますが、これは、あなたとScott Meyersが言うように、「正しく使用するのは簡単で、間違って使用するのは難しい」というすべての重要なポイントに到達するはずです。
ここで、実際には が s のSystem
何らかの基本クラスを扱いSubsystem
、そこからさまざまなSubsystem
s が派生すると仮定しています。として以下に紹介しましたSubsystemBase
。以下のProxyオブジェクトを導入する必要があります。このオブジェクトは、プロキシしているオブジェクトにリクエストを転送することによってSubsystemProxy
のインターフェイスを実装します。(この意味で、これはDecorator PatternSubsystemBase
の特別な目的のアプリケーションに非常によく似ています。) それぞれがこれらのオブジェクトの 1 つを作成し、 を介して保持し、 を介して要求されたときに戻ります。これは、 が呼び出されたときに親オブジェクトによって呼び出されます。Subsystem
shared_ptr
GetProxy()
System
GetSubsystem()
がSystem
範囲外になると、そのSubsystem
オブジェクトのそれぞれが破壊されます。デストラクタで を呼び出しますmProxy->Nullify()
。これにより、ProxyオブジェクトのStateが変更されます。これは、インターフェイスを実装するNull Objectを指すように変更することによって行われSubsystemBase
ますが、何もしないことによって行われます。
ここでState パターンを使用すると、クライアント アプリケーションは、特定のパターンが存在するかどうかを完全に認識できなくなりSubsystem
ます。さらに、ポインターをチェックしたり、破棄されたはずのインスタンスを保持したりする必要はありません。
プロキシ パターンを使用すると、クライアントは、API の内部動作の詳細を完全にラップし、一定の統一されたインターフェイスを維持する軽量オブジェクトに依存することができます。
Null オブジェクト パターンを使用すると、元のオブジェクトが削除された後でもプロキシを機能させることができます。Subsystem
サンプルコード
大まかな疑似コード品質の例をここに載せましたが、満足できませんでした。上記で説明したことの正確なコンパイル(g ++を使用)の例になるように書き直しました。これを機能させるには、他のいくつかのクラスを紹介する必要がありましたが、それらの用途は名前から明らかなはずです。クラスに シングルトン パターンを採用しました。はプロキシ動作を から完全に抽象化し、この動作を無視できるようにします。クラスの UML ダイアグラムは次のとおりです。NullSubsystem
ProxyableSubsystemBase
Subsystem

コード例:
#include <iostream>
#include <string>
#include <vector>
#include <boost/shared_ptr.hpp>
// Forward Declarations to allow friending
class System;
class ProxyableSubsystemBase;
// Base defining the interface for Subsystems
class SubsystemBase
{
public:
// pure virtual functions
virtual void DoSomething(void) = 0;
virtual int GetSize(void) = 0;
virtual ~SubsystemBase() {} // virtual destructor for base class
};
// Null Object Pattern: an object which implements the interface to do nothing.
class NullSubsystem : public SubsystemBase
{
public:
// implements pure virtual functions from SubsystemBase to do nothing.
void DoSomething(void) { }
int GetSize(void) { return -1; }
// Singleton Pattern: We only ever need one NullSubsystem, so we'll enforce that
static NullSubsystem *instance()
{
static NullSubsystem singletonInstance;
return &singletonInstance;
}
private:
NullSubsystem() {} // private constructor to inforce Singleton Pattern
};
// Proxy Pattern: An object that takes the place of another to provide better
// control over the uses of that object
class SubsystemProxy : public SubsystemBase
{
friend class ProxyableSubsystemBase;
public:
SubsystemProxy(SubsystemBase *ProxiedSubsystem)
: mProxied(ProxiedSubsystem)
{
}
// implements pure virtual functions from SubsystemBase to forward to mProxied
void DoSomething(void) { mProxied->DoSomething(); }
int GetSize(void) { return mProxied->GetSize(); }
protected:
// State Pattern: the initial state of the SubsystemProxy is to point to a
// valid SubsytemBase, which is passed into the constructor. Calling Nullify()
// causes a change in the internal state to point to a NullSubsystem, which allows
// the proxy to still perform correctly, despite the Subsystem going out of scope.
void Nullify()
{
mProxied=NullSubsystem::instance();
}
private:
SubsystemBase *mProxied;
};
// A Base for real Subsystems to add the Proxying behavior
class ProxyableSubsystemBase : public SubsystemBase
{
friend class System; // Allow system to call our GetProxy() method.
public:
ProxyableSubsystemBase()
: mProxy(new SubsystemProxy(this)) // create our proxy object
{
}
~ProxyableSubsystemBase()
{
mProxy->Nullify(); // inform our proxy object we are going away
}
protected:
boost::shared_ptr<SubsystemProxy> GetProxy() { return mProxy; }
private:
boost::shared_ptr<SubsystemProxy> mProxy;
};
// the managing system
class System
{
public:
typedef boost::shared_ptr< SubsystemProxy > SubsystemHandle;
typedef boost::shared_ptr< ProxyableSubsystemBase > SubsystemPtr;
SubsystemHandle GetSubsystem( unsigned int index )
{
assert( index < mSubsystems.size() );
return mSubsystems[ index ]->GetProxy();
}
void LogMessage( const std::string& message )
{
std::cout << " <System>: " << message << std::endl;
}
int AddSubsystem( ProxyableSubsystemBase *pSubsystem )
{
LogMessage("Adding Subsystem:");
mSubsystems.push_back(SubsystemPtr(pSubsystem));
return mSubsystems.size()-1;
}
System()
{
LogMessage("System is constructing.");
}
~System()
{
LogMessage("System is going out of scope.");
}
private:
// have to hold base pointers
typedef std::vector< boost::shared_ptr<ProxyableSubsystemBase> > SubsystemList;
SubsystemList mSubsystems;
};
// the actual Subsystem
class Subsystem : public ProxyableSubsystemBase
{
public:
Subsystem( System* pParentSystem, const std::string ID )
: mParentSystem( pParentSystem )
, mID(ID)
{
mParentSystem->LogMessage( "Creating... "+mID );
}
~Subsystem()
{
mParentSystem->LogMessage( "Destroying... "+mID );
}
// implements pure virtual functions from SubsystemBase
void DoSomething(void) { mParentSystem->LogMessage( mID + " is DoingSomething (tm)."); }
int GetSize(void) { return sizeof(Subsystem); }
private:
System * mParentSystem; // raw pointer to avoid cycles - can also use weak_ptrs
std::string mID;
};
//////////////////////////////////////////////////////////////////
// Actual Use Example
int main(int argc, char* argv[])
{
std::cout << "main(): Creating Handles H1 and H2 for Subsystems. " << std::endl;
System::SubsystemHandle H1;
System::SubsystemHandle H2;
std::cout << "-------------------------------------------" << std::endl;
{
std::cout << " main(): Begin scope for System." << std::endl;
System mySystem;
int FrankIndex = mySystem.AddSubsystem(new Subsystem(&mySystem, "Frank"));
int ErnestIndex = mySystem.AddSubsystem(new Subsystem(&mySystem, "Ernest"));
std::cout << " main(): Assigning Subsystems to H1 and H2." << std::endl;
H1=mySystem.GetSubsystem(FrankIndex);
H2=mySystem.GetSubsystem(ErnestIndex);
std::cout << " main(): Doing something on H1 and H2." << std::endl;
H1->DoSomething();
H2->DoSomething();
std::cout << " main(): Leaving scope for System." << std::endl;
}
std::cout << "-------------------------------------------" << std::endl;
std::cout << "main(): Doing something on H1 and H2. (outside System Scope.) " << std::endl;
H1->DoSomething();
H2->DoSomething();
std::cout << "main(): No errors from using handles to out of scope Subsystems because of Proxy to Null Object." << std::endl;
return 0;
}
コードからの出力:
main(): Creating Handles H1 and H2 for Subsystems.
-------------------------------------------
main(): Begin scope for System.
<System>: System is constructing.
<System>: Creating... Frank
<System>: Adding Subsystem:
<System>: Creating... Ernest
<System>: Adding Subsystem:
main(): Assigning Subsystems to H1 and H2.
main(): Doing something on H1 and H2.
<System>: Frank is DoingSomething (tm).
<System>: Ernest is DoingSomething (tm).
main(): Leaving scope for System.
<System>: System is going out of scope.
<System>: Destroying... Frank
<System>: Destroying... Ernest
-------------------------------------------
main(): Doing something on H1 and H2. (outside System Scope.)
main(): No errors from using handles to out of scope Subsystems because of Proxy to Null Object.
他の考え:
Game Programming Gems の本の 1 つで読んだ興味深い記事で、デバッグと開発に Null オブジェクトを使用する方法について説明しています。彼らは特に、チェッカーボード テクスチャなどの Null グラフィックス モデルとテクスチャを使用して、欠落しているモデルを目立たせることについて話していました。呼び出しと、アクセスされるたびに呼び出しスタックをログに記録するNullSubsystem
for を変更することで、ここでも同じことが適用できます。ReportingSubsystem
これにより、あなたまたはあなたのライブラリのクライアントは、スコープ外になったものに依存している場所を追跡できますが、クラッシュを引き起こす必要はありません。
@Arkadiy のコメントで、彼が持ち出した循環依存関係は少し不快だSystem
と述べました。これは、Robert C Martin のDependency Inversion Principleを適用して、依存するインターフェイスから派生Subsystem
させることで簡単に修正できます。s が必要とする機能を親から分離し、そのためのインターフェイスを作成し、そのインターフェイスの実装者を保持してs に渡し、 . たとえば、ログへの書き込みに使用する があり、それから派生したり、そこからインスタンスを保持したりできます。System
Subsystem
Subsystem
System
Subsystem
shared_ptr
LoggerInterface
Subsystem
CoutLogger
FileLogger
System
