由“为什么VO不能继承PO?” 引出的为什么组合优于继承?

news2024/11/15 9:20:26

简述VO、DTO、PO的概念。

如下概念是我个人的理解:

  • VO(View Object)视图对象,用于展示,这很好理解,就是前端页面所需数据封装,一般所需要的属性比 PO 多并且。
  • DTO(Data Transfer Object)数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载。(个人理解:原先需要获取用户信息和用户订单需要调用俩个接口,将返回数据整合为一个 DTO,调用一次就可以获取所有数据)
  • PO实体类,一般与持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应 PO 的一个(或若干个)属性。

对于这些概念,网上众说纷纭。没要必要纠结具体的定义,不要纠结DTO与VO的区别是什么。在实际编码过程中,按照每个人自己的规范来做就好了。在实际的编码当中,Service层处理数据的时候,我们使用DTO来进行数据的传输,然后再包装成 VO 返回页面所需数据就可以了

关于 VO 层的一次设计

在工作中编码过程中的感受, 在写 VO 和 DTO 的时候,发现总是在重复写代码,不断的拷贝对象字段。

这其实是一种组合方式的 VO 设计,在这个例子中,我们创建了 GoodsVO 类,通过组合实体类和定制属性,实现了前端展示所需的信息。

组合方式的VO设计

// 实体类
public class Goods {
    private Long id;
    private String name;
    private BigDecimal price;
    private String description;
    // 其他属性和方法...
}

// VO类通过组合实现
public class GoodsVO {
    private Long id;
    private String name;
    private BigDecimal price;

    // 构造方法或工厂方法,将实体类转换为VO类
    public GoodsVO(Goods goods) {
        this.id = goods.getId();
        this.name = goods.getName();
        this.price = goods.getPrice();
    }

    // Getter 和 Setter 方法...
}

通过组合,我们实现了 VO 类对实体类的定制化展示,同时保留了灵活性,使得 VO 类的设计不受实体类的限制。但会重复编写大量的 setget 代码,对象之间频繁复制。

继承方式的VO设计

VO 类继承 PO 实体类,转而调用实体类的部分属性,可以达到复用相同属性的效果。

如果你决定使用继承,下面是一个简单的示例。在这个例子中,GoodsVO 类继承自 Goods 类,通过继承,GoodsVO 类拥有了 Goods 类的所有属性和方法。

// VO类通过继承实现
public class GoodsVO extends Goods {
    // 新增或覆盖需要展示的属性
    private String displayInfo;

    // 构造方法或工厂方法,将实体类转换为VO类
    public GoodsVO(Goods goods) {
        // 调用父类构造方法,复制基本属性
        super.setId(goods.getId());
        super.setName(goods.getName());
        super.setPrice(goods.getPrice());
        super.setDescription(goods.getDescription());

        // 初始化VO类特有的属性
        this.displayInfo = "Additional information for display";
    }

    // Getter 和 Setter 方法...
}

通过继承,我们可以在 GoodsVO 类中新增或覆盖需要展示的属性,实现对特定场景的定制化。但需要注意的是,继承关系通常带来类之间的紧密耦合,可能会限制类的扩展性和灵活性。

这种方式生成的文档很不清晰,每个接口都有一堆没返回给前端的字段;业务中的额外字段会污染VOPOJO 承担的责任太重了,

为什么组合优先于继承

我在个人项目中喜欢直接使用各种 model 来继承 po,单纯的因为可以直接省去写各种重复 gettersetter 的步骤,并且不需要使用各种拷贝工具。但是,搞vodopo,还有其他各种 o,是为了解藕它们之间的联系,而继承却是建立它们之间的耦合关系,确实是会限制类的扩展性和灵活性。

比如:当表结构做了增减,do 变了 vo 也变了,你的接口返回项也会变化,线上的项目容易翻车(虽然我觉得表结构不会那么轻易变化),不利于节流,会返回很多不必要的字段给前端,容易被不怀好意的人推测出数据库的字段。

面向对象编程中,有一条非常经典的设计原则,那就是:组合优于继承,多用组合少用继承。同样地,在《阿里巴巴Java开发手册》中有一条规定:谨慎使用继承的方式进行扩展,优先使用组合的方式实现

在这里插入图片描述
在编程中,继承组合是用于在面向对象语言中设计和构建类和对象的两种基本技术。

继承,它允许一个类(称为派生类或子类)从另一个类(称为基类或超类)继承属性和行为。换句话说,子类“是”超类的一种类型。它建立了一种“是”关系。例如,如果我们有一个类 Animal 和一个类 Dog ,则 Dog 类继承自 Animal ,因为狗是一种动物。

组合,涉及使用其他对象作为组件来构建对象。类不是继承属性和行为,而是使用其他类的实例来实现其功能。它建立了“有”关系。例如,Car 类可以具有 Engine 类和 Wheel 类的组合。

有一张图可以很形象的表示他们两者之间的关系

在这里插入图片描述

为什么不推荐使用继承

降低类之间的耦合度: 在继承关系中,子类与父类之间存在紧密的耦合关系,子类对父类的任何修改都可能产生影响。通过组合,类之间的关系更为松散,一个类的改变通常不会影响到其他类,除非它们共享相同的成员变量。

假设我们要设计一个关于鸟的类。我们将“鸟”这样一个抽象的事物概念,定义为一个抽象类AbstractBird 。所有更细分的鸟,比如麻雀、鸽子、乌鸦等,都继承这个抽象类。我们知道,大部分鸟都会飞,那我们可不可以在 AbstractBird抽象类中,定义一个 fly() 方法呢?

答案是否定的。尽管大部分鸟都会飞,但也有特例,比如鸵鸟就不会飞。鸵鸟继承具有 fly() 方法的父类,那鸵鸟就具有“飞”这样的行为,这显然不对。如果在鸵鸟这个子类中重写 fly() 方法,让它抛出UnSupportedMethodException异常呢?具体的代码实现如下所示:


public class AbstractBird {
  //...省略其他属性和方法...
  public void fly() { //... }
}

public class Ostrich extends AbstractBird { //鸵鸟
  //...省略其他属性和方法...
  public void fly() {
    throw new UnSupportedMethodException("I can't fly.'");
  }
}

这种写法虽然可以解决问题,但不优雅。因为除了法师之外,不会飞的鸟还有很多,比如企鹅。对于这些不会飞的鸟来说,全部都去重写 fly() 方法,抛出异常,完全属于代码重复。理论上这些不会飞的鸟根本就不应该拥有 fly() 方法,让不会飞的鸟暴露 fly() 接口给外部,增加了被误用的概率。

要解决上面的问题,就得让 AbstractBird 类派生出两个更加细分的抽象类:会飞的鸟类 AbstractFlyableBird 和不会飞的鸟类 AbstractUnFlyableBird ,让麻雀、乌鸦这些会飞的鸟都继承 AbstractFlyableBird ,让鸵鸟、企鹅这些不会飞的鸟,都继承 AbstractUnFlyableBird 类。具体的继承关系如下图所示:
在这里插入图片描述
这样一来,继承关系变成了三层。但是如果我们不只关注“鸟会不会飞”,还要继续关注“鸟会不会叫”,将鸟划分得更加细致时呢?两个关注行为自由搭配起来会产生四种情况:会飞会叫、不会飞会叫、会飞不会叫、不会飞不会叫。如果继续沿用刚才的设计思路,继承层次会再次加深。
在这里插入图片描述
如果继续增加“鸟会不会下蛋”这样的行为,类的继承层次会越来越深、继承关系会越来越复杂。而这种层次很深、很复杂的继承关系,一方面,会导致代码的可读性变差。因为我们要搞清楚某个类具有哪些方法、属性,必须阅读父类的代码、父类的父类的代码……一直追溯到最顶层父类的代码。另一方面,这也破坏了类的封装特性,将父类的实现细节暴露给了子类。子类的实现依赖父类的实现,两者高度耦合,一旦父类代码修改,就会影响所有子类的逻辑。

继承最大的问题就在于:继承层次过深、继承关系过于复杂时会影响到代码的可读性和可维护性。

组合相比继承有哪些优势

复用性是面向对象技术带来的很棒的潜在好处之一。如果运用的好的话可以帮助我们节省很多开发时间,提升开发效率。但是,如果被滥用那么就可能产生很多难以维护的代码。作为一门面向对象开发的语言,代码复用是 Java 引人注意的功能之一。Java代码的复用有继承、组合以及委托三种具体的实现形式。

对于上面提到的继承带来的问题,可以利用**组合(composition)、接口、委托(delegation)**三个技术手段一块儿来解决。

接口表示具有某种行为特性。针对“会飞”这样一个行为特性,我们可以定义一个 Flyable 接口,只让会飞的鸟去实现这个接口。对于会叫、会下蛋这些行为特性,我们可以类似地定义 Tweetable 接口、 EggLayable 接口。我们将这个设计思路翻译成 Java 代码的话,就是下面这个样子:


public interface Flyable {
  void fly();
}
public interface Tweetable {
  void tweet();
}
public interface EggLayable {
  void layEgg();
}
public class Ostrich implements Tweetable, EggLayable {//鸵鸟
  //... 省略其他属性和方法...
  @Override
  public void tweet() { //... }
  @Override
  public void layEgg() { //... }
}
public class Sparrow implements Flayable, Tweetable, EggLayable {//麻雀
  //... 省略其他属性和方法...
  @Override
  public void fly() { //... }
  @Override
  public void tweet() { //... }
  @Override
  public void layEgg() { //... }
}

不过,接口只声明方法,不定义实现。也就是说,每个会下蛋的鸟都要实现一遍 layEgg() 方法,并且实现逻辑几乎是一样的(可能极少场景下会不一样),这就会导致代码重复的问题。那这个问题又该如何解决呢?有以下两种方法。

1.使用委托

针对三个接口再定义三个实现类,它们分别是:实现了 fly() 方法的 FlyAbility 类、实现了 tweet() 方法的TweetAbility 类、实现了 layEgg() 方法的 EggLayAbility 类。然后,通过组合和委托技术来消除代码重复。


public interface Flyable {
  void fly()}
public class FlyAbility implements Flyable {
  @Override
  public void fly() { //... }
}
//省略Tweetable/TweetAbility/EggLayable/EggLayAbility

public class Ostrich implements Tweetable, EggLayable {//鸵鸟
  private TweetAbility tweetAbility = new TweetAbility(); //组合
  private EggLayAbility eggLayAbility = new EggLayAbility(); //组合
  //... 省略其他属性和方法...
  @Override
  public void tweet() {
    tweetAbility.tweet(); // 委托
  }
  @Override
  public void layEgg() {
    eggLayAbility.layEgg(); // 委托
  }
}

2.使用Java8的接口默认方法

Java8 中,我们可以在接口中写默认实现方法。使用关键字 default 定义默认接口实现,当然这个默认的方法也可以重写。

public interface Flyable {
  default void fly() {
    //默认实现... 
  }
}


public interface Flyable {
  default void fly() {
    //默认实现... 
  }
}

public interface Tweetable {
  default void tweet() {
    //默认实现... 
  }
}

public interface EggLayable {
  default void layEgg() {
    //默认实现... 
  }
}

public class Ostrich implements Tweetable, EggLayable {//鸵鸟
  //... 省略其他属性和方法...
}
public class Sparrow implements Flayable, Tweetable, EggLayable {//麻雀
  //... 省略其他属性和方法...
}

继承主要有三个作用:表示is-a关系、支持多态特性、代码复用。而这三个作用都可以通过其他技术手段来达成。比如is-a关系,我们可以通过组合和接口的has-a关系来替代;多态特性我们也可以利用接口来实现;代码复用我们可以通过组合和委托来实现。所以,从理论上讲,通过组合、接口、委托三个技术手段,我们完全可以替换掉继承,在项目中不用或者少用继承关系,特别是一些复杂的继承关系。

参考文章:
https://blog.csdn.net/fuzhongmin05/article/details/108646872

https://blog.csdn.net/Mr_YanMingXin/article/details/139540085

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2098123.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

二手电脑配置给你不一样的成就感之四

今天测了e3 1220 v1 没有想象中的好,鲁大师才评20多万分,比fm2平台6600k强一点点有限。 6600k fm2平台是20万出头。cpuz的评分还是比较高,实际使用效果比6600k稍好一些。 单盒能力确实比apu强一些。同样拿world frvr 测试,调到最低…

微分方程_by小崔说数

可降解的微分方程 不显含x:y两撇dp/dxdp/dy*dy/dx 不显含y:dp/dx 都是y撇等于p 自变量与因变量呼唤 讲解为一阶线性微风方程 ,变成可分离得 公式得 高阶可降解得微分方程 通解非齐次特解齐次通解 非齐次特解;解得叠加原…

旗晟智能助推浙大海创人形机器人创新中心发展!

8月27日上午,余杭区成功举办机器人产业高质量发展大会。会上,由地方政府与浙江大学共同建设的人形机器人产业创新中心成立并揭牌。该中心目标明确,致力于打造机器人领域国内、国际的高等级创新高地。浙江大学校领导、院士专家、机器人企业代表…

js调试--本地替换

js调试--本地替换 一、本地替换的作用二、操作方法(以百度首页为例)1、选中目标资源地址二、替换为本地资源三、修改本地内容一、本地替换的作用 本地替换的作用就是将原本访问服务器的数据改成访问本地。 二、操作方法(以百度首页为例) 1、选中目标资源地址 以百度首页…

智能工厂MES实施规划

智能工厂MES(制造执行系统)实施规划是一个复杂而系统的过程,旨在通过数字化手段提升工厂的生产效率、降低成本并提高产品质量。以下是一个全面的智能工厂MES实施规划方案,涵盖主要步骤和关键点: 一、前期准备与需求分析…

手撸瀑布流

一、需求? 要求实现 一排两列 瀑布流样式,样式如下:其中A为容器,B为组件样式,卡片高度会因为标题的多少来自适应。 二、解法 1.使用CSS的column(⚠️不推荐) 使用CSS 属性 column 用来设置…

朋友们!pinterest视频保存教学给你们做出来啦

有没有在为pinterest视频无法下载而烦恼的小伙伴呀?我反正是被这个困扰好久了,每每看到喜欢的视频素材不能下载,真的很捉急!最近发现了这款神器,就是之前给大家推荐【水印抹布】app可以完美解决pinterest视频保存的问题…

Vue2与Vue3区别以及兼容那些浏览器酌情使用

一、vue2与vue3区别 vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。 项目地址:https://gitcode.com/gh_mirrors/vu/vue 免费下载资源 1.根节点不同 vue2中必须要有根标签vue3中可以没…

pod进阶:

pod进阶: pod的生命周期当中的状态: 1、Running 运行中,pod已经分配到节点上,且pod内的容器正常运行。正常状态(ready 1/1) 2、complete 完成之后退出,容器内的返回码是0 echo $? 表示容器是…

基于Easy-Wav2lip-Gradio的AI数字人

数字人技术近年来在多个领域得到广泛应用,从虚拟主播到电影特效,都需要精确的音视频同步技术来实现逼真的效果。传统的嘴型同步技术往往面临着准确性不高、处理速度慢等问题。为了解决这些问题,原有的Wav2Lip项目应运而生。Wav2Lip通过人工智能技术,实现了音频和视频的高精…

从LED硬件控制流程认识Android架构!

0.Android架构图 一上来就是一张框架图,发明了框架图的天才真是个天才! 简单点评一下: 1.对于安卓应用开发来说:App层以下都是操作系统,反正我只关心Android Studio(其实是SDK)给我什么包&…

字节-人工智能编辑代码方向面试-题目记录

问的都是自己简历里写的!不会就不要写 简述一下几个排序算法 二分查找的时间复杂度(是O(log2n)!!!) find函数和count函数的时间复杂度都是O(n),因为都是遍历整个数组来找的&#x…

Ubuntu Linux Server安装Kubernetes

本文主要描述在Ubuntu Linux Server操作系统中安装Kubernetes云原生对应的microk8s组件。 sudo snap install microk8s --classic 如上所示,在Ubuntu服务器中安装microk8s组件完成,对应的版本是microk8s v1.30版本 microk8s enable dashboard 如上所…

Java小白一文讲清Java中集合相关的知识点(二)

List List接口和常用方法 基本介绍 List接口是Collection接口的子接口 List集合类中的元素有序–即添加顺序和取出顺序一致、且可重复 public class Journey {SuppressWarnings({"all"})public static void main(String[] args) {List list new ArrayList();li…

基于Video-Retalking-Gradio的AI数字人

随着数字内容的普及,如何在视频中实现高精度的音频与唇形同步成为一个重要课题。传统方法通常需要大量的人工干预和调整,效果也往往不尽如人意。为了应对这一挑战,VideoReTalking 系统应运而生。由西安电子科技大学和腾讯AI实验室的研究人员联合开发,VideoReTalking 是一个…

【微信小程序】微信小程序如何使用 MobX 进行状态管理?

微信小程序官方在 2019 年针对小程序发布了 MobX 辅助绑定库,可以让我们在微信小程序中使用 Mobx 进行状态管理: mobx-miniprogram:相当于 MobX;mobx-miniprogram-bindings:针对小程序的 MobX 辅助绑定库,…

【全志H616】【开源】 ARM-Linux 智能分拣项目:阿里云、网络编程、图像识别

【全志H616】【开源】 ARM-Linux 智能分拣项目:阿里云、网络编程、图像识 文章目录 【全志H616】【开源】 ARM-Linux 智能分拣项目:阿里云、网络编程、图像识1、实现功能2、软件及所需环境3、逻辑流程图及简述3.1 完整逻辑流程图3.2 硬件接线3.3 功能简述…

【TomCat】安装部署

首先得进行Java的安装和部署java1.8对应tomcat9 TomCat下载Apache Tomcat - Apache Tomcat 10 Software Downloads

Vue(四) 组件、单文件组件、非单文件组件,重要的内置关系

文章目录 1. 组件2. 非单文件组件2.1 定义组件2.2 注册组件2.3 使用组件2.4 组件命名、标签等注意点2.5 组件嵌套2.6 VueComponent构造(这部分看视频更易理解)2.7 内置关系 3. 单文件组件 1. 组件 组件是实现局部功能代码和资源的集合 传统方式&#x…

unreal engine5.4.3动画重定向

UE5系列文章目录 文章目录 UE5系列文章目录前言 前言 ue5.4和ue3动画重定向之间存在差异,跟ue5.2差别更大一点,总之ue5.4越来越简化动画重定向,不想之前还需要制作RTG文件 这是ue5.3.2的制作动画重定向的界面 这是ue5.4.2的制作动画重定向…