写了这么久代码你了解Java面向对象的设计原则吗?(二)

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 写了这么久代码你了解Java面向对象的设计原则吗

里氏替换原则实例

  • 下面通过一个简单实例来加深对里氏代换原则的理解。

实例说明

某系统需要实现对重要数据(如用户密码)的加密处理,在数据操作类(DataOperator)中需要调用加密类中定义的加密算法,系统提供了两个不同的加密类CipherA和CipherB,它们实现不同的加密方法,在 DataOperator中可以选择其中的一个实现加密操作,如图下图所示。

b9be99612da9448a82fc4b16c1977166.png

在DataOperator类的encrypt(O)方法中,将调用加密类CipherA 或CipherB的加密方法encrypt()。如在客户类Client的 main()函数中可能存在如下代码片段:


CipherA cipherA = new CipherA();
DataOperator do = new DataOperator();
do.setCipherA(cipherA);
...
  • 与之对应,在:DataOperator类的encryptO)方法中可能存在如下代码片段:
...
return cipherA.encrypt(plainText)


如果需要更换一个加密算法类或者增加并使用一个新的加密算法类,如将上述CipherA改为CipherB,则需要修改客户类Client和数据操作类DataOperator的源代码,违背了开闭原则。

现使用里氏替换原则对其进行重构,使得系统可以灵活扩展,符合开闭原则。


实例解析


在本实例中,导致系统灵活性和可扩展性差的本质原因是Client类和 DataOperator类都针对每一个具体类进行编程,每增加一个具体类都将修改源代码,此时,可以将CipherB作为CipherA的子类,Client类和 DataOperator类都针对CipherA进行编程,根据里氏代换原则,所有能够接受CipherA类对象的地方都可以接受CipherB类的对象,因此可以简化DataOperator类和Client类的代码,而且将CipherA类对象替换成CipherB类对象很方便,无须修改任何源代码。如果需要增加一个新的加密算法类,如CipherC,只须将CipherC类作为CipherA类或CipherB类的子类即可。重构后的类图如下图所示。


2ec6bedb0cb7410f895cc5ceae889364.png

在上图中,由于CipherB是CipherA的子类,因此所有能够使用CipherA对象的地方都可以使用CipherB对象来替换,且可以将具体类的类名存储至配置文件中,如果需要使用CipherA 的encrypt()方法﹐则配置文件中存储的类名为CipherA,如果需要使用CipherB的encrypt()方法,则配置文件中存储的类名为CipherB。


如果需要增加一个新的加密类﹐如CipherC,则可将CipherC继承CipherA或CipherB,并覆盖其中定义的encrypt()方法,并将配置文件中存储的类名改为CipherC,所有现有类的代码无须做任何改变,完全符合开闭原则。


依赖倒转原则

  • 如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是实现面向对象设计的主要机制。依赖倒转原则是系统抽象化的具体实现。

依赖倒转原则定义

  • 高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
  • 另一种表述:要针对接口编程,不要针对实现编程。

依赖倒转原则分析

简单来说,依赖倒转原则就是指:代码要依赖于抽象的类,而不要依赖于具体的类﹔要针对接口或抽象类编程,而不是针对具体类编程。也就是说,在程序代码中传递参数时或在组合聚合关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明,参数类型声明,方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口和抽象类中声明过的方法,而不要给出多余的方法。否则将无法调用到在子类中增加的新方法。


实现开闭原则的关键是抽象化,并且从抽象化导出具体化实现,如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。有了抽象层,可以使得系统具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生变化,只需要扩展抽象层,并修改配置文件,而无须修改原有系统的源代码,在不修改的情况下来扩展系统的功能,满足开闭原则的要求。依赖倒转原则是COM,CORBA,EJB、Spring 等技术和框架背后的基本原则之一。


下面简单介绍一下依赖倒转原则中经常提到的两个概念,类之间的耦合和依赖注入。


1.类之间的耦合


在面向对象系统中,两个类之间通常可以发生三种不同的耦合关系(依赖关系)。


1.零耦合关系:如果两个类之间没有任何耦合关系,称为零耦合。


2.具体耦合关系:具体耦合发生在两个具体类(可实例化的类)之间,由一个类对另一个具体类实例的直接引用产生。


3.抽象耦合关系:抽象耦合关系发生在一个具体类和一个抽象类之间,也可以发生在两个抽象类之间,使两个发生关系的类之间存有最大的灵活性。由于在抽象耦合中至少有一端是抽象的,因此可以通过不同的具体实现来进行扩展。


依赖倒转原则要求客户端依赖于抽象耦合,以抽象方式耦合是依赖倒转原则的关键。

由于一个抽象耦合关系总要涉及具体类从抽象类继承,并且需要保证在任何引用到基类的地方都可以替换成其子类,因此,里氏代换原则是依赖倒转原则的基础。


2.依赖注入


  • 对象与对象之间的依赖关系是可以传递的,通过传递依赖,在一个对象中可以调用另一个对象的方法,在传递时要做好抽象依赖,针对抽象层编程。简单来说,依赖注入就是将一个类的对象传入另一个类,注入时应该尽量注入父类对象,而在程序运行时再通过子类对象来覆盖父类对象。依赖注入有以下三种方式。


1.构造注入

  • 构造注入是通过构造函数注入实例变量,代码如下:
public interface AbstractBook
{
  public void view();
}
public interface AbstractReader
{
  public void read();
}
public class ConcreteBook implements AbstractBook
{
  public void view(){
    ...
  }
}
public class ConcreteReader implements AbstractReader
{
  private AbstractBook book;
  public ConcreteReader(AbstractBook book){
    this.book = book;
  }
  public void read(){
    book.view();
  }
}

2.设值注入

  • 设值注入是通过Setter方法注入实例变量,代码如下:
public interface AbstractBook
{
  public void view();
}
public interface AbstractReader
{
  public void setBook(AbstractBook book);
  public void read();
}
public class ConcreteBoak implements AbstractBook
{
  public void view(){
    ...
  }
}
public class ConcreteReader implements AbstractReader
{
  private AbstractBook book;
  public void setBook(AbstractBook book){
    this.book = book;
  }
  public void read(){
    book.view();
  }
}

2.接口注入

  • 接口注入是通过接口方法注人实例变量,代码如下:
public interface AbstractBook
{
  public void view();
}
public interface AbstractReader
{
  public void view(){
    ...
  }
}
public class ConcretReader implements AbstractReader
{
  public void read(AbstractBook book){
    book.view();
  }
}

依赖倒转实例

  • 下面通过一个简单实例来加深对依赖倒转原则的理解。

实例说明


某系统提供一个数据转换模块,可以将来自不同数据源的数据转换成多种格式,如可以转换来自数据库的数据(DatabaseSource),也可以转换来自文本文件的数据(TextSource),转换后的格式可以是XMI文件(XMI.Transformer),也可以是XL.S文件( XLSTransformer)等。


某设计人员设计如下原始类图,用于实现该数据转换模块,如下图所示


838f511cc880419082a8d5c3a372594e.png


由于需求的变化,该系统可能需要增加新的数据源或者新的文件格式,每增加一个新的类型的数据源或者新的类型的文件格式,客户类 MainClass都需要修改源代码,以便使用新的类,违背了开闭原则。现使用依赖倒转原则对其进行重构。


实例解析


在本实例中,MainClass类针对具体类编程,如果增加新的具体类必须修改 MainClass类的源代码,系统的可扩展性和灵活性受到局限﹐因此可以对这些具体类进行抽象化,使得 MainClass类针对抽象层进行编程,而将具体类放在配置文件中,重构后的系统类图如下图所示。


image.png


在上图中,引入了两个抽象类(或接口)AbstractSource和AbstractTransformer,MainClass依赖于这两个抽象类,针对抽象类进行编程,而将具体类类名存储在配置文件config.xml中,通过XML解析技术和Java反射机制生成具体类的实例,代换 MainClass类中的抽象对象,实现真正的业务处理。在这个过程中使用了里氏代换原则,依赖倒转原则必须以里氏代换原则为基础。增加新的数据源或文件格式时,只需要增加一个AbstractSource或 AbstractTransformer类的子类,同时修改config.xml 配置文件,更换具体类类名,无须对原有类的代码进行任何修改,满足开闭原则的要求。


相关文章
|
1月前
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
181 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
7天前
|
传感器 监控 Java
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
27 5
|
2月前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
419 11
|
2月前
|
JSON Java 数据挖掘
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
|
3月前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
90 3
|
3月前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
89 2
|
3月前
|
Java
java中面向过程和面向对象区别?
java中面向过程和面向对象区别?
44 1
|
3月前
|
安全 Java API
Java中的Lambda表达式:简化代码的现代魔法
在Java 8的发布中,Lambda表达式的引入无疑是一场编程范式的革命。它不仅让代码变得更加简洁,还使得函数式编程在Java中成为可能。本文将深入探讨Lambda表达式如何改变我们编写和维护Java代码的方式,以及它是如何提升我们编码效率的。
|
13天前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
133 60
【Java并发】【线程池】带你从0-1入门线程池
|
2天前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
46 23

热门文章

最新文章