2

私が完全に間違っていない限り、ゲッター/セッター パターンは、次の 2 つの目的で使用される一般的なパターンです。

  1. getVariableメソッドを提供するだけで (または、まれに、メソッドを提供するだけで、変更可能になるだけで)プライベート変数を使用できるが変更できないようにすることsetVariable
  2. 将来、変数がクラスに出入りする前に変数を処理することが適切な解決策であるという問題が発生した場合、実際の実装を使用して変数を処理できることを確認する単に値を返したり設定したりするのではなく、getter および setter メソッドで。そうすれば、変更は残りのコードには反映されません。

質問 #1: アクセサーを使用していませんか、それとも私の仮定が間違っていますか? 私がそれらについて正しいかどうかはわかりません。

質問 2: メンバー変数のアクセサーを書かなくて済むようなテンプレートの利点はありますか? 何も見つかりませんでした。

質問 #3: 次のクラス テンプレートは、アクセサーを実際に記述せずにゲッターを実装する良い方法でしょうか?

template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
    typedef T Type;
};

template <typename T,class Owner>
class Getter
{
public:
    friend class TemplateParameterIndirection<Owner>::Type; // Befriends template parameter

    template <typename ... Args>
    Getter(Args args) : value(args ...) {} // Uses C++0x

    T get() { return value; }

protected:
    T value;
};

class Window
{
public:
    Getter<uint32_t,Window> width;
    Getter<uint32_t,Window> height;

    void resize(uint32_t width,uint32_t height)
    {
        // do actual window resizing logic

        width.value = width; // access permitted: Getter befriends Window
        height.value = height; // same here
    }
};

void someExternalFunction()
{
    Window win;

    win.resize(640,480); // Ok: public method

    // This works: Getter::get() is public
    std::cout << "Current window size: " << win.width.get() << 'x' << win.height.get() << ".\n";

    // This doesn't work: Getter::value is private
    win.width.value = 640;
    win.height.value = 480;
}

私には公平に見えますし、get他の部分的なテンプレート特殊化のトリックを使用してロジックを再実装することもできます. 同じことが、ある種の Setter または GetterSetter クラス テンプレートにも適用できます。

あなたの考えは何ですか?

4

8 に答える 8

4

このソリューションは実装の観点からは優れていますが、アーキテクチャ的にはまだ道半ばです。Getter/Setter パターンのポイントは、クラスにデータの制御を与え、結合を減らすことです (つまり、他のクラスはデータの格納方法を知っています)。このソリューションは前者を達成しますが、後者は完全には達成しません。

実際、もう一方のクラスは、変数の名前と getter のメソッド (つまり.get()、1 つではなく)の 2 つのことを知る必要がありますgetWidth()。これにより、カップリングが増加します。

そうは言っても、これはことわざの建築上の髪の毛を分割しています。結局のところ、それほど重要ではありません。

編集OK、たわごとと笑いのために、ここに演算子を使用したゲッターのバージョンがあります.value.get()

template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
    typedef T Type;
};

template <typename T,class Owner>
class Getter
{
public:
    friend TemplateParameterIndirection<Owner>::Type; // Befriends template parameter

    operator T()
    {
        return value;
    }

protected:
    T value;

    T& operator=( T other )
    {
       value = other;
       return value;  
    }


};

class Window
{
public:
    Getter<int,Window> _width;
    Getter<int,Window> _height;

    void resize(int width,int height)
    {
        // do actual window resizing logic
        _width = width; //using the operator
        _height = height; //using the operator
    }
};

void someExternalFunction()
{
    Window win;

    win.resize(640,480); // Ok: public method
    int w2 = win._width; //using the operator
    //win._height = 480; //KABOOM
}

EDITハードコードされた代入演算子を修正しました。型自体に代入演算子がある場合、これはかなりうまく機能するはずです。デフォルトでは、構造体にはそれらが含まれているため、単純な構造体の場合はそのまま使用できます。

より複雑なクラスでは、十分に公平な代入演算子を実装する必要があります。RVOとCopy On Writeの最適化により、これは実行時にかなり効率的になります。

于 2010-01-07T02:14:30.310 に答える
1

Igor Zevakaがこれの1つのバージョンを投稿したので、私がずっと前に書いたものを投稿します。これは少し異なります-get/setペアのほとんどの実際の使用(実際には何でもしました)は、事前に決定された範囲内にとどまる変数の値を強制することでした。これは、I / O演算子の追加など、もう少し広範であり、エクストラクタは引き続き定義された範囲を適用します。また、それが何をするのか、そしてそれがどのようにそれを行うのかについての一般的な考えを示すために、少しのテスト/演習コードがあります:

#include <exception>
#include <iostream>
#include <functional>

template <class T, class less=std::less<T> >
class bounded {
    const T lower_, upper_;
    T val_;

    bool check(T const &value) {
        return less()(value, lower_) || less()(upper_, value);
    }

    void assign(T const &value) {
        if (check(value))
            throw std::domain_error("Out of Range");
        val_ = value;
    }

public:
    bounded(T const &lower, T const &upper) 
        : lower_(lower), upper_(upper) {}

    bounded(bounded const &init) 
        : lower_(init.lower), upper_(init.upper)
    { 
        assign(init); 
    }

    bounded &operator=(T const &v) { assign(v);  return *this; }

    operator T() const { return val_; }

    friend std::istream &operator>>(std::istream &is, bounded &b) {
        T temp;
        is >> temp;

        if (b.check(temp))
            is.setstate(std::ios::failbit);
        else
            b.val_ = temp;
        return is;
    }
};


#ifdef TEST

#include <iostream>
#include <sstream>

int main() {
    bounded<int> x(0, 512);

    try {
        x = 21;
        std::cout << x << std::endl;

        x = 1024;
        std::cout << x << std::endl;
    }

    catch(std::domain_error &e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    std::stringstream input("1 2048");
    while (input>>x)
        std::cout << x << std::endl; 

    return 0;
}

#endif
于 2010-01-07T05:34:29.637 に答える
1

FWIW ここにあなたの質問に対する私の意見があります:

  1. 通常、要点は、setter に適用されるビジネス ロジックまたはその他の制約があることです。インスタンス変数をアクセサーメソッドと分離することにより、計算変数または仮想変数を使用することもできます。
  2. 私が知っていることではありません。私が取り組んできたプロジェクトには、そのようなメソッドを打ち消すための C マクロのファミリがありました。
  3. はい; それはかなりきれいだと思います。面倒なことをする価値がないのではないかと心配しています。他の開発者を混乱させるだけで (頭に収まる必要があるもう 1 つの概念)、そのようなメソッドを手動で打ち消すよりも多くの節約にはなりません。
于 2010-01-07T01:52:52.943 に答える
0

この場合、これはやり過ぎかもしれませんが、賢明な友情の使用法については、弁護士/依頼人のイディオムを確認する必要があります。このイディオムを見つける前に、私は友情を完全に避けました。

http://www.ddj.com/cpp/184402053/

于 2010-01-07T01:54:54.453 に答える
0

あなたは間違った問題を解決しています。適切に設計されたアプリケーションでは、getter と setter はまれであり、自動化されていません。意味のあるクラスは、ある種の抽象化を提供します。単なるメンバーのコレクションではなく、メンバー変数の単なる合計以上の概念をモデル化します。また、通常、個々のメンバーを公開しても意味がありません。

クラスは、それがモデル化する概念で意味のある操作を公開する必要があります。ほとんどのメンバー変数は、この抽象化を維持し、必要な状態を格納するために存在します。ただし、通常は直接アクセスしないでください。それが、そもそもクラスのプライベート メンバーである理由です。

より簡単な記述方法を見つけるのではなくcar.getFrontLeftWheel()、そもそもクラスのユーザーが左前輪を必要とする理由を自問してください。あなたは通常、運転中にそのホイールを直接操作しますか? 車はあなたのためにすべてのホイールスピンビジネスを処理するはずですよね?

于 2010-01-07T21:42:56.327 に答える
0
  1. また、getter または setter 型のメソッドを使用して、計算可能な値を取得または設定することもできます。これは、C# などの他の言語でプロパティが使用される方法と同じです。

  2. 不明な数の値/プロパティの取得と設定を抽象化する合理的な方法は考えられません。

  3. 私は C++ox 標準についてコメントできるほど詳しくありません。

于 2010-01-07T01:48:54.383 に答える
0

#defineこれは、 s がまだ有用であると私が思うところです。

テンプレートのバージョンは複雑でわかりにくい - 定義のバージョンは明らか

#define Getter(t, n)\
     t n;\
     t get_##n() { return n; }

class Window
{
    Getter(int, height);
}

構文が少し間違っていると確信していますが、要点はわかります。

たとえば、ブーストによく知られた一連のテンプレートがあれば、それらを使用します。しかし、私は自分自身を書きません。

于 2010-01-07T01:52:42.210 に答える
0

そして今、質問ですsetter

私はあなたのことを知りませんが、私は (大まかに) 2 種類のクラスを持っている傾向があります。

  • ロジックのクラス
  • ブロブ

ブロブは、ビジネス オブジェクトのすべてのプロパティの緩やかなコレクションです。たとえば、 aPersonには、surnamefirstname、いくつかの住所、いくつかの職業があります...したがって、Personロジックがない場合があります。

ブロブの場合、クライアントから実際の実装を抽象化するため、正規のプライベート属性 + ゲッター + セッターを使用する傾向があります。

ただし、テンプレート (および Igor Zeveka によるその進化) は非常に優れていますが、設定の問題やバイナリ互換性の問題は解決されていません。

おそらくマクロに頼るだろうと思います...

何かのようなもの:

// Interface
// Not how DEFINE does not repeat the type ;)
#define DECLARE_VALUE(Object, Type, Name, Seq) **Black Magic Here**
#define DEFINE_VALUE(Object, Name, Seq) ** Black Magic Here**

// Obvious macros
#define DECLARE_VALUER_GETTER(Type, Name, Seq)\
   public: boost::call_traits<Type>::const_reference Name() const

#define DEFINE_VALUE_GETTER(Object, Name)\
   boost::call_traits<Name##_type>::const_reference Object::Name ()const\
   { return m_##Name; }

#define DECLARE_VALUE_SETTER(Object, Type, Name)\
   public: Type& Name();\
   public: Object& Name(boost::call_traits<Type>::param_type i);

#define DEFINE_VALUE_SETTER(Object, Name)\
   Name##_type& Object::Name() { return m_##Name; }\
   Object& Object::Name(boost::call_traits<Name##_type>::param_type i)\
   { m_##Name = i; return *this; }

次のように使用されます。

// window.h
DECLARE_VALUE(Window, int, width, (GETTER)(SETTER));

// window.cpp
DEFINE_VALUE(Window, width, (GETTER)); // setter needs a bit of logic

Window& Window::width(int i) // Always seems a waste not to return anything!
{ 
  if (i < 0) throw std::logic_error();
  m_width = i;
  return *this;
} // Window::width

少しのプリプロセッサ マジックを使用すると、非常にうまく機能します。

#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/tuple/rem.hpp>

#define DECLARE_VALUE_ITER(r, data, elem)\
  DECLARE_VALUE_##elem ( BOOST_PP_TUPLE_REM(3)(data) )

#define DEFINE_VALUE_ITER(r, data, elem)\
  DEFINE_VALUE_##elem ( BOOST_PP_TUPLE_REM(2)(data) )

#define DECLARE_VALUE(Object, Type, Name, Seq)\
   public: typedef Type Name##_type;\
   private: Type m_##Name;\
   BOOST_PP_SEQ_FOREACH(DECLARE_VALUE_ITER, (Object, Type, Name), Seq)

#define DEFINE_VALUE(Object, Name, Seq)\
   BOOST_PP_SEQ_FOREACH(DEFINE_VALUE_ITER, (Object, Name), Seq)

さて、タイプセーフではありませんが、次のとおりです。

  • それは私が思う合理的なマクロのセットです
  • 使い方は簡単で、ユーザーは最終的に 2 つのマクロについて心配するだけで済みますが、テンプレートのようにエラーが発生する可能性があります
  • 効率化のための boost.call_traits の使用 (const& / 値の選択)
  • より多くの機能があります: getter/setter duo

  • 残念ながら、これは一連のマクロです...

  • アクセサー (public、protected、private) に大混乱をもたらすため、クラス全体に挿入しないことをお勧めします。

次に、標準的な例を示します。

class Window
{
  // Best get done with it
  DECLARE_VALUE(Window, int, width, (GETTER));
  DECLARE_VALUE(Window, int, height, (GETTER));

// don't know which is the current access level, so better define it
public:

};
于 2010-01-07T20:07:36.330 に答える