设计模式的六大原则介绍

95 阅读10分钟

一、单一职责原则

基本介绍

对类来说,一个类应当只负责一项职责,如果类A负责两个不同的职责,如职责1,职责2。当职责1需求变更而改变类A时,可能会导致职责2执行报错,所以需要将A的粒度分解为A1,A2。

示例

/**
 * @author songle
 * @create 2021-05-16 10:06
 * @descreption 单一职责原则示例
 */
public class SingleResponse {

    public static void main(String[] args) {
        Vichel vichel = new Vichel();
        vichel.run("摩托车");
        vichel.run("轮船");
        vichel.run("飞机"); // 违反单一职责原则
        // 解决办法一
        RoadVichel roadVichel = new RoadVichel();
        roadVichel.run("摩托车");

        WaterVichel waterVichel = new WaterVichel();
        waterVichel.run("轮船");

        AirVichel airVichel = new AirVichel();
        airVichel.run("飞机");


        // 解决方案二
        Traffic traffic = new Traffic();
        traffic.run("摩托车");
        traffic.runWater("轮船");
        traffic.runAir("飞机");
    }

}

class Vichel{
    public void run(String vichel) {
        System.out.println(vichel + "在公路上跑************");
    }
}


// 解决方法一:从类上实现粒度分解   缺点:改动较大
class RoadVichel{
    public void run(String vichel) {
        System.out.println(vichel + "在公路上跑************");
    }
}

class WaterVichel{
    public void run(String vichel) {
        System.out.println(vichel + "在水上跑************");
    }
}

class AirVichel{
    public void run(String vichel) {
        System.out.println(vichel + "在天上飞************");
    }
}


// 解决方法二  在类上没有遵守单一职责原则,在方法上遵守单一职业原则
class Traffic{
    public void run(String vichel) {
        System.out.println(vichel + "在公路上跑************");
    }


    public void runWater(String vichel) {
        System.out.println(vichel + "在水上跑************");
    }

    public void runAir(String vichel) {
        System.out.println(vichel + "在天上飞************");
    }
}

总结

降低类的复杂度,一个类只负责一项职责。
提高类的可读性,可维护性。
降低变更引起的风险线
通常情况下,我们应该遵守单一职责原则,只有逻辑足够简单,才可以在代码级别违反单一职责原则;只有类中方法较少,才可以在方法级别保持单一职责原则。

二、接口隔离原则

基本介绍

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。

举个例子:当类A通过接口interface1依赖类B,类C通过接口interface1依赖类D,如果接口interface1对于类A和类C都不是最小接口,那么类B和类D都必须要去实现他们不需要的方法。

按照接口隔离原则应该这样处理:将接口Interface1拆分为几个独立的接口,类A与类C分别与他们需要的接口建立依赖关系。

示例

/**
 * @author songle
 * @create 2021-05-16 11:02
 * @descreption 接口隔离原则
 */
public class Segregation {
    public static void main(String[] args) {

        ExampleA a = new ExampleA();
        a.depend1(new ExampleB());
        a.depend2(new ExampleB());
        a.depend3(new ExampleB());


        ExampleC c = new ExampleC();
        c.depend1(new ExampleD());
        c.depend4(new ExampleD());
        c.depend5(new ExampleD());
    }
}


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


class B implements Interface1 {

    @Override
    public void operation1() {
        System.out.println("类B实现了operation1方法");
    }

    @Override
    public void operation2() {
        System.out.println("类B实现了operation2方法");
    }

    @Override
    public void operation3() {
        System.out.println("类B实现了operation3方法");
    }

    @Override
    public void operation4() {
        System.out.println("类B实现了operation4方法");
    }

    @Override
    public void operation5() {
        System.out.println("类B实现了operation5方法");
    }
}


class D implements Interface1 {

    @Override
    public void operation1() {
        System.out.println("类D实现了operation1方法");
    }

    @Override
    public void operation2() {
        System.out.println("类D实现了operation2方法");
    }

    @Override
    public void operation3() {
        System.out.println("类D实现了operation3方法");
    }

    @Override
    public void operation4() {
        System.out.println("类D实现了operation4方法");
    }

    @Override
    public void operation5() {
        System.out.println("类D实现了operation5方法");
    }
}





class A { // 类A通过接口interface1依赖使用B类,但是只会用到123方法
    public void depend1(Interface1 interface1) {
        interface1.operation1();
    }
    public void depend2(Interface1 interface1) {
        interface1.operation2();
    }
    public void depend3(Interface1 interface1) {
        interface1.operation3();
    }
}


class C { // 类C通过接口interface1依赖使用D类,但是只会用到145方法
    public void depend1(Interface1 interface1) {
        interface1.operation1();
    }
    public void depend4(Interface1 interface1) {
        interface1.operation4();
    }
    public void depend5(Interface1 interface1) {
        interface1.operation5();
    }
}


// 解决方法,使用接口隔离原则,将接口interface1进行拆分

interface Interface2 {
    void operation1();
}

interface Interface3 {
    void operation2();
    void operation3();
}


interface Interface4 {
    void operation4();
    void operation5();
}


class ExampleB implements Interface2,Interface3 {

    @Override
    public void operation1() {
        System.out.println("类B实现了operation1方法");
    }

    @Override
    public void operation2() {
        System.out.println("类B实现了operation2方法");
    }

    @Override
    public void operation3() {
        System.out.println("类B实现了operation3方法");
    }
}


class ExampleD implements Interface2,Interface4 {

    @Override
    public void operation1() {
        System.out.println("类B实现了operation1方法");
    }

    @Override
    public void operation4() {
        System.out.println("类B实现了operation4方法");
    }

    @Override
    public void operation5() {
        System.out.println("类B实现了operation5方法");
    }
}

class ExampleA {
    public void depend1(Interface2 interface2) {
        interface2.operation1();
    }
    public void depend2(Interface3 interface3) {
        interface3.operation2();
    }
    public void depend3(Interface3 interface3) {
        interface3.operation3();
    }
}


class ExampleC {
    public void depend1(Interface2 interface2) {
        interface2.operation1();
    }
    public void depend4(Interface4 interface4) {
        interface4.operation4();
    }
    public void depend5(Interface4 interface4) {
        interface4.operation5();
    }
}

三、依赖倒转原则

基本介绍

依赖倒转原则是指:

1)高层模块不应该依赖低层模块,二者都应该依赖其抽象。

2)抽象不应该依赖细节,细节应该依赖抽象。

3)依赖倒转的中心思想是面向接口编程

4)依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多

。以抽象为基础搭成的架构比以细节为基础搭成的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节指的是具体的实现。

5)使用接口或者抽象类的目的是为了制定规范,而不涉及任何具体的操作,把展现细节的操作交给他们的实现类去完成。

示例

/**
 * @author songle
 * @create 2021-05-16 15:02
 * @descreption 依赖倒转原则
 */
public class DependInversion {

    public static void main(String[] args) {
        Person person = new Person();
        person.recieve(new Email());

        Person2 person2 = new Person2();
        person2.recieve(new Email2());
        person2.recieve(new Weixin());
    }
}

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


// 不适用依赖倒转原则的分析:
// 1、简单,比较容易想到
// 2、如果要获取的是微信消息,QQ消息,短息消息等,需要新增类,同时person中也需要新增相应的接收方法
// 3、解决思路:引入一个接口IReciever,让person和IReciever发生依赖
class Person {
    public void recieve(Email email) {
        System.out.println(email.getInfo());
    }
}


// 依赖倒转原则优化
class Person2 {
    public void recieve(IReciever iReciever) {
        System.out.println(iReciever.getInfo());
    }
}


interface IReciever {
    String getInfo();
}



class Email2 implements IReciever{

    @Override
    public String getInfo() {
        return "接收电子邮件信息:hello,world";
    }
}


class Weixin implements IReciever{

    @Override
    public String getInfo() {
        return "接收微信:hello,world";
    }
}

总结

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

四、里氏替换原则

面向对象中的继承性的思考和说明

1)继承包含这样的一层含义:父类中凡是已经实现的方法,实际上是在设定一些规范和契约,虽然它不强制要求所有的子类都必须要遵守这些规范和契约,但是如果子类对这些已经实现的方法进行修改,就会对整个继承的体系产生破坏。

2)继承在给程序设计带来便利的同时,也带来了一些弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,那么当这个类需要修改时,必须考虑到它的所有子类,并且父类修改之后可能会对子类的功能产生影响。

3)这个时候,就需要使用到里氏替换原则去规避这些问题。

基本介绍

1)如果对每个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说,所有引用基类的地方都必须能透明的使用其子类的对象。

2)在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法。

3)里氏替换原则告诉我们,继承实际上是让两个类之间的耦合性增强了,在适当的情况下可以通过聚合,组合,依赖的方式来解决问题。

示例

/**
 * @author songle
 * @create 2021-05-16 15:50
 * @descreption 里氏替换原则
 */
public class Liskvo {
    public static void main(String[] args) {

        Func1 func1 = new Func1();
        int i = func1.func1(10, 8);
        System.out.println("正确答案" + (i + 9));


        Func2 func2 = new Func2();
        int i1 = func2.func2(10, 8);
        System.out.println("错误答案" + i1);


        Func3 func3 = new Func3();
        int i2 = func3.func1(10, 8);
        System.out.println("正确答案" + (i2 + 9));


        Func4 func4 = new Func4();
        int i3 = func4.func2(10, 8);
        System.out.println("正确答案" + i3);

        int i4 = func4.func3(10,8);
        System.out.println("正确答案" + (i4 + 9));
    }
}


class Func1 {
    public int func1(int a,int b) {
        return a - b;
    }
}


class Func2 extends Func1 {
   public int func1(int a,int b) {
       return a + b;
   }


    public int func2(int a,int b) {
        return func1(a,b) + 9;
    }
}


// 解决方法,使用里氏替换原则
// 创建一个更加基础的类
class Base {

}


class Func3 extends Base {
    public int func1(int a,int b) {
        return a - b;
    }
}


class Func4 extends Base {
    private Func1 func1 = new Func1();

    public int func1(int a,int b) {
        return a + b;
    }

    public int func2(int a,int b) {
        return func1(a,b) + 9;
    }

    public int func3(int a,int b) {
        return this.func1.func1(a,b);
    }
}

五、开闭原则

基本介绍

1)开闭原则是变成最基础。最重要的设计原则

2)一个软件实体如类,函数和模块应该对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节。

3)当软件需求发生变化时,尽量通过扩展软件实体的方法来实现变化,而不是通过修改已有的代码来实现变化。

4)编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则。

示例

/**
 * @author songle
 * @create 2021-05-16 19:49
 * @descreption 开闭原则
 */
public class Ocp {

    public static void main(String[] args) {
        // 传统写法
        GraphicEdit graphicEdit = new GraphicEdit();
        graphicEdit.draw(new Rectangle());
        graphicEdit.draw(new Circle());


        // 开闭原则写法
        OcpGraphicEdit ocpGraphicEdit = new OcpGraphicEdit();
        ocpGraphicEdit.draw(new OcpRectangle());
        ocpGraphicEdit.draw(new OcpCircle());
        ocpGraphicEdit.draw(new OcpTriangle());
    }
}


class Shape {
    int shape_type;
}


// 矩形
class Rectangle extends Shape {
    Rectangle () {
        super.shape_type = 1;
    }
}


// 圆形
class Circle extends Shape {
    Circle () {
        super.shape_type = 2;
    }
}


class GraphicEdit {
    public void draw(Shape s) {
        if (s.shape_type == 1) {
            drawRectangle();
        }else if (s.shape_type == 2) {
            drawCircle();
        }
    }


    public void drawRectangle() {
        System.out.println("绘制矩形");
    }


    public void drawCircle() {
        System.out.println("绘制圆形");
    }

}


// 原代码扩展的时候会修改原来的代码,违背开闭原则,使用抽象类优化代码,新增绘制三角形,无需改变原来的代码即可实现
abstract class OcpShape {
    public abstract void drawShape();
}


// 矩形
class OcpRectangle extends OcpShape {

    @Override
    public void drawShape() {
        System.out.println("绘制矩形");
    }
}

// 圆形
class OcpCircle extends OcpShape {

    @Override
    public void drawShape() {
        System.out.println("绘制圆形");
    }
}


// 三角形
class OcpTriangle extends OcpShape {

    @Override
    public void drawShape() {
        System.out.println("绘制三角形");
    }
}


class OcpGraphicEdit {
    public void draw(OcpShape s) {
        s.drawShape();
    }
}

六、迪米特法则

基本介绍

1)一个对象应该对其它对象保持最少的了解。

2)类与类关系越密切,耦合度越大。

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

4)迪米特法则还有个更直接的定义:只与直接的朋友通信。

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

示例

/**
 * @author songle
 * @create 2021-05-16 22:23
 * @descreption 迪米特法则
 */
public class Demeter {

    public static void main(String[] args) {
        SchoolManager schoolManager = new SchoolManager();
        schoolManager.printAllEmployee(new CollegeManager());
    }
}


@Data
class SchoolEmployee {
    private Integer id;

}


@Data
class CollegeEmployee {
    private Integer id;

}


class SchoolManager {
    // 返回学校员工的总数
    public List getAllShcoolEmployee() {
        List schoolEmployeeList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            SchoolEmployee schoolEmployee = new SchoolEmployee();
            schoolEmployee.setId(i);
            schoolEmployeeList.add(schoolEmployee);
        }
        return schoolEmployeeList;
    }



    void printAllEmployee(CollegeManager collegeManager) {
        // 获取到学院员工
        // 这里的CollegeEmployee是以局部变量的方式出现在SchoolManager类中的,属于陌生类,不是直接的朋友,违反了迪米特法则
//        List collegeEmployeeList = collegeManager.getAllCollegeEmployee();
//        System.out.println("****学院员工********");
//        for (CollegeEmployee collegeEmployee:collegeEmployeeList) {
//            System.out.println(collegeEmployee.getId());
//        }
        // 优化后
        collegeManager.printCollegeEmployee();


        List allShcoolEmployee = getAllShcoolEmployee();
        System.out.println("****学校总部员工********");
        for (SchoolEmployee schoolEmployee:allShcoolEmployee) {
            System.out.println(schoolEmployee.getId());
        }
    }
}


class CollegeManager {
    // 返回学院员工的总数
    public List getAllCollegeEmployee() {
        List collegeEmployeeList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            CollegeEmployee collegeEmployee = new CollegeEmployee();
            collegeEmployee.setId(i);
            collegeEmployeeList.add(collegeEmployee);
        }
        return collegeEmployeeList;
    }


    void printCollegeEmployee() {
        // 获取到学院员工
        List collegeEmployeeList = this.getAllCollegeEmployee();
        System.out.println("****学院员工********");
        for (CollegeEmployee collegeEmployee:collegeEmployeeList) {
            System.out.println(collegeEmployee.getId());
        }
    }
}