为了账号安全,请及时绑定邮箱和手机立即绑定

浅谈软件设计中的SOLID原则

SOLID 原则是帮助软件开发者设计坚固、可扩展性和可维护系统的指导方针。这些原则由 Robert C. Martin(也被称为 Uncle Bob)提出,对于面向对象编程领域来说,在创建灵活且可复用的代码方面至关重要。

在这篇文章中,我们将逐一探讨每个SOLID原则,并解释其用途。我们还将通过Java示例展示如何应用这些原则。

1. 单一职责原则 (SRP)

定义:一个类只有一个改变的理由。这意味着一个类应该只承担一项职责。

为什么SRP这么重要

当一个类具有多个职责时,对其中一个职责的更改可能会影响到代码的其他部分,甚至导致代码出错。通过遵循SRP(SRP指的是单一职责原则),我们可以确保更好的维护和测试。

例子

    // 违反单一职责原则(SRP):一个处理用户认证和数据库操作的类。
    类 UserManager {
        public void authenticateUser(String username, String password) {
            // 认证逻辑
        }

        public void saveUserToDatabase(User user) {
            // 数据库逻辑
        }
    }

    // 遵循单一职责原则:将职责划分到不同的类中,如下面的示例所示。
    类 AuthService {
        public void authenticateUser(String username, String password) {
            // 认证逻辑
        }
    }

    类 UserRepository {
        public void saveUserToDatabase(User user) {
            // 数据库逻辑
        }
    }

点击这里进入全屏模式 点击这里退出全屏模式

在此示例中,AuthService 负责认证,UserRepository 管理数据库操作。每个类只有一个功能,从而使代码更清晰、更模块化。

2. 开闭原则 OCP

定义是:类应该是开放扩展,但封闭修改。也就是说,你应该能够在不修改现有代码的情况下增加新功能。

OCP为何如此重要

当你修改现有代码时,你可能引入错误。OCP 主张通过继承或组合来扩展功能,而不是修改原有实现。

例子:

    // 违反OCP原则:添加新的折扣类型需要修改现有代码,这不符合OCP原则。
    class 折扣计算器 {
        public double 计算折扣(String 折扣类型, double 金额) {
            if ("NEWYEAR".equals(折扣类型)) {
                return 金额 * 0.10;
            } else if ("BLACKFRIDAY".equals(折扣类型)) {
                return 金额 * 0.20;
            }
            return 0;
        }
    }

    // 遵循OCP原则:使用多态来添加新的折扣类型,而不改变现有代码,这样符合OCP原则。
    interface 折扣 {
        double 应用(double 金额);
    }

    class 新年折扣 implements 折扣 {
        public double 应用(double 金额) {
            return 金额 * 0.10;
        }
    }

    class 黑色星期五折扣 implements 折扣 {
        public double 应用(double 金额) {
            return 金额 * 0.20;
        }
    }

    class 折扣计算器 {
        public double 计算折扣(折扣 折扣类型, double 金额) {
            return 折扣类型.应用(金额);
        }
    }

全屏模式,退出全屏

现在,添加一种新的折扣类型只需要创建一个新类,它实现了Discount接口。

3. 里科夫替换原则 (LSP),

简单来说:子类必须能够替换其基类,从而不会影响程序的正确性。

LSP为什么重要

违反LSP原则可能导致在使用多态时出现意外行为和错误情况。派生类必须遵守基类定义的约定。

例子:

    // 违反LSP:子类以意外方式改变了父类的行为。
    class Bird {
        public void fly() {
            System.out.println("飞行...");
        }
    }

    class Penguin extends Bird {
        @Override
        public void fly() {
            throw new UnsupportedOperationException("企鹅不会飞!");
        }
    }

    // 遵循LSP:重构层次结构以实现替换原则。
    abstract class Bird {
        public abstract void move();
    }

    class FlyingBird extends Bird {
        public void move() {
            System.out.println("飞行...");
        }
    }

    class Penguin extends Bird {
        public void move() {
            System.out.println("游泳...");
        }
    }

切换到全屏,再退出

通过重新设计层级结构,不管是 FlyingBird 还是 Penguin,替代 Bird 时都能正确表现。

4. 界面分割原则 (ISP):

定义是:客户端不应该被迫实现那些他们不用的接口。相反,应该创建更精简、更具体的接口。

为什么ISP(互联网服务提供商)重要

较大的接口迫使实现类包含它们实际上不需要的方法,从而导致代码臃肿和不必要的依赖关系。

例子

    // 违反 ISP:一个包含无关方法的大接口。
    interface Worker {
        void work();
        void eat();
    }

    class Robot implements Worker {
        public void work() {
            System.out.println("工作...");
        }

        public void eat() {
            // 机器人不吃,但不得不实现这个方法。
            throw new UnsupportedOperationException("机器人不吃!");
        }
    }

    // 遵循 ISP:将接口拆分成更小、更具体的接口。
    interface Workable {
        void work();
    }

    interface Eatable {
        void eat();
    }

    class Robot implements Workable {
        public void work() {
            System.out.println("工作...");
        }
    }

    class Human implements Workable, Eatable {
        public void work() {
            System.out.println("工作...");
        }

        public void eat() {
            System.out.println("吃...");
        }
    }

全屏 退出全屏

目前,robot只实现了Workable接口,省去了不必要的方法。

5. 依赖倒置原则,简称 DIP

定义:高层模块不应依赖于低层模块,两者都应依赖于抽象概念。

DIP 为什么重要

直接依赖于具体实现使代码变得僵硬且难以测试。DIP提倡使用抽象(如接口)来解耦组件。

例子

    // 违反DIP:高层业务类依赖于底层实现。
    class MySQLDatabase {
        public void connect() {
            System.out.println("连接到MySQL数据库...");
        }
    }

    class UserService {
        private MySQLDatabase database;

        public UserService() {
            this.database = new MySQLDatabase(); // 紧密耦合:高层业务类直接创建了具体的数据库实例。
        }

        public void performDatabaseOperation() {
            database.connect();
        }
    }

    // 遵循DIP:高层次类依赖于抽象层。
    interface Database {
        void connect();
    }

    class MySQLDatabase implements Database {
        public void connect() {
            System.out.println("连接到MySQL数据库...");
        }
    }

    class UserService {
        private Database database;

        public UserService(Database database) {
            this.database = database; // 依赖抽象层:高层次类通过接口依赖于数据库抽象层。
        }

        public void performDatabaseOperation() {
            database.connect();
        }
    }

    // 使用:通过接口传递具体实现。
    Database db = new MySQLDatabase();
    UserService userService = new UserService(db);
    userService.performDatabaseOperation();

全屏显示;切换回正常视图

有了这样的设计,你可以轻松地切换数据库类型,比如 PostgreSQL 或 MongoDB,而无需改动 UserService 类。

结论部分

SOLID 原则是创建易于维护、可扩展且稳健的软件的强大工具,我们快速回顾一下:

  1. SRP:一个类承担一个职责。
  2. OCP:在不修改现有代码的情况下扩展功能。
  3. LSP:子类应该能够替代它们的基类。
  4. ISP:尽量使用更小、更专注的接口。
  5. DIP:依赖于抽象,而不是依赖于具体实现。

通过应用这些原则,你的代码将更容易理解、测试和适应变化。从小处开始,根据需要进行重构,并逐渐将这些原则融入你的开发流程中!

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
8
获赞与收藏
25

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消