前言
在软件架构设计畛域,有一个赫赫有名的设计准则——SOLID准则,它是由由Robert C. Martin(也称为 Uncle Bob)提出的,领导咱们写出可保护、能够测试、高扩大、高内聚、低耦合的代码。是不是很牛,然而你们都了解这个设计准则吗,如果了解不深刻的话,更这我通过JAVA示例深入浅出的明确这个重要的准则吧。SOLID实际上是由5条准则组成, 咱们逐个介绍。
S:繁多职责准则(SRP)
O : 开闭准则 (OSP)
L : 里氏替换准则 (LSP)
I:接口隔离准则(ISP)
D:依赖倒置准则(DIP)
繁多职责准则(SRP)
这个准则指出“一个类应该只有一个扭转的理由”,这意味着每个类都应该有繁多的责任或繁多的目标。
举个例子来了解其中的核心思想,假如有一个BankService的类须要执行以下操作:
1.存钱
2.取钱
3.打印通票簿
4.获取贷款信息
5.发送一次性明码
package com.alvin.solid.srp;public class BankService { // 存钱 public long deposit(long amount, String accountNo) { //deposit amount return 0; } // 取钱 public long withDraw(long amount, String accountNo) { //withdraw amount return 0; } // 打印通票簿 public void printPassbook() { //update transaction info in passbook } // 获取贷款信息 public void getLoanInterestInfo(String loanType) { if (loanType.equals("homeLoan")) { //do some job } if (loanType.equals("personalLoan")) { //do some job } if (loanType.equals("car")) { //do some job } } // 发送一次性明码 public void sendOTP(String medium) { if (medium.equals("email")) { //write email related logic //use JavaMailSenderAPI } }}
当初咱们来看看这么写会带来什么问题?
比方对于获取贷款信息 getLoanInterestInfo() 办法,当初银行服务只提供集体贷款、房屋贷款和汽车贷款的信息,假如未来银行的人想要提供一些其余贷款性能,如黄金贷款和学习贷款,那么你须要批改这个类实现对吗?
同样,思考 sendOTP() 办法,假如 BankService 反对将 OTP 媒体作为电子邮件发送,但未来他们可能心愿引入将 OTP 媒体通过手机短信发送,这时候须要再次批改BankService来实现。
发现没有,它不遵循繁多职责准则,因为这个类有许多责任或工作要执行,不仅会让BankService这个类很宏大,可维护性差。
为了实现繁多职责准则的指标,咱们应该实现一个独自的类,它只执行繁多的性能。
- 打印相干的工作PrinterService
public class PrinterService { public void printPassbook() { //update transaction info in passbook }}
- 贷款相干的工作LoanService
public class LoanService { public void getLoanInterestInfo(String loanType) { if (loanType.equals("homeLoan")) { //do some job } if (loanType.equals("personalLoan")) { //do some job } if (loanType.equals("car")) { //do some job } }}
- 告诉相干的工作NotificationService
public class NotificationService{ public void sendOTP(String medium) { if (medium.equals("email")) { //write email related logic //use JavaMailSenderAPI } }}
当初,如果你察看到每个类都有繁多的责任来执行他们的工作。这正是 繁多职责 SRP 的核心思想。
开闭准则(OSP)
该准则指出“软件实体(类、模块、函数等)应该对扩大凋谢,但对批改敞开”,这意味着您应该可能扩大类行为,而无需批改它。
让咱们通过一个例子来了解这个准则。让咱们考虑一下咱们刚刚创立的同一个告诉服务。
public class NotificationService { public void sendOTP(String medium) { if (medium.equals("email")) { //write email related logic //use JavaMailSenderAPI } }}
如前所述,如果您想通过手机号码发送 OTP,那么您须要批改 NotificationService,对吗?
然而依据OSP准则,对扩大凋谢,对批改敞开, 因而不倡议为减少一个告诉形式就批改NotificationService类,而是要扩大,怎么扩大呢?
定义一个告诉服务接口
public interface NotificationService { public void sendOTP();}
E-mail形式告诉类EmailNotification
public class EmailNotification implements NotificationService{ public void sendOTP(){ // write Logic using JavaEmail api }}
手机形式告诉类MobileNotification
public class MobileNotification implements NotificationService{ public void sendOTP(){ // write Logic using Twilio SMS API }}
同样能够增加微信告诉服务的实现WechatNotification
public class WechatNotification implements NotificationService{ public void sendOTP(String medium){ // write Logic using wechat API }}
这样的形式就是遵循开闭准则的,你不必批改外围的业务逻辑,这样可能带来动向不到的结果,而是扩大实现形式,由调用方依据他们的理论状况调用。
里氏替换准则(LSP)
该准则指出“派生类或子类必须可代替其基类或父类”。换句话说,如果类 A 是类 B 的子类型,那么咱们应该可能在不中断程序行为的状况下用 A 替换 B。
这个原理有点辣手和乏味,它是基于继承概念设计的,所以让咱们通过一个例子更好地了解它。
让咱们考虑一下我有一个名为 SocialMedia 的抽象类,它反对所有社交媒体流动供用户娱乐,如下所示:
package com.alvin.solid.lsp;public abstract class SocialMedia { public abstract void chatWithFriend(); public abstract void publishPost(Object post); public abstract void sendPhotosAndVideos(); public abstract void groupVideoCall(String... users);}
社交媒体能够有多个实现或能够有多个子类,如 Facebook、Wechat、Weibo 和 Twitter 等。
当初让咱们假如 Facebook 想要应用这个个性或性能。
package com.alvin.solid.lsp;public class Wechat extends SocialMedia { public void chatWithFriend() { //logic } public void publishPost(Object post) { //logic } public void sendPhotosAndVideos() { //logic } public void groupVideoCall(String... users) { //logic }}
咱们都晓得Facebook都提供了所有上述的性能,所以这里咱们能够认为Facebook是SocialMedia类的齐全替代品,两者都能够无中断地代替。
当初让咱们探讨 Weibo 类
package com.alvin.solid.lsp;public class Weibo extends SocialMedia { public void chatWithFriend() { //logic } public void publishPost(Object post) { //logic } public void sendPhotosAndVideos() { //logic } public void groupVideoCall(String... users) { //不实用 }}
咱们都晓得Weibo微博这个产品是没有群视频性能的,所以对于 groupVideoCall办法来说 Weibo 子类不能代替父类 SocialMedia。所以咱们认为它是不合乎里式替换准则。
那有什么解决方案吗?
那就把性能拆开呗。
public interface SocialMedia { public void chatWithFriend(); public void sendPhotosAndVideos() }
public interface SocialPostAndMediaManager { public void publishPost(Object post); }
public interface VideoCallManager{ public void groupVideoCall(String... users); }
当初,如果您察看到咱们将特定性能隔离到独自的类以遵循LSP。
当初由实现类决定反对性能,依据他们所需的性能,他们能够应用各自的接口,例如 Weibo 不反对视频通话性能,因而 Weibo 实现能够设计成这样:
public class Instagram implements SocialMedia,SocialPostAndMediaManager{ public void chatWithFriend(){ //logic } public void sendPhotosAndVideos(){ //logic } public void publishPost(Object post){ //logic }}
这样子就是合乎里式替换准则LSP。
接口隔离准则(ISP)
这个准则是第一个实用于接口而不是 SOLID 中类的准则,它相似于繁多职责准则。它申明“不要强制任何客户端实现与他们无关的接口”。
例如,假如您有一个名为 UPIPayment 的接口,如下所示
public interface UPIPayments { public void payMoney(); public void getScratchCard(); public void getCashBackAsCreditBalance();}
当初让咱们谈谈 UPIPayments 的一些实现,比方 Google Pay 和 AliPay。
Google Pay 反对这些性能所以他能够间接实现这个 UPIPayments 但 AliPay 不反对 getCashBackAsCreditBalance() 性能所以这里咱们不应该强制客户端 AliPay 通过实现 UPIPayments 来笼罩这个办法。
咱们须要依据客户须要拆散接口,所以为了反对这个ISP,咱们能够如下设计:
创立一个独自的接口来解决现金返还。
public interface CashbackManager{ public void getCashBackAsCreditBalance(); }
当初咱们能够从 UPIPayments 接口中删除getCashBackAsCreditBalance ,AliPay也不须要实现getCashBackAsCreditBalance()这个它没有的办法了。
依赖倒置准则(DIP)
该准则指出咱们须要应用形象(抽象类和接口)而不是具体实现,高级模块不应该间接依赖于低级模块,但两者都应该依赖于形象。
咱们间接上例子来了解。
如果你去当地一家商店买货色,并决定应用刷卡付款。因而,当您将卡交给店员进行付款时,店员不会查看你提供的是哪种卡,借记卡还是信用卡,他们只会进行刷卡,这就是店员和你之间传递“卡”这个形象。
当初让咱们用代码替换这个例子,以便更好地了解它。
借记卡
public class DebitCard { public void doTransaction(int amount){ System.out.println("tx done with DebitCard"); } }
信用卡
public class CreditCard{ public void doTransaction(int amount){ System.out.println("tx done with CreditCard"); } }````当初用这两张卡你去购物中心购买了一些订单并决定应用信用卡领取
public class ShoppingMall {
private DebitCard debitCard;
public ShoppingMall(DebitCard debitCard) {this.debitCard = debitCard; }
public void doPayment(Object order, int amount){ debitCard.doTransaction(amount);
}
public static void main(String[] args) {DebitCard debitCard=new DebitCard(); ShoppingMall shoppingMall=new ShoppingMall(debitCard); shoppingMall.doPayment("some order",5000);
}
}下面的做法是一个谬误的形式,因为 ShoppingMall 类与 DebitCard 严密耦合。当初你的借记卡余额有余,想应用信用卡,那么这是不可能的,因为 ShoppingMall 与借记卡紧密结合。当然你也能够这样做,从构造函数中删除借记卡并注入信用卡。但这不是一个好的形式,它不合乎依赖倒置准则。**那该如何正确设计呢?**
定义依赖的形象接口BankCard
public interface BankCard { public void doTransaction(int amount); }
当初 DebitCard 和 CreditCard 都实现BankCard
public class CreditCard implements BankCard{ public void doTransaction(int amount){ System.out.println("tx done with CreditCard"); }}
public class DebitCard implements BankCard { public void doTransaction(int amount){ System.out.println("tx done with DebitCard"); } }
当初从新设计购物中心这个高级类,他也是去依赖这个形象,而不是间接低级模块的实现类
public class ShoppingMall { private BankCard bankCard; public ShoppingMall(BankCard bankCard) { this.bankCard = bankCard; } public void doPayment(Object order, int amount){ bankCard.doTransaction(amount); } public static void main(String[] args) { BankCard bankCard=new CreditCard(); ShoppingMall shoppingMall1=new ShoppingMall(bankCard); shoppingMall1.doPayment("do some order", 10000); }
当初,如果您察看购物中心与 BankCard 涣散耦合,任何类型的卡解决领取都不会产生任何影响,这就是合乎依赖倒置准则的。## 总结咱们再来回顾总结下SOLID准则,繁多职责准则:每个类应该负责零碎的单个局部或性能。开闭准则:软件组件应该对扩大凋谢,而不是对批改凋谢。里式替换准则:超类的对象应该能够用其子类的对象替换而不毁坏零碎。接口隔离准则不应强制客户端依赖于它不应用的办法。依赖倒置准则:高层模块不应该依赖低层模块,两者都应该依赖形象。这些准则看起来都很简略,但用起来用的好就比拟难了,心愿大家在平时的开发的过程中多多思考、多多实际。