复用类(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智能体架构设计:从成本黑洞到价值引擎的解耦之道
1. 从成本黑洞到价值引擎:为什么你的AI智能体架构正在吞噬预算又到了季度技术复盘会,财务那边递过来的云账单和工程人力成本,是不是又让你倒吸一口凉气?你看着报表上那个名为“AI智能体平台”的项目,它的资源消耗曲线几…...
Claude in Excel:原生集成的AI表格协作者
1. 项目概述:这不是插件,是Excel里长出来的AI同事“Claude in Excel”这个标题刚看到时,我下意识点开几个技术社区翻了一圈,发现多数人第一反应是:“又一个AI插件?”——其实完全不是。它根本没走传统Offic…...
tools.simonwillison.net图像处理工具集:从裁剪到优化的完整指南
tools.simonwillison.net图像处理工具集:从裁剪到优化的完整指南 【免费下载链接】tools Assorted useful tools, almost entirely generated using LLMs 项目地址: https://gitcode.com/gh_mirrors/tools23/tools tools.simonwillison.net图像处理工具集是一…...
Python基础语法:访问器@property和修改器@xxx.setter
一、简介 访问器和修改器也是装饰器的一种。 property: 访问器,getter xxx.setter: 修改器,setter 访问器和修改器的根本目的是想将属性私有化,提供getter&setter去访问。 访问器和修改器能够做到访问属性其实在调用getter方法࿰…...
浏览器 Profile 环境排查:Cookie、LocalStorage、网络出口与自动化任务配置清单
一、为什么浏览器环境经常“今天能用,明天失效”很多团队遇到登录状态丢失、页面配置异常、自动化任务失败时,会先怀疑网络、脚本或系统本身。但在实际项目里,问题经常不是单点故障,而是浏览器环境缺少稳定管理:对象常…...
skills CANN开源社区贡献技能包开发指南
前言 开源社区的健康运转,不仅依赖核心代码的贡献,还需要降低贡献门槛、提供清晰的指南和自动化工具。skills仓库是CANN开源社区的"贡献技能包",提供了一系列辅助脚本、代码模板、CI检查和文档生成工具,帮助新手快速上…...
NanaZip:现代Windows文件压缩问题的终极解决方案
NanaZip:现代Windows文件压缩问题的终极解决方案 【免费下载链接】NanaZip The 7-Zip derivative intended for the modern Windows experience 项目地址: https://gitcode.com/gh_mirrors/na/NanaZip 还在为Windows文件压缩工具界面老旧、功能单一而烦恼吗&…...
别再只比参数了!从插件生态到中文优化,聊聊ChatGPT和文心一言的“隐形”差异
超越参数之争:ChatGPT与文心一言的生态与本土化实战解析 当技术评测文章还在反复比较模型参数量与发布时间时,真正影响日常工作效率的往往是那些未被量化的"软实力"。本文将从插件生态构建与中文场景优化两个维度,带您重新认识这两…...
终极AMD Ryzen调试指南:为什么你需要SMUDebugTool这个免费神器?
终极AMD Ryzen调试指南:为什么你需要SMUDebugTool这个免费神器? 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. …...
注释覆盖率从42%→91%仅用8小时,DeepSeek R1/R2模型注释优化全链路实操,
更多请点击: https://kaifayun.com 第一章:注释覆盖率跃升的工程价值与DeepSeek模型适配性洞察 注释覆盖率并非代码“装饰”,而是可量化的知识沉淀密度指标。当函数级注释覆盖率从32%提升至89%,CI流水线中PR评审平均耗时下降41%&…...
