了解一下,Java中的SOLID原则是什么?

SOLID 原则是软件工程中使用的面向对象的设计概念,可以在多种语言中使用此概念。本文将重点关注Java中的SOLID原则。

微信搜索关注《Java学研大本营》,加入读者群,分享更多精彩

SOLID是Single Responsibility Principle、Open-Closed Principle、Liskov Substitution Principle、Interface Segregation Principle、Dependency Inversion Principle的首字母缩写。

下面,将依次详细介绍这五个原则。

单一职责原则(SRP)

该原则表明计算机程序中的每个模块、类或函数都应该对该程序功能的单个部分负责,并且应该封装该部分。或者换句话说,一个类应该有并且只有一个改变的理由。

例如,如果有一个简单的类来实现如下所示的 Cashback 规则。

package br.com.supercash.services.cashback;

import java.math.BigDecimal;

public class Cashback {

    public BigDecimal calculate(BigDecimal total, BigDecimal percent) {

        return total.multiply(percent);
    }

... another rules here   
}

已经实施了有关它的规则。现在,需要保存与客户关联的Cashback。

因此,将其保存在同一个 Cashback 类中。

package br.com.supercash.services.cashback;

import java.math.BigDecimal;

public class Cashback {

    public BigDecimal calculate(BigDecimal total, BigDecimal percent) {

        return total.multiply(percent);
    }
... another rules here 
public void save(BigDecimal value, String clientAccount) {

        //imagine calling a save method here
        
    }
}

现在有一个问题。这个类破坏了 SRP,因为 Cashback 类知道如何将信息保存在表中。那么,有两个理由改变这个类:第一个是Cashback规则是否改变,另一个是save changes的实现(例如,改变数据库,或者表名等)。

这就让代码难以维护。如何解决这个问题呢?可以将这段代码分成两个不同的类。

package br.com.supercash.services.cashback;

import java.math.BigDecimal;

public class Cashback {

    public BigDecimal calculate(BigDecimal total, BigDecimal percent) {

        return total.multiply(percent);
    }
... another rules here
}
package br.com.supercash.services.cashback;

import java.math.BigDecimal;

public class CashbackPersistence {

    public void save(BigDecimal value, String clientAccount) {

        //imagine calling a save method here


    }

现在,代码中有一层持久性和一层业务。

开闭原则

OCP原则的定义是:

应该能够扩展类行为,而无需修改它。

例如,假设需要实施一个计算返现的规则,但每种类型的产品都有特定的返现百分比。如果客户买衣服,那么他将获得 5% 的返现,但如果他购买电子产品,他将获得 1% 的返现。一种简单的实现方法:

public class Cashback {

    public BigDecimal calculate(BigDecimal value, ProductType productType) {

        if(productType.equals(ProductType.ELETRONICS)) {
            return value.multiply(BigDecimal.valueOf(0.01));
        }
        if(productType.equals(ProductType.GIFT_CARD)) {
            return value.multiply(BigDecimal.valueOf(0.1));
        }
        if(productType.equals(ProductType.CLOTHES)) {
            return value.multiply(BigDecimal.valueOf(0.05));
        }
        else {
            return BigDecimal.ZERO;
        }
    }
}

这是有效的,但未来可能会有问题。如果明天有新的产品类型,或者利益相关者决定子产品会有不同的返现,那么这段代码将很难修改。修改这段代码的原因有很多,而且很危险。

解决该问题的最佳实践之一就是这样做:

创建一个抽象的 Cashback 类:

public abstract class Cashback {

    public BigDecimal calculate(BigDecimal value) {
        return value.multiply(percentageCashback());
    }

    protected abstract BigDecimal percentageCashback();
}

所以,返现的计算是一样的。变化的独特之处在于返现的百分比。所以可以创建可以扩展这种行为的类,只改变返现的百分比:

public class ClothesCashback extends Cashback{

    @Override
    protected BigDecimal percentageCashback() {
        return BigDecimal.valueOf(0.05);
    }
}
public class EletronicsCashback extends Cashback{

    @Override
    protected BigDecimal percentageCashback() {
        return BigDecimal.valueOf(0.01);
    }

}
public class GiftCardCashback extends Cashback{
    @Override
    protected BigDecimal percentageCashback() {
        return BigDecimal.valueOf(0.1);
    }
}

现在,无需修改目前已经工作的代码,就可以扩展代码了。

里氏替换原则与接口隔离原则

想象一下,有一个接口 Animal,它有一些可以实现的动作。

public interface Animal {

    void walk();

    void bark();

    void meow();
}

如果创建一个 Dog 类来实现 Animal,就会发现问题。狗不会喵喵叫。所以,我们正在使用一个没有实现的方法。

public class Dog implements Animal {

    @Override
    public void walk() {

    }

    @Override
    public void bark() {

    }

    @Override
    public void meow() throws ExecutionControl.NotImplementedException {
        throw new ExecutionControl.NotImplementedException("Not implemented");
    }
}

如果有一个将实现该接口的 Cat 类,将遇到同样的问题。猫不叫。

但是狗和猫会走路。

所以,可以通过分离接口来解决这个问题(创建接口Animal、Dog和Cat)。

依赖倒置原则。

根据 Uncle Bob 的说法,这个原则可以定义如下:

高级模块不应该依赖于低级模块,两者都应该依赖于抽象。

这是什么意思呢?

举个例子来说明这个定义的含义。

假设有一个名为 Cashback 的类:

package br.com.supercash.services.cashback;

import java.math.BigDecimal;

public class CashbackPersistence {

    public void save(BigDecimal value, String clientAccount) {

        //imagine calling a save method here
        Log log = new Log("Log.txt");
        log.print("client:" + clientAccount + " cashback saved: " + value);

    }
    
}

所以,这个类有一个问题,实例化一个类 Log 并将一些特性传递给这个类。如果以后想将日志的名称更改为“Log.json”,例如,更改 Cashback 类。

不应该更改 Cashback 类中的日志文件的名称,因为这不是它的责任。

然后,解决方案之一是:

package br.com.supercash.services.cashback;

import java.math.BigDecimal;

public class CashbackPersistence {

    public void save(BigDecimal value, String clientAccount) {

        //imagine calling a save method here
        Log log = new Log("Log.txt");
        log.print("client:" + clientAccount + " cashback saved: " + value);

    }
    
}

现在,创建实例和命名日志文件的责任在 CashbackPersistence 类外部(在构造函数中使用 Log 接口)。所以,颠倒了依赖的控制。

Ps:依赖倒置的解决方案之一就是依赖注入。

推荐书单

《项目驱动零起点学Java》

购买链接:https://u.jd.com/VtU5jYL

《项目驱动零起点学Java》贯穿6个完整项目,经过作者多年教学经验提炼而得,项目从小到大、从短到长,可以让读者在练习项目的过程中,快速掌握一系列知识点。

作者是国内知名Java教学者和传播者,一路披荆斩棘,兢兢业业20余年。积累了丰富的“培”“训”经验,也产出了很多优质的教学理论。

Java语言经过数十年的发展,体系逐渐变得庞大而复杂,本书芟繁就简,提炼出了最为重要的知识点,可以让读者轻松上手。本书配套有专栏课程,课程中提供了扩展内容。

《项目驱动零起点学Java》共分 13 章,围绕 6 个项目和 258 个代码示例,分别介绍了走进Java 的世界、变量与数据类型、运算符、流程控制、方法、数组、面向对象、异常、常用类、集合、I/O流、多线程、网络编程相关内容。《项目驱动零起点学Java》总结了马士兵老师从事Java培训十余年来经受了市场检验的教研成果,通过6 个项目以及每章的示例和习题,可以帮助读者快速掌握Java 编程的语法以及算法实现。扫描每章提供的二维码可观看相应章节内容的视频讲解。

精彩回顾

想要代码干净又整洁?这里有十大原则

通过自学成为开发者的 9 种方法

怎么做一个有产品意识的软件工程师?

微信搜索关注《Java学研大本营》

访问【IT今日热榜】,发现每日技术热点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值