条款18:让接口容易被正确使用,不容易被误用
开发者要注意,你的功能要尽全力去减少不确定性 。
一、有什么问题
取原书的例子:
存在一个日期类,他的本意是用来记录年月日的,因此构造函数就有着年月日三个入参;
class Date
{
public:
Date(int month, int day, int year);
// ...
};
现在考虑创建这个日期类的,这里会出现一个问题:
Date d(30, 3, 1995); // 不存在30天
Date d(2, 30, 1995); // 2月没有30天
这类错误数据其实很常见,它的成因,有可能是入参看错,有可能是不知 “ 常识 ” ,但归根结底就是,使用限制不明确。
我们无法强求用户,只能尽力限制住,用户什么不可以。
二、怎么解决
1、方法一
最笨的方法可以是:
Date::Date(int month, int day, int year)
{
if(month == 2 && day >= 30)
return;
处理代码
}
在函数内部,具体的处理代码之前进行判断,如果出现不想要的情况就提前返回;
但,这种方法,只是治标不治本,穷举全部不想要情况,还是尽量作为一种辅助手段。
2、方法二
enum eMonth
{
eMonth_Jan,
eMonth_Feb,
...
};
class Date
{
public:
Date(eMonth month, int day, int year);
// ...
};
Date d(eMonth_Jan, 30, 1995);
使用枚举类型就进行限制,就可以很好的限制住像月份这样明确且有限的数据输入,保证使用者,不会给你写进去一个第30月进去,但也要考虑,日怎么写,用枚举?那要是输入限制在1-10000内的日也用枚举,那就不用干别的了。
3、方法三
许多客户端错误可以通过导入新类型预防。我们可以导入简单的外覆类型(wrapper type)来区别天数、月份、年份,然后于Date构造函数中使用这些类型:
class Month
{
public:
static Month Jan() // 函数,返回有效月份,稍后解释为什么使用函数而非对象
{
return Month(1);
}
static Month Feb()
{
return Month(2);
}
// ...
static Montn Dec()
{
return Month(12);
}
// ... 其他成员函数
private:
explicit Month(int m); // 阻止生成其他月份,这是private的
// ...
};
class Date
{
public:
Date(Month month, int day, int year);
// ...
};
Date d(Month::Jan(), 30, 1995);
如此设计,也可以达到相同的防御效果。
三、拓展问题
Investment *createInvestment();
当有这样一个返回值为一个指针的接口,我们在使用的时候,就要注意别忘了用完就释放,但总会有人忘记释放;
因此,我们作为设计者,这种释放问题也是要防御的点,也是要限制住的。
要怎么做呢?
std::tr1::shared_ptr<Investment> createInvestment();
强迫用户使用智能指针不失为一种好选择。
四、总结
1、好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。
2、促进正确使用的方法包括接口的一致性,以及与内置类型的行为兼容。
3、阻止误用的方法包括建立新类型,限制类型上的操作,束缚类型值,以及消除客户的资源管理责任。
4、trl::shared_ptr 支持定制型删除器(custom deleter)。这可防范 DLL 问题,可被用来自动解除互斥锁(mutexes,见条款14)等等。