如何进行面向对象的设计?
我们知道,面相对象的分析产出是详细的需求描述,那面相对象设计的产出就是类。在面相对象设计环节,我们将需求描述转化为具体的类的设计。我们把这一环节拆解细化一下,主要包含以下几个部分:
- 划分职业进而识别出有哪些类
- 定义类及其属性和方法
- 定义类与类之间的交互关系
- 将类组装起来并提供执行入口
1. 划分职责进而识别出有哪些类
根据需求描述,把其中涉及的功能点,一个一个罗列出来,然后再去看哪些功能点职责相近,操作同样的属性,可否应该归为同一个类。我们来看一下,针对鉴权这个例子,具体该如何来做。
先是详细的需求描述:
- 调用方进行接口请求的时候,将 URL、AppID、密码、时间戳拼接在一起,通过加密算法生成 token,并且将 token、AppID、时间戳拼接在 URL 中,一并发送到微服务端。
- 微服务端在接收到调用方的接口请求之后,从请求中拆解出 token、AppID、时间戳。
- 微服务端首先检查传递过来的时间戳跟当前时间,是否在 token 失效时间窗口内。如果已经超过失效时间,那就算接口调用鉴权失败,拒绝接口调用请求。
- 如果 token 验证没有过期失效,微服务端再从自己的存储中,取出 AppID 对应的密码,通过同样的 token 生成算法,生成另外一个 token,与调用方传递过来的 token 进行匹配。如果一致,则鉴权成功,允许接口调用;否则就拒绝接口调用。
首先,要做的是主句阅读上面的需求描述,拆解成小的功能点,一条一条的罗列下来。注意,拆解出的每个功能点要尽可能的小。每个功能点只负责做一件很小的事情(单一职责)。得到以下功能点列表:
- 把URL、AppID、密码、时间戳拼接为一个字符串;
- 对字符串通过加密算法加密生成token;
- 将token、AppID、时间戳拼接到URL中,形成新的URL;
- 解析URL,得到token、AppID、时间戳等信息;
- 从存储中取出AppID和对应的密码;
- 根据时间戳判断token是否过期失效;
- 验证两个token是否匹配。
从功能列表中我们发现,1、2、6、7都是跟token有关,负责token的生成、验证;3、4都是在处理URL,负责URL的拼接、解析;5是操作AppID和密码,负责从存储中读取AppID和密码。
所以我们可以粗略的得到三个核心的类:AuthToken、URL、CredentialStorage。AuthToken负责实现1、2、6、7这四个操作;URL负责3、4两个操作;CredentialStorage负责5这个操作。
2.定义类及其属性和方法。
AuthToken相关的功能点有4个:
把URL、AppID、密码、时间戳拼接为一个字符串。
对字符串通过加密算法生成token;
根据时间戳判断token是否过期失效;
验证两个token是否匹配
对于方法的识别,很多面相对象的书籍,一般都是这么讲的,识别出需求描述中的动词,作为候选方法,再进一步过滤筛选。类比一下方法的识别,我们可以把功能中涉及的名词,作为候选属性,然后同样进行过滤筛选。
我们可以借鉴这个思路,识别出来AuthToken类的属性和方法。
URL相关的功能有两个:
将token、AppID、时间戳拼接到URL中,形成新的URL。
解析URL,得到token、AppID、时间戳等信息。
虽然需求描述中,我们是以URL来代替接口请求,但是接口请求并不是以URL的形式来表达,还有可能是dubbo、rpc等其他形式。为了让这个类更加通用,我们把它命名为ApiRequest。
CredentialStorage相关功能点有一个:
- 从存储中取出AppID和对应的密码
这里给出一个demo仅供参考:
public interface ApiAuthenticator {
void auth(String url);
void auth(ApiRequest apiRequest);
}
public class DefaultApiAuthenticatorImpl implements ApiAuthenticator {
private CredentialStorage credentialStorage;
public DefaultApiAuthenticator() {
this.credentialStorage = new MysqlCredentialStorage();
}
public DefaultApiAuthenticator(CredentialStorage credentialStorage) {
this.credentialStorage = credentialStorage;
}
@Override
public void auth(String url) {
ApiRequest apiRequest = ApiRequest.buildFromUrl(url);
auth(apiRequest);
}
@Override
public void auth(ApiRequest apiRequest) {
String appId = apiRequest.getAppId();
String token = apiRequest.getToken();
long timestamp = apiRequest.getTimestamp();
String originalUrl = apiRequest.getOriginalUrl();
AuthToken clientAuthToken = new AuthToken(token, timestamp);
if (clientAuthToken.isExpired()) {
throw new RuntimeException("Token is expired.");
}
String password = credentialStorage.getPasswordByAppId(appId);
AuthToken serverAuthToken = AuthToken.generate(originalUrl, appId, password, timestamp);
if (!serverAuthToken.match(clientAuthToken)) {
throw new RuntimeException("Token verfication failed.");
}
}