本节书摘来自华章出版社《面向对象的思考过程(原书第4版)》一书中的第2章,第2.3节,[美] 马特·魏斯费尔德(Matt Weisfeld) 著黄博文 译更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2.3 尽可能提供最小化的用户接口
当设计类时,通用规则是尽量不要让用户知道类内部的工作原理。为了达到这点,请遵守以下简单的规则:
只提供给用户绝对需要的东西。实际上,这意味着类的接口要尽可能少。当你开始设计一个类时,先从最小化的接口开始。类的设计是迭代式的,所以随后即使你发现最小化的接口可能不合适,也没关系。
最好只有用户真正需要时才添加接口,不要提供超出用户需求的接口。用户拥有某些接口可能会出问题。比如,你不该提供给所有用户关于薪水信息的接口,而应该只提供给需要的用户。
这次我们用硬件示例来阐述软件示例。想象一个用户有一台个人计算机主机,但没有显示器和键盘。显然,这个个人计算机主机用处不大。你只提供给了该用户关于该个人计算机最小化的接口。然而,该最小化的接口是不合适的,有必要立即添加接口。
公共接口定义了用户可以访问什么。你可以刚开始设置所有接口为私有的,这样向用户隐藏了整个类,当程序员开始使用这个类时,再把某些方法公开出来,这些方法变成了公共接口。
从用户角度定义类至关重要,而不是从信息系统的角度定义类。通常类(而不是软件)的设计者设计类时会使其适用一个具体的技术模型。即使该设计者站在用户的视角,它仍然可能是一个技术用户的视角。而这个类的设计往往只考虑了技术因素,而没有基于用户的视角。
确保设计类时你向真正的用户了解了需求和设计。这些人并不局限于开发人员。当构建系统原型时,类通常都会需要更新和演化。
2.3.1 确定用户?
我们再看出租车示例。我们已经确定用户是实际使用这个系统的人。那么,谁是用户?
第一反应是客户。答案只对了一半。客户确实是用户,出租车司机必须为客户提供成功的服务。换句话说,提供一个诸如“免费送我到机场”的接口毋容置疑会取悦客户,但对出租车司机则不是这样。因此,实际上,为了构建一个真实可用的接口,必须考虑客户和出租车司机都是用户。
同样在软件领域,用户可能想要程序员提供某个功能。然而,如果程序员发现该需求在技术上是不可行的,不管程序员多想提供帮助,这样的需求是无法满足的。
总之,任何给出租车对象发送消息的对象都可以认为是用户(是的,用户也是对象)。
图2-6展示了出租车司机如何提供服务。
展望
出租车司机也是一个对象。
2.3.2 对象行为
识别用户只是实践的一部分。识别用户之后,你必须确定对象行为。需要从每个用户的视角来开始识别每个对象的目的以及需要做的事情。注意,很多初始的决策无法保留到最终的公共接口中。可以通过使用各种方法(比如UML用例)聚合需求来进行决策。
2.3.3 环境约束
在《Java面向对象设计》一书中,Gilbert和McCarty指出环境通常会限制对象。事实上,环境限制往往都是影响因子。计算机硬件可能会限制软件功能。比如,一个系统可能没有连接到网络,或者一个公司可能会使用特定类型的打印机。在出租车例子中,司机不能通过一座坏了的桥,尽管这条路是去机场的捷径。
2.3.4 识别公共接口
我们已经收集到了用户信息、对象行为和环境信息,接下来需要为每个用户对象指定公共接口。所以,想象一下会如何使用出租车:
上车。
告诉司机你想去哪里。
付车费。
享受旅途。
下车。
你需要如何使用出租车对象?
知道去哪里。
租用出租车。
付给司机钱。
刚开始,你只需考虑如何使用这个对象,不用思考如何构建这个对象。稍后你可能会发现对象需要更多的接口,比如“把行李放到车厢中”或者“和司机进行随意交谈”。图2-7提供了一个类图来列出了Cabbie类的可能的方法。
得到最终的接口始终是一个迭代的过程。你必须确定每个接口是否有利于对象的操作。如果不利于,那么它可能没必要存在。很多面向对象的文章推荐每个接口模型只包含一个行为。这带给我们的问题是我们的设计究竟要抽象到哪种层次。如果有一个叫作enterTaxi()的接口,我们肯定不想enterTaxi()包含付费的逻辑。这样做不但设计上毫无道理,而且事实上该类的使用者会不知道到底如何付费。
2.3.5 识别实现
明确了公共接口后,接下来需要识别实现。设计完类,该类已经包括所有需要操作的方法,接下来需要具体考虑如何保证该类正常工作。
从技术角度来讲,任何非公共接口可以视为实现。这意味着用户不会看到具体的实现方法,包括方法签名(方法签名包括方法名和参数列表)和方法中的实际代码。
类可能有一些私有方法仅供内部使用。任何私有方法都可以视为实现的一部分,用户绝不会看到它,从而也不能访问它。例如,一个类有一个changePassword()方法,然而,这个类可以有一个私有方法来对密码做加密处理。这个方法应该对用户是隐藏的,并且只能通过changePassword()方法调用它。
应该对用户完全隐藏实现细节。公共方法中的代码是实现的一部分,因为用户不能看到它。(用户只能看到接口的调用结构,而看不到里面的代码。)
理论上来说,任何对实现的修改都不应该影响用户通过接口与类的交互方式。当然,前提假设是实现提供的结果是用户期望的结果。
接口代表了用户如何看待对象,实现则是对象的具体细节。实现包含了代表对象状态的代码。