设计模式觉醒系列(01)设计模式的基石 | 六大原则的核心是什么?
读书笔记:活得通透的人,他们都有一个共性:自己不喜欢的事,尽量不做;如果是不得不做,就干脆自己主动去做,做完就放下;即使没有意义、却又不得不做的事,发起后也迅速放下。对于那些意义重大,但是发现自已无法完成的事,也会主动去清除并放下。通透的人不纠结也不会为难自己,专注享受自己喜欢的、擅长的事。即使去追逐那些看起来难度很大的事,也会坚持并享受其中。
关键在于:找到自己的优势、以及劣势,并干脆利落放下那些不擅长的事,坚持充分发挥自己的优势。
一、前言背景
二、设计模式概述
三、单一职责原则(SRP)
3.1 违反单一职责案例
3.2 遵循单一职责正例
四、开闭原则(OCP)
4.1 违反开闭原则案例
4.2 遵循开闭原则正例
4.2.1 代码demo
4.2.2 UML关系类图
五、里氏替换原则(LSP)
六、接口隔离原则(ISP)
七、依赖倒置原则(DIP)
八、迪米特法则
九、六大原则的核心是什么?
【公众号搜索:拉丁解牛说技术】欢迎一起交流讨论。
一、前言背景
随着工作年限的不断增长,在技术经验积累的路上,我们在技术框架、性能优化、业务系统架构研发、踩坑经验等方面上投入了非常多的时间。然而在具体的代码架构设计、代码复用性、可读性、可扩展性、可靠性上容易被忽略。而编码能力的底子,除了丰富研发实践经验,设计模式的融会贯通也同样重要。设计模式的思想就像参天大树的根基,对未来可以触达的高度有着举足轻重的影响。
设计模式的思想理念的应用,不限于技术栈、不限于岗位职责、不限于技术经验水平,只要工作内容与系统项目研发设计相关,都可以学习应用设计模式的优秀设计理念,可以说:每一个IT人员的工作都需要用到设计模式。
那什么时候需要学习设计模式?个人觉得【有兴趣】任何时候都可以,而且永远都不晚。那什么时候才不需要学设计模式?有一本书作者说:当你心中没有设计模式的时候,就不再需要学习实践设计模式了。也就是人剑合一的最高境界,此后不再需要研究设计模式。
今天正式开启设计模式觉醒系列分享,将通过结合正反案例、源码分析进行展开。希望未来我们每个人,都可以顺利成为优秀的架构师、行业的技术大佬。
二、设计模式概述
从JAVA开发角度来看,在做JAVA项目开发时候,最基本的原则是面向对象开发,以及遵循面向对象的三大特性封装、多态、继承,此外还有接口、抽象类、重载等规范。这些都是JAVA开发的【设计模式】规范。而接口、抽象类在设计模式用的非常频繁。
开篇先简单回顾一下,可能我们被我们忽略或者已经陌生的对象、类、抽象类、接口的概念关系。通俗的讲,类是对象的抽象,接口是对象行为的抽象,而抽象类是类的抽象。
抽象类和接口的区别在于设计思路,抽象类是自下而上抽象出来,也就是从多个相似对象抽出公共部分。而接口是自上而下的抽象,先定义抽象的功能方法,最后再去实现。
设计模式,作为核心思想理念,它也有自己的特性原则,以及具体的23个范例模式。
今天我们简单分享她的6大基本原则,尤其梳理各个原则的优势优点,并打下基础。后续系列2开始,将具体对每个经典设计模式进行详细探讨。
三、单一职责原则(SRP)
单一职责原则(Single Responsibility Principle),简称是SRP。单一职责原则,原义是:要求一个类只负责一个职责。具体要求是There should never be more than one reason for a class to change,也就是修改该类的原因只能有一个。单一职责是不仅是因为SOLID原则的S,也是六大原则最好理解的一个,所以放在第一位进行分享,下面举例说明。
3.1 违反单一职责案例
举一个违反该原则的反例,公司实习生小白设计了订单Order这个类,但是承担了多个职责,同时负责订单金额计算、订单报告生成两个功能。以下demo设计问题在于,如果修改报告生成的样式内容,需要修改Order类,可维护性变差。
具体如下:
package lading.java.designpattern.srp.baddeom;
/**
* 订单类,负责了订单计算和订单报告生产两个功能
* 违反单一职责原则
* @author lading
*/
public class Order {
private double price;
private String customerType;
public Order(double price, String customerType) {
this.price = price;
this.customerType = customerType;
}
/**
* 功能1:根据客户类型,计算订单的总价
*
* @return
*/
public double calculateTotal() {
if (customerType.equals("vip")) {
return price * 0.8;
} else if (customerType.equals("normal")) {
return price;
}
return price;
}
/**
* 功能2:生成订单报告打印
*
* @return
*/
public String generateReport() {
return "原价:" + price + ",用户类型" + customerType + ",订单最终总价为:" + calculateTotal();
}
}
3.2 遵循单一职责正例
将Order类拆分为2个类,将订单报告生成功能拆到到OrderReport类去实现。改进后,修改订单生成功能,彼此职责分离,不在需要修改Order类。
package lading.java.designpattern.srp.nicedemo;
/**
* 订单类,只负责订单最终金额计算
* @author lading
*/
public class Order {
private double price;
private String customerType;
public Order(double price, String customerType) {
this.price = price;
this.customerType = customerType;
}
public double getPrice() {
return price;
}
public String getCustomerType() {
return customerType;
}
/**
* 功能1:根据客户类型,计算订单的总价
*
* @return
*/
public double calculateTotal() {
if (customerType.equals("vip")) {
return price * 0.8;
} else if (customerType.equals("normal")) {
return price;
}
return price;
}
}
package lading.java.designpattern.srp.nicedemo;
/**
* 订单报告
*/
public class OrderReport {
private Order order;
public OrderReport(Order order) {
this.order = order;
}
/**
* 功能2:生成订单报告打印
*
* @return
*/
public String generateReport() {
return "原价:" + order.getPrice() + ",用户类型" + order.getCustomerType() + ",订单最终总价为:" + order.calculateTotal();
}
}
单一职责的好处是:由于一个类只负责一个职责,可以有效降低类的复杂性。此外可以提高类的可读性、可维护性,最重要也是降低了变更引起的风险,单元测试,精准测试覆盖将会非常容易实现。
单一职责原则在现实研发设计实践当中,也不限于类,还有接口、方法也遵循适用。
四、开闭原则(OCP)
开闭原则(Open-Closed Principle),简称OCP。原意是open for extention对扩展开放,close for modification对修改封闭。它核心思想是实体、类、方法函数,应当允许扩展,但是不许修改。也就是新增功能,可以通过扩展代码去实现,但是不能修改原来的代码。
接下来我们结合一个案例具体分析应用。比如设计开发订单结算系统核心计算模块,需要根据用户VIP级别、促销活动规则来计算最终订单金额。具体规则有:客户有普通客户无折扣、VIP客户8折、节假日85折活动。
4.1 违反开闭原则案例
设计一个订单对象Order,一个订单计算器OrderCaculator,还有一个柜台计费终端CounterDemo类。以下demo问题在于,每次新增活动规则,需要修改计算器OrderCaculator,代码的可维护性较差。
package lading.java.designpattern.ocp.baddemo;
public class Order {
private String customerType;//客户类型
private double totalPrice;//订单总价
public Order(String customerType, double totalPrice){
this.customerType = customerType;
this.totalPrice = totalPrice;
}
public String getCustomerType() {
return customerType;
}
public double getTotalPrice() {
return totalPrice;
}
}
package lading.java.designpattern.ocp.baddemo;
/**
* 订单计费
*/
public class OrderCalculator {
public double calculate(Order order){
//VIP客户 8 折
if("VIP客户".equals(order.getCustomerType())){
return order.getTotalPrice() * 0.8;
}
//节假日 85 折
if("节假日".equals(order.getCustomerType())){
return order.getTotalPrice() * 0.85;
}
//普通客户 无折扣
return order.getTotalPrice();
}
}
package lading.java.designpattern.ocp.baddemo;
/**
* 计费终端
*/
public class CounterDemo {
public static void main(String[] args) {
//订单总价
double totalPrice = 1000;
Order orderVip = new Order("VIP客户", totalPrice);
Order orderHoliday = new Order("节假日", totalPrice);
Order orderNormalCus = new Order("普通客户", totalPrice);
OrderCalculator orderCalculator = new OrderCalculator();
System.out.println("订单总价:"+ totalPrice);
System.out.println(orderVip.getCustomerType() +" : " + orderCalculator.calculate(orderVip));
System.out.println(orderVip.getCustomerType() +" : " + orderCalculator.calculate(orderHoliday));
System.out.println(orderVip.getCustomerType() +" : " + orderCalculator.calculate(orderNormalCus));
}
}
4.2 遵循开闭原则正例
引入一个价格策略IPriceStrategy接口,Order订单对象不再依赖具体规则,而是依赖价格策略,新增促销规则,只需要单独实现IPriceStrategy接口,无需修改原来的代码。
具体代码和UML关系类图如下。
4.2.1 代码demo
1、价格策略接口
package lading.java.designpattern.ocp.nicedemo;
/**
* 价格策略
* 1. 定义一个接口,里面定义一个方法,方法入参是订单的价格,返回值是订单的最终价格
*/
public interface IPriceStrategy {
double calculateTotal(double price);
}
2、三个价格策略实现类
package lading.java.designpattern.ocp.nicedemo;
/**
* 会员价格策略
* 8折
*/
public class VipPriceStrategy implements IPriceStrategy {
@Override
public double calculateTotal(double price) {
return price * 0.8;
}
}
package lading.java.designpattern.ocp.nicedemo;
/**
* 普通客户收费策略-无折扣
*/
public class NormalCusPriceStrategy implements IPriceStrategy
{
@Override
public double calculateTotal(double price) {
return price;
}
}
package lading.java.designpattern.ocp.nicedemo;
/**
* 节假日价格策略
* 85折
*/
public class HolidayPriceStrategy implements IPriceStrategy{
@Override
public double calculateTotal(double price) {
return price * 0.85;
}
}
// 如要要新增收费规则或者促销策略,继续新增实现IPriceStrategy类即可
3、订单类
package lading.java.designpattern.ocp.nicedemo;
public class Order {
private double price;
private IPriceStrategy priceStrategy;
public Order(double price, IPriceStrategy priceStrategy) {
this.price = price;
this.priceStrategy = priceStrategy;
}
public double getPrice() {
return price;
}
public double getTotal(){
return priceStrategy.calculateTotal(price);
}
}
4、柜台收费终端
package lading.java.designpattern.ocp.nicedemo;
public class CounterDemo {
public static void main(String[] args) {
//订单总价
double price = 1000;
System.out.println("订单总价:"+ price);
Order vipOrder = new Order(price, new VipPriceStrategy());
Order normalOrder = new Order(price, new NormalCusPriceStrategy());
Order holidayOrder = new Order(price, new HolidayPriceStrategy());
System.out.println("VIP客户 total price: " + vipOrder.getTotal());
System.out.println("普通客户 total price: " + normalOrder.getTotal());
System.out.println("节假日 total price: " + holidayOrder.getTotal());
}
}
4.2.2 UML关系类图
在这个正例当中,为了方便管理生成收费策略,可以引入简单工厂模式进行优化。
避免阅读疲劳以及控制篇幅长度,后面4个原则将不再举例。将在后续23个设计模式里,再继续结合demo案例和六大原则的应用进行探讨。
五、里氏替换原则(LSP)
里氏替换原则(Liskov Substitution Principle),简称LSP。核心思想是,要求子类必须能够替换父类的对象,而且替换后程序的逻辑行为没有变。该原则对应的就是面向对象编程里的继承思想,子类继承父类后,拥有父类全部功能属性,而且子类可以扩展新增新的功能。但是子类不应该去改变父类之前原有的功能。
LSP原则的最直观优点,就是提供代码的可复用性,以及降低子类和父类的耦合,提供系统可扩展性。
六、接口隔离原则(ISP)
接口隔离原则(Interface Segregation Principle),简称ISP,它的核心思想是要求减少接口的复杂性,每个接口应该只负责一项功能,避免一个接口有过多的方法。
如果一个接口方法比较多,也就是负责的功能可能也会非常多。实现接口的的类,将可能被迫要实现一些自己根本用不上的功能方法。这里就可以看出【接口隔离原则】和【单一职责原则】有异曲同工之妙。虽然各自侧重点不同,一个是针对接口,一个是针对类的设计要求。但在实践中都是要求功能行为职责单一,有效解决接口污染问题,定义小而专注的接口有效提高系统可扩展性、可读性。
七、依赖倒置原则(DIP)
依赖倒置原则(Dependency Inversion Principle),简称DIP。它的核心思想是通过抽象降低模块间的依赖,所有编程面向接口或者抽象类去进行,
依赖倒置原则,要求模块与模块(或者说类与类)之间的关系通过抽象类或者接口进行交互,达到模块耦合度最小化的目标,它最大的优点就是降低耦合,增强系统代码灵活性、可读性和降低风险错误传播。
看到这里是有点生涩,而且有没有发现,各个原则一直在强调都是接口、抽象、解耦、可扩展等等这些面向对象设计的核心特点?这个感觉就对了,设计模式的六大原则与面向对象设计思想其实是一致的。
慢慢来,后面一个个去详细探讨。
八、迪米特法则
迪米特法则Law of Demeter,也称为最少知识原则(Least Knowledge Principle)。核心思想是要求一个类对依赖的另一个类知道的越少越好,被依赖的类不管多复杂,它的逻辑都封装在内部,尤其是要定义为private属性或者方法,允许对外提供的才修饰为public。该原则最大优点是降低类与类之间的耦合关系。
九、六大原则的核心是什么?
设计模式的六大原则,有其中五个被称为面向对象设计的五大原则,简称SOLID核心原则。S、O、L、I、D分别是单一职责原则SRP,开闭原则OCP,里氏替换原则LSP,接口隔离原则ISP、依赖倒置原则DIP。
如果说这些原则我们无法时刻完整记住,他们是否有一些共同的核心特性、或者优点?每个人的理解不一样,个人倾向于说,六大原则的核心在于:确保系统代码的可维护性、可复用性、可扩展性(灵活性)、可阅读性,模块之间实现低耦合高内聚。