一文教你如何编写出优雅代码

设计原理

前言:如何编写优雅的代码,以及如何提高代码的可读性、可扩展性、复用性、可维护性,这些都是我们在工作中需要注意的事项。接下来介绍SOLID、KISS、YAGNI、DRY、LOD等设计原理。

SOLID

SOLID包含五个设计原理,分别是:

  • 单一职责原则(SRP:Single Responsibility Principle)
  • 开闭原则(OCP:Open Closed Principle)
  • 里式替换原则(LSP:Liskov Substitution Principle)
  • 接口隔离原则(ISP:Interface Segregation Principle)
  • 依赖反转原则(DIP:Dependency Inversion Principle)

单一职责原理(SRP:Single Responsibility Principle)

首先单一职责原理简单来说就是:一个类或者模块只负责完成一个职责(或者功能)。

单一职责原则的定义描述非常简单,也不难理解。一个类只负责完成一个职责或者功能。也就是说,不要设计大而全的类,要设计粒度小、功能单一的类。假如一个类具有两个以上的业务不相关的功能,那么他的职责就不够单一,所以应该需要把功能进行拆解。

举个例子来说明的话就是:假如一个类中包含有用户服务以及订单相关服务,那么此时就违反了单一职责原理,那么我们可以对其进行拆解,把用户服务和订单相关服务分开拆成两个功能不互相干的两个类,因为用户服务和订单服务是相独立的领域模型。

如何判断类的职责是否足够单一?

如何判断职责的单一这个是需要见仁见智的事情,他需要根据项目的实际需求或者业务功能进行判定的。

举个例子:
1、当前项目中的需求是需要完成一个用户信息的一个管理UserInfo
我们假设在UserInfo中设置了id、username、password、role、address、age、telephone、provinceOfAddress、cityOfAddress、detailedAddress等众多属性
2、此时要设计用户对于权限的一个校验的时候,是否需要将role进行一个拆分?
3、如果对于用户的一个登录校验的话是否需要将username、passwrod等进行拆分?
4、如果此时引入一个收货地址,是否需要provinceOfAddress、cityOfAddress、detailedAddress这些属性进行拆分,单独封装成一个类?

对于职责单一的划分,我们并没有一个非常明确的、可以量化的标准。所在在日常开发过程中,我们没有必要过渡设计,而是先设计一个粗粒度的类,满足当前的业务需求。如果随着时间的推移,业务的不断扩展,代码越来越多的时候,我们可以将这个粗粒度的类进行一个拆分重构,重构成粒度较低的多个类(持续重构)。

那么如何判断一个类的职责是否单一呢?我们需要如何处理呢?
  • 类中的代码行数、函数或属性过多,会影响代码的可读性和可维护性,这时就需要考虑对其进行拆分;
  • 一个类过多依赖于其他类,这不符合高内聚、低耦合的设计思想;
  • 类中方法多数操作某个类的某几个属性,那么可以考虑将经常性调用的类的那几个属性进行一个拆分,封装成单独的一个类等
类的职责是否设计得越单一越好?

这个问题的答案当然是:不是设计的越单一越好,职责过于分明的时候,可能引发代码的可维护性变差,原本仅仅只是维护一个类中的代码,现在可能由于职责的拆分后导致实现一个业务功能或者一个逻辑的时候需要维护多个类中的代码。

综上所述

单一职责原理是:一个类只负责完成一个职责或者功能。不要设计大而全的类,要设计粒度小、功能单一的类。单一职责原则是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性。
其次,判断一个类是否符合单依职责原理这个是见仁见智的,是需要根据业务场景、业务需求进行一个判断,也没有一个标准或者明确界限的划分。
再者,并不是类的职责设计的越单一越好,俗话说物极必反,适可而止

开闭原则(OCP:Open Closed Principle)

开闭原则顾名思义就是对某些操作进行开放,对于某些操作进行关闭。那么那些操作具体是对什么进行开发扩展,对于什么进行关闭?

答案是:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。

如何理解对扩展开放?对修改关闭呢?

举个Product获取产品最终价格的一个例子:


public interface PayIntf {
    //获取价格
    public Float getProductPrice();
}


public class Product implements PayIntf{

    private String productName;

    private Float price;

    @Override
    public Float getProductPrice() {
        return this.price;
    }
}

假如此时,加入了一个VIP角色,VIP购买商品是可以打85折的,此时我们可以参照通过“基于修改的方式”来进行功能的实现。

public interface PayIntf {
    //增加参数
    public Float getProductPrice(String memberType);
}

public class Product implements PayIntf{

    private String productName;

    private Float price;

    @Override
    public Float getProductPrice(String memberType) {
	//增加判断逻辑
        if(memberType.equals("VIP")){
            return this.price * 0.85f;
        } else {
            return this.price;
        }
    }
}

此时我们会发现问题,第一个是我们需要增加PayIntf中获取价格方法的参数,以及对Product中getProductPrice方法添加判断的逻辑,这不仅不利于代码的维护以及扩展,不仅如此相应的单元测试也需要进行修改。

我们尝试换另外一种思路解决问题:通过开放扩展的方式进行操作:

//未修改
public interface PayIntf {
    public Float getProductPrice();
}

//未修改
public class Product implements PayIntf {

    private String productName;

    private Float price;

    @Override
    public Float getProductPrice() {
        return this.price;
    }
}

//新增类
public class VipProduct extends Product implements PayIntf{
    @Override
    public Float getProductPrice() {
        return super.getProductPrice() * 0.85f;
    }
}

public static void main(String[] args) {
        VipProduct vipProduct = new VipProduct();
        Float productPrice = vipProduct.getProductPrice();
    }

通过扩展开放的方式来进行操作,可以提高类的可扩展性,以及代码的维护性,使得代码更加灵活和易扩展。

如何做到“对扩展开放、修改关闭”?

关于如何做到对扩展开发以及对修改关闭这个问题上,通过上述例子我们通过引入了一个子类的方式实现了开闭原则。所以要想做到对扩展开发,对修改关闭的话,我们作为开发者来说我们应当调高我们的扩展意识、抽象意识、封装意识。其次的话就是,在我们写代码的时候后,我们要多花点时间往前多思考一下,这段代码未来可能有哪些需求变更、如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,不需要改动代码整体结构、做到最小代码改动的情况下,新的代码能够很灵活地插入到扩展点上,做到“对扩展开放、对修改关闭”。

综上所述

开闭原则就是:“对扩展开放、对修改关闭”。
也就是相当于在添加一个新的功能的时候,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。当然,开闭原则也不一定意味着,对修改完全屏蔽,我们也可以进行适当最小的修改以达到最好的效果。

里式替换原则(LSP:Liskov Substitution Principle)

里式替换原则就是子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。

简单来说就是:子类的功能不变, 方法的功能和规则不能变,具体的实现逻辑可以变。

举个例子,父类ScoreSort的scoreSort方法的目的在于把学生的成绩从小到大排序

public class ScoreSort {

    /**
     * 目的 实现学生成绩从小到大排序
     * @param score
     * @return
     */
    public static int[] scoreSort(int[] score){
	System.out.println("学生未排序成绩:" + score);
        return bubbleSort(score);
    }

    /**
     * 冒泡算法
     */
    public static int[] bubbleSort(int[] array){
        for (int i = 0; i < array.length - 1; i++) {
            for (int j = 0; j < array.length - 1; j++) {
                //从小到大排序,最大元素先归位
                if(array[j] > array[j + 1]){
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j+1] = temp;
                }
            }
        }
        return array;
    }
}

ScoreSortSon 子类

public class ScoreSortSon extends ScoreSort{

    /**
     * 目的 实现学生成绩从小到大排序
     * @param score
     * @return
     */
    @Override
    public int[] scoreSort(int[] score) {
	System.out.println("学生未排序成绩:" + score);
        return bubbleSort3(score);
    }

    /**
     * 冒泡算法改进版
     * @param array
     */
    public static int[] bubbleSort3(int[] array){
        int left = 0;
        int right = array.length - 1;

        while(left < right){
            //最大元素归位
            for (int i = left; i < right; i++){
                if(array[i] > array[i + 1]){
                    int temp = array[i];
                    array[i] = array[i + 1];
                    array[i+1] = temp;
                }
            }
            right --;
            //最小元素归位
            for (int i = right; i > left; i--) {
                if(array[i] < array[i - 1]){
                    int temp = array[i];
                    array[i] = array[i + 1];
                    array[i+1] = temp;
                }
            }
            left ++;
        }
        return array;
    }
}

上述例子很好了解释了里氏替换原则,也就是说父类定义了函数的行为约定,那子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为约定。

注意:

  • 子类不能够违背父类声明要实现的功能。(上述例子中的 成绩从小到大排序 就是所要实现的功能,子类不允许从大到小或者按照其他顺序进行排序)
  • 子类不能够违背父类对输入、输出、异常的约定(上述例子中 输入是数组,输出是数组,类型不能够改变)
  • 子类不能够违背父类注释中所罗列的任何特殊说明(上述例子中 scoreSort的注释是目的 实现学生成绩从小到大排序,子类中的注释也需一样,不能够发生改变)
综上所述

里式替换原则是用来指导,继承关系中子类该如何设计的一个原则。理解里式替换原则,最核心的就是理解“design by contract,按照协议来设计”这几个字。父类定义了函数的“约定”,那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。

接口隔离原则(ISP:Interface Segregation Principle)

接口隔离原则指的是客户端不应该被强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。

举个例子来说明接口隔离原则:
UserServiceForUser 是一个用户服务的一个接口,里面定义了四个方法。

public interface UserServiceForUser {
    //获取用户信息
    public User getUserInfo(Integer id);

    //修改用户信息
    public void updateUserInfo(User user);

    //添加用户信息
    public void addUserInfo(User user);

    //删除用户
    public void delUserInfo(Integer id);
}


public class UserServiceImpl implements UserServiceForUser{
    @Override
    public User getUserInfo(Integer id) {
        return null;
    }

    @Override
    public void updateUserInfo(User user) {

    }

    @Override
    public void addUserInfo(User user) {

    }

    @Override
    public void delUserInfo(Integer id) {
        
    }
}

在看完上述代码的时候,你可能会发现有一个方法delUser。如果针对于用户来使用一个系统的时候,用户往往只会使用前三个方法,而删除用户这个方法更多的是管理员才会使用到的。所以方法调用者(这里指用户)强迫依赖它不需要的方法delUser。

在这里你可以有疑问为什么是强迫依赖它不需要的方法,而不是接口。其实把“接口”理解为单个 API 接口或函数。不过我们可以看下述一个例子来验证这个问题:

我把上述接口分为两个接口:UserServiceForUser 、UserServiceForAdmin

public interface UserServiceForUser {
    //获取用户信息
    public User getUserInfo(Integer id);

    //修改用户信息
    public void updateUserInfo(User user);

    //添加用户信息
    public void addUserInfo(User user);
    
}

public interface UserServiceForAdmin {
    //删除用户
    public void delUserInfo(Integer id);
}

public class UserServiceImpl implements UserServiceForUser ,UserServiceForAdmin{
    @Override
    public User getUserInfo(Integer id) {
        return null;
    }

    @Override
    public void updateUserInfo(User user) {

    }

    @Override
    public void addUserInfo(User user) {

    }

    @Override
    public void delUserInfo(Integer id) {

    }
}

上述两个例子都是等价的,UserServiceImpl在上述的示例当中是否就是强迫依赖它不需要的接口(UserServiceForAdmin)。

所以根据上述案例,符合接口隔离原则的示例如下:

public class UserServiceImpl implements UserServiceForUser{
    @Override
    public User getUserInfo(Integer id) {
        return null;
    }

    @Override
    public void updateUserInfo(User user) {

    }

    @Override
    public void addUserInfo(User user) {

    }
}
综上所述

接口隔离原则就是要让函数或者Api进行隔离,然后使得方法调用者不应该被强迫依赖它不需要的接口(函数或API)。也就是说如果把“接口”理解为单个 API 接口或函数,部分调用者只需要函数中的部分功能,那我们就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。

单一职责原则针对的是模块、类、接口的设计。接口隔离原则相对于单一职责原则,一方面更侧重于接口的设计,另一方面它的思考角度也是不同的。接口隔离原则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。

依赖反转原则(DIP:Dependency Inversion Principle)

学过Spring框架可能都了解过“控制反转”和“依赖注入”这两个概念。
控制反转:控制指的是开发者创建对象实例的控制权,或者程序中的流程都由开发者来进行控制。反转指的是,控制权已经不在于开发者,对于SpringIoc容器而言,对象的控制权已经交由Ioc容器去帮我们进行管理,针对于框架而言,程序中的流程已经是框架来决定的(执行的顺序之类的,框架已经定义好了)。

依赖注入(DI):不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。简单的例子就是@Autowired注解往类中注入一个对象。

依赖反转原则(DIP):高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。

看文字可能很难以理解,举个例子来说明:
司机开车

public class Driver {
    public void run(Benz benz){
       benz.run();
    }
}

public class Benz {
    public void run(){
        System.out.println("奔驰车跑起来了");
    }
}

public class Test {
    public static void main(String[] args) {
        Driver zhangsan = new Driver();
        Benz benz = new Benz();
        zhangsan.run(benz);
    }
}

上述代码展示了司机能够开奔驰车。现在假如又多了一辆车宝马,按照上述代码来说,我们发现张三并不能让宝马车跑起来,因为Driver依赖Benz太紧,当多了BMW后无法扩展。因此针对接口编程,依赖于抽象而不依赖于具体。如下代码所示:

public interface IDriver {
    //司机会开车
    public void drive(Car car);
}

public class Driver implements IDriver{
    //司机开车
    @Override
    public void drive(Car car) {
        car.run();
    }
}

public interface Car {
    //车能跑
    public void run();
}

//奔驰车能跑
public class Benz implements Car{
    @Override
    public void run(){
        System.out.println("奔驰车跑起来了");
    }
}

//宝马车能跑
public class BMW implements Car{
    @Override
    public void run(){
        System.out.println("宝马跑起来了");
    }
}

//张三奔驰和宝马都会开
public class Test {

    public static void main(String[] args) {
        Driver zhangsan = new Driver();
        zhangsan.drive(new BMW());
        zhangsan.drive(new Benz());
    }
}

后续如果有新增的车的话只需要新增一个类即可实现。

根据上述的例子我们来理解什么叫依赖反转原则,首先我们理解以下几个概念:
所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。
在上述例子中zhangsan司机就是属于高层,从代码zhangsan.run(benz);可以看出。然后benz属于低层。在第一个代码示例中可以看出高层模块依赖于低层模块:

public class Driver {
    //Benz benz
    public void run(Benz benz){
       benz.run();
    }
}

高层模块和低层模块应该通过抽象(abstractions)来互相依赖。
在上述代码示例中可以印证该描述,如下:

public interface IDriver {
    //司机会开车
    public void drive(Car car);
}

public interface Car {
    //车能跑
    public void run();
}

抽象不要依赖具体实现细节,具体实现细节依赖抽象
在上述代码示例中可以印证该描述,如下:

public class Driver implements IDriver{
    //司机开车
    @Override
    public void drive(Car car) {
        car.run();
    }
}

public class BMW implements Car{
    @Override
    public void run(){
        System.out.println("宝马跑起来了");
    }
}

public class Benz implements Car{
    @Override
    public void run(){
        System.out.println("奔驰车跑起来了");
    }
}
综上所述

依赖反转原则也叫作依赖倒置原则。这条原则跟控制反转有点类似,主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。

KISS原则

KISS可以看作是Keep It Simple and Stupid.的缩写,顾名思义就是尽量保持简单。

代码的可读性和可维护性是衡量代码质量非常重要的两个标准。而 KISS 原则就是保持代码可读和可维护的重要手段。代码足够简单,也就意味着很容易读懂,bug 比较难隐藏。即便出现 bug,修复起来也比较简单。

KISS原则分析

那么代码何为简洁?代码行数比较少才算是简洁么?

代码何为简洁:对于我的理解来说,简洁的代码应该是可读性好,条理清晰,可维护性强的。举个例子来说,一个开发人员编写了一段代码,假如该名开发人员已经离职了,然后后一个开发人员进行接手,对于接手的开发人员来说,这段代码的实现逻辑能够被接手的开发人员快速的理解,这样子可以方便该开发人员对这段代码的维护,同时也是代码可维护性的一个体现。

代码行数比较少并不一定是简洁。举个例子来说,对于Java中的lambda表达式,代码可以变得更加简洁,效率高,但是它也存在缺点:对于不熟悉Lambda表达式的语法的人,不太容易看得懂,代码的可读性差,不利于维护。代码行数多也是一种简洁的体现,只要代码中的逻辑清晰,可读性好,也是代码简洁的范畴。

####代码逻辑复杂就违背KISS原则吗?
代码逻辑复杂并不是违背KISS原则,这个是需要根据实际的业务需求和场景来决定的。举个例子来说,学习过算法的可能会听说过KMP字符串匹配算法,KMP算法以快速高效著称,当我们需要处理长文本字符串匹配的问题的时候,使用KMP算法可以提升系统的性能,以及提高效率。如果系统需要经常处理长文本字符串匹配问题,而且还针对于系统性能做出要求的时候,使用逻辑复杂的KMP算法是符合KISS原则的。
但是需要注意的一点是,复杂的代码逻辑,会造成系统出现bug的概率增高,以及增加了系统维护的难度。

如何写出满足KISS原则的代码?
  • 不要使用一些奇奇怪怪的技术来实现功能,达到目的。(常规技术,常规解题思路)
  • 避免重复造轮子。(如果已经有现成的工具类或者一些代码类库,可以直接进行调用)
  • 不要过度优化,避免因此降低代码的可读性和可维护性。
重复造轮子的缺点

前提是:新造的轮子对于整个项目系统而言,并没有起到举足轻重的作用。
在这个前提条件下,重复造轮子的缺点在于,存在多个实现相同功能的轮子,那么对于其他开发者来说,他可能会进行选取,但是一旦项目后期对于该功能出现需求变动,需要进行功能调整的时候,如果要对轮子进行修改的话,这是需要多个轮子同时进行修改,一旦有一个已经在使用的轮子忘记修改后,可能引发比较严重的问题。

综上所述

KISS原则就是尽量保持简单,总结来说就是在日常的开发工作中,代码尽量达到可读性好,可维护性好。尽量写大家都能看懂的代码段,方便维护,避免重复造轮子。

YAGNI 原则

YAGNI 原则的英文全称是:You Ain’t Gonna Need It。顾名思义,不需要用到的就别用。

YAGNI 原则分析

何为不需要用到的就别用?

在我们日常开发中,对于一个系统未来所需要用到的依赖,我们可能会在pom文件中,添加上未来所需要在项目中所需要的依赖。对此,这个添加未来所需要在项目中所需要的依赖的动作是不符合YAGNI原则的,我们没有必要提前把这个依赖添加上去。当然并不是不考虑系统的扩展性,而是等到有需要的时候我们才进行添加。又或者,在编写代码的时候,开发者根据系统未来的需要,编写出后期可能需要的一些工具类等,这也是违背了YAGNI原则。

综上所述

YAGNI原则急速不要做过度设计,不要去设计当前用不到的功能;不要去编写当前用不到的代码。

DRY 原则(Don’t Repeat Yourself)

DRY原则是指不要写重复的代码。

DRY原则分析

何为重复的代码?

关于代码重复,无非指的是实现逻辑重复、功能语义重复和代码执行重复,这三种重复。不过这三种代码重复,并不全是违反DRY原则。

逻辑重复
 public String login(String username,String password){
        if(username == null || username.isEmpty()){
            return "用户名为空";
        }
        if(password == null || password.isEmpty()){
            return "密码为空";
        }
        //....
        return "登录成功";
    }

这段代码中判断username和判断password从代码的实现逻辑上看是重复的。
那我们是不是可以将两者合并呢?答案是可以进行和并的,但是这样子就会出现一个问题,它违反了“单一职责原则”和“接口隔离原则”。但是即便如此,他还是存在问题,假如以后对于用户名或者密码使用了其他的校验规则进行校验的时候,我们就需要将合并的一个逻辑拆分出来。

所以对于上述的代码来说,虽然它存在代码的实现逻辑的重复,但是它的语义并没有重复,一个是针对于用户名的校验,一个是针对于密码的校验,因此它并没有违反DRY原则。

语义重复

在上述KISS原则的时候有一个避免重复造轮子的思想。语义重复可以看做是,有两个或者两个以上相同功能的代码段,他们具有不同的逻辑实现,但是代码的功能是相同的,也就是它们语义是重复的。据此,我们就认为它是违反DRY原则的。

代码执行重复
 public String login(String username,String password){
        if(username == null || username.isEmpty()){
            return "用户名为空";
        }
        if(password == null || password.isEmpty()){
            return "密码为空";
        }
        User user = getUserByUsername(username);
        //....
        if(user != null){
            return "登录成功";
        }else {
            return "登录失败";
        }
    }

    public User getUserByUsername(String username){
        User user = null;
        if(username == null || username.isEmpty()){
            //....
        }else { 
            user = getUser(username);
        }
        return user;
    }

从上述代码中我们可以看出 if(username == null || username.isEmpty()){}代码判断用户名是否为空重复执行了。其实不需要进行重复执行用户名判空校验,只需要执行一次就可以了。这种操作也是违反了DRY原则。

综上所述

实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则。实现逻辑不重复,但功能语义重复的代码,也算是违反 DRY 原则。除此之外,代码执行重复也算是违反 DRY 原则。

迪米特法则(The Least Knowledge Principle)

迪米特法则指的是不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口,它能够帮我们实现代码的“高内聚、松耦合”。
迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。英文简写为: LoD.

迪米特法则分析

何为“高内聚、松耦合”?

高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中,代码容易维护。
松耦合是指在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动不会或者很少导致依赖类的代码改动。

如何理解不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口?

举个妈妈喊儿子写作业的例子如下:

 public static class Mother{
        public void tellSonToDoHomeWork(Son son){
            if(son.doChineseWork()){
                System.out.println("做完语文作业,去做数学作业");
                if(son.doMathWork()){
                    System.out.println("做完数学作业,去做英语作业");
                    if(son.doEnglishWork()){
                        System.out.println("做完英语作业");
                    }
                }
            }
        }
    }

    public static class Son{
        public boolean doChineseWork(){
            System.out.println("写语文作业");
            return true;
        }
        public boolean doMathWork(){
            System.out.println("写数学作业");
            return true;
        }
        public boolean doEnglishWork(){
            System.out.println("写英语作业");
            return true;
        }
    }

    public static void main(String[] args) {
        Mother mother = new Mother();
        mother.tellSonToDoHomeWork(new Son());
    }

在这里插入图片描述

按照迪米特法则进行改造:

 public static class Mother{
        public void tellSonToDoHomeWork(Son son){
            son.doAllHomeWork();
        }
    }

    public static class Son{
        public boolean doChineseWork(){
            System.out.println("写语文作业");
            return true;
        }
        public boolean doMathWork(){
            System.out.println("写数学作业");
            return true;
        }
        public boolean doEnglishWork(){
            System.out.println("写英语作业");
            return true;
        }

        public void doAllHomeWork(){
            if(this.doChineseWork()){
                System.out.println("做完语文作业,去做数学作业");
                if(this.doMathWork()){
                    System.out.println("做完数学作业,去做英语作业");
                    if(this.doEnglishWork()){
                        System.out.println("做完英语作业");
                    }
                }
            }
        }

    }

    public static void main(String[] args) {
        Mother mother = new Mother();
        mother.tellSonToDoHomeWork(new Son());
    }

在这里插入图片描述

不该有直接依赖关系的类之间,不要有依赖,在上述代码中可以体现出来,作为妈妈仅仅的职责只是让儿子完成作业,至于儿子如何完成,按照什么顺序去完成是不需要关心的。在第二个示例中Mother的tellSonToDoHomeWork方法仅仅只依赖了一个Son的函数doAllHomeWork(),即可让儿子完成作业。
有依赖关系的类之间,尽量只依赖必要的接口,在上述代码中,第一个示例中Mother的tellSonToDoHomeWork方法中依赖了多个Son的函数,在第二个示例中,Mother的tellSonToDoHomeWork方法仅仅只依赖了一个Son的函数doAllHomeWork()即可实现相同的功能。

综上所述

迪米特法则是指不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的类就会比较少

书籍推荐

以下是一些设计模式书籍的推荐及其推荐理由:

  • 《Head First设计模式》
  • 《大话设计模式》
  • 《图解设计模式》

这些书籍都是非常好的设计模式入门书籍,其中《Head First设计模式》是最通俗易懂的,整本书围绕几个人的对话来展开,口语化、场景化。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TorlesseLiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值