复用类(2):代理、结合使用组合和继承
1 代理
第三种关系称为代理,这是继承与组合之间的中庸之道,因为我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承)。例如,太空船需要一个控制模版:
/*** 宇宙飞船控制模版*/
public class SpaceShipControls {void up(int velocity) {}void down(int velocity) {}void left(int velocity) {}void right(int velocity) {}// 向前void forward(int velocity) {}// 返回void back(int velocity) {}// 涡轮推动void turboBoost() {}
}
构造太空船的一种方式是使用继承:
/*** 太空船*/
public class SpaceShip extends SpaceShipControls {private String name;public SpaceShip(String name) {this.name = name;}public String toString() {return name;}public static void main(String[] args) {SpaceShip protector = new SpaceShip("NSEA protector");protector.forward(100);}}
然而,SpaceShip并非真正的SpaceShipControls类型,即便你可以“告诉”SpaceShip向前运动(forward())。更准确地讲,SpaceShip包含了SpaceShipControls,与此同时,SpaceShipControls的所有方法在SpaceShip中都暴露了出来。代理解决了此难题:
/*** 太空船代理*/
public class SpaceShipDelegation {private String name;private SpaceShipControls controls = new SpaceShipControls();public SpaceShipDelegation(String name) {this.name = name;}// Delegated methods:public void back(int velocity) {controls.back(velocity);}public void down(int velocity) {controls.down(velocity);}public void forward(int velocity) {controls.forward(velocity);}public void left(int velocity) {controls.left(velocity);}public void right(int velocity) {controls.right(velocity);}public void turboBoost() {controls.turboBoost();}public void up(int velocity) {controls.up(velocity);}public static void main(String[] args) {SpaceShipDelegation protector = new SpaceShipDelegation("NSEA Protector");protector.forward(100);}
}
可以看到,上面的方法是如何转递给了底层的controls对象,而其接口由此也就与使用继承得到的接口相同了。但是,我们使用代理时可以拥有更多的控制力,因为我们可以选择只提供在成员对象中的方法的某个子集。
2 结合使用组合和继承
同时使用组合和继承是很常见的事。下例就展示了同时使用这两种技术,并配以必要的构造器初始化,来创建更加复杂的类:
/*** 盘*/
class Plate {Plate(int i) {System.out.println("Plate constructor");}
}/*** 餐盘*/
class DinnerPlate extends Plate {DinnerPlate(int i) {super(i);System.out.println("DinnerPlate constructor");}
}/*** 餐具*/
class Utensil {Utensil(int i) {System.out.println("Utensil constructor");}
}/*** 勺子*/
class Spoon extends Utensil {Spoon(int i) {super(i);System.out.println("Spoon constructor");}
}/*** 叉子*/
class Fork extends Utensil {Fork(int i) {super(i);System.out.println("Fork constructor");}
}/*** 刀具*/
class Knife extends Utensil {Knife(int i) {super(i);System.out.println("Knife constructor");}
}/*** 自定义*/
class Custom {Custom(int i) {System.out.println("Custom constructor");}
}/*** 餐位*/
public class PlaceSetting extends Custom {private Spoon sp;private Fork frk;private Knife kn;private DinnerPlate pl;PlaceSetting(int i) {super(i + 1);sp = new Spoon(i + 2);frk = new Fork(i + 3);kn = new Knife(i + 4);pl = new DinnerPlate(i + 5);System.out.println("PlaceSetting constructor");}public static void main(String[] args) {PlaceSetting x = new PlaceSetting(9);}
}
虽然编译器强制你去初始化基类,并且要求你要在构造器起始处就要这么做,但是它并不监督你必须将成员对象也初始化,因此在这一点上你自己必须时刻注意。
这些类如此清晰地分离着实使人惊讶。甚至不需要这些方法的源代码就可以复用这些代码,我们至多只需要导入一个包。(对于继承与组合来说都是如此。)
3 确保正确清理
java中没有C++中析构函数的概念。析构函数是一种在对象被销毁时可以被自动调用的函数。其原因可能是因为在java中,我们的习惯只是忘掉而不是销毁对象,并且让垃圾回收器在必要时释放其内存。
通常这样做是好事,但有时类可能要在其声明周期内执行一些必需的清理活动。你并不知道垃圾回收器何时将会被调用,或者它是否将被调用。因此,如果你想要某个类清理一些东西,就必须显示地编写一个特殊方法来做这件事,并要确保客户端程序员知晓他们必须要调用这一方法。必须将这一清理工作置于finally子句之中,以预防异常的出现。
请思考一下下面这个能在屏幕上绘制图案的计算机辅助设计系统示例:
/*** 形状*/
class Shape {Shape(int i) {System.out.println("Shape constructor");}void dispose() {System.out.println("Shape dispose");}
}/*** 圆形*/
class Circle extends Shape {Circle(int i) {super(i);System.out.println("Drawing Circle");}void dispose() {System.out.println("Erasing Circle");super.dispose();}
}/*** 三角形*/
class Triangle extends Shape {Triangle(int i) {super(i);System.out.println("Drawing Triangle");}void dispose() {System.out.println("Erasing Triangle");super.dispose();}
}/*** 线*/
class Line extends Shape {private int start, end;Line(int start, int end) {super(start);this.start = start;this.end = end;System.out.println("Drawing Line: " + start + ", " + end);}void dispose() {System.out.println("Erasing Line: " + start + ", " + end);super.dispose();}
}public class CADSystem extends Shape {private Circle c;private Triangle t;private Line[] lines = new Line[3];CADSystem(int i) {super(i + 1);for (int j = 0; j < lines.length; j++)lines[j] = new Line(j, j * j);c = new Circle(1);t = new Triangle(1);System.out.println("Combined constructor");}public void dispose() {System.out.println("CADSystem的dispose()");t.dispose();c.dispose();for (int i = lines.length - 1; i >= 0; i--)lines[i].dispose();super.dispose();}public static void main(String[] args) {CADSystem x = new CADSystem(47);try {// Code and exception handling...} finally {x.dispose();}}}
此系统中的一切都是某种Shape(Shape自身就是一种Object,因为Shape继承自根类Object)。每个类都覆写Shape的dispose()方法,并运用super来调用该方法的基类版本。尽管对象生命期中任何被调用的方法都可以做一些必需的清理工作,但是Circle、Triangle和Line这些特定的Shape类仍然都带有可以进行“绘制”的构造器。每个类都有自己的dispose()方法将存于内存之中的东西恢复到对象存在之前的状态。
在main()中可以看到try和finally这两个关键字。关键字try表示,下面的块(用一组大括号括起来的范围)是所谓的保护区(guarded region),这意味着他需要被特殊处理。其中一项特殊处理就是无论try块是怎么退出的,保护区后的finally子句中的代码总是要被执行的。这里finally子句表示的是“无论发生什么事,一定要为x调用dispose()”。
在清理方法(dispose())中,还必须注意对基类清理方法和成员对象清理方法的调用顺序,以防某个子对象依赖于另一个子对象情形的发生。一般而言,所采用的形式应该与C++编译器在其析构函数上所施加的形式相同:首先,执行类的所有特定的清理动作,其顺序同生成顺序相反(通常这就要求基类元素仍旧存活);然后,就如我们所示范的那样,调用基类的清理方法。
许多情况下,清理并不是问题,仅需让垃圾回收器完成该动作就行。但当必须亲自处理清理时,就得多做努力并多加小心。因为,一旦涉及垃圾回收,能够信赖的事就不会很多了。垃圾回收器可能永远也无法被调用,即使被调用,它也可能以任何它想要的顺序来回收对象。最好的办法是除了内存以外,不能依赖垃圾回收器去做任何事。如果需要进行清理,最好是编写自己的清理方法,但不要使用finalize()。
4 名称屏蔽
如果java的基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名并不会屏蔽其在基类中的任何版本(这一点与C++不同)。因此,无论是在该层或者它的基类中对方法进行定义,重载机制都可以正常工作:
class Homer {char doh(char c) {System.out.println("doh(char)");return 'd';}float doh(float f) {System.out.println("doh(float)");return 1.0f;}
}class Milhouse {
}class Bart extends Homer {void doh(Milhouse m) {System.out.println("doh(Milhouse)");}
}public class Hide {public static void main(String[] args) {Bart b = new Bart();b.doh(1);b.doh('x');b.doh(1.0f);b.doh(new Milhouse());}
}
可以看到,虽然Bart引入了一个新的重载方法(C++中若要完成这项工作则需要屏蔽基类方法),但是在Bart中Homer的所有重载方法都是可用的。使用与基类完全相同的特征签名及返回类型来覆盖具有相同名称的方法,是一件极其平常的事。但它也令人迷惑不解。
java SE5新增了@Override注解,它并不是关键字,但是可以把它当做关键字使用。当你想要覆写某个方法时,可以选择添加这个注解,在你不留心重载而并非覆写了该方法时,编译器就会生成一条错误消息:
class Lisa extends Homer {@Overridevoid doh(Milhouse m) {System.out.println("doh(Milhouse)");}
}
这样,@Override注解可以防止你在不想重载时而意外地进行了重载。
相关文章:
复用类(2):代理、结合使用组合和继承
1 代理 第三种关系称为代理,这是继承与组合之间的中庸之道,因为我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承)。例如ÿ…...
浅谈云计算07 | 云安全机制
云计算安全机制 一、引言二、加密技术:数据的隐形护盾三、散列机制:数据完整性的忠诚卫士四、数字签名:数据来源与真伪的鉴定专家五、公钥基础设施(PKI):信任的基石六、身份与访问管理(IAM&…...
【机器学习】零售行业的智慧升级:机器学习驱动的精准营销与库存管理
我的个人主页 我的领域:人工智能篇,希望能帮助到大家!!!👍点赞 收藏❤ 在当今数字化浪潮汹涌澎湃的时代,零售行业正站在转型升级的十字路口。市场竞争的白热化使得企业必须另辟蹊径࿰…...
深入理解 Entity、VO、QO、DTO 的区别及其在 MVC 架构中的应用
文章背景 在现代软件开发中,我们经常会接触到各种数据结构的概念,比如 Entity、VO(Value Object)、QO(Query Object)、DTO(Data Transfer Object)等。这些概念尽管看似相似ÿ…...
vue集成高德地图API实现坐标拾取功能
安装与配置: 组件 | vue-amapDescriptionhttps://elemefe.github.io/vue-amap/#/zh-cn/introduction/install简介 | vuemap/vue-amap简介https://vue-amap.guyixi.cn/zh-cn/introduction/introduction.html 我的应用 | 高德控制台高德开放平台官网控…...
Spring Boot Actuator 详细介绍
Spring Boot Actuator 详细介绍 1. 简介 Spring Boot Actuator 是 Spring Boot 提供的一个用于监控和管理应用程序的强大功能模块。它可以帮助我们了解应用程序的运行状况、指标收集、环境信息、日志级别管理等。 2. 添加依赖 2.1 在 pom.xml 中添加以下依赖: …...
联通用户管理系统(一)
#联通用户管理系统(一) 1.新建项目 如果你是windows的话,界面应该是如下的: 2.创建app python manage.py startapp app01一般情况下:我们是在pycharm的终端中运行上述指令,但是pychrm中为我们提供了工具…...
go chan底层分析
go chan底层分析 底层源码hchanmakechan 方法 环形队列阻塞机制向管道写数据流程图源码 从管道读数据流程图源码 关闭通道 底层源码 hchan type hchan struct {qcount uint // 当前队列中剩余元素个数dataqsiz uint // 环形队列长度,即可以…...
idea上git log面板的使用
文章目录 各种颜色含义具体的文件的颜色标签颜色🏷️ 节点和路线 各种颜色含义 具体的文件的颜色 红色:表示还没有 git add 提交到暂存区绿色:表示已经 git add 过,但是从来没有 commit 过蓝色:表示文件有过改动 标…...
WOA-Transformer鲸鱼算法优化编码器时间序列预测(Matlab实现)
WOA-Transformer鲸鱼算法优化编码器时间序列预测(Matlab实现) 目录 WOA-Transformer鲸鱼算法优化编码器时间序列预测(Matlab实现)预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现WOA-Transformer鲸鱼算法优化编…...
dock 制作 python环境
报错 :Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) 解决方法 配置加速地址 vim /etc/docker/daemon.json 添加以下内容 { "registry-mirror…...
2025第3周 | json-server的基本使用
目录 1. json-server是什么?2. json-server怎么用?2.1 安装2.2 创建db.json2.3 启动服务2.4 查看效果 3. 前端进行模拟交互3.1 创建demo.html3.2 创建demo.js 2025,做想做的事,读想读的书,持续学习,自律生活…...
Autodl转发端口,在本地机器上运行Autodl服务器中的ipynb文件
通过 SSH 隧道将远程端口转发到本地机器 输入服务器示例的SSH指令和密码,将远程的6006端口代理到本地 在服务器终端,激活conda虚拟环境 conda activate posecnnexport PYOPENGL_PLATFORMegljupyter notebook --no-browser --port6006 --allow-root从…...
flutter Get GetMiddleware 中间件不起作用问题
当使用 get: ^5.0.0-release-candidate-9.2.1最新版本时,中间件GetMiddleware各种教程都是让我们在redirect中实现,比如: overrideRouteSettings? redirect(String? route) {return RouteSettings(name: "/companyAuthIndexPage"…...
RabbitMQ(三)
RabbitMQ中的各模式及其用法 工作队列模式一、生产者代码1、封装工具类2、编写代码3、发送消息效果 二、消费者代码1、编写代码2、运行效果 发布订阅模式一、生产者代码二、消费者代码1、消费者1号2、消费者2号 三、运行效果四、小结 路由模式一、生产者代码二、消费者代码1、消…...
【Python】Python之locust压测教程+从0到1demo:基础轻量级压测实战(1)
文章目录 一、什么是Locust二、Locust 架构组成三、实战 Demo准备一个可调用的接口编写一个接口测试用例编写一个性能测试用例执行性能测试用例代码1、通过 Web UI 执行(GUI模式)2、通过命令行执行(非GUI模式) 小知识:…...
【JavaScript】基础内容,HTML如何引用JavaScript, JS 常用的数据类型
HTML 嵌入 Javascript 的方式 引入外部 js 文件 <head> <script Language "javaScript" src"index.js"/> </head>内部声明 <head> <script language"javascript">function hello(){alert("hello word&qu…...
vue使用自动化导入api插件unplugin-auto-import,避免频繁手动导入
unplugin-auto-import是一个现代的自动导入插件,旨在简化前端开发中的导入过程,减少手动导入的繁琐工作,提升开发效率。它支持多种构建工具,包括Vite、Webpack、Rollup和esbuild,并且可以与TypeScript配合使用&…...
在 C# 中的Lambda 表达式
在 C# 中,Lambda 表达式是用来定义匿名函数的一种简洁方式,通常用于简化代码,尤其是在 LINQ 查询、事件处理或方法作为参数的场景中。Lambda 表达式的语法如下: 基本语法 (parameters) > expression_or_statement_blockparam…...
奉加微PHY6230兼容性:部分手机不兼容
从事嵌入式单片机的工作算是符合我个人兴趣爱好的,当面对一个新的芯片我即想把芯片尽快搞懂完成项目赚钱,也想着能够把自己遇到的坑和注意事项记录下来,即方便自己后面查阅也可以分享给大家,这是一种冲动,但是这个或许并不是原厂希望的,尽管这样有可能会牺牲一些时间也有哪天原…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...
