我的Getter / Setter方法在设置/返回之前检查该值。当值无效时,它们会抛出异常(BadArgumentException或IllegalStateException)。这是必需的,因为我们使用无效值初始化所有成员 - 因此我们避免使用这些无效值(==在其他地方获取错误/段错误/异常)。
好处是:
这似乎非常不寻常,因为大多数新团队成员首先抱怨它 - 即使他们在向我们解释之后同意我的意见。
问题:这是一种很好的编程风格吗? (即使它有一点性能)
示例代码:
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 );
}
答案 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)。这样你就可以毫无例外地返回,但你可以提供一个值......或者不是。