Java设计模式笔记------设计原则完结

里氏替换原则

可以说这个原则是在继承这个层次上提出的规范

00中的继承性的思考和说明

1)继承包含这样一层含义:

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

基本介绍

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

即要想透明的使用其子类对象,就是子类尽量不要去重写父类中的方法,如果迫不得已要重写这个方法,那么里氏替换原则提出这样的观点:A类就不要去继承B类,可以通过聚合,组合,依赖来解决问题,或者将我们的A类提升,让AB两个类共同继承一个更为基础的类,这样我们的A类就不用去重写B类中的方法

案例

package com.wang.principle.liskov;

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/12/19 20:26
 * @Description TODO
 * @pojectname 里氏替换原则代码
 */
public class Liskov1 {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("A类的操作");
        A a = new A();
        System.out.println("11-3="+a.func1(11,3));
        System.out.println("1-8="+a.func1(1,8));
        System.out.println("---------------");
        System.out.println("B类的操作");
        B b = new B();
        System.out.println("11-3="+b.func1(11,3));//本意是求11-3的差
        System.out.println("1-8="+b.func1(1,8));
        System.out.println("11+3+9="+b.func2(11,3));
    }
}
class A{
    
    
    /**
     * 返回两数差
     * @param num1
     * @param num2
     * @return
     */
    public  int func1(int num1,int num2){
    
    
        return num1 - num2;
    }
}

/**
 * B类继承A类
 * 增加一个新功能,完成两个数想加,然后和9求和
 */
class B extends A{
    
    
    /**
     * 无意间重写了父类的func1方法
     * @param num1
     * @param num2
     * @return
     */
    public  int func1(int num1,int num2){
    
    
        return num1 + num2;
    }

    public int func2(int a,int b){
    
    
        return func1(a,b)+9;
    }
}
A类的操作
11-3=8
1-8=-7
---------------
B类的操作
11-3=14
1-8=9
11+3+9=23

不经意间重写了这个方法,导致减法功能错误,怎么解决呢?

通用的做法是:原来的父类和子类都继承-一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替.

在这里插入图片描述

代码

package com.wang.principle.liskov.improve;

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/12/19 20:26
 * @Description TODO
 * @pojectname 里氏替换原则代码
 */
public class Liskov {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("A类的操作");
        A a = new A();
        System.out.println("11-3="+a.func1(11,3));
        System.out.println("1-8="+a.func1(1,8));
        System.out.println("---------------");
        System.out.println("B类的操作");
        B b = new B();
        //因为B类不在继承A类,因此调用者不会再认为func1是求减法了
        System.out.println("11+3="+b.func1(11,3));//本意是求11+3的差
        System.out.println("1+8="+b.func1(1,8));
        System.out.println("11+3+9="+b.func2(11,3));
        //使用组合仍然可以使用A的方法
        System.out.println("11-3="+b.func3(11,3));
    }
}
//创建一个更加基础的类Base
class Base{
    
    
    //将更加基础的方法和成员写到Base类中

}

class A extends Base{
    
    
    /**
     * 返回两数差
     * @param num1
     * @param num2
     * @return
     */
    public  int func1(int num1,int num2){
    
    
        return num1 - num2;
    }
}

/**
 * B类继承Base类
 * 增加一个新功能,完成两个数想加,然后和9求和
 */
class B extends Base{
    
    
    //如果我们的B类需要使用A类的方法
    private A a = new A();

    public  int func1(int num1,int num2){
    
    
        return num1 + num2;
    }

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

开闭原则

它是编程中最基础、最重要的设计原则

介绍

  1. 一个软件实体如类,模块和函数应该对扩展开放(对提供方也就是我们开发人员),对修改关闭(对使用方关闭)。用抽象构建框架,用实现扩展细节。也就是说,当我们有一个功能要扩展,增加了类或者功能,但是我们的使用方的代码并没有改变
  2. 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
  3. 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。

代码

package com.wang.principle.ocp;

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/12/19 20:55
 * @Description TODO
 * @pojectname 开闭原则
 */
public class Ocp {
    
    
    public static void main(String[] args) {
    
    
        //
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
    }
}
//这是一个用于绘图的类	使用方
class GraphicEditor{
    
    
    //接收Shape对象,根据type,来绘制不同的图形
    public void drawShape(Shape s){
    
    
        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{
    
    
    public Circle() {
    
    
        super.m_type = 2;
    }
}

这个方法好么?有缺点么?

优点是比较好理解,简单易操作。
缺点是违反了设计模式的ocp原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码.
比如我们这时要新增加一个图形种类,我们需要做如下修改,修改的地方较多.

我们要增加一个三角形的类

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/12/19 20:55
 * @Description TODO
 * @pojectname 开闭原则
 */
public class Ocp {
    
    
    public static void main(String[] args) {
    
    
        //
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());
    }
}
//这是一个用于绘图的类	使用方
class GraphicEditor{
    
    
    //接收Shape对象,根据type,来绘制不同的图形
    public void drawShape(Shape s){
    
    
        if (s.m_type == 1){
    
    
            drawRectangle(s);
        }else if(s.m_type == 2){
    
    
            drawCircle(s);
        }else if (s.m_type == 3){
    
    
            drawTriangle(s);
        }
    }
    //绘制矩形
    public void drawRectangle(Shape r){
    
    
        System.out.println("绘制矩形");
    }
    //绘制圆形
    public void drawCircle(Shape r){
    
    
        System.out.println("绘制圆形");
    }
    //绘制三角形
    public void drawTriangle(Shape r){
    
    
        System.out.println("绘制三角形");
    }
}
//Shape类,基类
class Shape{
    
    
    int m_type;
}
//矩形
class Rectangle extends Shape{
    
    
    Rectangle(){
    
    
        super.m_type = 1;
    }
}
//圆形
class Circle extends Shape{
    
    
    public Circle() {
    
    
        super.m_type = 2;
    }
}
class Triangle extends Shape{
    
    
    public Triangle() {
    
    
        super.m_type = 3;
    }
}

我们可以看出,我们修改的地方有很多:1.增加一个三角形的类 2.绘图类中要新增一个绘制三角形的方法 3.调用绘制方法的时候多进行一次判断 4.主函数调用时传参数修改

我们在绘图类中要新增一个绘制三角形的方法的时候是在使用方进行了修改

OCP原则改进代码

思路

把创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现即可,这样我们有新的图形种类时,只需要让新的图形类继承Shape,并实现draw方法即可,使用方的代码就不需要修改->满足了开闭原则

代码

package com.wang.principle.ocp.improve;

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/12/19 20:55
 * @Description TODO
 * @pojectname 开闭原则
 */
public class Ocp {
    
    
    public static void main(String[] args) {
    
    
        //
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());
        graphicEditor.drawShape(new Other());

    }
}


//这是一个用于绘图的类        使用方
class GraphicEditor{
    
    
    //接收Shape对象,根据type,来绘制不同的图形
    public void drawShape(Shape s){
    
    
       s.draw();
    }
}

//Shape类,基类
abstract class Shape{
    
    
    int m_type;
    public abstract void draw();//抽象方法
}

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

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

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

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

class Triangle extends Shape {
    
    
    public Triangle() {
    
    
        super.m_type = 3;
    }

    @Override
    public void draw() {
    
    
        System.out.println("绘制三角形");
    }
}
///
class Other extends Shape{
    
    
    public Other() {
    
    
        super.m_type = 4;
    }
    @Override
    public void draw() {
    
    
        System.out.println("绘制其他图形");
    }
}

我们发现,我们的使用方变得很简洁,直接调用抽象类的draw方法即可完成我们的绘制不同的图形,而且即使我们要在新增图形或者修改绘制的图形我们也不需要动使用方的代码,就像上面最后注释行后的代码那样

迪米特法则

基本介绍

  1. 一个对象应该对其他对象保持最少的了解,比如A类中用到一个对象,A对这个对象应该不能知道太多
  2. 类与类关系越密切,耦合度越大
  3. 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息
  4. 迪米特法则还有个更简单的定义:只与直接的朋友通信

直接的朋友:

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

简单来说就是现在有两个类 A类和B类 且A类依赖B类

直接朋友就是

A类中,诶,他有个变量是**这么定义的 B b;**这个b直接在A类中生效,那么B类就是A类的直接朋友;

还有呢?A用有一个方法是这么写的method1(B b){方法体} 我的B作为方法参数了,诶,B是A的直接朋友;

再比如呢?A类中还有一个方法是这么写的 public B method2(xxx){方法体},诶B作为返回值类型,B是A的直接朋友。

那什么不是直接朋友呢?A类中还有一个方法是这样写的public xxx(返回值不是B类型) method3(xxxx){ B b = new B();},诶,B是在方法内部,也就是局部定义的,那么B不是A的直接朋友

代码

不遵守迪米特法则

有一个学校,下属有各个学院和总部,现要求打印出学校总部员工ID和学院员工的id

package com.wang.principle.demeter;
import java.util.ArrayList;
import java.util.List;
/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/12/19 21:38
 * @Description TODO
 * @pojectname 迪米特法则
 */
//客户端
public class Demeter {
    
    
    public static void main(String[] args) {
    
    
        //创建学校的管理类
        SchoolManager schoolManager = new SchoolManager();
        //输出学院的员工id和学校总部的员工信息
        schoolManager.printAllEmployee(new CollegeManager());
    }
}
//学校总部员工
class Employee{
    
    
    private String id;
    public String getId() {
    
    
        return id;
    }
    public void setId(String id) {
    
    
        this.id = id;
    }
}
//学院的员工
class CollegeEmployee{
    
    
    private String id;
    public String getId() {
    
    
        return id;
    }
    public void setId(String id) {
    
    
        this.id = id;
    }
}
//管理学院员工的类      管理类
class  CollegeManager{
    
    
    /**
     * 返回学院的所有员工
     * @return
     */
    public List<CollegeEmployee> getAllEmployee(){
    
    
        List<CollegeEmployee> list = new ArrayList<>();
        for (int i = 0; i <10 ; i++) {
    
    //这里我们模拟增加了10个员工到list
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("学院员工id="+i);
            list.add(emp);
        }
        return list;
    }
}
//管理学校员工的类      管理类
class  SchoolManager{
    
    
    /**
     * 返回学校的所有员工
     * @return
     */
    public List<Employee> getAllEmployee(){
    
    
        List<Employee> list = new ArrayList<>();
        for (int i = 0; i <5 ; i++) {
    
    //这里我们模拟增加了5个员工到list学校总部
            Employee emp = new Employee();
            emp.setId("学校员工id="+i);
            list.add(emp);
        }
        return list;
    }
    //打印员工
    void printAllEmployee(CollegeManager sub){
    
    
        List<CollegeEmployee> list1 = sub.getAllEmployee();//获取学院所有员工
        System.out.println("-----分公司员工---------");
        for (CollegeEmployee e : list1) {
    
    
            System.out.println(e.getId());
        }
        List<Employee> list2 = this.getAllEmployee(); //获取到学校总部的所有员工
        System.out.println("----学校总部员工-----");
        for (Employee e : list2) {
    
    
            System.out.println(e.getId());
        }
    }
}

我们以SchoolManager为例,找出他的直接朋友,和间接朋友(这样叫着方便)

直接朋友:

public List getAllEmployee(){ 诶,我们发现这个返回值上面有一个Employee,那么Employee就是他的直接朋友
void printAllEmployee(CollegeManager sub){ 诶,作为方法参数,那CollegeManager 也是他的直接朋友

间接朋友:

List list1 = sub.getAllEmployee(); //获取学院所有员工

CollegeEmployee只是在我们的打印员工方法局部变量方式出现,间接朋友,违背了迪米特法则,作为一个陌生类

改进

package com.wang.principle.demeter.improve;
import java.util.ArrayList;
import java.util.List;
/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/12/19 21:38
 * @Description TODO
 * @pojectname 迪米特法则
 */
//客户端
public class Demeter {
    
    
    public static void main(String[] args) {
    
    
        //创建学校的管理类
        SchoolManager schoolManager = new SchoolManager();
        //输出学院的员工id和学校总部的员工信息
        schoolManager.printAllEmployee(new CollegeManager());
    }
}
//学校总部员工
class Employee{
    
    
    private String id;
    public String getId() {
    
    
        return id;
    }
    public void setId(String id) {
    
    
        this.id = id;
    }
}
//学院的员工
class CollegeEmployee{
    
    
    private String id;

    public String getId() {
    
    
        return id;
    }

    public void setId(String id) {
    
    
        this.id = id;
    }
}
//管理学院员工的类      管理类
class  CollegeManager{
    
    
    /**
     * 返回学院的所有员工
     * @return
     */
    public List<CollegeEmployee> getAllEmployee(){
    
    
        List<CollegeEmployee> list = new ArrayList<>();
        for (int i = 0; i <10 ; i++) {
    
    //这里我们模拟增加了10个员工到list
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("学院员工id="+i);
            list.add(emp);
        }
        return list;
    }
    //输出学院员工信息
    public void printEmployee(){
    
    
        List<CollegeEmployee> list1 = getAllEmployee();//获取学院所有员工
        System.out.println("-----分公司员工---------");
        for (CollegeEmployee e : list1) {
    
    
            System.out.println(e.getId());
        }
    }
}
//管理学校员工的类      管理类
class  SchoolManager{
    
    
    /**
     * 返回学校的所有员工
     * @return
     */
    public List<Employee> getAllEmployee(){
    
    
        List<Employee> list = new ArrayList<>();
        for (int i = 0; i <5 ; i++) {
    
    //这里我们模拟增加了5个员工到list学校总部
            Employee emp = new Employee();
            emp.setId("学校员工id="+i);
            list.add(emp);
        }
        return list;
    }
    //打印员工
    void printAllEmployee(CollegeManager sub){
    
    
        sub.printEmployee();
        List<Employee> list2 = this.getAllEmployee(); //获取到学校总部的所有员工
        System.out.println("----学校总部员工-----");
        for (Employee e : list2) {
    
    
            System.out.println(e.getId());
        }
    }
}

我们改进了那些:

1.把我们SchoolManager中的printAllEmployee方法里面的输出学院员工的方法移走了,移到哪了?移到了我们学院管理类中,诶,我是学校管理类,我不管你怎么做,你也别告诉我你怎么做。你该告诉谁告诉谁,别告诉我

2.原本SchoolManager中的printAllEmployee方法里面的输出学院员工的部分,直接用sub.printEmployee();就行了,我调用一下,诶,我调用的是直接朋友,不是间接朋友了

迷点误点

迪米特法则的核心是降低类之间的耦合
但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系

合成复用原则

基本介绍

原则是尽量使用合成/聚合的方式,而不是使用继承

这个地方涉及到了UML类图,在这就先了解合成复用原则

在这里插入图片描述

依赖方法,方法参数依赖我们的A

在这里插入图片描述

聚合方法,我们通过属性和setA方法,把A聚合到B类中

在这里插入图片描述

合成方法,也叫组合,直接在B类中new出A的实例

在这里插入图片描述

设计原则核心思想

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

猜你喜欢

转载自blog.csdn.net/qq_22155255/article/details/111411586