一、面向对象分析
如果针对框架,类库,组件等非业务系统的开发,就有一个比较大的难点:需求一般比较抽象,模糊,需要你自己去挖掘,做合理取舍,权衡,假设,把抽象的问题具体化,最终产生清晰的,可落地的需求定义。
需求定义是否清晰合理,直接影响后续的设计,编码实现是否顺畅。
需求分析的过程实际上是一个不断迭代优化的过程,不要试图一下就能给出完美的解决方案,而是先给出一个粗糙,基础的方案,有一个迭代的基础,然后再慢慢优化,这样一个思考过程能让我们摆脱无法下手的窘境。
二、面向对象设计
1、划分职责而识别出有哪些类
根据需求描述,我们把其中涉及的功能点,以一个个罗列出来,然后再去看哪些功能点职责相近,操作同样的属性,可否归为同一个类。
2、定义类及其属性和方法
我们识别出需求描述中的动词,作为候选的方法,再进一步过滤筛选,把功能点中涉及的名词,作为候选属性,然后同样再进行过滤筛选。
3、定义类与类之间的交互关系
UML统一建模语言中定义了六种类之间的关系。它们分别是:泛化,实现,关联,聚合,组合,依赖。
4、将类组装起来并提供执行入口
我们将所有的类组装在一起,提供一个执行入口。这个入口可能是main()函数,也可能是一个组给外部用的API接口。通过这个入口,我们能触发整个代码跑起来。
三、面向对象开发案例:接口鉴权功能
1、面向对象分析
需求分析是不断迭代优化的过程。
第一轮基础分析
通过用户名和密码做接口权限认证:
(1)给允许访问服务的调用方,派发一个AppId和密码
(2)调用方每次接口请求时候,携带这个AppId和密码
(3)服务方接收到请求后,对比AppId和密码
第二轮分析优化
AppId和密码用明文传输,被截获不安全,可以借助加密算法(比如SHA)进行加密。
不过,未认证系统截获加密密码,还可以利用加密密码伪装成已认证系统访问接口。
我们可以利用OAuth的验证思路来解决:
第三轮分析优化
这种token方式可以解决截获方修改id参数获取更多接口数据的问题。但是,截获方还是可以不改动id参数,重复不断调用接口。
我们可以引入随机变量时间戳,只能在一定时间窗口内(比如一分钟)才能请求成功,避免截获方重复攻击的风险。
2、面向对象设计
(1)划分职责而识别出有哪些类
根据需求描述,罗列功能点:
【1】把 URL、AppID、密码、时间戳拼接为一个字符串;
【2】对字符串通过加密算法加密生成 token;
【3】将 token、AppID、时间戳拼接到 URL 中,形成新的URL;
【4】解析 URL,得到 token、AppID、时间戳等信息;
【5】从存储中取出 AppID 和对应的密码;
【6】根据时间戳判断 token 是否过期失效;
【7】验证两个 token 是否匹配;
职责相近,操作同样属性的归为同一个类:
【1】【2】【6】【7】负责token的生成和验证,归为AuthToken类;
【3】【4】负责URL生成和解析,归为URL类;
【5】负责从存储读取AppID和密码,归为CredentialStorage类;
(2)定义类及其属性和方法
AuthToken类:
从业务模型角度看,URL,AppID,密码,时间戳并不属于AuthToken类的属性,就没有放在这个类里,而是在外部拼接成token参数传进构造函数里。
还从业务模型角度看,理应还具有哪些属性,增加了createTime,expiredTimeInterval属性。
类的属性和方法设计要从业务模型角度思考,保证类定义的完整性,甚至需要为未来需求做些准备。
ApiRequest类:
接口请求并不一定以URL形式来表达,还可能是Dubbo,RPC等其他形式,为了让类更加通用,所以类命名为ApiRequest。
CredentialStorage接口:
为了做到抽象封装具体的存储方式,将CredentialStorage设计成接口,基于接口而非具体的实现编程。
(3)定义类与类之间的交互关系
UML统一建模语言定义了六种类之间的关系:泛化,实现,关联,聚合,组合,依赖。
泛化:继承关系,比如B子类继承A父类;
public class A {
... }
public class B extends A {
...