Java软件设计原则

 
(注意:代码均存放在GitHub仓库,希望拉取源码学习时点一个星标,文章中也有源码)

https://github.com/yang20040/design_patterns

(这是b站黑马设计模式 的笔记整理以及加入了我自己的理解整理出来的文章)

目录

六大设计原则:

开闭原则:

里氏代换原则:

依赖倒置原则:

接口隔离原则: 

迪米特法则(最小知识原则):

合成复用原则:


目的:

为了提高系统的可维护性,可复用性,扩展性灵活性,从而提高开发效率,节约软件开发成本和维护成本.

六大设计原则:

开闭原则:

定义:

对扩展开放,对修改关闭.在程序功能进行扩展时,不修改原有代码,为了程序的扩展性好.

如何实现:接口抽象类.抽象的灵活性好,适用性广.合理的抽象能保持软件架构的稳定.当软件需求发生变化时,我们重新派生一个实现类来扩展就好了,软件中易变的细节可以通过派生的实现类进行扩展.

举个例子:(搜狗输入法)

搜狗输入法的皮肤设计:皮肤由背景图片,窗口颜色和声音等元素组成.

不同皮肤的共同特点可以定义为一个抽象类,每个具体的皮肤是其子类,新皮肤只需要派生一个实现类即可,不同皮肤的选择只需要切换到不同的实现类即可.这样的设计就是满足开闭原则的

1.创建一个抽象AbstractSkin类

(这个抽象类的目的是抽取这些输入法的共性,如背景图片,窗口颜色等)

2.创建一个抽象方法dispaly();抽象方法不需要方法体

3.创建一个DeafultSkin类继承AbstractSkin类并重写方法

打印"默认皮肤"

4.创建一个MySkin类继承重写方法

打印"我的皮肤"

(这体现了开闭原则,我们扩展功能只需要派生一个类即可)

5.定义一个类SougouInput用来聚合

  1. 先用构造器注入一个AbstractSkin对象
  2. 创建一个display方法

(用来链接皮肤和客户端)

6.使用搜狗输入法:

创建一个Client(客户端)表示搜狗输入法,调用SougouInput使用不同的皮肤

具体表现:


SougouInput用来链接启动类和不同皮肤,启动类只需要创建SougouInput对象和对应的皮肤对象,将皮肤对象传入SougouInput,这样Client想使用新皮肤时,只需要修改一行代码
Myskin->其它Skin
同时SougouInput也可以补充一些其它的逻辑
例如:

package com.henu.principles.designPrinciples.OpenClosed;

public class SougouInput {
    private AbstractSkin skin;

    public void setSkin(AbstractSkin skin) {
        this.skin = skin;
        // 可以在这里添加皮肤初始化逻辑
        System.out.println("皮肤初始化完成");
    }

    public void display() {
        if (skin != null) {
            // 可以在这里添加皮肤切换动画逻辑
            System.out.println("正在切换皮肤...");
            skin.display();
        }
    }
}
源代码如下:

源代码地址:https://github.com/yang20040/design_patterns

​抽象类:

package com.henu.principles.designPrinciples.OpenClosed;

public abstract class AbstractSkin {
    public abstract void display();
}

客户端:相当于搜狗的启动类 

package com.henu.principles.designPrinciples.OpenClosed;

public class Client {
    public static void main(String[] args) {
        MySkin mySkin = new MySkin();
        SougouInput sougouInput = new SougouInput();
        sougouInput.setSkin(mySkin);
        sougouInput.display();
    }
}

默认皮肤 

package com.henu.principles.designPrinciples.OpenClosed;

public class DeaufltSkin extends AbstractSkin{
    @Override
    public void display() {
        System.out.println("默认皮肤");
    }
}

自创皮肤: 

package com.henu.principles.designPrinciples.OpenClosed;

public class MySkin extends AbstractSkin{
    @Override
    public void display() {
        System.out.println("MySkin");
    }
}

中间类

package com.henu.principles.designPrinciples.OpenClosed;

public class SougouInput {
    private AbstractSkin skin;
    public void setSkin(AbstractSkin skin)
    {
        this.skin = skin;
    }
    public void display()
    {
        skin.display();
    }
}

里氏代换原则:

定义:

任何基类出现的地方,子类一定可以出现.

简单理解:子类继承父类后可以扩展新的功能(方法),但尽量不要重写父类的方法

原因:1.子类重写了父类的方法,父类的方法写出来有什么意义?

2.父类要求子类重写方法时,会将方法定义为抽象方法

3.重写父类方法完成新功能虽然简便,但在多态使用频繁时容易出错

例如:(正方形不是长方形)

在数学领域,正方形是长方形的特例,属于长方形

Rectangle:长方形,分别设置了长和宽

Square:正方形,继承长方形并重写设长宽方法(因为正方形长宽相等)

RectangleDemo:

resize(拓宽长方形的宽使它大于长方形的长)

printLengthAndWidth(打印长宽)

源代码:

根据类图写代码

1.创建一个里氏代换例子包

2.创建长方形,包括成员变量和get,set方法,通过alt+insert快速生成

3.创建正方形,继承长方形重写方法

重写设置长宽,调用父类方法设置长宽,长宽必须相等

4.常见RectangleDemo来测试里氏替换

  • psvm快捷键创建main类
  • 创建resize拓宽方法
  • 创建printLengthAndWidth方法
  • main方法中分别调用长方形和正方形测试resize方法

这是长方形类:

package com.henu.principles.designPrinciples.RichterSubstitution;

/**
 * Created by ys on 2017/7/12.
 */
public class Rectangle {
    private double width;
    private double length;

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }
}

正方形类:

特点:长宽相等,set方法重写设置长或宽时均设置

package com.henu.principles.designPrinciples.RichterSubstitution;
/**
 * @author ys
 * @date   2025/7/27
 */
public class Square extends Rectangle{
    public void setLength(double length)
    {
        super.setLength(length);
        super.setWidth(length);
    }
    public void setWidth(double width)
    {
        super.setLength(width);
        super.setWidth(width);
    }
}

测试类:
长方形使用时正常,正方形错误 使得程序死循环

原因在这段代码,设置宽时会把长设置与宽相等,导致一直是宽度增加

package com.henu.principles.designPrinciples.RichterSubstitution;

/**
 * @author ys
 * @date   2025/7/27
 */
public class RectangleDemo {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setLength(10);
        rectangle.setWidth(5);
        resize( rectangle);
        printLengthAndWidth(rectangle);
        System.out.println("-----------------");
        Square square = new Square();
        square.setLength(10);
        resize(square);
        printLengthAndWidth(square);
    }
    public static void  resize(Rectangle rectangle){
        //长大于宽时,宽+1(循环请求)
        while (rectangle.getLength() >= rectangle.getWidth()){
            rectangle.setWidth(rectangle.getWidth() + 1);
        }
    }
    public static void printLengthAndWidth(Rectangle rectangle){
        System.out.println("长:" + rectangle.getLength());
        System.out.println("宽:" + rectangle.getWidth());
    }
}

结论:

根据历史代换原则,子类必须能替代父类所有的基类,因此,正方形不属于长方形

改进方案:

我们要让长方形和正方型符合里氏替换原则,长方形和正方形没有继承关系,但获取长宽的方法是共性,我们可以抽象为一个接口(一种规范),这样我们既复用了代码,又可在使用长方形拓宽方法时不会造成潜在的死循环错误.

重新设计类图

让长方形和正方形共同抽取一个四边形接口 

1.定义一个接口获得长宽

2.定义长方形

3.定义正方形

4.测试(测试结果如下,此时编译无法通过)

新源代码:

接口

package com.henu.principles.designPrinciples.RichterSubstitution.After;


/**
 * @author ys
 * @date   2025/7/27
 */
public class RectangleDemo {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setLength(10);
        rectangle.setWidth(5);
        resize( rectangle);
        printLengthAndWidth(rectangle);
        System.out.println("-----------------");
        Square square = new Square();
        square.setSide(10);
        //测试
//        resize(square);
//        printLengthAndWidth(square);
    }
    public static void  resize(Rectangle rectangle){
        //长大于宽时,宽+1(循环请求)
        while (rectangle.getLength() >= rectangle.getWidth()){
            rectangle.setWidth(rectangle.getWidth() + 1);
        }
    }
    public static void printLengthAndWidth(Rectangle rectangle){
        System.out.println("长:" + rectangle.getLength());
        System.out.println("宽:" + rectangle.getWidth());
    }
}

长方形 

package com.henu.principles.designPrinciples.RichterSubstitution.After;

public class Rectangle implements Quadrilateral{
    private double length;
    private double width;

    public void setLength(double length) {
        this.length = length;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    @Override
    public double getLength() {
        return length;
    }

    @Override
    public double getWidth() {
        return width;
    }
}

正方形 

package com.henu.principles.designPrinciples.RichterSubstitution.After;

public class Square implements Quadrilateral{
    private double side;

    public double getSide() {
        return side;
    }

    public void setSide(double side) {
        this.side = side;
    }

    @Override
    public double getLength() {
        return side;
    }

    @Override
    public double getWidth() {
        return side;
    }
}

测试类 

package com.henu.principles.designPrinciples.RichterSubstitution.After;


/**
 * @author ys
 * @date   2025/7/27
 */
public class RectangleDemo {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setLength(10);
        rectangle.setWidth(5);
        resize( rectangle);
        printLengthAndWidth(rectangle);
        System.out.println("-----------------");
        Square square = new Square();
        square.setSide(10);
        //测试
//        resize(square);
//        printLengthAndWidth(square);
    }
    public static void  resize(Rectangle rectangle){
        //长大于宽时,宽+1(循环请求)
        while (rectangle.getLength() >= rectangle.getWidth()){
            rectangle.setWidth(rectangle.getWidth() + 1);
        }
    }
    public static void printLengthAndWidth(Rectangle rectangle){
        System.out.println("长:" + rectangle.getLength());
        System.out.println("宽:" + rectangle.getWidth());
    }
}

依赖倒置原则:

高层模块不依赖于低层模块,两者都应该依赖于其抽象,抽象不应该依赖于细节,细节应该依赖于抽象.要求是面向抽象编程,而不是面向实现编程,这样就降低了客户和模块之间的耦合.

看这张图:

A类的成员变量定义了一个B类的对象(A依赖了B),那么A类就是高层模块,B类就是低层模块,抽象就是将B类抽象为一个抽象父类或者父接口,B类继承或实现抽象父类或者父接口,而A类依赖于抽象父类或父接口.

举个例子:
1.springboot三层架构


controller

service

serviceImpl

controller层不直接依赖于serviceImpl,而是依赖于service,降低了模块之间的耦合度

2.组装电脑

组装一台电脑需要CPU,显卡,内存条,硬盘等,不同的配件又分为了不同的型号

硬盘有保存和获取数据的功能

CPU有运行功能

内存有保存数据的功能

根据类图创建项目源码

硬盘:

package com.henu.principles.designPrinciples.dependencyInversion.Before;

public class XiJieHardDisk {
    public void save(String data)
    {
        System.out.println("使用希捷硬盘保存数据:" + data);
    }
    public String get()
    {
        System.out.println("使用希捷硬盘获取数据");
        return "数据";
    }
}

CPU:
 

package com.henu.principles.designPrinciples.dependencyInversion.Before;

public class IntelCpu {
    public void run()
    {
        System.out.println("IntelCpu运行了");
    }
}

内存条:

package com.henu.principles.designPrinciples.dependencyInversion.Before;

public class KingstonMemory {
    public void save()
    {
        System.out.println("使用金士顿内存条");
    }
}

电脑:

package com.henu.principles.designPrinciples.dependencyInversion.Before;

public class Computer {
    private IntelCpu cpu;
    private KingstonMemory memory;
    private XiJieHardDisk hardDisk;

    public IntelCpu getCpu() {
        return cpu;
    }

    public void setCpu(IntelCpu cpu) {
        this.cpu = cpu;
    }

    public KingstonMemory getMemory() {
        return memory;
    }

    public void setMemory(KingstonMemory memory) {
        this.memory = memory;
    }

    public XiJieHardDisk getHardDisk() {
        return hardDisk;
    }

    public void setHardDisk(XiJieHardDisk hardDisk) {
        this.hardDisk = hardDisk;
    }

    public void run() {
        System.out.println("电脑开始运行");
        String data = hardDisk.get();
        System.out.println("从硬盘中获取数据" + data);
        cpu.run();
        memory.save();
    }
}

客户端:

package com.henu.principles.designPrinciples.dependencyInversion.Before;

public class ComputerDemo {
    public static void main(String[] args)
    {
        IntelCpu cpu = new IntelCpu();
        KingstonMemory memory = new KingstonMemory();
        XiJieHardDisk hardDisk = new XiJieHardDisk();
        Computer computer = new Computer();
        computer.setCpu(cpu);
        computer.setMemory(memory);
        computer.setHardDisk(hardDisk);
        computer.run();
    }
}
问题:

这个代码不够灵活,电脑中写死了硬盘等配件的种类,客户想要修改配置,不仅要修改客户端的代码,还要修改电脑类的代码

改进方案:

抽象出一个接口,这样Computer的代码就不需要修改了,切换新配件只需要让新配件代替老配件去实现接口,在客户端修改创建的对象即可

新源代码:

结构:

代码:

  接口

package com.henu.principles.designPrinciples.dependencyInversion.after;

public interface HardDisk {
    public void save(String data);
    public String get();
}
package com.henu.principles.designPrinciples.dependencyInversion.after;

public interface Cpu {
    public void run();
}
package com.henu.principles.designPrinciples.dependencyInversion.after;

public interface Memory {
    public void save();
}

实现类:

package com.henu.principles.designPrinciples.dependencyInversion.after;

public class XiJieHardDisk implements HardDisk{
    public void save(String data)
    {
        System.out.println("使用希捷硬盘保存数据为:" + data);
    }
    public String get()
    {
        System.out.println("使用希捷硬盘获取数据");
        return "数据";
    }
}
package com.henu.principles.designPrinciples.dependencyInversion.after;

public class IntelCpu implements Cpu{
    @Override
    public void run() {
        System.out.println("IntelCpu开始运行");
    }
}
package com.henu.principles.designPrinciples.dependencyInversion.after;

public class KingstonMemory implements Memory{
    @Override
    public void save() {
        System.out.println("Kingston内存条保存数据");
    }
}

电脑(传入参数为接口)

package com.henu.principles.designPrinciples.dependencyInversion.after;

public class Computer {
    private Cpu cpu;
    private Memory memory;
    private HardDisk hardDisk;

    public Cpu getCpu() {
        return cpu;
    }

    public void setCpu(Cpu cpu) {
        this.cpu = cpu;
    }

    public Memory getMemory() {
        return memory;
    }

    public void setMemory(Memory memory) {
        this.memory = memory;
    }

    public HardDisk getHardDisk() {
        return hardDisk;
    }

    public void setHardDisk(HardDisk hardDisk) {
        this.hardDisk = hardDisk;
    }

    public void run()
    {
        System.out.println("电脑开始运行");
        String data = hardDisk.get();
        System.out.println("从硬盘中获取数据" + data);
        cpu.run();
        memory.save();
    }
}

测试类 

package com.henu.principles.designPrinciples.dependencyInversion.after;

public class ComputerDemo {
    public static void main(String[] args) {
        Cpu cpu = new IntelCpu();
        Memory memory = new KingstonMemory();
        HardDisk hardDisk = new XiJieHardDisk();
        Computer computer = new Computer();
        computer.setCpu(cpu);
        computer.setMemory(memory);
        computer.setHardDisk(hardDisk);
        computer.run();
    }
}

接口隔离原则: 

定义:

客户端不应该被迫依赖于它不使用的对象,一个类对另一个类的依赖应该建立在最小接口上.

定义讲解:
问题:

(客户端不应该被迫依赖于它不使用的对象)
B类想使用A类的方法1,B类就继承了A类,但是B类不想使用方法2,那么它就被迫依赖于它不使用的对象

改进方案:

将A类两个功能拆为A接口和B接口,这样新接口只需要实现自己想要的方法的接口

例如:(安全门案例)

安全门具有防盗,防火,防水功能,我们可以自定义自己的安全门实现这个接口

问题是,我如果想要一个新的安全门,能防水火,不能防盗,如果实现上述接口,岂不是违背了最小接口原则

改进前源代码:

package com.henu.principles.designPrinciples.minimalInterface.Before;

public interface SafetyDoor {
    void antiTheft();
    void fireProof();
    void waterProof();
}
package com.henu.principles.designPrinciples.minimalInterface.Before;

public class MySafetyDoor implements SafetyDoor{
    @Override
    public void antiTheft() {
        System.out.println("防盗");
    }

    @Override
    public void fireProof() {
        System.out.println("防火");
    }

    @Override
    public void waterProof() {
        System.out.println("防水");
    }
}
package com.henu.principles.designPrinciples.minimalInterface.Before;

public class SafetyDemp {
    public static void main(String[] args) {
        MySafetyDoor mySafetyDoor = new MySafetyDoor();
        mySafetyDoor.antiTheft();
        mySafetyDoor.fireProof();
        mySafetyDoor.waterProof();
    }
}
改进后源代码:
package com.henu.principles.designPrinciples.minimalInterface.After;

public interface AntiTheft {
    void antiTheft();
}
package com.henu.principles.designPrinciples.minimalInterface.After;

public interface Fireproof {
    void fireProof();
}

package com.henu.principles.designPrinciples.minimalInterface.After;

public interface Waterproof {
    void waterProof();
}
package com.henu.principles.designPrinciples.minimalInterface.After;

public class MySafetyDoor implements AntiTheft, Fireproof, Waterproof{
    public void antiTheft()
    {
        System.out.println("防盗");
    }
    public void fireProof()
    {
        System.out.println("防火");
    }
    public void waterProof()
    {
        System.out.println("防水");
    }
}
package com.henu.principles.designPrinciples.minimalInterface.After;

public class NewSafetyDoor implements Fireproof, Waterproof{
    @Override
    public void fireProof() {
        System.out.println("更防火");
    }

    @Override
    public void waterProof() {
        System.out.println("更防水");
    }
}
package com.henu.principles.designPrinciples.minimalInterface.After;

public class SafetyDoorDemo {
    public static void main(String[] args) {
        MySafetyDoor mySafetyDoor = new MySafetyDoor();
        mySafetyDoor.antiTheft();
        mySafetyDoor.fireProof();
        mySafetyDoor.waterProof();
        NewSafetyDoor newSafetyDoor = new NewSafetyDoor();
        newSafetyDoor.fireProof();
        newSafetyDoor.waterProof();
    }
}

迪米特法则(最小知识原则)

定义:

只和你的直接朋友交谈,不和"陌生人说话"

含义:

如果两个软件实体无须直接通信,尽量通过第三方转发(如租房通过中介,经纪人安排明星粉丝见面等,在软件行业要做软件的公司与软件公司对接,而不是工程师)

目的:

降低类之间的耦合度,提高模块独立性

"朋友":对象本身,成员变量,当前对象创建的对象,方法参数等

例如:(明星和经济人实例)

明星专注于演艺,经纪人负责粉丝见面会,媒体公司交涉等工作,这个例子中经纪人是明星的朋友,而粉丝是陌生人,适合迪米特法则

源代码:

package com.henu.principles.designPrinciples.dimmitLaw;

public class Star {
    private String name;

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

    public String getName() {
        return name;
    }
}
package com.henu.principles.designPrinciples.dimmitLaw;

public class Fans {
    private String name;

    public Fans(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
package com.henu.principles.designPrinciples.dimmitLaw;

public class Company {
    private String name;

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

    public String getName() {
        return name;
    }
}
package com.henu.principles.designPrinciples.dimmitLaw;

public class Agent {
    private Star star;
    private Fans fans;
    private Company company;

    public void setStar(Star star) {
        this.star = star;
    }

    public void setFans(Fans fans) {
        this.fans = fans;
    }

    public void setCompany(Company company) {
        this.company = company;
    }

    public void business()
    {
        System.out.println("明星:" + star.getName() + "和公司:" + company.getName() + "洽谈业务");
    }
    public void meeting()
    {
        System.out.println("明星:" + star.getName() + "和粉丝:" + fans.getName() + "见面");
    }
}
package com.henu.principles.designPrinciples.dimmitLaw;

public class dimmitLawDemo {
    public static void main(String[] args) {
        Star star = new Star("高圆圆");
        Company company = new Company("中国传媒公司");
        Fans fans = new Fans("小王");
        Agent agent = new Agent();
        agent.setStar(star);
        agent.setCompany(company);
        agent.setFans(fans);
        //商业活动
        agent.business();
        //粉丝见面
        agent.meeting();
    }
}

合成复用原则

定义:

尽量先用组合或者聚合等关联关系实现复用,其次再考虑继承关系

继承:白箱复用
优点:
简单
缺点:

1.破坏了父类的封装性,父类内部细节暴露给子类

2.父类和子类的耦合度高,父类的任何改变都可能影响子类

3.限制了复用的灵活性,子类从父类继承的实现是静态的,在编译时已经确定,无法修改

组合和聚合:黑箱复用

将已有对象纳入新对象,使它作为新对象的一部分

优点:

1.维护了封装性,不知道已有对象的内部细节

2.对象之间耦合度第,可以在类的成员位置声明抽象

3.复用的灵活性高,(多态)方法参数可以动态传入抽象的子类对象或实现

例子:
原实现:

汽车按"动力源"电力和汽油,按"颜色"可以分为白,红等(继承关系如下)

定义新类添加子类非常多

改进方案:

添加新动力汽车时的对比:

源代码:

package com.henu.principles.designPrinciples.syntheticMultiplexing;

public abstract class Car implements Color{
    public abstract void move();
}
package com.henu.principles.designPrinciples.syntheticMultiplexing;

public interface Color {
    void carColor();
}

        

package com.henu.principles.designPrinciples.syntheticMultiplexing;

public class ElectricCar extends Car{
    @Override
    public void move() {
        System.out.println("电力发电");
    }

    @Override
    public void carColor() {
        System.out.println("白色");
    }
}
package com.henu.principles.designPrinciples.syntheticMultiplexing;

public class PotrolCar extends Car{
    @Override
    public void move() {
        System.out.println("柴油车");
    }

    @Override
    public void carColor() {
        System.out.println("红色");
    }
}
package com.henu.principles.designPrinciples.syntheticMultiplexing;

public class Demo {
    public static void main(String[] args) {
        Car car = new ElectricCar();
        car.move();
        car.carColor();
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值