您的位置:首页 > 其它

条款18: 让接口容易被正确使用,不易被误用

2009-06-15 17:02 239 查看
条款18: 让接口容易被正确使用,不易被误用
(Make interfaces easy to use correctly and hard to use incorrectly.)

内容:
假设现在的你需要提供一些接口给你的客户去使用,而现在的你没有任何这个方面的经验,那么你就要考虑
下面这些情况的发生:
(1)你没有告诉你的客户该如何使用该接口;
(2)你告诉用户你怎么使用这些接口的方法,但是当用户使用的时候遗忘了某一步骤(甚至几个步骤);
(3)客户正确地按照你说的方法使用,但是却没有达到预期的效果.
对于以上的任何一种情况都会导致客户得不到你最终想提供的功能,而作为接口的提供方你也承担了不
可推卸的责任,为了避免或减少这种情况发生的概率,我们这里提出了接口设计应当遵循的基本原则:要让你提供
给别人使用的接口容易被正确使用,不易被误用.而要开发出这样的接口,首先你必须要了解客户可能做出什么样
的错误,比如我们先来看书上举的这个例子,假设你要设计一个用来表现日期的类Date:
class Date{
public:
Date(int month,int day,int year);
...
};
//test.cpp
Date oneDay(29,3,1995); //晕,月日颠倒了.应该是Date oneDay(3,29,1995);
为了防止这样的错误(客户记错了参数的类型意义)发生我用类型系统(type system)来进行保护,于是你构造
了三个类型:
struct Day{
explicit Day(int dayValue):value_(dayValue){}
int value_;
};
struct Month{
explicit Month(int monthValue):value_(monthValue){}
int value_;
};
struct Year{
explicit Year(int yearValue):value_(yearValue){}
int value_;
};
class Date{
public:
Date(const Month& m,const Day& d,const Year& y);
...
};
Date d(29,3,1995); //error,wrong type
Date d(Day(29),Month(3),Year(1995)); //error,wrong type
Date d(Month(3),Day(29),Year(1995)); //OK
Date d(Month(15),Day(29),Year(1995)); //wow,can't detected this
最后一个没有检测出来,于是我们再次修改代码,重新设计类型,增加对其值的限定,我们就可以对Month作如
下修改:
class Month{
public:
static Month Jan(){return Month(1);}
static Month Feb(){return Month(2);}
...
static Month Dec(){return Month(12);}
...
private:
explicit Month(int m); //prvent create object from outward
...
};
//test.cpp
Date d(Month::Mar(),Day(30),Year(1995));
哦也,这样的设计是不是比原始版本好多了,呵呵.
预防客户错误的另一个方法是,限制类型内什么事可做,什么事不能做.常见的限制是加上const.如果忘记
const用法的话,回过头去看看条款3的讲解,比如"以const修饰operator*的返回类型"可阻止客户因"用户自定义类型"而犯
错:
if(a*b = c)...//perfect,compile error
下面是另一个一般性准则"让types容易被正确使用,不容易被误用"的表现形式:"除非有好理由,否则应该尽量令你
的types的行为与内置类型保持一致".客户已经知道像int这样的type有些什么行为,所以你应该努力让你的
types在合样合理的前提下也有相同的表现.为了避免无端的与内置类型不兼容,真正的理由是为了提供行为一
致的接口,这样的"一致性"更能导致"接口容易被正确使用".STL容器的接口十分一致,这使得它们非常容易被使用,例
如每个STL容器都有一个size的成员函数.
前面我们讨论过的用管理类对象管理资源类的例子,现在我们再来看这个例子,我们来看那个工厂函数接
口:
Image* createImage();
这里我们来讨论一下客户怎么去使用这个接口?客户应该先得到新对象的指针,然后开始进行对该对象进
行操作,用完以后要取保删除掉它,如果客户遗漏了这一步骤就会发生资源泄露.而这里你"要求"客户去确保该
对象的删除操作被完成无疑是增加了风险,客户很可能忘掉去做它.
Image* pImage = createImage();
...
//delete pImage;//这里忘记调用delete pImage
...
这里你很不服气,他(客户)忘记调用是他的责任,怎么能够怪我的接口不好呢?他没按照我的步骤来导致
得不到最终的效果,这能怪我嘛?可气!呵呵,你说的貌似有道理,那么我们这里说明一点:导致用户用错或者遗
漏你强调的必须步骤,可能是你的接口让客户用起来复杂,不方便导致的.这可就是你的问题了,你要让你的接
口更加简单,更加适合客户调用,这样就会在一定程度上减少了客户由于误用该接口而失败的概率.
我们先来解决上面的问题,我们这里可以将返回值类型为智能指针类型,因为管理类对象将释放资源操作
从客户这边转移到你这边了,减少了客户的操作也就是减少了出错的概率,你说是不是?
std::tr1::shared_ptr<Image > createImage();
条款14我们也谈到了tr1::shared_ptr容许当智能指针被建立起来时指定一个资源释放函数绑定在智能指
针身上.假设这里的释放函数为getRidOfImage那么我们就可以这样实现函数体:
std::tr1::shared_ptr<Image> createImage(){
std::tr1::shared_ptr<Image> retVal(static_cast<Image*>(0),getRidOfImage);
...
retVal=...;//让retval指向正确的对象
return retVal;
}

请记住:
■ 好的接口很容易被正确使用,不容易被误用.你应该在你的所有接口中努力达成这些性质.
■ "促进正确使用"的办法包括接口的一致性,以及与内置类型的行为兼容.
■ "阻止误用"的办法包括建立新类型、限制类型上的操作、束缚对象值以及消除客户的资源管理责任.
■ tr1::shared_ptr支持定制型删除器.这可防范DLL问题,可被用来自动解除互斥锁等等.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: