“Getter / Setter中的价值验证”是一种好的风格吗?

时间:2011-11-18 07:43:49

标签: c++ exception setter getter

我的Getter / Setter方法在设置/返回之前检查该值。当值无效时,它们会抛出异常(BadArgumentException或IllegalStateException)。这是必需的,因为我们使用无效值初始化所有成员 - 因此我们避免使用这些无效值(==在其他地方获取错误/段错误/异常)。

好处是:

  • 当您从模型中收到成员值时,您知道它们是有效的
  • 有效性检查仅在模型对象
  • 中执行
  • 值范围在model-object
  • 中定义

这似乎非常不寻常,因为大多数新团队成员首先抱怨它 - 即使他们在向我们解释之后同意我的意见。

问题:这是一种很好的编程风格吗? (即使它有一点性能)

示例代码

inline bool MyClass::HasGroupID const { return m_iGroupID != 0; }

int MyClass::GetGroupID() const
{
    if( !HasGroupID() )
        throw EPTIllegalStateException( "Cannot access uninitialized group ID!" );

    return m_iGroupID;

}   // END GetGroupID() const

void MyClass::SetGroupID( int iGroupID )
{
    // negative IDs are allowed!
    if( iGroupID != 0 )
        throw EPTBadArgumentException( "GroupID must not be zero!", iGroupID );

    m_iGroupID = iGroupID;

}   // END SetGroupID( int )

用法

// in serialization
if( myObject.HasGroupID() )
    rStream.writeAttribute( "groupid", GetGroupID() );

// in de-serialization
try {
    // required attribute - throw exception if value is invalid
    myObject.SetGroupID( rStream.GetIntegerAttribute( "groupid" );
} catch( EPTBadArgumentException& rException )
{
    throw EPTBadXmlAttribute( fileName, rException );
}

4 个答案:

答案 0 :(得分:4)

我认为这是一种常见的风格,而且它肯定是第一个想法中吸气/定位者的核心动机之一。但是,在你的特定例子中,我认为它们没有意义,我认为通常有更好的选择:

如果GetGroupID产生错误,则调用HasGroupID会引发异常,那么这是程序员错误 - 所以您应该使用assert。实际上,为什么首先可能没有组ID的对象?这听起来像是一种略显不雅的设计。

此外,如果只有非零ID有效,那么您应该尝试使用专用类型更早地强制执行此限制。这样,处理组ID的所有代码都可以确保ID是有效的(实际上,编译器强制执行此操作!)并且您不可能忘记在某个地方签入。考虑:

class GroupID {
public:
    explicit GroupID( int id ) : m_id( id ) {
        if ( m_id == 0 ) {
            throw EPTBadArgumentException( "GroupID must not be zero!", m_id );
        }
    }

    // You may want to allow implicit conversion or rather use an explicit getter
    operator int() const { return m_id; }
};

使用它,你可以写

void MyClass::SetGroupID( GroupID id )
{
    // No error checking needed; we *know* it's valid, because it's a GroupID!
    // We can also use a less verbose variable name without losing readability
    // because the type name reveals the semantics.
    m_iGroupID = id;

} // END SetGroupID( int )

实际上,由于组ID现在是专用类型,因此您可以将函数重命名为Set并使用重载。所以你有MyClass::Set(UserID)MyClass::Set(GroupID)等等。

答案 1 :(得分:1)

基本上,这是getter / setter想法最重要的目的之一。封装本身是不舒服的 - 编写直接访问成员变量的简单代码更容易。但是通过封装,您可以限制对变量的访问:完整性检查值,通过互斥锁同步访问,将更改绑定到触发器,记录它们等等。

通常情况下,在最基本的情况下,这一切都不是必需的,但是稍后会出现惊人的数量,如果你一直在摆弄类成员,那么需要修复很多代码。如果你从一开始就使用简单的getter / setter,现在你可以添加任何你需要的东西。

答案 2 :(得分:0)

我同意你的新团队成员。对我而言,通常情况并非如此,但如果你必须这是唯一的方法,你应该这样做 我会尝试制作充满合意数据的对象。
您可以通过检查构造函数中的值并且不提供setter来达到此目的。
首先,我可以成为编写C#的借口,但我的C ++有点生锈: - )

class MyClass{

    private int groupId; 

    public MyClass(int groupId) {
        if(groupId ==0){
            throw new ArgumentOutOfRangeException("groupId",groupId,"Group Id must be >0");
        }
        this.groupId = groupId;
    }

    public int GroupId{ 
        get{ 
             return groupId;
        }
    }
}

现在,您可以确定只有所有属性符合其要求时才会初始化Class MyClass。您可以实现一个构造函数,它将获取MyClass实例并克隆所有值以创建克隆对象 如果您需要更改属性,请创建一个接口并定义MyClassChangeable

public class Programm {
    static void main(string[] args) {
        IMyClass myClassChangable = new MyClassChangable(-123);
        try {
            MyClass correctMyClass = new MyClass(myClassChangable);
            useThisClass(correctMyClass);

        } catch (Exception ex) {
            //Display to user or something
            Debug.WriteLine(ex);
        }
    }

    static void useThisClass(MyClass myClass) {
        //this is alway correct!
        int groupId = myClass.GroupId;

    }
}

interface IMyClass {
    int GroupId { get; }
}

class MyClass : IMyClass {
    private int groupId;

    public MyClass(int groupId) {
        if (groupId == 0) {
            throw new ArgumentOutOfRangeException("groupId", groupId, "Group Id must be >0");
        }
        this.groupId = groupId;
    }

    public MyClass(IMyClass instance)
        : this(instance.GroupId) {
    }

    #region Implementation of IMyClass

    public int GroupId {
        get { return groupId; }
    }

    #endregion
}


public class MyClassChangable : IMyClass {
    private int groupId = 0;

    public MyClassChangable() {

    }

    public MyClassChangable(int groupId) {
        this.groupId = groupId;
    }

    #region Implementation of IMyClass

    public int GroupId {
        get { return groupId; }
        set { groupId = value; }
    }

    #endregion
}

你现在可以使用一个类但是你想要使用它,而不是只在一个点“检查”它(所以只有一个例外)。在“BusinessCore”中,您使用的MyClass始终具有正确的值。所以没有异常,也没有OutOfBounds!
希望我能提供帮助,如果您有其他问题或者出现问题,请与我联系,很高兴学习! 我很乐意收到我的想法的反馈!

答案 3 :(得分:0)

关于验证:Setter完全通常会验证类不变量是否得到尊重(实际上,这是与公共值相比的大部分附加值)。这在Getters中是最不寻常的(懒惰的验证是调试的地狱),虽然在Debug构建中,在那个阶段重新验证可能会很有趣,只是为了捕获更多的bug。

但是,这不是你在这里做的。您的类Invariant明确允许可选元素(如GroupId),但您禁止用户将此GroupId设置为无效状态...为什么!?这是不规则的。

关于Getter,有一个简单的解决方案:可选类型(如Boost.Optional)。这样你就可以毫无例外地返回,但你可以提供一个值......或者不是。