提高代码可扩展性和复用性
对类中的属性进行限制,提升可读性、易用性
忽略非关键性的实现细节
- 表示 is-a 关系
- 代码复用(继承层次过深导致可读性、可维护性变差)
- 支持多态特性
- 继承加方法重写
- duck-typing
-
滥用 getter、setter 方法 违反了面向对象的封装特性 getter 返回可修改对象
-
滥用全局变量和全局方法 将 Constants 类拆解为功能更加单一的多个类 细化 Utils 类
-
定义数据和方法分离的类
抽象类特点
- 解决代码复用
- 强制要求子类重写抽象方法
public class AbstractBird {
public void fly() {}
}
public class Ostrich extends AbstractBird {
public void fly() {
throw new UnSupportedMethodException("Can't fly");
}
}
- 徒增了编码的工作量
- 违背了最小知识原则,暴露不该暴露的接口给外部
public class AbstractBird {
}
public class AbstractFlyableBird extends AbstractBird {
public void fly() {}
}
public class AbstractUnFlyableBird extends AbstractBird {
}
public class AbstractFlyableTweetableBird extends AbstractFlyableBird {
}
public class Ostrich extends AbstractUnFlyableBird {
}
- 类的继承层次越来越深、继承关系会越来越复杂,导致代码的可读性变差
- 破坏了类的封装特性,将父类的实现细节暴露给了子类。子类的实现依赖父类的实现,两者高度耦合,一旦父类代码修改,就会影响所有子类的逻辑
抽象类来模拟接口
public class MockInterface {
// 避免被实例化
protected MockInterface() {}
public void funcA() {
throw new MethodUnSupportedException();
}
}
接口特点
- 解耦
- 可扩展
基于接口而非实现编程
- 函数的命名不能暴露任何实现细节
- 封装具体的实现细节
- 为实现类定义抽象的接口,实现类依赖统一的接口定义,遵从一致的上传功能协议。使用者依赖接口,而不是具体的实现类来编程
通过组合、接口、委托三个技术手段替换掉继承
public interface Flyable {
void fly();
}
public interface Tweetable {
void tweet();
}
public interface EggLayable {
void layEgg();
}
public class FlyAbility implements Flyable {
public void fly() {}
}
public class Ostrich implements Tweetable, EggLayable {
public void tweet() {}
public void layEgg() {}
}
public class Sparrow impelents Flayable, Tweetable, EggLayable {
private FlyAbility flyAbility;
public void fly() {
FlyAbility.fly();
}
public void layEgg() {}
public void tweet() {}
}
通过用户名加密码来做认证,给每个服务调用方下发 appId 和一个对应的密钥。调用方每次请求时,都携带自己的 appId 和密钥。服务在接收到请求后,解析出 appId 和密钥,然后进行验证
- appId 与密钥的验证方式,密钥容易被截获,是不安全的。可以借助加密算法,对密钥进行加密传输
对传输信息进行加密,虽然密钥不会被泄露,但是加密之后的密钥及 appId 照样可以被未认证系统截获,未认证系统可以携带这个加密之后的密钥以及对应的 appId,伪装成已认证系统来访问我们的服务
- 借助 OAuth 的验证思路,调用方将请求接口的 URL 跟 appID、密钥拼接在一起进行加密,生成一个 token。然后将这个 token 及 appId,随 URL 一块传递给服务端。微服务端接收请求后,根据 appId 从数据库中取出对应的密码,并通过同样的 token 生成算法,生成另外一个 token 进行验证
由于每个 URL 拼接上 appId、密钥生成的 token 都是固定的。未认证系统截获 URL、token 和 appId 之后,还是可以通过重放攻击的方式,伪装成认证系统,调用对应的接口
- 优化 token 生成算法,引入一个随机变量,让每次接口请求生成的 token 都不一样。选择时间戳作为随机变量,将 URL、appId、密码、时间戳四者进行加密来生成 token。服务端在接受请求后,会验证当前时间戳跟传递过来的时间戳,是否在一定的时间窗口内(比如一分钟)。如果超过一分钟,则判定 token 过期,拒绝接口请求。如果没有超过一分钟,然后再对 token 进行验证
未认证系统还是可以在一个时间段内,通过截获请求、重放请求,来调用我们的接口
- 引入一个唯一标识 nonce 来标记每一次请求,服务端在接受请求后,会验证 nonce 是否是已使用的
根据需求描述,把其中涉及的功能点罗列出来,然后再去看哪些功能点职责相近,操作同样的属性,可否应该归为同一个类。在拆解需求描述之后,得到如下功能点列表:
- 把 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
public AuthToken(String token, long createTime);
public AuthToken(String token, long createTime, long expireTimeInterval);
public static AuthToken create(String baseUrl, long createTime, Map<String, String> params);
public String getToken();
public boolean isExpired();
public boolean match(AuthToken authToken);
private static final long DEFAULT_EXPIRED_TIME_INTERVAL = 60 * 1000;
private String token;
private long createTime;
private long expireTimeInterval = DEFAULT_EXPIRED_TIME_INTERVAL;
ApiRequest
public ApiRequest(String baseUrl, String token, String appId, long timestamp);
public static ApiRequest createFromFullUrl(String url);
public String getBaseUrl();
public String getToken();
public String getAppId();
public long getTimestamp();
private String baseUrl;
private String token;
private String appId;
private long timestamp;
CredentialStorage
String getPasswordByAppId(String appId);
泛化,可以简单理解为继承
public class A {}
public class B extends A {}
实现,一般是指接口和实现类之间的关系
public interface A {}
public class B implements A {}
聚合是一种包含关系,A 类对象包含 B 类对象,B 类对象的生命周期可以不依赖 A 类对象的生命周期
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
组合也是一种包含关系。A 类对象包含 B 类对象,B 类对象的生命周期依赖 A 类对象的生命周期
public class A {
private B b;
public A() {
this.b = new B();
}
}
关联是一种非常弱的关系,包含聚合、组合两种关系。如果 B 类对象是 A 类的成员变量,那 B 类和 A 类就是关联关系
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
public class A {
private B b;
public A() {
this.b = new B();
}
}
依赖是一种比关联关系更加弱的关系,包含关联关系。不管是 B 类对象是 A 类对象的成员变量,还是 A 类的方法使用 B 类对象作为参数或者返回值、局部变量,只要 B 类对象和 A 类对象有任何使用关系,都称它们有依赖关系
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
public class A {
private B b;
public A() {
this.b = new B();
}
}
public class A {
public void func(B b) {}
}
设计一个最顶层的 ApiAuthencator 接口类,暴露一组给外部调用者使用的 API 接口,作为触发执行鉴权逻辑的入口
ApiAuthencator
void auth(String url);
void auth(ApiRequest ApiRequest);
DefaultApiAuthencator
private CredentialStorage credentialStorage;
public DefaultApiAuthencator();
public DefaultApiAuthencator(CredentialStorage credentialStorage);
void auth(String url);
void auth(ApiRequest ApiRequest);
public class DefaultApiAuthencatorImpl implements ApiAuthencator {
private CredentialStorage credentialStorage;
public ApiAuthencator() {
this.credentialStorage = new MysqlCredentialStorage();
}
public ApiAuthencator(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);
if (!serverAuthToken.match(clientAuthToken)) {
throw new RuntimeException("Token verfication failed.");
}
}
}