【7大设计原则】理解+案例(个人总结)

本文详细讲解了七个核心的设计原则:单一职责、开闭原则、里氏替换、依赖倒转、接口隔离、合成复用和迪米特法则,并通过实际案例,如搜狗输入法、组装电脑等,深入剖析了这些原则在实际开发中的应用,旨在提高代码的可维护性和可扩展性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、单一职责原则

1.理解

二、开闭原则

1.理解

2.搜狗输入法案例

三、里氏替换原则

1.理解

2.四边形案例

四、依赖倒转原则

1.理解

2.组装电脑案例

五、接口隔离原则

        1.理解

2.电子设备案例

六、合成复用原则

1.理解

2.数据库连接案例

七、迪米特法则

1.理解

2.网络地址案例

八、总结回顾


一、单一职责原则

1.理解

        就是面向对象的思想,每个对象只需要负责好自己的事情,例如此时有一个People类,里面有一些方法,比如送外卖、打螺丝、摸鱼,这些虽然都属于人类的行为,但这样写的话类会显得极为臃肿,此时我们创建外卖员类和打工人类,送外卖就是外卖员的行为,打螺丝和摸鱼就是打工人的行为,这样就划分出了每个类的职责。

二、开闭原则

1.理解

        “对扩展开放,对修改关闭,强调使用抽象接口进行架构,通过实现类来扩展细节”,其实不太好理解,通俗易懂来说就是提供一个接口或者抽象类,通过多个实现类去实现,然后提供一种热插拔的效果。

        例如搜狗输入法的换皮肤功能,皮肤的提供者可能是厂商A或厂商B,此时搜狗输入法就可以为不同厂商提供一个统一的接口,然后厂商去实现这个接口以更换自己的皮肤。和Web开发中MVC架构类似,例如service层,我们要先定义接口,然后去实现这个接口,只不过大多数情况下我们的接口和实现类是1:1的。实现热插拔的效果的话,接口和实现类比例为1:n。

2.搜狗输入法案例

/**
 * 抽象皮肤类
 */
public abstract class AbstractSkin {
    public abstract void display();
}
/**
 * 默认皮肤类
 */
public class DefaultSkin extends AbstractSkin {
    @Override
    public void display() {
        System.out.println("我是默认皮肤");
    }
}
/**
 * 黑马皮肤类
 */
public class HeiMaSkin extends AbstractSkin {
    @Override
    public void display() {
        System.out.println("我是黑马皮肤");
    }
}
/**
 * 搜狗输入法类
 */
public class SouGouInput {
    private AbstractSkin skin;

    public void display() {
        skin.display();
    }

    public void setSkin(AbstractSkin skin) {
        this.skin = skin;
    }
}
/**
 * 开闭原则:对扩展开放,对修改关闭。实现热插拔。我们需要使用到接口或者抽象类
 */
public class Test {
    public static void main(String[] args) {
        //1.创建搜狗输入法
        SouGouInput souGouInput = new SouGouInput();
        //2.插入默认皮肤
        souGouInput.setSkin(new DefaultSkin());
        //3.展示
        souGouInput.display();
        
        //4.替换成黑马皮肤
        souGouInput.setSkin(new HeiMaSkin());
        //5.展示
        souGouInput.display();
    }
}

三、里氏替换原则

1.理解

        “父类出现的地方,子类也一定可以出现;子类可以拓展父类的功能,但不能重写父类原有的功能”

2.四边形案例

        刚开始学面向对象编程的时候,一直是通过extends长方形去实现的正方形,但是这样就不符合里氏替换原则,因为实现正方形的时候重写了长方形已经实现好了的方法。此时我们可以单独抽象出来一个四边形接口或者抽象类,然后正方形和长方形都去实现这个接口,这时对于四边形接口来说,我们只是对其进行了扩展,而不是重写覆盖了原有的功能。

/**
 * 四边形
 */
public interface Quadrilateral {
    double getLength();

    double getWidth();
}
public class Rectangle implements Quadrilateral {
    private double length;
    private double width;

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

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

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

    public void setWidth(double width) {
        this.width = width;
    }
}
public class Square implements Quadrilateral {
    private double side;

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

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

    @Override
    public double getWidth() {
        return this.side;
    }
}
/**
 * 里氏代换原则:父类出现的地方,子类也一定能出现。子类可以扩展父类的功能,但不能重写父类原有的功能。
 * 案例:正方形不是长方形
 * 原本正方形是需要继承长方形的,但这样会导致在resize的时候长方形可以,但正方向不行,违背了里氏代换原则,
 * 所以单独拿出来一个四边形接口,让长方形和正方形去实现这个接口,这样即抽取了他们共有的部分,也避免了之间的继承关系
 */
public class Test {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setLength(20);
        rectangle.setWidth(10);

        resize(rectangle);
        printLengthAndWidth(rectangle);
    }

    public static void resize(Rectangle rectangle) {
        while (rectangle.getLength() >= rectangle.getWidth()) {
            rectangle.setWidth(rectangle.getWidth() + 1);
        }
    }

    public static void printLengthAndWidth(Rectangle rectangle) {
        System.out.println("Length = " + rectangle.getLength());
        System.out.println("Width = " + rectangle.getWidth());
    }
}

四、依赖倒转原则

1.理解

        “高层模块不应该依赖于底层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象”。例如我们使用spring进行开发的时候,通过构建mvc架构,每一层有每一层的接口,然后是实现类,通过@Service、@Mapper、@Controller等注解把bean注入到容器中,在我们需要使用到这些实现类的时候,我们只需要@Autowired它的接口即可,这就是高层模块依赖于抽象,当我们需要改动实现类的时候,只要实现了这个接口,相比我们直接使用实现类会少改动很多代码。

        依赖倒转原则和开闭原则很像,依赖倒转是高层模块不依赖底层模块,而是依赖于抽象,抽象不依赖于细节,细节应该依赖于抽象。开闭原则是对扩展开放,对修改关闭,强调使用抽象接口进行架构,通过实现类来实现具体细节。我的理解就是依赖倒转原则实现了开闭原则,因为依赖倒转原则说的更加具体一些,而且也是通过抽象接口的方式去进行依赖倒转的,依赖倒转原则强调高层不依赖底层,开闭原则强调使用接口架构。

2.组装电脑案例

/**
 * 依赖倒转原则:
 * 通过在computer类中定义cpu、硬盘、内存的抽象,这样我们不光可以使用intel的cpu,
 * 也可以使用其他厂商的cpu,此时上层computer不会依赖于底层cpu的实现,而是依赖cpu的抽象,
 */
public class Computer {
    private Cpu cpu;
    private HardDisk hardDisk;
    private Memory memory;

    @Override
    public String toString() {
        return "Computer{" +
                "cpu=" + cpu +
                ", hardDisk=" + hardDisk +
                ", memory=" + memory +
                '}';
    }

    public Cpu getCpu() {
        return cpu;
    }

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

    public HardDisk getHardDisk() {
        return hardDisk;
    }

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

    public Memory getMemory() {
        return memory;
    }

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

public class IntelCpu implements Cpu{
    @Override
    public void run() {
        System.out.println("IntelCpu...");
    }
}
public interface HardDisk {
    void run();
}

public class XiJieHardDisk implements HardDisk{
    @Override
    public void run() {
        System.out.println("XiJieHardDisk...");
    }
}
public interface Memory {
    void run();
}

public class KingstonMemory implements Memory {
    @Override
    public void run() {
        System.out.println("KingstonMemory...");
    }
}
public class Test {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.setCpu(new IntelCpu());
        computer.setMemory(new KingstonMemory());
        computer.setHardDisk(new XiJieHardDisk());
        computer.getCpu().run();
        computer.getMemory().run();
        computer.getHardDisk().run();
        System.out.println(computer);
    }
}

五、接口隔离原则

1.理解

        “客户端不应该依赖那些它不需要的接口”,换句话说,其实就是需要让我们细粒度化接口。

2.电子设备案例

        电脑和风扇虽然都属于电子设备,但是风扇没有cpu和内存,如果我们使用统一的接口,这样会造成风扇使用该接口的时候有的方法返回null值,此时需要进行接口细粒度的划分,分为普通电子设备和智能电子设备,然后电脑和风扇分别去实现他们,每个实现类要充分利用到它所实现的接口。

public interface NormalDevice {
    String getType();
}

public interface SmartDevice {
    String getType();

    String getCpu();

    String getMemory();
}

public class Fan implements NormalDevice {
    @Override
    public String getType() {
        return "风扇";
    }
}

public class Computer implements SmartDevice {

    @Override
    public String getType() {
        return "电脑";
    }

    @Override
    public String getCpu() {
        return "intel";
    }

    @Override
    public String getMemory() {
        return "kingston";
    }
}

六、合成复用原则

1.理解

        “优先使用对象组合的方式,而不是通过继承来达到复用的目的”。我们都知道java中的继承,大多数情况下我们为了减少代码的重复而去选择继承一个对象,但这样也会导致耦合度增加,例如此时我们有一个yourbatis的框架,他需要连接mysql数据库,我们可以直接让yourbatis继承connect类,但这样就会增加耦合度的同时导致connect类中的一些无关的方法和字段在yourbatis中也可以访问到,所以我们优先考虑使用组合的方式,在yourbatis中设置他的接口,再去set这个接口把connect传进去。

2.数据库连接案例

public class YourBatis {
    private ConnectDatabase connectDatabase;

    public void test() {
        connectDatabase.connect();
    }

    public ConnectDatabase getConnectDatabase() {
        return connectDatabase;
    }

    public void setConnectDatabase(ConnectDatabase connectDatabase) {
        this.connectDatabase = connectDatabase;
    }
}

public interface ConnectDatabase {
    void connect();
}

public class MySQL implements ConnectDatabase {
    @Override
    public void connect() {
        System.out.println("连接mysql数据库...");
    }
}

public class Test {
    public static void main(String[] args) {
        YourBatis yourBatis = new YourBatis();
        yourBatis.setConnectDatabase(new MySQL());
        yourBatis.test();
    }
}

七、迪米特法则

1.理解

        “每个软件单位对其他单位都只有最少的知识,而且局限于那些于本单位密切相关的软件单位”,简单来说就是,一个类/模块对其他类/模块有越少的交互越好,当一个类发生改动,那么与其他相关的类(比如用到此类方法的类)需要尽可能少的受影响(比如修改了方法名、字段名等,可能其他用到这些方法或是字段的类也需要跟着修改),这样有利于维护。

2.网络地址案例

public class Socket {
    private String domain;
    private String port;

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    public String getPort() {
        return port;
    }

    public void setPort(String port) {
        this.port = port;
    }

    public String getUrl() {
        return this.domain + ":" + this.port;
    }

    @Override
    public String toString() {
        return "Socket{" +
                "domain='" + domain + '\'' +
                ", port='" + port + '\'' +
                '}';
    }
}
/**
 * 迪米特原则:
 * 不同类或者模块间的交互越少越好,
 * 在test入参时传入string类型的url,而不是传入socket对象,
 * 如果传入socket对象进去,当socket对象内的方法发生改变,也会波及到test
 */
public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        Socket socket = new Socket();
        socket.setDomain("www.soulzzz.cn");
        socket.setPort("8080");
        test.test(socket.getUrl());
    }

    public void test(String url) {
        System.out.println(url);
    }
}

八、总结回顾

        1.单一职责原则:每个对象/模块只负责一类功能。

        2.开闭原则:通过面向接口编程,对扩展开放,对修改关闭,强调使用接口进行架构,再通过实现具体的实现类来描述细节。

        3.里氏替换原则:父类出现的地方,子类也一定可以出现。子类可以扩展父类的功能,但不能覆盖重写父类原有的实现。

        4.依赖倒转原则:通过面向接口编程,使上层模块不依赖于底层模块,他们都依赖于抽象,而抽象不依赖于细节,细节应依赖于抽象。

        5.接口隔离原则:一个类不应该依赖于它不需要的接口,保证接口的粒度,细粒化接口。

        6.合成复用原则:当考虑复用功能或者对象时,尽量使用合成的方式,而不是继承的方式。

        7.迪米特法则:对象/模块之间的交互越少越好,当一个对象/模块发生了改动,要使其他和这个对象/模块有关联的对象/模块受到的影响越小。

        总之,我认为,设计模式主要目的就是通过oop优化我们的代码结构,减少耦合,易于维护。我们的项目中使用没使用到设计模式其实对于功能而言是无所谓的,这更多体现的是开发者的软实力和精益求精、追求完美的性格。每种设计原则都是针对于一些场景,如果没有具体的业务,那我们单单在这里扯设计模式就是纸上谈兵,所以此贴长期更新,如果有更好的案例我会进行替换,也欢迎大家一起讨论!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值