您现在的位置是:首页 >技术交流 >设计模式-七大原则网站首页技术交流

设计模式-七大原则

Owen Guo 2024-06-17 11:28:25
简介设计模式-七大原则

简述

七大原则

单一职责原则
接口隔离原则
依赖倒转原则
里氏替换原则
开闭原则
迪米特法则
合成复原原则

七大原则和设计模式的关系是什么

无规矩不成方圆。七大原则可以理解为设计模式的底层要素,设计模式是必须要遵守这七大原则的。

七大设计模式

单一职责原则(Single responsibility principle)

单一职责原则就是说一个类只能负责一块职责,要是有不用的职责则要拆分成多个类来实现。例如我们在平常开发中,一个表就有一个DAO类,这个DAO类里面写关于这个表的SQL语句,不同的DAO类负责不同的表。

遵守这个原则可以充分降低类的复杂度,提高类的可读性和可维护性。

应用实例

1、我们写这样一个代码:

public class Demo {
    public static void main(String[] args) {
        Transportation transportation = new Transportation();
        transportation.run("汽车");
    }
}

class Transportation {
    public void run(String vehicle) {
        System.out.println(vehicle + "在公路上行驶");
    }
}

运行结果:
在这里插入图片描述

2、我们发现这里只能写在陆地上运行的交通工具:

在这里插入图片描述
在这里插入图片描述

3、我们来改造一下这个代码,并且遵守单一职责原则:

public class Demo {
    public static void main(String[] args) {
        WayTransportation wayTransportation = new WayTransportation();
        wayTransportation.run("汽车");

        SkyTransportation skyTransportation = new SkyTransportation();
        skyTransportation.run("飞机");

        WaterTransportation waterTransportation = new WaterTransportation();
        waterTransportation.run("轮船");
    }
}

class WayTransportation {
    public void run(String vehicle) {
        System.out.println(vehicle + "在公路上行驶");
    }
}

class SkyTransportation {
    public void run(String vehicle) {
        System.out.println(vehicle + "在天空上飞行");
    }
}

class WaterTransportation {
    public void run(String vehicle) {
        System.out.println(vehicle + "在水中上行驶");
    }
}

运行结果:
在这里插入图片描述
这就是单一职责原则的体现,一个类只负责一个职责,公路上行驶的交通工具就通过WayTransportation实现,天空上飞行的就通过SkyTransportation实现,水中行驶的就通过WaterTransportation实现。

注意事项和细节

通常代码都应该遵守单一职责原则,只有当类的逻辑足够简单,才能违背单一职责原则,可以在方法层面保持单一职责原则。

public class Demo {
    public static void main(String[] args) {
        Transportation ransportation = new Transportation();

        ransportation.runWay("汽车");

        ransportation.runSky("飞机");

        ransportation.runWater("轮船");
    }
}

class Transportation {
    public void runWay(String vehicle) {
        System.out.println(vehicle + "在公路上行驶");
    }

    public void runSky(String vehicle) {
        System.out.println(vehicle + "在天空上飞行");
    }

    public void runWater(String vehicle) {
        System.out.println(vehicle + "在水中上行驶");
    }
}

接口隔离原则(Interface Segregation Principle)

官方:客户端不应该依赖它不需要的接口类,类之间的依赖关系应该建立在最小接口上。
大白话:一个实现接口的类中,如果类中有用不着的方法,那就需要将接口拆分。拆分成最小粒度的接口,即类不会存在不需要的方法。

应用实例

我们先看一下这张UML类图
在这里插入图片描述
我们来写一下这样的代码:

public interface Interface1 {
    void operation1();
    void operation2();
    void operation3();
    void operation4();
    void operation5();
}

类B的代码

public class B implements Interface1{
    @Override
    public void operation1() {

    }

    @Override
    public void operation2() {

    }

    @Override
    public void operation3() {

    }

    @Override
    public void operation4() {

    }

    @Override
    public void operation5() {

    }
}

类D的代码

public class D implements Interface1{
    @Override
    public void operation1() {

    }

    @Override
    public void operation2() {

    }

    @Override
    public void operation3() {

    }

    @Override
    public void operation4() {

    }

    @Override
    public void operation5() {

    }
}

类A和C的代码

// 只会使用到接口1,2,3三个方法
public class A {
    public static void main(String[] args) {
        B b = new B();
        b.operation1();
        b.operation2();
        b.operation3();
    }
}
// 只会使用到接口1,4,5三个方法
public class C {
    public static void main(String[] args) {
        D d = new D();
        d.operation1();
        d.operation4();
        d.operation5();
    }
}

从上面我们发现什么缺点:就是B类其实完全不需要实现4、5方法。D类完全不需要实现2、3方法。我们这样的代码没有遵循接口隔离,就会产生这样的缺点。

下面我们写一下遵循接口隔离的代码:
在这里插入图片描述

public interface Interface1 {
    void operation1();
}

public interface Interface2 {
    void operation2();
    void operation3();
}

public interface Interface3 {
    void operation4();
    void operation5();
}
public class B implements Interface1, Interface2{
    @Override
    public void operation1() {

    }

    @Override
    public void operation2() {

    }

    @Override
    public void operation3() {

    }
}

public class D implements Interface1, Interface3{
    @Override
    public void operation1() {

    }

    @Override
    public void operation4() {

    }

    @Override
    public void operation5() {

    }
}

依赖倒转原则(Dependency Inversion Principle)

依赖倒转原则是指:抽象不应该依赖细节,细节应该依赖抽象。在Java中,抽象指的就是接口或抽象类,细节就是实现类。(面向接口编程

依赖倒转原则是基于这样的设计理念
相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。
使用接口或抽象类的目的是为了制定好规范,而不涉及任何具体操作,把展现细节的任务交给他们的实现类去完成。

应用实例

public class Demo {
    public static void main(String[] args) {
        Person person = new Person();
        // 这里就只能接受邮件信息,非常不灵活
        person.receive(new Email());
    }
}

class Person {
    public void receive(Email email ) {
        System.out.println(email.getInfo());
    }
}

class Email {
    public String getInfo() {
        return "电子邮件信息: hello,world";
    }
}

遵循依赖倒转原则后:

public class Demo {

    public static void main(String[] args) {
        Person person = new Person();
        //当为电子邮件时,传入邮件对象
        person.receive(new Email());
        //当为微信时,传入微信对象
        person.receive(new WeiXin());
    }

}

//定义接口
interface IReceiver {
    public String getInfo();
}
//原电子邮件类,实现接口
class Email implements IReceiver {
    public String getInfo() {
        return "电子邮件信息: hello,world";
    }
}

//增加微信
class WeiXin implements IReceiver {
    public String getInfo() {
        return "微信信息: hello,ok";
    }
}

//方法中传入接口
class Person {
    //这里我们是对接口的依赖
    public void receive(IReceiver receiver ) {
        System.out.println(receiver.getInfo());
    }
}

注意事项和细节

1、依赖传递的方式
通过参数传递
通过构造方法传递
通过setter传递

2、底层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
3、变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于 程序扩展和优化。
4、继承时遵循里氏替换原则

里氏替换原则

在父类中已经实现好的方法,在子类中一定不要重写,可以让子类换一个方法名称。如果非要用这个方法名称,那就再向上抽象一层,子类和父类继承来变为同一个级别。

我们想象一下,父类A中已经实现了一个方法test,逻辑是实现加法,在子类B中却重写了这个方法test,逻辑编程了减法。类C继承类A时不重写test方法,还是加法。那这样A类的子类对于test的方法就完全变了意思,用户在使用类B和类C是就容易产生问题。

面向对象编程中对继承的思考

1、继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
2、继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他类所继承,则当这个类要修改时,必须考虑到所有的子类,并且父类修改后,所有设计到子类的功能都有可能产生故障。
3、对于这些问题,在编程中就要遵循里氏替换原则

开闭原则

对扩展开放(对提供方),对修改关闭(对使用方)
在开发过程中,当需求变化时,尽量是通过扩展类的方式来实现,而不是通过修改已有的代码来实现。

应用实例

//这是一个用于绘图的类 [使用方]
class GraphicEditor {
    //接收Shape对象,然后根据type,来绘制不同的图形
    public void drawShape(Shape s) {
                //**问题所在:此类属于使用方,但当我们需要扩展新的图形时,却要修改使用方,就不符合OCP原则
            if (s.m_type == 1) {
              drawRectangle(s);
                }else if (s.m_type == 2) {
              drawCircle(s);
                }
    }
 
    //绘制矩形
    public void drawRectangle(Shape r) {
        System.out.println(" 绘制矩形 ");
    }
    //绘制圆形
    public void drawCircle(Shape r) {
        System.out.println(" 绘制圆形 ");
    }
}
 
//Shape类,基类
class Shape {
    int m_type;
}
 
class Rectangle extends Shape {
    Rectangle() {
        super.m_type = 1;
    }
}
class Circle extends Shape {
    Circle() {
        super.m_type = 2;
    }
}

遵循开闭原则(对扩展开发,对修改关闭),修改代码:

//这是一个用于绘图的类 [使用方]
class GraphicEditor {
    //接收Shape对象,调用draw方法
    public void drawShape(Shape s) {
                //直接调用公共方法即可,就算增加新的图形也无需修改此处,
                //当多个地方调用时,更能体现OCP的重要性,这里只是简单举例
        s.draw();
    }
}
 
//Shape类,基类
abstract class Shape {
    //抽象方法
    public abstract void draw();
}
//[提供方]
class Rectangle extends Shape {
    @Override
    public void draw() {
        // TODO Auto-generated method stub
        System.out.println(" 绘制矩形 ");
    }
}
 
class Circle extends Shape {
    @Override
    public void draw() {
        // TODO Auto-generated method stub
        System.out.println(" 绘制圆形 ");
    }
}

迪米特法则

1、迪米特法则就是一个对象要对其他对象保持最少的了解。就是为了降低耦合度。

2、官方:迪米特法则,又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类,不管多么复杂,都尽量将逻辑封装在类的内部。对外除提供public方法,不对外泄露任何信息。

3、迪米特法则还有个更简单的定义:只与直接的朋友通信

4、直接朋友:对象和对象之间的耦合有很多种(依赖、关联、组合、聚合等),我们称出现在成员便利,方法参数,方法返回值中的类为直接朋友,而出现在局部变量中的类为陌生朋友。迪米特法则就是最好不要出现陌生朋友。

应用实例

public class Demo {

    public static void main(String[] args) {
    	// print方法中存在陌生朋友,不遵循迪米特法则
        print(new School());
    }


    static void print(School school) {
        // 这里的Child就是陌生朋友,尽量少出现
        List<Child> childLis = school.getChildLis();
        for (Child child : childLis) {
            System.out.println(child.name);
        }
    }
}

/**
 * 学生
 */
class Child {
    String name;

    public Child(String name) {
        this.name = name;
    }
}

/**
 * 学校
 */
class School {
    /**
     * 学校学生
     */
    List<Child> childLis;

    public List<Child> getChildLis() {
        return childLis;
    }

    School() {
        List<Child> list = new ArrayList<>();
        list.add(new Child("张三"));
        list.add(new Child("李四"));
        childLis = list;
    }
}

修改方法,遵循迪米特法则:

public class Demo {

    public static void main(String[] args) {
        // 在学校中增加打印学生名字的方法,这样代码就没有陌生朋友了
        School school = new School();
        school.print();
    }

}

/**
 * 学生
 */
class Child {
    String name;

    public Child(String name) {
        this.name = name;
    }
}

/**
 * 学校
 */
class School {
    /**
     * 学校学生
     */
    List<Child> childLis;

    public List<Child> getChildLis() {
        return childLis;
    }

    School() {
        List<Child> list = new ArrayList<>();
        list.add(new Child("张三"));
        list.add(new Child("李四"));
        childLis = list;
    }

    public void print() {
        for (Child child : childLis) {
            System.out.println(child.name);
        }
    }
}

注意事项和细节

1、迪米特法则的核心就是为了降低类之间的耦合
2、迪米特法则只是为了降低不必要的依赖,不是要求完全没有依赖

合成复用原则

在代码中尽量使用合成或聚合的方式,而不是使用继承的方式。

应用实例

尽量通过参数传递,通过成员变量传递,通过setter方法传递
在这里插入图片描述

设计原则核心思想

1、找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
2、面向接口编程,而不是针对接口编程。
3、为了交互对象之间的松耦合设计而努力。

简单理解:
开闭原则是总纲,它指导我们要对扩展开放,对修改关闭;单一职责原则指导我们实现类的职责要单一;里氏替换原则指导我们不要破坏继承体系;依赖倒转指导我们要面向接口编程;接口隔离原则指导我们设计接口的时候要精简单一;迪米特法则指导我们要降低耦合。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。