复用类(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兼容性:部分手机不兼容
从事嵌入式单片机的工作算是符合我个人兴趣爱好的,当面对一个新的芯片我即想把芯片尽快搞懂完成项目赚钱,也想着能够把自己遇到的坑和注意事项记录下来,即方便自己后面查阅也可以分享给大家,这是一种冲动,但是这个或许并不是原厂希望的,尽管这样有可能会牺牲一些时间也有哪天原…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...

实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...