设计模式六大原则(三、四、五)

134 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情

依赖倒置原则

定义

  • 高层模块不应该依赖低层模块,两者都应该依赖其抽象
  • 抽象不应该依赖细节,细节应该依赖于抽象 (细节和抽象都应该依赖抽象)
  • 面向接口编程而不是面向实现编程

抽象就是抽象类和接口

细节就是可实例化的具体类或实现类,也就是可以new出来的类。

第一点就是松耦合,第二点和第三点就是面向接口编程,不要面向实现编程。

举例

每个程序员都有自己的主攻编程语言。我们使用代码实现一下:

以下例子:Programer就是高层模块、ProgramLanguageJava就是底层模块,此刻高层模块依赖底层模块,且高层模块依赖了细节。那么此例就是不符合依赖倒置原则的。

如果此刻程序员看着Go语言前景较好,改为主攻Go语言,那么此例将不利于扩展。

public class ProgramLanguageJava {
    public void java(){
        System.out.println("java语言");
    }
}
class Programmer {
    public void majorIn(ProgramLanguageJava programLanguageJava){
        programLanguageJava.java();
    }
}

应该这样实现。 细节和抽象都依赖于细节,也就是面向接口编程。

public class Sure {
    @Test
    public void test() {

        ILanguage ljava = new Ljava();
        ILanguage lGo = new LGo();

        LProgrammer lProgrammer = new LProgrammer();

        lProgrammer.work(lGo);
        lProgrammer.work(ljava);
    }
}
interface ILanguage {
    void majorIn();
}
interface IProgrammer {
    void work(ILanguage language);
}
class LProgrammer implements IProgrammer {
    @Override
    public void work(ILanguage language) {
        language.majorIn();
    }
}
class Ljava implements ILanguage {
    @Override
    public void majorIn() {
        System.out.println("主攻java");
    }
}
class LGo implements ILanguage {
    @Override
    public void majorIn() {
        System.out.println("主攻Go");
    }
}

依赖注入的方式

构造器注入

将依赖的抽象组合进来,通过构造器给他赋值。

public class Demo03 {
    @Test
    public void test(){
        IProgramL lJava = new LJava();
        IProgramL lgo = new Lgo();
        ProGrammer proGrammer1 = new ProGrammer(lJava);

        proGrammer1.work();
        ProGrammer proGrammer2 = new ProGrammer(lgo);
        proGrammer2.work();
    }
}
interface IProgramL{
    void majorIn();
}
class LJava implements IProgramL{
    @Override
    public void majorIn() {
        System.out.println("主修Java");
    }
}
class Lgo implements IProgramL{
    @Override
    public void majorIn() {
        System.out.println("主修Go");
    }
}
class ProGrammer{
    private IProgramL language;
    public ProGrammer(IProgramL language) {
        this.language = language;
    }
    void work(){
        language.majorIn();
    }
}
setter方式

通过setProperty方法初始化赋值。

public class Demo04 {
    @Test
    public void test() {
        IProgramLD4 lJava = new LJavaD4();
        IProgramLD4 lgo = new LgoD4();

        ProGrammerD4 proGrammer1 = new ProGrammerD4();
        proGrammer1.setLanguage(lJava);
        proGrammer1.work();

        ProGrammerD4 proGrammer2 = new ProGrammerD4();
        proGrammer2.setLanguage(lgo);
        proGrammer2.work();
    }

}
interface IProgramLD4 {
    void majorIn();
}
class LJavaD4 implements IProgramLD4 {
    @Override
    public void majorIn() {
        System.out.println("主修Java");
    }
}
class LgoD4 implements IProgramLD4 {
    @Override
    public void majorIn() {
        System.out.println("主修Go");
    }
}
class ProGrammerD4 {
    private IProgramLD4 language;
    public void setLanguage(IProgramLD4 language) {
        this.language = language;
    }
    void work() {
        language.majorIn();
    }
}
接口方法中声明依赖

也就是上面我们举的例子,抽象依赖抽象不能依赖细节。即在抽象类或接口中使用抽象作为方法参数。


接口隔离原则

客户端(调用者)只依赖于它所需要的接口;它需要什么接口就提供什么接口,把不需要的接口剔除掉。

也就是一个类中尽量定义较少的方法,从架构的角度细化接口。

与单一原则比较

两者都是为了提高代码内聚、松耦合

单一原则从业务的角度来约束我们代码,大到模块的设计,小到一个方法的设计,并且通常情况下一个类是不符合单一原则的,也就是单一原则的界限不明确,只要符合业务、并且容易维护即可。

而接口隔离原则是以架构的角度,教我们怎么定义接口,如何避免臃肿的接口,如何合理的细化接口。


迪米特法则

迪米特法则(Law of Demeter )又叫做最少知识原则,也就是说,一个对象应当对其他对象尽可能少的了解。只和朋友说话,不和陌生人说话。英文简写为: LOD。

  • 只和存在直接关系的类交流 (和朋友说话)
  • 减少对存在直接关系的类的了解 (朋友的事也少打听)

什么样的类可以直接和当前类交互?

  • 当前实例本身(this)
  • 传入参数
  • 实例变量 (类的属性)
  • 当前对象所返回的对象(返回参数)

除以上几点,其他情况和当前类直接交互,都违反迪米特法则。

例子

直接相关

有一个学生,想知道自己总成绩(不关心每科成绩),就去问老师

那么这个场景下,成绩与学生不是直接相关的。而下例却在学生类中引用了成绩类,显然违反了迪米特法则。

public class Demo01 {
    @Test
    public void test() {
        Teacher teacher = new Teacher();
        Student student = new Student();
        student.getSumSource(teacher);
    }
}

@Data
@AllArgsConstructor
class Source {
    Integer source;
    String subject;
}
class Student {
    void getSumSource(Teacher teacher) {
        Source language = new Source(60, "语文");
        Source math = new Source(60, "数学");
        Source english = new Source(60, "英语");
        List<Source> sources = new ArrayList<>();
        sources.add(language);
        sources.add(math);
        sources.add(english);
        teacher.calculate(sources);
    }
}

class Teacher {

    void calculate(List<Source> list) {
        int sum = list.stream().mapToInt(Source::getSource).sum();
        System.out.println("总成绩:" + sum);
    }
}

那我们做如下修改

public class Demo02 {
    @Test
    public void test(){
        Source language = new Source(60, "语文");
        Source math = new Source(60, "数学");
        Source english = new Source(60, "英语");
        List<Source> sources = new ArrayList<>();
        sources.add(language);
        sources.add(math);
        sources.add(english);
        Teacher teacher = new Teacher(sources);
        Student student = new Student();
        student.getSumSource(teacher);
    }
}
@Data
@AllArgsConstructor
class Source {
    Integer source;
    String subject;
}
class Student {
    void getSumSource(Teacher teacher) {
        teacher.calculate();
    }
}
@Data
@AllArgsConstructor
class Teacher {
    List<Source> list;
    void calculate() {
        int sum = list.stream().mapToInt(Source::getSource).sum();
        System.out.println("总成绩:" + sum);
    }
}
少知道东西

对朋友的事也少担心,即无需知道存在直接关系的类的内部实现。对于一个类来说,如果一个方法没必要暴露给客户端,那么就必须使用private私有化。

老板让员工造个飞机。老板不关心你咋做的,而且如果此刻造飞机需要添加一个流程,上漆,员工类和老板类都要修改。

public class Demo03 {
    @Test
    public void test(){
        Engineer engineer = new Engineer();
        Boss boss = new Boss();
        boss.create(engineer);
    }
}

//造个飞机
class Engineer{
    String eNo;
    void draw(){
        System.out.println("制图");
    }
    void components(){
        System.out.println("制造零件");
    }
    void install(){
        System.out.println("组装");
    }
}
//老板说造个飞机
class Boss{

    void create(Engineer engineer){
        engineer.draw();
        engineer.components();
        engineer.install();
    }

}

修改

public class Demo04 {

    @Test
    public void test() {
        Engineer engineer = new Engineer();
        Boss boss = new Boss();
        boss.create(engineer);
    }


}
//造个飞机
class Engineer {
    private void draw() {
        System.out.println("制图");
    }

    private void components() {
        System.out.println("制造零件");
    }

    private void install() {
        System.out.println("组装");
    }

    public void make() {
        this.draw();
        this.components();
        this.install();
    }
}
//老板说造个飞机
class Boss {

    void create(Engineer engineer) {
        engineer.make();
    }

}