java设计模式4,不要和陌生人说话-LMLPHP

一、迪米特法则

迪米特法则(Law of Demeter)又叫作最少知道原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。

1、目的

降低类与类之间的耦合度

2、定义

迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好,对于依赖的类不管有多复杂,都尽量将逻辑封装在类的内部,对外除了提供public方法,不泄漏任何信息。

更简单的说法:只与直接朋友通信。

直接朋友:每个对象都会与其它对象有耦合关系,耦合的方式有很多,依赖、关联、组合、聚合等。我们称出现在成员变量,方法参数,方法返回值中的类称为直接朋友,而出现在局部变量中的类不能称为直接朋友,也就是说,陌生的类不要以局部变量的形式出现在类的内部。

3、注意事项和细节

  1. 类的结构设计上,尽量降低类成员的访问权限;
  2. 在类的设计上,优先考虑将一个类设计成不变类;
  3. 在类的引用上,将引起其他类的次数降到最低;
  4. 不暴露类的属性成员,而应该提供相应的访问器(getter、setter);
  5. 谨慎使用序列化(serializable)功能;

过分的使用迪米特原则,会产生大量这样的中介和传递类,类之间需要通信就通过第三方转发的方式,就会造成系统的不同模块之间的通信效率降低、使系统的不同模块之间不容易协调等缺点,同时大大增加了系统的复杂度。所以在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。

二、模拟场景

通过模拟开发部经理、项目经理、员工之间的关系的例子来说明迪米特法则。

  • 部门经理需要负责开发部各项目的运营情况;
  • 项目经理需要负责一个项目的立项、开发、测试、运维、结算;
  • 员工负责一个项目中某个模块的设计、开发、测试;

三、违背原则方案

1、程序员类

员工负责一个项目中某个模块的设计、开发、测试;有姓名、项目、模块、进度、计划完成时间属性。

package com.guor.beanutil.principle.dimit;

import lombok.Data;

@Data
public class Programmer {
    // 姓名
    private String name;
    // 项目
    private String project;
    // 模块
    private String module;
    // 进度
    private String schedule;
    // 计划完成时间
    private String completePlanTime;

    public Programmer(String name, String project, String module, String schedule, String completePlanTime) {
        this.name = name;
        this.project = project;
        this.module = module;
        this.schedule = schedule;
        this.completePlanTime = completePlanTime;
    }
}

2、项目经理类

在项目经理类中初始化了项目人员信息,同时提供了简单的接口。

package com.guor.beanutil.principle.dimit;

import java.util.ArrayList;
import java.util.List;

public class ProjectManager {
    // 姓名
    private String name;
    // 项目
    private String project;
    // 进度
    private String schedule;
    // 计划完成时间
    private String completePlanTime;
    // 项目开发人员
    private static List<Programmer> programmerList;

    static {
        programmerList = new ArrayList<>();
        programmerList.add(new Programmer("哪吒","哪吒闹海","技术选型,框架搭建","50%","2022-08-31"));
        programmerList.add(new Programmer("妲己","哪吒闹海","前端","30%","2022-08-31"));
        programmerList.add(new Programmer("敖丙","哪吒闹海","权限项目","20%","2022-08-31"));
        programmerList.add(new Programmer("申公豹","哪吒闹海","管理模块","20%","2022-08-31"));
        programmerList.add(new Programmer("二郎神","哪吒闹海","数据迁移","10%","2022-08-31"));
    }

    public static List<Programmer> getProgrammerList(){
        return programmerList;
    }
}

3、开发部经理类

可以获取项目开发人员信息、开发进度、预计交付时间等。

类中通过Stream对代码进行了简化,我觉得Stream是java8新特性中最好用,也是最简洁的,有兴趣的可以体验一下【Java8 新特性 5】Java 8 stream的详细用法

package com.guor.beanutil.principle.dimit;

import java.util.*;
import java.util.stream.Collectors;

public class DepartmentManager {

	/**
	* 获取项目信息
	*/
	public static Map<String, Object> getProjectInfo(){
        Map<String, Object> map = new HashMap<>();
        String project = ProjectManager.getProgrammerList().get(0).getProject();
        Double schedule = getSchedule3();
        Date completePlanTime = getCompletePlanTime2();
        map.put("项目",project);
        map.put("项目进度",schedule);
        map.put("预计完成时间",completePlanTime);
        return map;
    }

    /**
     * 项目列表
     */
    public static List<String> getProjects(){
        List<String> list = new ArrayList<>();
        List<Programmer> programmerList = ProjectManager.getProgrammerList();
        for (Programmer programmer : programmerList){
            String project = programmer.getProject();
            list.add(project);
        }
        return list;
    }

    /**
     * 获取项目进度
     * 写法1
     */
    public static double getSchedule1(){
        List<Programmer> programmerList = ProjectManager.getProgrammerList();
        double sum = 0;
        for (Programmer programmer : programmerList){
            double schedule = programmer.getSchedule();
            sum += schedule;
        }
        double avg = sum/programmerList.size();
        return avg;
    }

    /**
     * 获取项目进度
     * 写法2
     */
    public static Double getSchedule2(){
        List<Programmer> programmerList = ProjectManager.getProgrammerList();
        List<Double> scheduleList = new ArrayList<>();
        for (Programmer programmer : programmerList){
            scheduleList.add(programmer.getSchedule());
        }
        return scheduleList.stream().collect(Collectors.averagingDouble(Double::doubleValue));
    }

    /**
     * 获取项目进度
     * 写法3
     */
    public static Double getAvgSchedule3(){
        return ProjectManager.getProgrammerList().stream().mapToDouble( Programmer::getSchedule ).average().orElse(0d);
    }

    /**
     * 项目预计完成时间
     *
     * 获取项目组内开发人员最晚完成时间即可
     * 写法1
     */
    public static Date getCompletePlanTime1(){
        List<Programmer> programmerList = ProjectManager.getProgrammerList();
        List<Date> dateList = new ArrayList<>();
        for (Programmer programmer : programmerList){
            dateList.add(programmer.getCompletePlanTime());
        }
        return dateList.stream().max(Date::compareTo).get();
    }

    /**
     * 项目预计完成时间
     *
     * 获取项目组内开发人员最晚完成时间即可
     * 写法2
     */
    public static Date getCompletePlanTime2(){
        return ProjectManager.getProgrammerList().stream().map(Programmer::getCompletePlanTime).max(Date::compareTo).get();
    }
}

通过部门经理管理所有程序员,项目经理只提供了非常简单的信息,都是由部门经理类来操作,也就是说,一切事宜都是部门经理和程序员沟通,那,要项目经理干啥?部门经理管理10多个项目,上百号人,岂不是得累死…是的,你违反了迪米特法则~~

四、通过迪米特法则改善代码

部门经理直接调用项目经理类中的方法,获取项目名称,项目进展,预计完成时间等信息。
这样一来,整个功能逻辑就非常清晰了。

1、项目经理类

package com.guor.beanutil.principle.dimit;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

public class ProjectManager {
    // 姓名
    private String name;
    // 项目
    private String project;
    // 进度
    private String schedule;
    // 计划完成时间
    private String completePlanTime;
    // 项目开发人员
    private static List<Programmer> programmerList;

    static {
        programmerList = new ArrayList<>();
        programmerList.add(new Programmer("哪吒","哪吒闹海","技术选型,框架搭建",0.5,Programmer.getDate("2022-08-20")));
        programmerList.add(new Programmer("妲己","哪吒闹海","前端",0.3,Programmer.getDate("2022-08-25")));
        programmerList.add(new Programmer("敖丙","哪吒闹海","权限项目",0.2,Programmer.getDate("2022-08-31")));
        programmerList.add(new Programmer("申公豹","哪吒闹海","管理模块",0.2,Programmer.getDate("2022-08-31")));
        programmerList.add(new Programmer("二郎神","哪吒闹海","数据迁移",0.1,Programmer.getDate("2022-09-05")));
    }

    public static List<Programmer> getProgrammerList(){
        return programmerList;
    }

    /**
     * 项目列表
     */
    public static List<String> getProjects(){
        return ProjectManager.getProgrammerList().stream().map(Programmer::getProject).collect(Collectors.toList());
    }

    /**
     * 获取项目进度
     */
    public static Double getAvgSchedule(){
        return ProjectManager.getProgrammerList().stream().mapToDouble( Programmer::getSchedule ).average().orElse(0d);
    }

    /**
     * 项目预计完成时间
     *
     * 获取项目组内开发人员最晚完成时间即可
     */
    public static Date getCompletePlanTime(){
        return ProjectManager.getProgrammerList().stream().map(Programmer::getCompletePlanTime).max(Date::compareTo).get();
    }
}

2、部门经理类

package com.guor.beanutil.principle.dimit;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class DepartmentManager {
    public static Map<String, Object> getProjectInfo(){
        Map<String, Object> map = new HashMap<>();
        String project = ProjectManager.getProjects().get(0);
        Double schedule = ProjectManager.getAvgSchedule();
        Date completePlanTime = ProjectManager.getCompletePlanTime();
        map.put("项目",project);
        map.put("项目进度",schedule);
        map.put("预计完成时间",completePlanTime);
        return map;
    }
}

3、控制台输出

java设计模式4,不要和陌生人说话-LMLPHP

迪米特法则虽然看似简单,但如果想在实际项目开发中,将各模块、功能规划的井井有条,运用的炉火纯青、恰到好处,真的很难。反复阅读,仔细体会。

设计模式系列文章:

java设计模式1,单一职责原则

java设计模式2,开闭原则

java设计模式3,里氏替换原则

java设计模式4,不要和陌生人说话-LMLPHP

07-30 18:27