前言在软件架构设计领域,有一个著名的设计原则——SOLID原则,由RobertC.Martin(又名UncleBob)提出,指导我们写出可维护、可测试、高-levelExtended,高内聚,低耦合的代码。是不是很牛逼,但是这个设计原理你们都懂吗?如果你理解的不深,让我通过JAVA实例简单的理解一下这个重要的原理。SOLID其实是由5条原则组成的,我们一一介绍。S:单一职责原则(SRP)O:开闭原则(OSP)L:里氏替换原则(LSP)I:接口隔离原则(ISP)D:依赖倒置原则(DIP)单一职责原则(SRP)该原则指出“Aclassshouldhaveonlyonereasontochange”意味着每个类应该有单一的责任或单一的目的。举个例子来理解核心思想。假设有一个BankService类需要执行以下操作:1.存款2.取款3.打印存折4.获取贷款信息5.发送一次性密码包com.alvin。solid.srp;publicclassBankService{//存款publiclongdeposit(longamount,StringaccountNo){//存款金额return0;}//取款publiclongwithDraw(longamount,StringaccountNo){//取款金额return0;}//打印存折publicvoidprintPassbook(){//更新存折中的交易信息}//获取贷款信息publicvoidgetLoanInterestInfo(StringloanType){if(loanType.equals("homeLoan")){//做一些工作}if(loanType.equals("personalLoan")){//做一些工作}if(loanType.equals("car")){//做一些工作}}//发送一次性密码publicvoidsendOTP(Stringmedium){if(medium.equals("email")){//写邮件相关逻辑//使用JavaMailSenderAPI}}}下面我们来看看这样写会带来什么问题?例如获取贷款信息的getLoanInterestInfo()方法,银行服务现在只提供个人贷款、房贷、车贷的信息。假设以后银行里的人想提供一些其他的贷款功能,比如黄金贷,学习贷,那么你需要修改这个类实现对吗?同样,考虑sendOTP()方法,假设BankService支持通过邮件发送OTP媒体,但未来他们可能想引入通过SMS发送OTP媒体,此时BankService需要再次修改以实现Discovery不,它确实不遵循单一职责原则,因为这个类有很多职责或任务要执行,不仅会使BankService类变得非常庞大,而且可维护性差。为了实现单一职责原则的目标,我们应该实现一个只执行单一功能的类。打印相关作业PrinterServicepublicclassPrinterService{publicvoidprintPassbook(){//更新存折中的交易信息}}贷款相关作业LoanServicepublicclassLoanService{publicvoidgetLoanInterestInfo(StringloanType){if(loanType.equals("homeLoan")){//做一些工作}if(loanType.equals("personalLoan")){//做一些工作}if(loanType.equals("car")){//做一些工作}}}通知相关工作NotificationServicepublicclassNotificationService{publicvoidsendOTP(Stringmedium){if(medium.equals("email")){//writeemailrelatedlogic//useJavaMailSenderAPI}}}现在如果你观察到每个类都有单一的责任来执行他们的任务。这就是SingleResponsibilitySRP的核心思想。Open-ClosedPrinciple(OSP)该原则指出“软件实体(类、模块、函数等)应该对扩展开放但对修改关闭”,这意味着您应该能够在不修改的情况下扩展类的行为它。让我们通过一个例子来理解这个原则。让我们考虑一下我们刚刚创建的同一个通知服务。publicclassNotificationService{publicvoidsendOTP(Stringmedium){if(medium.equals("email")){//写邮件相关逻辑//使用JavaMailSenderAPI}}}前面说了,如果要通过手机号发送OTP,那么你需要修改NotificationService,对吧?但是,根据OSP原则,它对扩展开放,对修改关闭。因此,不建议为了增加通知方法而修改NotificationService类,而是对其进行扩展。如何扩展它?定义一个通知服务接口publicinterfaceNotificationService{publicvoidsendOTP();}邮件方法通知类EmailNotificationpublicclassEmailNotificationimplementsNotificationService{publicvoidsendOTP(){//使用JavaEmailapi写逻辑}}手机方法通知类MobileNotificationpublicclassMobileNotificationimplementsNotificationService{publicvoidsendOTP(){//使用TwilioSMSAPI编写逻辑}}打开和关闭。您不需要修改核心业务逻辑,这可能会带来意想不到的后果。而是扩展实现方法,调用者根据自己的实际情况。这种情况称为Liskov替换原则(LSP),它指出“派生类或子类必须可替换其基类或父类”。换句话说,如果类A是类B的子类型,那么我们应该能够在不破坏程序行为的情况下用A替换B。原理有点玄乎也有点意思,它是基于继承的概念设计的,下面通过一个例子来加深理解。让我们考虑一下我有一个名为SocialMedia的抽象类,它支持用户娱乐的所有社交媒体活动,如下所示:packagecom.alvin.solid.lsp;publicabstractclassSocialMediapublicabstractvoidpublishPost(Objectpost);公共抽象无效发送照片和视频();publicabstractvoidgroupVideoCall(String...users);}社交媒体可以有多个实现或者可以有多个子类,比如Facebook、Wechat、Weibo和Twitter等。现在让我们假设Facebook想要使用这个特性或功能。packagecom.alvin.solid.lsp;publicclassWechatextendsSocialMedia{publicvoidchatWithFriend(){//logic}publicvoidpublishPost(Objectpost){//logic}publicvoidsendPhotosAndVideos(){//logic}publicvoidgroupVideoCall(String...users){//logic}}我们都知道Facebook提供了以上所有的功能,那么这里我们可以认为Facebook是对SocialMedia类的完全替代,两者都可以不间断的替代。下面讨论微博类包com.alvin.solid.lsp;publicclassWeiboextendsSocialMedia{publicvoidchatWithFriend(){//logic}publicvoidpublishPost(Objectpost){//logic}publicvoidsendPhotosAndVideos(){//logic}publicvoidgroupVideoCall(String...users){//不适用}}我们都知道微博微博没有群视频功能,所以对于groupVideoCall方法,微博子类不能替代父类SocialMedia。所以我们认为不符合李式代换原则。有什么解决办法吗?然后反汇编函数。publicinterfaceSocialMedia{publicvoidchatWithFriend();publicvoidsendPhotosAndVideos()}publicinterfaceSocialPostAndMediaManager{publicvoidpublishPost(Objectpost);}publicinterfaceVideoCallManager{publicvoidgroupVideoCall(String...users);现在,如果您观察到我们将特定功能隔离到一个单独的类以遵循LSP。现在由实现类来决定支持的功能。根据自己需要的功能,可以使用各自的接口。比如微博不支持视频通话功能,那么微博的实现可以这样设计:publicvoidpublishPost(Objectpost){//逻辑}}这符合李式替换原则LSP。InterfaceSegregationPrinciple(ISP)该原则是SOLID中第一个应用于接口而不是类的原则,类似于单一职责原则。它指出“不要强迫任何客户端实现与他们无关的接口”。例如,假设您有一个名为UPIPayment的接口,如下所示publicinterfaceUPIPayments{publicvoidpayMoney();publicvoidgetScratchCard();publicvoidgetCashBackAsCreditBalance();}现在我们来谈谈UPIPayments的一些实现,比如GooglePay和AliPay。GooglePay支持这些函数所以他可以直接实现这个UPIPayments但是AliPay不支持getCashBackAsCreditBalance()函数所以这里我们不应该强制客户端AliPay通过实现UPIPayments来覆盖这个方法。我们需要根据客户需求分离接口,所以为了支持这个ISP,我们可以设计如下:创建一个单独的接口来处理现金返还。publicinterfaceCashbackManager{publicvoidgetCashBackAsCreditBalance();现在我们可以从UPIPayments接口中删除getCashBackAsCreditBalance,支付宝不需要实现getCashBackAsCreditBalance(),它没有。依赖倒置原则(DIP)该原则指出我们需要使用抽象(抽象类和接口)而不是具体实现,高层模块不应该直接依赖低层模块,但两者都应该依赖抽象。下面直接上例子来理解。假设您去当地一家商店买东西并决定使用信用卡付款。因此,当您将卡交给店员付款时,店员不会检查您提供的是借记卡还是信用卡,他们只是刷卡,这就是在店员和您之间传递“卡”的抽象。现在让我们用代码替换这个例子,以便更好地理解它。借记卡publicclassDebitCard{publicvoiddoTransaction(intamount){System.out.println("txdonewithDebitCard");}}信用卡publicclassCreditCard{publicvoiddoTransaction(intamount){System.out.println("txdonewithCreditCard");}````现在有了这两张卡,你去商场买一些订单,并决定用信用卡支付publicclassShoppingMall{privateDebitCarddebitCard;publicShoppingMall(DebitCarddebitCard){这个。借记卡=借记卡;}publicvoiddoPayment(Objectorder,intamount){debitCard.doTransaction(amount);}publicstaticvoidmain(String[]args){DebitCarddebitCard=newDebitCard();ShoppingMallshoppingMall=newShoppingMall(借记卡);shoppingMall.doPayment("someorder",5000);}}上述方法是错误的方法,因为ShoppingMall类与DebitCard紧密耦合。现在你的借记卡余额不足,想用信用卡,那是不可能的,因为ShoppingMall和借记卡结合的很紧密。当然你也可以这样做,从构造函数中取出借记卡并注入信用卡。但这不是一个好办法,它不符合依赖倒置原则。**那如何正确设计呢?**定义依赖的抽象接口BankCardpublicinterfaceBankCard{publicvoiddoTransaction(intamount);现在DebitCard和CreditCard都实现了BankCard}}publicclassDebitCardimplementsBankCard{publicvoiddoTransaction(intamount){System.out.println("txdonewithDebitCard");现在重新设计shoppingmall的高级类,他也是依赖这个Abstract,而不是直接低级模块实现类publicclassShoppingMall{privateBankCardbankCard;publicShoppingMall(BankCardbankCard){this.bankCard=bankCard;}publicvoiddoPayment(Objectorder,intamount){bankCard.doTransaction(amount);}publicstaticvoidmain(String[]args){BankCardbankCard=newCreditCard();ShoppingMallshoppingMall1=newShoppingMall(bankCard);shoppingMall1.doPayment("做一些订单",10000);现在,如果你观察商场与银行卡松散耦合,任何类型的卡处理支付不会有任何影响,这就是符合依赖倒置原则的##。总结让我们回顾和总结SOLID原则,即单一职责原则:每个类应该负责系统的单个部分或功能。开闭原则:软件组件应该对扩展而不是修改开放。李式替换原则:超类的对象应该可以用其子类的对象替换而不破坏系统。接口隔离原则不应强制客户端依赖于它不使用的方法。依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖抽象。这些原则看似很简单,但要用好却更难。希望大家在平时的开发过程中多思考,多实践。
