2017-03-17 1 views
2

次の簡略化されたC++クラス階層を例に挙げてください。私が達成したいのは、Serviceが任意のModelオブジェクトを保存する仮想メソッドを提供するということです。しかし、Serviceの各サブクラス、例えば。 BoxServiceは、Boxオブジェクトのみ保存してください。私の質問は、そのための任意の好適な設計パターンやベストプラクティスがあるさC++共変量パラメータ - デザインパターン

void save(Box box); 

によりC++は、メソッドのパラメータに共分散をサポートしていないという事実に私は単純に同じようBoxService.hに保存する方法を宣言することはできません問題?または、到着するModelオブジェクトがBox型で、それ以外の場合は例外をスローすると、save関数の実装をBoxService.cppでチェックする必要がありますか? Model.h

class Model { 
private: 
    int id; 
}; 

Box.h

class Box : public Model { 
private: 
    int size; 
}; 

Service.h

class Service { 
public: 
    virtual void save(Model model); 
}; 

BoxSe rvice.h

class BoxService : public Service { 
public: 
    void save(Model box); 
}; 

BoxService.cpp

void BoxService::save(Model box) { 
    // TODO: save box and make sure that box is of type 'Box' and not any other subtype of 'Model' 
} 
+5

これは多型を破ります。 'Service'を与えられ、' save(model) 'を呼び出すと、' BoxService'が与えられると失敗しません。反対のパラメータは逆になります。 'Service'が' Box'をセーブすることができるならば、 'BoxService'は' Model'をパラメータとして受け取ることができます。 'Service'を通る呼び出し者は' Model'sである 'Box'esを渡しますが、' BoxService '' Model'型を渡すことができます。これはC++がサポートしていないものです。 – chris

+0

'BoxService'は' Service'を継承するのはなぜですか?りんごの袋は果物の袋ではありません。 – aschepler

+0

私は同意します!ポリモーフィズムを破ることなく、上記のようなデザインをレイアウトすることも可能ですか? –

答えて

1

あなたはモデルタイプによってグループ操作の実装にしたいようなので、あなたが聞こえます。よりOOPのアプローチについて説明します。

実装から分離Serviceが、我々は厄介なパラメータを取り除くつもりだ:

class Service { ... }; 
class ServiceImpl { 
    virtual ~ServiceImpl() {} 
    void save() const = 0; 
    void remove() const = 0; 
}; 

各実装は軽量であることと操作をサポートしていますが、コンストラクタのパラメータを取るだろう。

class BoxService : public ServiceImpl { 
    Box _b; 

public: 
    BoxService(Box b) : _b(b) {} 

    void save() const { ... } 
    void remove() const { ... } 
}; 

今、我々は彼らを必要とするような実装を作成するための抽象工場を持っている:

class ServiceImplFactory { 
public: 
    std::unique_ptr<ServiceImpl> create(Model m) const { 
     if (auto b = dynamic_cast<Box*>(m)) { 
      return std::make_unique<BoxService>(*b); 
     } else if (auto x = dynamic_cast<X*>(m)) { 
      return std::make_unique<XService>(*x); 
     } else { 
      assert(!"Not all Models covered by Service implementations"); 
     } 
    } 
}; 

class Service { 
    ServiceImplFactory _implFactory; 

public: 
    void save(Model m) const { 
     return _implFactory.create(m)->save(); 
    } 

    void remove(Model m) const { 
     return _implFactory.create(m)->remove(); 
    } 
}; 

さらなるステップ:

  • が主張するのではなく、コンパイル時にエラーを与えるソリューションエンジニア。
  • (多くの)既存のコードを変更せずに、さらに多くのモデルタイプと追加の操作を追加できます。これは表現問題と同等でなければならない。モデルタイプ別に操作をグループ化するこのアプローチでは、新しいモデルタイプを追加するよりも、新しい操作を追加するためにはるかに広範な変更が必要です。その逆は、訪問者を使用し、操作によってモデルタイプをグループ化する場合に当てはまります。Expression Problemには、object algebrasのような解決策がありますが、それはやや曖昧になります。ここ
1

は、おそらくそれ以上の機能的なアプローチは次のとおり

ペア実装各モデルタイプ:

template<typename T, typename ExtraType> 
struct WithType { 
    T value; 
    using extra_type = ExtraType; 

    WithType(T value) : value(value) {} 
}; 

代わりに継承階層のバリアントとしてModelを定義:

using Model = std::variant<WithType<Box, BoxService>, WithType<X, XService>>; 

今すぐバリアントにアクセスしてください:

class Service { 
public: 
    void save(Model m) const { 
     visit([](auto withService) { 
      typename decltype(withService)::extra_type service; 
      service.save(withService.value); 
     }, m); 
    } 

    void remove(Model m) const { 
     visit([](auto withService) { 
      typename decltype(withService)::extra_type service; 
      service.remove(withService.value); 
     }, m); 
    } 
}; 
+0

ほとんどの場合、あなたのモデルでBagServiceを使用している可能性があるBoxServiceの型安全性保証はありません。 – lorro

+0

@lorro、私は分かりません。このように 'Model'を定義することにより、モデルオブジェクトに正しいサービスタイプをアタッチします。 'save(Box {}) 'を呼び出すと、' BoxService'が使用されます。 'save(Bag {}) 'を呼び出すと' Model'の定義が正しいと仮定して 'BagService'を使います。これを壊して 'BagService'を' Box'につけると 'visit'呼び出しはコンパイラエラーになると思います。 – chris

+0

あなたは正しいです、私はモデルのdefを誤解しました。 – lorro

関連する問題