设计模式篇(一):十分钟搞懂设计原则

内容需要思考和理解,请耐心阅读

开篇

在学习设计模式之前,首先应该学习设计原则,设计模式是围绕着设计原则去展开的,因此理解设计原则才能更好的去理解设计模式

想到设计原则脑海中会出现‘SOLID’原则,而SOLID原则就是我们要去学习的设计原则,SOLID代表的是五个设计原则,每个字母代表一个,拆开来看分别是:

单一职责、里氏代换、接口隔离、开闭原则、依赖反转,这篇文章会详细将这五种原则的

单一职责

从名字上去理解,可以通俗的理解为:一个类或者模块只负责完成一个职责(或者功能)

简单来说,我们不要设计大而全的类,要对类进行更细粒度的划分,如果一个类中包含两个或以上无关系的功能,那么我们就认为这个类违反单一职责,应该更细粒度的拆解这个类

那么如何去区分一个类是否符合单一职责,举个例子,一个类中有支付功能和列表展示功能,这种是一眼就能看出来的,但实际上往往并不能一眼看出来,我再举个例子:

public class UserInfo {
 private long userId;
 private String username;
 private String email;
 private String telephone;
 private long createTime;
 private long lastLoginTime;
 private String avatarUrl;
 private String provinceOfAddress; // 省
 private String cityOfAddress; // 市
 private String regionOfAddress; // 区
 private String detailedAddress; // 详细地址
 // ... 省略其他属性和方法...
}

这是一个用户详情信息的表,那这符合单一职责吗?可以说符合,也可以说不符合,看这个类里,如果省市区等这些信息只是用来展示而并无其他用途,那么这个类可以被认为是符合单一职责的。但如果有更多的实际用途,那么这些地区信息就需要单独抽取成一个地区类。

因此,我的总结是,是否符合单一职责要结合业务实际去分析。所以,我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构。

开闭原则

开闭原则,顾名思义,对扩展开放对修改关闭,我认为开闭原则和单一职责是最有用的,但开闭原则却也比较难理解。之所以说这条原则难理解,那是因为,“怎样的代码改动才被定义为‘扩展’?怎样的代码改动才被定义为‘修改’?怎么才算满足或违反‘开闭原则’?修改代码就一定意味着违反‘开闭原则’吗?”等等这些问题,都比较难理解。

我们先理解字面意思,对扩展开放,对修改关闭。如果我们详细表述一下,那就是,添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法
等)。

我还是举例子去讲下,现在有一个告警功能:

public class Alert {
private AlertRule rule;
private Notification notification;
public Alert(AlertRule rule, Notification notification) {
this.rule = rule;
this.notification = notification;
}
public void check(String api, long requestCount, long errorCount, long durati
long tps = requestCount / durationOfSeconds;
if (tps > rule.getMatchedRule(api).getMaxTps()) {
notification.notify(NotificationEmergencyLevel.URGENCY, "...");
}
if (errorCount > rule.getMatchedRule(api).getMaxErrorCount()) {
notification.notify(NotificationEmergencyLevel.SEVERE, "...");
}
}
}

其中notification是一个告警类,包含不同的级别的告警。业务逻辑主要集中在 check() 函数中。当接口的 TPS 超过某个预先设置的最大值时,以及当接口请求出错数大于某个最大允许值时,就会触发告警,通知接口的相关负责人或者团队.

现在我们增加需求,增加一个超时数量大于设置的最大阈值,也需要触发告警,那该怎么做。如果直接去修改代码,首先check方法需要增加一个参数,其次,再chech方法中需要写对应的逻辑。这样的设计明显不满足开闭原则,而且代码复用性不高,那么改如何设计呢?

手下对于方法的参数,我们可以单独抽取成一个参数类,这样,后续如果增加更多参数我们就不需要在这里进行修改

public class Alert {
private List<AlertHandler> alertHandlers = new ArrayList<>();
public void addAlertHandler(AlertHandler alertHandler) {
this.alertHandlers.add(alertHandler);
}
public void check(ApiStatInfo apiStatInfo) {
for (AlertHandler handler : alertHandlers) {
handler.check(apiStatInfo);
}
}
}
public class ApiStatInfo {// 省略 constructor/getter/setter 方法
private String api;
private long requestCount;
private long errorCount;
private long durationOfSeconds;
}
public abstract class AlertHandler {
protected AlertRule rule;
protected Notification notification;
public AlertHandler(AlertRule rule, Notification notification) {
this.rule = rule;
this.notification = notification;
}
public abstract void check(ApiStatInfo apiStatInfo);
}
public class TpsAlertHandler extends AlertHandler {
public TpsAlertHandler(AlertRule rule, Notification notification) {
super(rule, notification);
}
@Override
public void check(ApiStatInfo apiStatInfo) {
long tps = apiStatInfo.getRequestCount()/ apiStatInfo.getDurationOfSeconds
if (tps > rule.getMatchedRule(apiStatInfo.getApi()).getMaxTps()) {
notification.notify(NotificationEmergencyLevel.URGENCY, "...");
}
}
}
public class ErrorAlertHandler extends AlertHandler {
public ErrorAlertHandler(AlertRule rule, Notification notification){
super(rule, notification);
}
@Override
public void check(ApiStatInfo apiStatInfo) {
if (apiStatInfo.getErrorCount() > rule.getMatchedRule(apiStatInfo.getApi())
notification.notify(NotificationEmergencyLevel.SEVERE, "...");
}
}
}

上面是修改后的代码,首先是参数,采用参数类,然后就是使用handler的写法,使不同的告警级别分别使用各自级别的handler处理逻辑。再后续如果有新的告警级别方便扩展。

那么现在又有了新的问题,我这样写符合开闭原则吗?我虽然将方法的参数单独抽取出参数类,但是如果后面出现新的参数我就需要修改参数类,那么问题就是:修改真的就代表着破坏了开闭原则吗?实际不是的,我认为开闭原则是建立在粗粒度上的,同样一个代码改动,在粗代码粒度下,被认定为“修改”,在细代码粒度下,又可以被认定为“扩展”,另外就是,不要被设计原则束缚了,设计原则包括设计模式的目的几乎都是程序的扩展性,只要是满足更好的扩展行,那么小部分的修改是完全能够接受的。

里氏代换

什么是里氏代换原则:子类对象(object of
subtype/derived class)能够替换程序(program)中父类对象(object of base/parentclass)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。

看起来和面像对象的多态非常相似,实际确实有相同的地方,但是里氏代换比多态的约束更多,比如,父类定义好的抽象方法,子类重写后不能改编其逻辑,比如父类的抽象方法是让按照时间正序排序,子类实现后逻辑变为按照时间倒叙排序,这样是不符合里氏代换的。再比如,父类定义的方法返回的是String,子类重写后返回int等等,这样都是不满足的

接口隔离

定义:不应该强迫依赖它不需要的接口。详细理解就是,比如一个接口中存在多个接口,其中A方法是实现类必须实现的方法,B方法是部分实现类要实现的方法,那现在有一个类来实现这个接口,那它必须将这两个方法都去实现了,它不需要B方法也得实现,这样是不合适的。

那具体应该怎么做?对于A方法是都需要的方法,应该再抽象出一个接口,然后需要实现哪些方法,再去实现具体的实现类即可,例子就不举了,这个比较好理解

依赖反转

依赖反转主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。

总结

想熟悉设计模式,就先理解设计原则,设计原则中,重点理解单一职责和开闭原则,很多设计模式都是基于这两个原则去提升代码的扩展性减小代码耦合性的。后面会更新常用的设计模式的,如果需要请收藏或关注下。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值