设计模式之美——多组合少继承

news2024/11/20 15:31:26

组合优于继承,多用组合少用继承。

继承举例

假设我们要设计一个关于鸟的类。我们将“鸟类”这样一个抽象的事物概念,定义为一个抽象类 AbstractBird。所有更细分的鸟,比如麻雀、鸽子、乌鸦等,都继承这个抽象类。

我们知道,大部分鸟都会飞,那我们可不可以在 AbstractBird 抽象类中,定义一个 fly() 方法呢?答案是否定的。鸵鸟就不会飞。鸵鸟继承具有 fly() 方法的父类,那鸵鸟就具有“飞”这样的行为,不合常规。

当然在鸵鸟这个子类中重写(override)fly() 方法,让它抛出 UnSupportedMethodException 异常虽然可以解决问题,但不够优美。因为除了鸵鸟之外,不会飞的鸟还有很多,比如企鹅。对于这些不会飞的鸟来说,我们都需要重写 fly() 方法,抛出异常。

这样的设计,一方面,徒增了编码的工作量;另一方面,也违背了最小知识原则(Least Knowledge Principle,也叫最少知识原则或者迪米特法则),暴露不该暴露的接口给外部,增加了类使用过程中被误用的概率。

那就“飞/不会飞”分类吧:
在这里插入图片描述但如果我们还关注“鸟会不会叫”,那这个时候,我们又该如何设计类之间的继承关系呢?是否会飞?是否会叫?两个行为搭配起来会产生四种情况:会飞会叫、不会飞会叫、会飞不会叫、不会飞不会叫。如果我们继续沿用刚才的设计思路,那就需要再定义四个抽象类。在这里插入图片描述
如果我们还需要考虑“是否会下蛋”这样一个行为,那估计就要组合爆炸了。类的继承层次会越来越深、继承关系会越来越复杂。而这种层次很深、很复杂的继承关系,一方面,会导致代码的可读性变差。因为我们要搞清楚某个类具有哪些方法、属性,必须阅读父类的代码、父类的父类的代码……一直追溯到最顶层父类的代码。另一方面,这也破坏了类的封装特性,将父类的实现细节暴露给了子类。子类的实现依赖父类的实现,两者高度耦合,一旦父类代码修改,就会影响所有子类的逻辑。

“层深”和“复杂”影响代码可读性和可维护性,怎么办?

实际上,我们可以利用组合(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 impelents Flyable, Tweetable, EggLayable {//麻雀
  //... 省略其他属性和方法...
  @Override
  public void fly() { //... }
  @Override
  public void tweet() { //... }
  @Override
  public void layEgg() { //... }
}

不过,我们知道,接口只声明方法,不定义实现。也就是说,每个会下蛋的鸟都要实现一遍 layEgg() 方法,并且实现逻辑是一样的,这就会导致代码重复的问题。那这个问题又该如何解决呢?

我们可以针对三个接口再定义三个实现类,它们分别是:实现了 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(); // 委托
  }
}

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


public class Url {
  //...省略属性和方法
}

public class Crawler {
  private Url url; // 组合
  public Crawler() {
    this.url = new Url();
  }
  //...
}

public class PageAnalyzer {
  private Url url; // 组合
  public PageAnalyzer() {
    this.url = new Url();
  }
  //..
}

还有一些特殊的场景要求我们必须使用继承。如果你不能改变一个函数的入参类型,而入参又非接口,为了支持多态,只能采用继承来实现。比如下面这样一段代码,其中 FeignClient 是一个外部类,我们没有权限去修改这部分代码,但是我们希望能重写这个类在运行时执行的 encode() 函数。这个时候,我们只能采用继承来实现了。


public class FeignClient { // Feign Client框架代码
  //...省略其他代码...
  public void encode(String url) { //... }
}

public void demofunction(FeignClient feignClient) {
  //...
  feignClient.encode(url);
  //...
}

public class CustomizedFeignClient extends FeignClient {
  @Override
  public void encode(String url) { //...重写encode的实现...}
}

// 调用
FeignClient client = new CustomizedFeignClient();
demofunction(client);

在这里插入图片描述


我们在基于 MVC 架构开发 Web 应用的时候,经常会在数据库层定义 Entity,在 Service 业务层定义 BO(Business Object),在 Controller 接口层定义 VO(View Object)。大部分情况下,Entity、BO、VO 三者之间的代码有很大重复,但又不完全相同。我们该如何处理 Entity、BO、VO 代码重复的问题呢?

答:Entity, Bo, Vo三者之间,显然并不存在 is-a关系,首先排除使用继承。

其次三者间也并非是严格的has-a关系,half measure之一是考虑使用组合(composition) + 委托(delegation)的方式解决代码重复的问题,但并不是我心中的最佳答案.

我的答案是不解决三者间的代码重复问题。Value Class就只是Value Class, 代码重复并不是业务上的代码重复,那就让它重复吧.

这三者其实就是三个领域的东西,不能因为属性的现实意义相同就混为一谈,海水里的鱼和鱼缸里的鱼虽然都是鱼,但是用法是不一样的,哈哈

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

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

相关文章

web前端期末大作业——贵州山地旅游介绍网页1页 HTML旅游网站设计与实现

👨‍🎓学生HTML静态网页基础水平制作👩‍🎓,页面排版干净简洁。使用HTMLCSS页面布局设计,web大学生网页设计作业源码,这是一个不错的旅游网页制作,画面精明,排版整洁,内容…

Qt+Win10使用QAxWidget控件实现远程桌面控制

Windows开始菜单-运行-输入mstsc,可以打开自带的远程桌面连接工具。如果想使用Qt来实现这个工具,怎么弄? 一、Win10环境的配置 1、Win10-我的电脑-属性-远程桌面-开启 2、打开控制面板-管理工具(Win11是【Windows工具】&#xff…

Ubuntu16.4安装搜狗拼音输入法

Ubuntu16.04安装搜狗输入法,总结可以分为5步: 1.下载搜狗输入法的安装包 2.安装fcitx输入法框架 3.安装搜狗输入法 4.重启Ubuntu 5.配置搜狗输入法 1.下载搜狗输入法的安装包 百度搜索“搜狗输入法 linux” https://pinyin.sogou.com/linux. ​​​​…

CY3/CY5/CY7标记牛血清白蛋白/人血清白蛋白,CY3/CY5/CY7-BSA/HSA

产品名称:CY3/CY5/CY7标记牛血清白蛋白/人血清白蛋白 英文名称:CY3/CY5/CY7-BSA/HSA 血清白蛋白一般指人血白蛋白,是由580个氨基酸残基单链组成的蛋白质,由肝脏分泌,在血浆中含量最高,约占52%-68%左右。血…

Flink之ProcessFunction

ProcessFunction基本处理函数处理函数的功能和使用ProcessFunction 解析处理函数的分类按键分区处理函数(KeyedProcessFunction)定时器(Timer)和定时服务(TimerService)KeyedProcessFunction 的使用窗口处理…

机房动环状态综合触摸屏监控解决方案

随着移动互联网、电子商务等迅速扩张,大型互联网企业的用户数再创新高,数据量爆发式增长,企业对IDC资源的需求越来越大。机房状态安全的重要性对于一个企业来说一直以来都是一个令人头疼的问题。因此,我们推出了动环状态网络触摸屏…

Linux学习-51-进程间通信和终止线程命令

12.9 常用信号(进程间通信)及其含义 进程的管理主要是指进程的关闭与重启。我们一般关闭或重启软件,都是关闭或重启它的程序,而不是直接操作进程的。比如,要重启 apache 服务,一般使用命令"service ht…

旅游定制服务|基于SSM实现旅游个性化定制网站平台

旅游定制订单管理 旅游订单管理 作者主页:编程千纸鹤 作者简介:Java、前端、Pythone开发多年,做过高程,项目经理,架构师 主要内容:Java项目开发、毕业设计开发、面试技术整理、最新技术分享 收藏点赞不迷路…

大一新生HTML期末作业,网页制作作业——明星介绍易烊千玺网站HTML+CSS

🎉精彩专栏推荐👇🏻👇🏻👇🏻 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业…

【配电网规划】配电网网架重构、DG位置选择容量配置(Matlab代码实现)

👨‍🎓个人主页:研学社的博客 💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜…

基于主动视觉机制的深度学习--一个综合池化框架

卷积神经网络(CNN)是深度学习的代表算法之一,长期以来被广泛应用于图像识别领域。它是受到了生物处理过程的启发,通过模仿人类视觉系统(HVS)的工作机制,完成各种视觉任务等。但与HVS相比,CNN不能够像人类一样,迅速的分…

项目管理(项目管理中的重要角色项目经理)

项目经理: 项目经理是由执行组织委派,领导团队实 现项目目标的个人。 项目经理如何进行沟通: 1、通过多种方法(例如口头、书面和非言语)培养完善的技能; 2、创建、维护和遵循沟通计划和进度计划; 3、不断地以可预见的方式进行沟通; 4、寻求了解项目相关方的沟通需求…

非人工智能方向粗糙理解深度学习

非人工智能方向粗糙理解深度学习线性模型基本形式线性回归数据集学习目标均方误差监督学习弱监督学习不完全监督主动学习半监督学习迁移学习不确切监督不准确监督线性模型基本形式 你要训练的线性模型(模型不一定是线性的,为方便理解,此处以…

ceph部署踩坑——OSD服务无法启动

前话:部署ceph时,所有OSD节点的服务启动报错,无法正常启动服务。 问题现象:OSD节点启动ceph-osd0.service服务报错,start request repeated too quickly for ceph-osd0.service 解决过程: 1、修改启动的…

线上演唱会成歌手身价新标准,十月天传媒正式合作腾格尔

曾记得某位音乐人说过,每一位歌手都有自己的段位,其实也就是所谓的身价和演出费用。歌手的身价段位,要通过演唱会的出场费来体现,可惜最近两年由于特殊原因,线下演唱会已经很难举办。 既然线下演唱会很难举办&#xff…

Nginx:过滤模块的实现

文章目录1、过滤模块的概念2、过滤模块原理2.1、过滤链表2.2、执行顺序3、过滤模块的实现3.1、编写模块结构3.1.1、模块配置结构3.1.2、模块配置命令3.1.3、模块上下文3.1.4、定义模块3.2、设置响应头3.3、设置响应体3.4、编译测试3.5、完整代码4、参考文章参考<零声教育>…

牛客网语法篇练习分支控制(二)

1.牛牛的通勤路上有两种选择&#xff0c;要么走路&#xff0c;要么打车&#xff0c;牛牛走路的速度是 1m/s 。打车的速度的 10m/s &#xff0c;但是打车需要等出租车 10 s&#xff0c;请你计算牛牛想尽快到公司应该选择打车还是走路。 a int(input()) if a < a / 10 10:p…

单商户商城系统功能拆解35—分销应用—分销概览

单商户商城系统&#xff0c;也称为B2C自营电商模式单店商城系统。可以快速帮助个人、机构和企业搭建自己的私域交易线上商城。 单商户商城系统完美契合私域流量变现闭环交易使用。通常拥有丰富的营销玩法&#xff0c;例如拼团&#xff0c;秒杀&#xff0c;砍价&#xff0c;包邮…

热烈祝贺|盏百年生物科技有限公司受邀参加2022世界滋补产业生态大会

自2017年“盏百年”品牌创立以来&#xff0c;公司致力于以鲜炖燕窝为导向&#xff0c;以燕窝全产业链建设为核心&#xff0c;打造中国燕窝文化专营品牌。 5年来&#xff0c;盏百年凭借实体体验服务店连锁经营&#xff0c;打造一对一私人滋补管家这一创新模式&#xff0c;树立了…

什么是分布式锁?他解决了什么样的问题?

相信对于朋友们来说&#xff0c;锁这个东西已经非常熟悉了&#xff0c;在说分布式锁之前&#xff0c;我们来聊聊单体应用时候的本地锁&#xff0c;这个锁很多小伙伴都会用 ✔本地锁 我们在开发单体应用的时候&#xff0c;为了保证多个线程并发访问公共资源的时候&#xff0c;…