4

OOD とデザイン パターンを使用してパスワード モジュールを作成しています。モジュールは、記録可能なイベントのログを保持し、ファイルへの読み取り/書き込みを行います。基本クラスにインターフェイスを作成し、派生クラスに実装を作成しました。基本クラスに派生クラスが1つしかない場合、これは一種の悪臭ではないかと思います。この種のクラス階層は不要ですか? クラス階層を排除するために、もちろん、すべてを 1 つのクラスで実行し、まったく派生させないようにすることができます。これが私のコードです。

class CLogFile
{
public:
    CLogFile(void);
    virtual ~CLogFile(void);

    virtual void Read(CString strLog) = 0;
    virtual void Write(CString strNewMsg) = 0;
};

派生クラスは次のとおりです。

class CLogFileImpl :
    public CLogFile
{
public:
    CLogFileImpl(CString strLogFileName, CString & strLog);
    virtual ~CLogFileImpl(void);

    virtual void Read(CString strLog);
    virtual void Write(CString strNewMsg);

protected:
    CString & m_strLog; // the log file data
    CString m_strLogFileName; // file name
};

今コードで

CLogFile * m_LogFile = new CLogFileImpl( m_strLogPath, m_strLog );

m_LogFile->Write("Log file created");

私の質問は、一方で、OOD プリンシパルに従い、最初にインターフェイスを作成し、派生クラスで実装していることです。一方、それはやり過ぎであり、物事を複雑にしますか? 私のコードはデザイン パターンを使用しないほど単純ですが、派生クラスによる一般的なデータのカプセル化に関しては、そこから手がかりを得ることができます。

最終的に、上記のクラス階層は適切ですか、それとも代わりに 1 つのクラスで行う必要がありますか?

4

6 に答える 6

7

いいえ、実際、あなたのデザインは良いと思います。後でクラスにモックまたはテストの実装を追加する必要が生じる場合がありますが、設計によりこれが容易になります。

于 2013-01-14T16:58:32.323 に答える
4

答えは、そのインターフェイスで複数の動作が発生する可能性がどの程度あるかによって異なります。

ファイルシステムの読み取りおよび書き込み操作は、今では完全に理にかなっているかもしれません。データベースなどのリモートに書き込むことにした場合はどうなりますか?その場合でも、新しい実装はクライアントに影響を与えることなく完全に機能します。

これは、インターフェイスを実行する方法の良い例だと思います。

デストラクタを純粋な仮想にするべきではありませんか?私が正しく思い出せば、それはスコットマイヤーズによるとC++インターフェースを作成するための推奨イディオムです。

于 2013-01-14T17:00:33.027 に答える
1

はい、これは、インターフェイスの実装が1つしかない場合でも許容できますが、実行時は単一のクラスよりも遅くなる可能性があります。(virtualディスパッチには、およそ1〜2個の関数ポインターをたどるコストがかかります)

これは、実装の詳細に対するクライアントへの依存を防ぐ方法として使用できます。例として、実装が上記のパターンで新しいデータフィールドを取得するという理由だけで、インターフェイスのクライアントを再コンパイルする必要はありません。

パターンを確認することもできますpImpl。これは、継承を使用せずに実装の詳細を非表示にする方法です。

于 2013-01-14T17:01:47.137 に答える
0

いいえ。動作中のポリモーフィズムがない場合、継承の理由はありません。リファクタリング ルールを使用して 2 つのクラスを 1 つにまとめる必要があります。「継承よりも構成を優先する」。

編集:@crushがコメントしたように、「継承よりも構成を好む」は、ここでの適切な引用ではないかもしれません。つまり、継承を使用する必要があると思われる場合は、よく考えてください。そして、それを使用する必要があると本当に確信している場合は、もう一度考えてみてください.

于 2013-01-14T17:02:04.683 に答える
0

Stephane Rolland によって参照されている、 Composition over InheritanceおよびSingle Responsibility Principleで定義されている別のモデルでは、次のモデルが実装されています。

まず、3 つの異なるクラスが必要です。

class CLog {
    CLogReader* m_Reader;
    CLogWriter* m_Writer;

    public:
        void Read(CString& strLog) {
            m_Reader->Read(strLog);
        }

        void Write(const CString& strNewMsg) {
            m_Writer->Write(strNewMsg);
        }

        void setReader(CLogReader* reader) {
            m_Reader = reader;
        }

        void setWriter(CLogWriter* writer) {
            m_Writer = writer;
        }
};

CLogReader は、ログを読み取る単一の責任を処理します。

class CLogReader {
    public:
        virtual void Read(CString& strLog) {
            //read to the string.
        }
};

CLogWriter は、ログを書き込む単一の責任を処理します。

class CLogWriter {
    public:
        virtual void Write(const CString& strNewMsg) {
            //Write the string;
        }
};

次に、たとえば CLog にソケットへの書き込みをさせたい場合は、CLogWriter を派生させます。

class CLogSocketWriter : public CLogWriter {
    public:
        void Write(const CString& strNewMsg) {
            //Write to socket?
        }
};

次に、CLog インスタンスの Writer を CLogSocketWriter のインスタンスに設定します。

CLog* log = new CLog();
log->setWriter(new CLogSocketWriter());
log->Write("Write something to a socket");

長所 この方法の長所は、すべてのクラスが単一の目的を持つという単一責任の原則に従うことです。とにかく変更しないコードをドラッグすることなく、単一の目的を拡張する機能を提供します。また、必要に応じてコンポーネントを交換することもできます。そのために新しい CLog クラス全体を作成する必要はありません。たとえば、ソケットに書き込みを行うライターと、ローカル ファイルを読み取るリーダーを使用できます。等。

短所 ここではメモリ管理が大きな問題になります。ポインタをいつ削除するかを追跡する必要があります。この場合、別のライターまたはリーダーを設定するときと同様に、CLog の破棄時にそれらを削除する必要があります。これを行うと、参照が別の場所に格納されている場合、ダングリング ポインターが発生する可能性があります。これは、すべての参照が失われたときにポインターを自動的に削除する参照カウンター コンテナーである、強参照と弱参照について学ぶ絶好の機会です。

于 2013-01-14T17:42:58.783 に答える
0

モデルは、多くの共有ポインターを操作するファクトリ モデルとうまく連携し、ファクトリ メソッドを呼び出して、抽象インターフェイスへの共有ポインターを「取得」します。

pImpl を使用することの欠点は、ポインター自体を管理することです。ただし、C++11 では、pImpl は移動可能でうまく機能するため、より実行しやすくなります。ただし、現時点では、「ファクトリ」関数からクラスのインスタンスを返したい場合、内部ポインターにコピー セマンティックの問題があります。

これにより、実装者は共有ポインタを外部クラスに返し、コピーできなくなります。つまり、内部クラスへのポインターを保持する 1 つのクラスへの共有ポインターがあるため、関数呼び出しはその余分なレベルの間接化を通過し、構築ごとに 2 つの "new" を取得します。これらのオブジェクトが少数しかない場合は大きな問題にはなりませんが、少し扱いに​​くい場合があります。

C++11 には、基になるものとムーブ セマンティクスの前方宣言をサポートする unique_ptr があるという利点があります。したがって、pImpl は、実装が 1 つだけであることを本当に知っている場合に、より実現可能になります。

ちなみに、私はこれらCStringの s を削除して に置き換えstd::string、すべてのクラスのプレフィックスとして C を付けません。また、実装のデータ メンバーを保護ではなく非公開にします。

于 2013-01-14T17:21:36.127 に答える