2

除非我完全弄错了,否则 getter/setter 模式是用于两件事的常用模式:

  1. 制作一个私有变量,以便它可以使用,但不能修改,只提供一个getVariable方法(或者,更罕见的是,只可修改,只提供一个setVariable方法)。
  2. 为了确保将来,如果您碰巧遇到一个问题,一个好的解决方案只是在变量进入和/或退出类之前对其进行处理,您可以通过使用实际实现来处理变量在 getter 和 setter 方法上,而不是简单地返回或设置值。这样,更改不会传播到代码的其余部分。

问题 #1:我是否遗漏了对访问器的任何使用,或者我的任何假设是否不正确?我不确定我是否正确。

问题 #2:是否有任何模板优点可以让我不必为我的成员变量编写访问器?我没有找到。

问题 #3:下面的类模板是否是实现 getter 而无需实际编写访问器的好方法?

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())而不是一个 - 例如getWidth()。这导致耦合增加。

说了这么多,这是众所周知的建筑问题。在一天结束时,这并不重要。

编辑好的,现在为了大便和咯咯笑,这里是使用运算符的吸气剂版本,所以你不必做.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
}

编辑固定硬编码赋值运算符。如果类型本身具有赋值运算符,这应该可以很好地工作。默认情况下,结构具有这些,因此对于简单的结构,它应该开箱即用。

对于更复杂的类,您将需要实现一个足够公平的赋值运算符。使用RVO写时复制优化,这在运行时应该是相当有效的。

于 2010-01-07T02:14:30.310 回答
1

FWIW 以下是我对您的问题的看法:

  1. 通常,关键是在设置器中强制执行业务逻辑或其他约束。您还可以通过将实例变量与访问器方法解耦来获得计算变量或虚拟变量。
  2. 从来没听说过。我从事的项目有一系列 C 宏来消除此类方法
  3. 是的; 我认为这很整洁。我只是担心这不值得麻烦,它只会让其他开发人员感到困惑(他们需要适应他们头脑中的另一个概念)并且与手动消除此类方法相比并没有节省太多。
于 2010-01-07T01:52:52.943 回答
1

由于 Igor Zevaka 发布了一个版本,我将发布一个我很久以前写的。这略有不同——我当时观察到, 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 回答
0
  1. 您还可以使用 getter 或 setter 类型方法来获取或设置可计算值,这与 C# 等其他语言中使用属性的方式非常相似

  2. 我想不出合理的方法来抽象未知数量的值/属性的获取和设置。

  3. 我对 C++ox 标准不够熟悉,无法发表评论。

于 2010-01-07T01:48:54.383 回答
0

这是我认为#defines 仍然有用的地方。

模板版本复杂难懂——定义版本一目了然

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

class Window
{
    Getter(int, height);
}

我确信我的语法略有错误 - 但你明白了。

如果在 boost 中有一组众所周知的模板,那么我会使用它们。但我不会自己写。

于 2010-01-07T01:52:42.210 回答
0

在这种情况下,这可能是矫枉过正,但您应该检查一下律师/客户的成语,以便明智地使用友谊。在找到这个成语之前,我完全避免了友谊。

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

于 2010-01-07T01:54:54.453 回答
0

现在的问题是,如果你也需要一个怎么办setter

我不了解你,但我倾向于(大致)两种类型的课程:

  • 逻辑类
  • 斑点

Blob 只是业务对象所有属性的松散集合。例如 aPerson将有一个surname, firstname, 几个地址,几个职业......所以Person可能没有逻辑。

对于 blob,我倾向于使用规范的私有属性 + getter + setter,因为它从客户端抽象了实际的实现。

但是,尽管您的模板(以及由 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& / value 选择)
  • 还有更多功能:getter/setter duo

  • 不幸的是,它是一组宏......

  • 它对访问器(公共的、受保护的、私有的)造成严重破坏,所以最好不要在整个班级中插入它

这是典型的例子:

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 回答
0

你正在解决错误的问题。在设计良好的应用程序中,getter 和 setter 应该很少见,而不是自动化。一个有意义的类提供了某种抽象。它不仅仅是一个成员的集合,它对一个概念进行建模,而不仅仅是其成员变量的总和。公开个别成员通常甚至没有意义。

一个类应该公开对其建模的概念有意义的操作。大多数成员变量都是为了维护这种抽象,存储您需要的状态。但通常不应直接访问它。这就是为什么它首先是类的私有成员

与其寻找更简单的编写方法,不如car.getFrontLeftWheel()问问自己为什么该类的用户首先需要左前轮。您通常在开车时直接操纵那个轮子吗?这辆车应该为你处理所有的车轮旋转业务,不是吗?

于 2010-01-07T21:42:56.327 回答