从头捋一遍Java项目中的五大设计原则,就不信你学不会!(上)

简介: 从头捋一遍Java项目中的五大设计原则,就不信你学不会!(上)

今天我们一起来聊聊关于设计原则相关的知识点。


SOLID五大原则是什么


SRP 单一责任原则


单一责任原则,从名字上我们就能比较好的去理解它。这项原则主张一个对象只专注于单个方面的逻辑,强调了职责的专一性。


举个例子:


学生管理系统中,我们需要提交一些学生的基本资料,那么学生信息相关的程序都交给了StudentService负责,如果我们要实现一个保存教师基本资料的功能就应该新建一个TeacherService去处理,而不应该写在StudentService当中。


OCP开放封闭原则


这项原则从我个人的角度去理解,它更加强调的是对于扩展的开放性,例如当我们需要调整某些实现逻辑的时候,尽量不要直接改动到原有的实现点。


但是这里面有几个点容易被人们误解:


第一点


开放封闭原则虽然强调的是不要随意改动代原先代码到逻辑结构,但是并没有要求一定不能对代码进行改动!


第二点


同样是代码改动,如果我们可以从功能,模块的角度去看,实际上代码的改动更多地可以被认作为是一种“扩展”。


关于如何做到开放封闭原则,下文我会专门用一个案例来进行介绍。


LSP里氏替换原则


里氏替换原则强调的是不能破坏一个原有类设计的原始设计体系。强调了子类可以对父类程序进行继承。但是有几个点需要注意下:


如果父类定义的规则最好是最基础,必须遵守的法则。如果子类继承了父类之后,在某个方法的实现上违背了初衷,那么这样的设计就是违背了里氏替换法则。


例如:


父类的设计是希望实现商品库存扣减的功能,但是子类的实现却是实现了库存+1的功能,这就很明显是牛头不对马嘴了。


子类不要违背父类对于入参,出参,异常方面的约定。例如:父类对于异常的抛出指定的是 NullPointException ,但是子类却在实现的时候声明了会出 illegalArgumentException,那么此时就需要注意到设计已经违背了LSP原则。


同样,具体的案例我在下文会列举出来和大家进行代码分享。


ISP接口隔离原则


理解“接口隔离原则”的重点是理解其中的“接口”二字。


这里有三种不同的理解。如果把“接口”理解为一组接口集合,可以是某个微服务的接口,也可以是某个类库的接口等。


如果部分接口只被部分调用者使用,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。


DIP依赖倒置原则


比较经典的例子,例如说Spring框架的IOC控制反转,将bean的管理交给了Spring容器去托管。依赖注入则是指不通过明确的new对象的方式来在类中创建类,而是提前将类创建好,然后通过构造函数,setter函数等方式将对应的类注入到所需使用的对象当中。

DIP的英文解释大致为:


High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.


解释过来就是,高层次的模块不应该依赖低层次的模块,不同的模块之间应该通过接口来互相访问,而并非直接访问到对方的具体实现。


清楚了这么多理论知识之后,接下来我们通过一些代码实战案例来进行更加深入的了解吧。


代码实战理解设计原则


单一责任原则案例


我们来看这么一个类,简单的一个用户信息类中,包含了一个叫做home的字段,这个字段主要用于记录用户所居住的位置。


/**
 * @Author linhao
 * @Date created in 7:22 上午 2021/9/3
 */
public class UserInfo {
    private String username;
    private short age;
    private short height;
    private String phone;
    private String home;
}


慢慢地随着业务的发展,这个实体类中的home字段开始进行了扩展,UserINfo类变成了以下模式:


/**
 * @Author linhao
 * @Date created in 7:22 上午 2021/9/3
 */
public class UserInfo {
    private String username;
    private short age;
    private short height;
    private String phone;
    private String home;
    /**
     * 省份
     */
    private String province;
    /**
     * 城市
     */
    private String city;
    /**
     * 地区
     */
    private String region;
    /**
     * 街道
     */
    private String street;
}


此时对于这个实体类的设计就会有了新的观点:


这个类中关于居住部分的字段开始渐渐增加,应该将住址部分抽象出来成一个Address字段,拆分后变成如下所示:


/**
 * @Author linhao
 * @Date created in 7:22 上午 2021/9/3
 */
public class UserInfo {
    private String username;
    private short age;
    private short height;
    private String phone;
    private String home;
    /**地址信息**/
    private Address address;
}


这样的拆分可以确保UserInfo对象的职责单一,类似的扩展还可以蔓延到后续的email,tel相关属性。


举这个例子只是想简单说明,我们在对一些类进行设计的时候,其实就已经使用到了单一责任原则。另外还有可能在以下场景中也有运用到该原则:


类中的属性字段特别多,一个bean中充斥了几十个属性。此时也可以尝试使用单一责任原则,将不同属性的字段归纳为一个bean进行收拢。


一个大对象,例如XXXManager或者XXXContext这种名词定义的对象中,可能引入了一大堆的外部依赖,此时可以按照依赖的类别来进行拆分。


业务代码块中,我们定义了一个UserService类,然后这个类里面写了一坨的用户密码,手机号,身份证号解密加密相关的私有函数,这时候可以不妨尝试将这些私有方法统统抽象成为一个独立的Util当中,从而减少UserService中的代码量。


所以最终你会发现,单一责任原则还是一个比较需要依靠主观意识去拿捏的一项技巧。随着我们实践开发经验的逐渐提升,自然就会明白什么样的代码该进行良好的抽象与优化了。


开放封闭原则案例


关于这条原则我个人感觉要想较好地理解它,需要有具体的实战案例代码,所以接下来我打算用一个自己曾经在工作中遇到的实际场景和你分享:


我做的一款社交小程序应用当中,当一个用户注册完信息之后,需要通知到系统下游,主要是修改某些后台数据,分配对应的员工去跟进这个用户。


所以大体的代码设计可能如下所示:


public class RegisterHandler {
    public void postProcessorAfterRegister(long userId){
        //通知员工
        notifyWorker(userId);
    }
    private void notifyWorker(long userId){
        //通知部分的逻辑
    }
}
public interface IRegisterHandler {
    /**
     * 用户注册之后处理函数
     *
     * @param userId 用户渠道ID
     */
    void postProcessorAfterRegister(long userId);
}


但是注册的渠道类型有许多种,例如公众号,小程序二维码传播,小程序的分享链接,其他App渠道等等。所以代码结构需要做部分调整:


首先需要修改一开始设计的接口模型:


/**
 * @Author linhao
 * @Date created in 7:56 上午 2021/9/3
 */
public interface IRegisterHandler {
    /**
     * 用户注册之后处理函数
     *
     * @param userId 用户ID
     * @param sourceId 注册渠道ID
     */
    void postProcessorAfterRegister(long userId,int sourceId);
}


然后还需要修改实际的实现规则:


/**
 * @Author linhao
 * @Date created in 7:48 上午 2021/9/3
 */
public class RegisterHandler implements IRegisterHandler {
    @Override
    public void postProcessorAfterRegister(long userId, int sourceId) {
        //通知员工
        if (sourceId == 1) {
            //doSth
        } else if (sourceId == 2) {
            //doSth
        } else if (sourceId == 3) {
            //doSth
        } else {
            //doSth
        }
        notifyWorker(userId, sourceId);
    }
    private void notifyWorker(long userId, int sourceId) {
        //通知部分的逻辑
    }
}


这样的代码扩展就会对原先定义好的结构造成破坏,也就不满足我们所认识的开放封闭原则了。(虽然我在上文中有提及过对于开放封闭原则来说,并不是强制要求不对代码进行修改,但是现在的这种扩展模式已经对内部结构造成了较大的伤害。)


所以我们可以换一种设计思路去实现。


首先我们需要将注册的传入参数定义为一个对象类型,这样在后续新增参数的时候只需调整对象内部的字段即可,不会对原有接口的设计造成影响:


/**
 * @Author linhao
 * @Date created in 8:07 上午 2021/9/3
 */
public class RegisterInputParam {
    private long userId;
    private int source;
    public long getUserId() {
        return userId;
    }
    public void setUserId(long userId) {
        this.userId = userId;
    }
    public int getSource() {
        return source;
    }
    public void setSource(int source) {
        this.source = source;
    }
}



相关文章
|
1月前
|
存储 Java Android开发
java实战项目音乐管理系统
java实战项目音乐管理系统
|
1月前
|
Java
java实战项目超市管理系统控制台版
java实战项目超市管理系统控制台版
|
1月前
|
设计模式 Java API
重构旧代码的秘诀:用设计模式 - 适配器模式(Adapter)给Java项目带来新生
【4月更文挑战第7天】适配器模式是解决接口不兼容问题的结构型设计模式,通过引入适配器类实现目标接口并持有不兼容类引用,实现旧代码与新接口的协作。适用于处理兼容性问题、整合遗留代码和集成第三方库。应用时,识别不兼容接口,创建适配器类转换方法调用,然后替换原有引用。注意保持适配器简单、使用组合和考虑扩展性。过度使用可能导致系统复杂和维护成本增加,应谨慎使用。
|
1天前
|
移动开发 前端开发 Java
STS里的java 工程项目名称修改和目录设置成源代码
STS里的java 工程项目名称修改和目录设置成源代码
|
1天前
|
JavaScript 前端开发 Java
java项目的打包将vue放到.jar里面部署
java项目的打包将vue放到.jar里面部署
|
4天前
|
缓存 安全 前端开发
来聊聊Java项目分层规范
本文讨论了Java项目的分层规范,强调了分层的重要性以避免代码不易扩展和职责边界模糊。作者分享了阿里提出的六层分层模型(开放接口层、终端显示层、Web层、Service层、Manager层、Mapper层)以及对应的领域模型(DO、DTO、VO、query)。同时,提出了简化版的分层规约,以提高开发效率。作者是CSDN Java博客专家,维护者之一的Java Guide项目,并提供了个人项目结构示例。文章鼓励读者关注其公众号以获取更多交流机会。
20 4
|
5天前
|
Java
springboot项目出现Exception in thread “main“ java.lang.NoClassDefFoundError: javax/servlet/Filter
springboot项目出现Exception in thread “main“ java.lang.NoClassDefFoundError: javax/servlet/Filter
12 0
|
9天前
|
Java Maven
Maven 构建 Java 项目
使用Maven的`maven-archetype-quickstart`插件在C:\MVN下创建Java应用,命令包括`groupId`, `artifactId`, 和 `archetypeArtifactId`参数。生成的项目包含src/main/java和src/test/java目录,分别用于存放源代码和测试代码,还有src/main/resources用于资源文件。默认提供App.java主类和AppTest.java测试类。按照预设结构组织文件,Maven将自动管理构建过程。
|
12天前
|
Java
IDEA云行项目提示Error: java: OutOfMemoryError
IDEA云行项目提示Error: java: OutOfMemoryError
|
12天前
|
Java API 数据安全/隐私保护
【亮剑】如何在Java项目中结合Spring框架实现邮件发送功能
【4月更文挑战第30天】本文介绍了如何在Java项目中结合Spring框架实现邮件发送功能。首先,需在`pom.xml`添加Spring和JavaMail依赖。然后,在`applicationContext.xml`配置邮件发送器,包括SMTP服务器信息。接着,创建一个使用依赖注入的`EmailService`类,通过`JavaMailSender`发送邮件。最后,调用`EmailService`的`sendSimpleEmail`方法即可发送邮件。最佳实践包括:使用配置管理敏感信息,利用`MimeMessage`构造复杂邮件,异常处理和日志记录,以及在大量发送时考虑使用邮件队列。