web-dev-qa-db-ja.com

C ++の既存のコードを変更せずに「アダプター」を使用する方法

ここに例としてTVクラスとDVDクラスがあります。

class TV
{
public:
    TV();
    virtual ~TV();

    void on() const;
    void off() const;
};

class DVDPlayer
{
public:
    DVDPlayer();
    ~DVDPlayer();

    void SetCD() const;
    void StartPlay() const;
    void Eject() const;
    void PowerOff() const;
};

DVDをTVに「装う」のに役立つアダプタを作成します。

class TVStyleAdapter :
    public TV
{
public:
    TVStyleAdapter(DVDPlayer* AdapteeDVD);
    ~TVStyleAdapter();

    void on() const;
    void off() const;

private:
    DVDPlayer* DVD;
};

// override base class funcs:
void TVStyleAdapter::on() const
{
    DVD->SetCD();
    DVD->StartPlay();
}

void TVStyleAdapter::off() const
{
    DVD->Eject();
    DVD->PowerOff();
}

その後、TV(Base)クラスに "virtual"を追加し、Adapterクラスのon()off()関数をオーバーライドできます。そしてそれは正しく動作します。

しかし、質問は次のとおりです。

  1. ベース( 'TV')クラスに変更を加えずに(たとえば、「仮想」を追加することなく)アダプタを作成できますか?
  2. この状況で、オープンクローズの原則に違反しないことは可能ですか?
  3. 将来、プログラムでアダプターを使用すると想定する場合、事前にクラスで仮想メソッドを実行する必要がありますか?
7
Anton

1。はい、しかしいいえ:

TVStyleAdapterの代わりにTVを使用して任意のコードをコンパイルでき、TVを変更しなくても機能します。したがって、ある意味では、答えは「はい」になる可能性があります。

ただし、このコンパイル時のインターフェイスの再利用は、TVStyleAdapterTVと無関係の場合にも機能します。残念ながら、それは動的ではありません。コンパイル時に実際のオブジェクトタイプを知る必要があり、実行時に動的なオブジェクト依存の動作を期待できませんでした。

ポリモーフィズムが必要な場合、つまりTV基本クラスへのポインターまたは参照を使用し、基本クラスに仮想オブジェクトが必要なオブジェクトの実際のタイプに応じて正しい動作が発生することを期待します。

2。はい、しかし原則を破ったのは誰ですか?

問題は、基本を変更する必要があるかどうかではなく、基本クラスを変更することではなく、基本クラスがまだ仮想化されていない理由です。

基本クラスに仮想を追加する必要があるため、近い原則を破ると思います。しかし実際には、基本クラスの設計者は最初の行でオープン原則を破っていました。

。最初からの多型の設計

クラスをポリモーフィックにしたい場合、つまり実際の型に応じてrutimeでの動作が異なる場合は、最初からクラスを設計する必要があります。 SOはい、事前に仮想です(その結果、空のままでも仮想デストラクタです)

11
Christophe

継承を利用する従来のアダプターパターンは、基本クラスが用意されていない場合は機能しません。そして、はい、あなたは正しいです。仮想メソッドがないTVがないと、OCPに準拠しません。 OCPは、必要な「拡張ポイント」またはパラメーターを事前に提供するクラスを必要とします。OCPについて、およびクラス設計にとっての意味について この以前のQ&A を参照してください。

ただし、C++では、テンプレートパラメーターを使用するTVクラスの種類を作成することにより、代わりにテンプレートを使用してアダプターパターンを実装する可能性があります。

    template <class T> void myFunction(T tv)
    {
         tv->on();
    }

これにより、TVおよびTVStyleAdapterメソッドを仮想化せずに、元のonクラスまたはoffをパラメーターとして取るコードを記述できます。これにより、2つのクラスのどちらを使用するかがコンパイル時に決定されますが、継承ソリューションでは実行時に切り替えることができます。

テンプレートソリューションと共に実行時の決定が必要な場合、決定はmyFunction<TV>またはmyFunction<TVStyleAdapter>を呼び出すコードによって行われる必要があります。あるいは、「継承よりも構成」の良い例である@JackAidleyのソリューションを使用することをお勧めします。

9
Doc Brown

はい、できます

解決策は簡単です。TVもラップし、TVDVDの両方のアダプターを共通の抽象基本クラスから継承します。その後、テレビを使用していたすべての場所の代わりにこの抽象基本クラスを使用して、実行時の柔軟性を得ることができます。

class TVBase {
    public:
    virtual ~TVBase() {}

    virtual void on() const = 0;
    virtual void off() const = 0;
};

class TVAdaptor : public TVBase {
    public:
    // Obviously you need a constructor!
    virtual void on() const { tv->on(); }
    virtual void off() const { tv->off(); }

    private:
    TV* tv;
};

class DVDAdaptor : public TVBase {
    // Insert contents of your TV style adaptor here...
};

上記ではさまざまな実装の詳細を省略しましたが、うまくいけば、私が行ったことを確認できます。 onoffの自動パススルーを使用してテンプレートとして実装し、DVDのようなクラスを別のインターフェイスで特殊化することもできます。

3
Jack Aidley