# 2026-07-24 多态(Polymorphism)基础练习(课堂练习用) > **说明**:以下习题围绕今日授课核心内容——**多态(Polymorphism)** 展开,涵盖多态的概念与前提、多态成员访问特点、向上转型/向下转型、instanceof 运算符、多态与抽象类/接口的结合、接口默认方法/静态方法以及模板方法设计模式。 > > 题目分为三部分:**随堂练习**(多态基础,4 题)、**进阶练习**(综合挑战,2 题)和 **课后作业**(接口默认方法/静态方法 + 模板方法,2 题)。 --- # 第一部分:随堂练习 > 以下练习围绕多态的基础概念和用法,由浅入深。 --- ## 练习 1:多态入门——Pet 宠物喂食体系 **难度**:⭐ **知识点**:多态概念、多态三个前提条件、父类引用指向子类对象 创建一个宠物喂食体系,体验 **同一个方法调用,不同子类表现出不同的行为**。 **`Pet` 类**(父类): - **普通方法**:`void eat()` —— 输出 `"宠物在吃饭..."` **`Dog` 类**(继承 Pet): - **重写** `eat()` —— 输出 `"小狗在啃骨头..."` **`Cat` 类**(继承 Pet): - **重写** `eat()` —— 输出 `"小猫在吃鱼..."` **测试类 `PetTest` 要求**: ```java public class PetTest { public static void main(String[] args) { // 1. 使用父类引用指向子类对象(多态) Pet p1 = new Dog(); Pet p2 = new Cat(); // 2. 调用 eat() 方法——运行期执行子类的重写方法 p1.eat(); p2.eat(); // 3. 思考:如果 Pet pet = new Pet(); 可以吗?使用 new Pet() 有什么缺点? } } ``` **预期输出**: ``` 小狗在啃骨头... 小猫在吃鱼... ``` **思考题**: 1. 以上代码体现了多态的哪个前提条件? 2. 如果 `eat()` 方法没有被重写,输出会是什么?为什么? 3. 如果 `Pet` 类中还有 `sleep()` 方法但 `Dog` 和 `Cat` 没有重写它,通过 `p1.sleep()` 调用会执行哪个类的 `sleep()`? --- ## 练习 2:多态成员访问特点——理解「编译看左边,运行看右边」 **难度**:⭐⭐ **知识点**:多态中成员变量和成员方法的访问特点、编译期 vs 运行期 通过代码验证多态的成员访问规则。 **`交通工具` 体系**: **`Vehicle` 类**(父类): - **成员变量**:`String name = "交通工具"` - **普通方法**:`void run()` —— 输出 `"交通工具在行驶..."` - **普通方法**:`void stop()` —— 输出 `"交通工具刹车停止"` - **静态方法**:`static void info()` —— 输出 `"Vehicle 静态方法"` **`Car` 类**(继承 Vehicle): - **成员变量**:`String name = "小汽车"` - **重写** `run()` —— 输出 `"小汽车在高速行驶..."` - **特有方法**:`void openSunroof()` —— 输出 `"打开天窗~"` **测试类要求**: 编写 `VehicleTest` 类,创建一个 `Car` 对象,分别使用「父类引用指向子类对象」和「子类引用指向子类对象」两种方式,观察成员变量、成员方法、特有方法的访问差异。 **模板代码**: ```java public class VehicleTest { public static void main(String[] args) { // 情况一:父类引用指向子类对象(多态) Vehicle v = new Car(); System.out.println(v.name); // 输出什么? v.run(); // 输出什么? v.stop(); // 输出什么? // v.openSunroof(); // 能否调用?编译能通过吗? // 情况二:子类引用指向子类对象(普通) Car c = new Car(); System.out.println(c.name); // 输出什么? c.run(); // 输出什么? c.openSunroof(); // 能否调用? // 情况三:静态方法 v.info(); // 调用谁的静态方法? c.info(); // 调用谁的静态方法? Vehicle.info(); // 推荐哪种调用方式? } } ``` **请写出你的预期输出**(先思考,再运行验证): ``` v.name → __________ v.run() → __________ v.stop() → __________ c.name → __________ c.run() → __________ v.info() → __________ ``` **总结填空**: 1. 成员变量:编译看______,运行看______ 2. 成员方法(非静态):编译看______,运行看______ 3. 静态方法:编译看______,运行看______(因为静态方法属于______,不参与多态) 4. 通过父类引用,______(能/不能)调用子类的特有方法 --- ## 练习 3:向下转型 + instanceof——动物园管理员 **难度**:⭐⭐⭐ **知识点**:向上转型、向下转型、instanceof 运算符、子类特有方法的调用 **场景描述**:动物园里有多种动物,管理员需要根据不同的动物执行不同的操作。有些动物会游泳,有些动物会飞翔,这些是特有行为。 **`Animal` 类**(父类): - **普通方法**:`void eat()` —— 输出 `"动物在吃东西"` - **普通方法**:`void sleep()` —— 输出 `"动物在睡觉"` **`Fish` 类**(继承 Animal): - **重写** `eat()` —— 输出 `"鱼儿在吃饲料"` - **重写** `sleep()` —— 输出 `"鱼儿睁着眼睛睡觉"` - **特有方法**:`void swim()` —— 输出 `"鱼儿在水中游来游去~"` **`Bird` 类**(继承 Animal): - **重写** `eat()` —— 输出 `"小鸟在啄米"` - **重写** `sleep()` —— 输出 `"小鸟在巢穴里睡觉"` - **特有方法**:`void fly()` —— 输出 `"小鸟在天空中飞翔~"` **请完成 `ZooKeeper` 类**: ```java public class ZooKeeper { // 这个方法接收 Animal 类型的参数(多态) // 要求: // 1. 调用 eat() 和 sleep() // 2. 使用 instanceof 判断是哪种动物 // 3. 如果是 Fish,向下转型并调用 swim() // 4. 如果是 Bird,向下转型并调用 fly() public void handleAnimal(Animal animal) { // 请补充代码 } public static void main(String[] args) { ZooKeeper keeper = new ZooKeeper(); // 使用向上转型创建对象 Animal a1 = new Fish(); Animal a2 = new Bird(); // 交给管理员处理 keeper.handleAnimal(a1); keeper.handleAnimal(a2); } } ``` **预期输出**: ``` 鱼儿在吃饲料 鱼儿睁着眼睛睡觉 鱼儿在水中游来游去~ 小鸟在啄米 小鸟在巢穴里睡觉 小鸟在天空中飞翔~ ``` **思考题**: 1. 如果调用 `keeper.handleAnimal(new Animal())` 会发生什么?`instanceof` 判断会进入哪个分支? 2. 如果去掉 `instanceof` 判断,直接强制转型 `(Fish) animal`,当传入 `Bird` 对象时会怎样? 3. 为什么需要向下转型?向上转型后直接调用 `animal.swim()` 可以吗?为什么? --- ## 练习 4:判断多态题型——哪些属于多态应用 **难度**:⭐⭐ **知识点**:多态的判断、多态与普通继承的区别 判断以下代码片段中,**哪些体现了多态**,哪些没有,并说明理由。 | 序号 | 代码片段 | 是否多态? | 理由 | |------|---------|-----------|------| | 1 | `Animal a = new Dog(); a.eat();` | | | | 2 | `Dog d = new Dog(); d.eat();` | | | | 3 | `Usb usb = new Mouse(); noteBook.useUsb(usb);` | | | | 4 | `Teacher t = new Teacher(); t.play();` | | | | 5 | `Person p = new Student(); ((Student)p).study();` | | | | 6 | `Object obj = "Hello";` | | | **拓展思考**: ```java // 以下代码是否是多态?为什么? public void feed(Pet pet) { pet.eat(); } // 调用 feed(new Dog()); feed(new Cat()); ``` **参考答案思路**: | 序号 | 是否多态? | 理由 | |------|-----------|------| | 1 | ✅ 是 | 父类引用指向子类对象,调用重写方法 —— 满足多态三前提 | | 2 | ❌ 否 | 子类引用指向子类对象,没有「父类引用指向子类对象」 | | 3 | ✅ 是 | 接口引用指向实现类对象,属于接口多态 | | 4 | ❌ 否 | 没有父类引用,没有方法重写的调用 | | 5 | ✅ 是 | 先向上转型(多态),再向下转型恢复子类完整形态 | | 6 | ✅ 是 | String 是 Object 的子类,属于继承多态 | --- # 第二部分:进阶练习 > 以下为综合挑战题,融合多态、向上转型/向下转型、instanceof、对象数组和排序等知识点。 --- ## 第 1 题:多态 + 对象数组 + instanceof——员工绩效管理系统 ⭐⭐⭐⭐ ### 需求说明 某公司有三种类型的员工:**正式员工**(全职)、**兼职员工**(按时计薪)和 **实习生**(固定津贴)。每种员工的薪资计算方式不同,且各有特有行为。 使用 **多态数组** 存储所有员工,遍历时通过 `instanceof` 判断类型并向下转型,调用特有方法。 ### 类设计 **`Employee` 抽象类**(父类): - **私有属性**:`id`(工号,int)、`name`(姓名,String) - **有参构造方法**:`Employee(int id, String name)` - **抽象方法**:`double calculateSalary()` —— 计算薪资 - **普通方法**:`void showInfo()` —— 输出 `"工号:xxx,姓名:xxx,薪资:xxx"`(调用 `calculateSalary()` 获得薪资) **`FullTimeEmployee` 类**(继承 Employee): - **私有属性**:`baseSalary`(基本工资,double)、`bonus`(绩效奖金,double) - **构造方法**:`FullTimeEmployee(int id, String name, double baseSalary, double bonus)` - **重写** `calculateSalary()` —— 返回 `baseSalary + bonus * 1.2`(绩效上浮 20%) - **特有方法**:`void attendMeeting()` —— 输出 `"正式员工 xxx 参加周会..."`(xxx 为姓名) **`PartTimeEmployee` 类**(继承 Employee): - **私有属性**:`hourlyRate`(时薪,double)、`hoursWorked`(工作小时数,int) - **构造方法**:`PartTimeEmployee(int id, String name, double hourlyRate, int hoursWorked)` - **重写** `calculateSalary()` —— 返回 `hourlyRate * hoursWorked` - **特有方法**:`void clockIn()` —— 输出 `"兼职员工 xxx 打卡上班..."`(xxx 为姓名) **`Intern` 类**(继承 Employee): - **私有属性**:`allowance`(实习津贴,double) - **构造方法**:`Intern(int id, String name, double allowance)` - **重写** `calculateSalary()` —— 返回 `allowance`(固定津贴,无其他扣减) - **特有方法**:`void learnSkill()` —— 输出 `"实习生 xxx 在学习新技能..."`(xxx 为姓名) ### 测试类要求 **`EmployeeManager` 类**: 1. **创建员工数组**:创建一个 `Employee[]` 数组,包含至少 5 名员工(各类型混合) | 类型 | id | 姓名 | 参数 | |------|----|------|------| | FullTimeEmployee | 1001 | 张三 | 基本工资 8000,绩效 2000 | | PartTimeEmployee | 2001 | 李四 | 时薪 50,工作 80 小时 | | Intern | 3001 | 王五 | 津贴 2000 | | FullTimeEmployee | 1002 | 赵六 | 基本工资 10000,绩效 3000 | | PartTimeEmployee | 2002 | 孙七 | 时薪 45,工作 60 小时 | 2. **遍历数组输出基本信息**:调用每个元素的 `showInfo()` 方法 3. **遍历数组,按类型执行特有行为**: - 如果是 `FullTimeEmployee` → 向下转型 → 调用 `attendMeeting()` - 如果是 `PartTimeEmployee` → 向下转型 → 调用 `clockIn()` - 如果是 `Intern` → 向下转型 → 调用 `learnSkill()` 4. **计算总薪资成本**:遍历数组,累加所有员工的 `calculateSalary()`,输出总薪资 5. **查找薪资最高的员工**:遍历数组,找出 `calculateSalary()` 最大的员工,输出其信息 ### 思考题 1. `Employee[]` 数组中存储的是不同类型对象,这体现了面向对象哪个特征? 2. 如果不用 `instanceof` 判断类型,直接向下转型会有什么风险? 3. 如果新增一个 `Contractor`(外包人员)类,需要修改哪些代码?这些修改是否符合「开闭原则」? ### 预期输出(参考) ``` ========== 员工薪资信息 ========== 工号:1001,姓名:张三,薪资:10400.00 工号:2001,姓名:李四,薪资:4000.00 工号:3001,姓名:王五,薪资:2000.00 工号:1002,姓名:赵六,薪资:13600.00 工号:2002,姓名:孙七,薪资:2700.00 ========== 执行特有行为 ========== 正式员工 张三 参加周会... 兼职员工 李四 打卡上班... 实习生 王五 在学习新技能... 正式员工 赵六 参加周会... 兼职员工 孙七 打卡上班... ========== 薪资统计 ========== 总薪资成本:32700.00 元 薪资最高的员工:工号:1002,姓名:赵六,薪资:13600.00 ``` > **提示**: > - 数组类型声明为 `Employee[]`,存储不同子类对象——这是多态的数组应用 > - 使用 `instanceof` 判断具体类型后再向下转型,是安全调用特有方法的标准做法 > - 薪资计算使用 `double` 类型,输出时保留两位小数使用 `System.out.printf("%.2f", value)` --- ## 第 2 题:综合应用——Usb 设备管理器(多态 + 接口 + instanceof)⭐⭐⭐⭐⭐ ### 需求说明 基于课堂上的 Usb 接口案例,扩展实现一个 Usb 设备管理器。要求: - `NoteBook` 类可以接入多个 Usb 设备(使用 Usb 数组存储) - 不同 Usb 设备有不同的特有功能 - 使用 `instanceof` 判断设备类型,调用特有方法 ### 类设计 **`Usb` 接口**(复用课堂代码): - **抽象方法**:`void startUsb()` —— 启动设备 - **抽象方法**:`void stopUsb()` —— 停止设备 **`Mouse` 类**(实现 Usb): - **私有属性**:`brand`(品牌,String) - **构造方法**:`Mouse(String brand)` - **实现** `startUsb()` —— 输出 `"鼠标已连接(品牌:xxx)"` - **实现** `stopUsb()` —— 输出 `"鼠标已断开"` - **特有方法**:`void click()` —— 输出 `"鼠标单击——嗒"` **`Keyboard` 类**(实现 Usb): - **私有属性**:`layout`(键盘布局,String,如 "QWERTY" / "AZERTY") - **构造方法**:`Keyboard(String layout)` - **实现** `startUsb()` —— 输出 `"键盘已连接(布局:xxx)"` - **实现** `stopUsb()` —— 输出 `"键盘已断开"` - **特有方法**:`void type()` —— 输出 `"键盘输入:Hello World!"` **`USBHub` 类**(实现 Usb,**拓展新设备类型**): - **私有属性**:`portCount`(USB 接口数量,int) - **构造方法**:`USBHub(int portCount)` - **实现** `startUsb()` —— 输出 `"USB 扩展坞已连接(xxx 个接口)"` - **实现** `stopUsb()` —— 输出 `"USB 扩展坞已断开"` - **特有方法**:`void expandPorts()` —— 输出 `"扩展出 xxx 个 USB 接口,可连接更多设备"` **`NoteBook` 类**: - **私有属性**:`brand`(品牌,String)、`ports`(Usb 设备数组,最多 4 个)、`portIndex`(当前已插入设备数) - **构造方法**:`NoteBook(String brand)` —— 初始化品牌,创建大小为 4 的 Usb 数组 - **方法**:`void plugIn(Usb device)` —— 插入 Usb 设备到 `ports` 数组 - **方法**:`void workAll()` —— 遍历 `ports` 数组,对每个设备: 1. 调用 `startUsb()` 2. 使用 `instanceof` 判断设备类型,向下转型调用特有方法 - 如果是 `Mouse` → 调用 `click()` - 如果是 `Keyboard` → 调用 `type()` - 如果是 `USBHub` → 调用 `expandPorts()` 3. 调用 `stopUsb()` ### 测试类要求 **`UsbManagerTest` 类**: 1. 创建 `NoteBook` 对象("ThinkPad") 2. 创建多个 Usb 设备: - `Mouse`("罗技") - `Keyboard`("QWERTY") - `USBHub`(4) - `Mouse`("雷蛇") 3. 将设备依次插入笔记本 4. 调用 `workAll()` 让所有设备工作一轮 ### 预期输出 ``` ========== ThinkPad 笔记本 Usb 设备工作 ========== 鼠标已连接(品牌:罗技) 鼠标单击——嗒 鼠标已断开 键盘已连接(布局:QWERTY) 键盘输入:Hello World! 键盘已断开 USB 扩展坞已连接(4 个接口) 扩展出 4 个 USB 接口,可连接更多设备 USB 扩展坞已断开 鼠标已连接(品牌:雷蛇) 鼠标单击——嗒 鼠标已断开 ``` ### 思考题 1. `NoteBook` 类中的 `workAll()` 方法,参数和返回值的类型设计体现了什么原则? 2. 如果要新增一个 `Printer`(打印机)Usb 设备,需要修改 `NoteBook` 类的代码吗?这符合哪个设计原则? 3. 对照课堂的 `Test02.java`,本练习增加了什么功能?(提示:从单个设备到多个设备 + instanceof 判断) ### 代码填空挑战 请补全 `NoteBook` 类的 `workAll()` 方法: ```java public void workAll() { System.out.println("========== " + brand + " 笔记本 Usb 设备工作 =========="); for (int i = 0; i < portIndex; i++) { Usb device = ports[i]; device.startUsb(); // 请使用 instanceof + 向下转型 调用特有方法 // 你的代码: device.stopUsb(); System.out.println(); } } ``` --- # 第三部分:课后作业 > 以下作业围绕接口 JDK8 新特性(默认方法、静态方法)和模板方法设计模式,结合课堂通知系统案例进行扩展。 --- ## 作业 1:接口默认方法 + 静态方法——消息通知系统扩展 **难度**:⭐⭐⭐ **知识点**:接口默认方法(default)、接口静态方法(static)、implements 实现接口、方法重写 ### 需求说明 在课堂 `Notifiable` 通知系统基础上,增加更多的通知方式。要求使用 **接口默认方法** 提供通用的日志记录和参数校验功能,使用 **接口静态方法** 提供工具方法。 ### 接口定义 **`Notifiable` 接口**(可直接复用课堂代码,或自行重新定义): - **抽象方法**:`void doSend(String recipient, String message)` - **默认方法**:`default boolean validateParams(...)` —— 校验参数 - **默认方法**:`default void log(String level, String msg)` —— 记录日志 - **默认方法**:`default void send(String recipient, String message)` —— 模板方法(固定发送流程) - **静态方法**:`static void printNotificationType()` —— 输出系统信息 ### 要求实现以下 3 个通知类 **`SMSNotifier` 类**(实现 Notifiable): - **重写** `doSend()` —— 输出 `"发送短信给 xxx:内容 -xxx"` - **不重写** `log()` —— 使用接口默认实现 - **不重写** `validateParams()` —— 使用接口默认实现 **`WeChatNotifier` 类**(实现 Notifiable): - **私有属性**:`appId`(应用 ID,String) - **构造方法**:`WeChatNotifier(String appId)` - **重写** `doSend()` —— 输出 `"发送微信消息给 xxx(appId: xxx):内容 -xxx"` - **重写** `log()` —— 输出 `"[微信][xxx]消息:xxx"`(添加 [微信] 前缀) **`StationNotifier` 类**(实现 Notifiable,**站内信通知**): - **私有属性**:`platformName`(平台名称,String) - **构造方法**:`StationNotifier(String platformName)` - **重写** `doSend()` —— 输出 `"[xxx] 站内信发送给 xxx:内容 -xxx"` - **重写** `log()` —— 输出 `"[站内信][xxx]消息:xxx"` ### 测试类要求 **`NotificationTest` 类**: ```java public class NotificationTest { public static void main(String[] args) { // 1. 调用接口静态方法 Notifiable.printNotificationType(); // 2. 创建不同通知方式的对象 Notifiable sms = new SMSNotifier(); Notifiable wechat = new WeChatNotifier("wx_app_001"); Notifiable station = new StationNotifier("企业办公平台"); // 3. 发送通知 System.out.println("--- 短信通知 ---"); sms.send("13800138000", "您的验证码是 123456"); System.out.println("--- 微信通知 ---"); wechat.send("user_openid_123", "您有一条新消息"); System.out.println("--- 站内信通知 ---"); station.send("全体员工", "下午 3 点会议室开会"); // 4. 测试空参数——触发校验失败 System.out.println("--- 空参数测试 ---"); sms.send("", "test"); } } ``` ### 预期输出 ``` 通知系统 v1.0——支持邮件/短信/微信通知 --- 短信通知 --- 参数校验通过 发送短信给 13800138000:内容 -您的验证码是 123456 [INFO]消息:发送成功 --- 发送流程结束 --- --- 微信通知 --- 参数校验通过 发送微信消息给 user_openid_123(appId: wx_app_001):内容 -您有一条新消息 [微信][INFO]消息:发送成功 --- 发送流程结束 --- --- 站内信通知 --- 参数校验通过 [企业办公平台] 站内信发送给 全体员工:内容 -下午 3 点会议室开会 [站内信][INFO]消息:发送成功 --- 发送流程结束 --- --- 空参数测试 --- 参数校验失败:收件人或消息不能为空 ``` ### 思考题 1. `SMSNotifier` 没有重写 `log()` 方法,调用时执行的是谁的 `log()`? 2. 接口静态方法 `printNotificationType()` 能通过实现类对象调用吗?如 `sms.printNotificationType()` —— 尝试后说说你的发现。 3. 如果 `Notifiable` 新增一个默认方法 `default void sendWithRetry(...)`,已有的 3 个实现类需要修改吗?这体现了接口默认方法的什么优势? --- ## 作业 2:模板方法设计模式——数据备份系统 **难度**:⭐⭐⭐⭐ **知识点**:模板方法设计模式、接口默认方法、抽象方法与默认方法的配合 ### 需求说明 开发一个数据备份系统,所有备份任务都遵循相同的流程,但不同的备份方式(数据库备份、文件备份、云备份)在具体执行步骤上有所不同。 **核心流程**(模板方法定义): 1. **校验参数**(所有备份相同)—— 检查备份路径/配置是否合法 2. **连接资源**(不同备份方式不同)← **抽象方法** 3. **执行备份**(不同备份方式不同)← **抽象方法** 4. **记录日志**(所有备份相同)—— 记录备份结果 5. **关闭资源**(不同备份方式不同)← **抽象方法** ### 使用接口 + 默认方法实现模板方法模式 **`Backupable` 接口**: ```java public interface Backupable { // ===== 抽象方法(实现类必须实现) ===== void connect(); // 连接资源 void executeBackup(); // 执行备份 void close(); // 关闭资源 // ===== 默认方法(可选重写) ===== default boolean validateConfig(String path) { if (path != null && !path.isEmpty()) { System.out.println("配置校验通过:" + path); return true; } System.out.println("配置校验失败:路径不能为空"); return false; } default void log(String operation, String status) { System.out.println("[备份日志] " + operation + " —— " + status); } // ===== 模板方法(定义固定流程,使用 default 关键字) ===== default void backup(String configPath) { // 1. 校验配置 if (!validateConfig(configPath)) return; // 2. 连接资源 connect(); // 3. 执行备份 executeBackup(); // 4. 记录日志 log("数据备份", "完成"); // 5. 关闭资源 close(); System.out.println("--- 备份流程结束 ---"); } // ===== 静态方法 ===== static void printSystemInfo() { System.out.println("数据备份系统 v2.0——支持数据库/文件/云备份"); } } ``` ### 要求实现以下 3 个备份类 **`DatabaseBackup` 类**(实现 Backupable): - **私有属性**:`dbName`(数据库名,String) - **构造方法**:`DatabaseBackup(String dbName)` - **实现** `connect()` —— 输出 `"连接数据库:xxx"` - **实现** `executeBackup()` —— 输出 `"执行数据库备份:导出表结构和数据..."` - **实现** `close()` —— 输出 `"关闭数据库连接"` - **重写** `log()` —— 输出 `"[数据库备份] xxx —— xxx"`(覆盖默认日志格式) **`FileBackup` 类**(实现 Backupable): - **私有属性**:`sourceDir`(源目录,String) - **构造方法**:`FileBackup(String sourceDir)` - **实现** `connect()` —— 输出 `"扫描本地目录:xxx"` - **实现** `executeBackup()` —— 输出 `"执行文件备份:压缩文件并复制到备份目录..."` - **实现** `close()` —— 输出 `"释放文件流资源"` - **注意**:不重写 `log()`,使用接口默认实现 **`CloudBackup` 类**(实现 Backupable): - **私有属性**:`cloudProvider`(云服务商,String,如 "阿里云OSS" / "腾讯云COS") - **构造方法**:`CloudBackup(String cloudProvider)` - **实现** `connect()` —— 输出 `"连接云存储服务:xxx"` - **实现** `executeBackup()` —— 输出 `"执行云备份:上传数据到云端..."`(上传完成后追加输出 `"上传完成!"`) - **实现** `close()` —— 输出 `"断开云存储连接"` - **重写** `validateConfig()` —— 在父类校验基础上,额外检查 `cloudProvider` 是否以 "云" 结尾,如果不是则输出 `"云服务商名称不合法"` 并返回 false ### 测试类要求 **`BackupTest` 类**: 1. 调用接口静态方法输出系统信息 2. 创建三种备份对象,分别调用 `backup()` 方法 3. 测试配置校验失败的情况(传入空路径) ```java public class BackupTest { public static void main(String[] args) { // 1. 输出系统信息 Backupable.printSystemInfo(); // 2. 数据库备份 System.out.println("--- 数据库备份 ---"); Backupable dbBackup = new DatabaseBackup("ERP_System"); dbBackup.backup("/data/backup/db"); // 3. 文件备份 System.out.println("--- 文件备份 ---"); Backupable fileBackup = new FileBackup("/home/user/documents"); fileBackup.backup("/data/backup/files"); // 4. 云备份 System.out.println("--- 云备份 ---"); Backupable cloudBackup = new CloudBackup("阿里云OSS"); cloudBackup.backup("/data/backup/cloud"); // 5. 测试校验失败 System.out.println("--- 配置校验失败测试 ---"); dbBackup.backup(""); } } ``` ### 预期输出 ``` 数据备份系统 v2.0——支持数据库/文件/云备份 --- 数据库备份 --- 配置校验通过:/data/backup/db 连接数据库:ERP_System 执行数据库备份:导出表结构和数据... [数据库备份] 数据备份 —— 完成 关闭数据库连接 --- 备份流程结束 --- --- 文件备份 --- 配置校验通过:/data/backup/files 扫描本地目录:/home/user/documents 执行文件备份:压缩文件并复制到备份目录... [备份日志] 数据备份 —— 完成 释放文件流资源 --- 备份流程结束 --- --- 云备份 --- 配置校验通过:/data/backup/cloud 连接云存储服务:阿里云OSS 执行云备份:上传数据到云端... 上传完成! [备份日志] 数据备份 —— 完成 断开云存储连接 --- 备份流程结束 --- --- 配置校验失败测试 --- 配置校验失败:路径不能为空 ``` ### 对比思考 | 对比维度 | 课堂 Notifiable + send() | 本作业 Backupable + backup() | |---------|------------------------|----------------------------| | 模板方法 | `send()` | `backup()` | | 抽象方法数量 | 1 个(`doSend`) | 3 个(`connect`、`executeBackup`、`close`) | | 默认方法数量 | 3 个(`validateParams`、`log`、`send`) | 3 个(`validateConfig`、`log`、`backup`) | | 静态方法 | `printNotificationType()` | `printSystemInfo()` | **思考题**: 1. 模板方法 `backup()` 用 `default` 修饰而不是 `abstract`,为什么这样设计? 2. 如果希望子类 **不能重写** 模板方法 `backup()`,应该用什么关键字修饰?(提示:回忆 abstract 中的 final 关键字) 3. 观察 `DatabaseBackup` 重写了 `log()`,而 `FileBackup` 没有重写 —— 这体现了接口默认方法的什么优势? 4. 本作业中 `Backupable` 是一个接口,如果改用抽象类实现,会有什么不同?(提示:单继承 vs 多实现) --- ## 📋 题目总览 | 序号 | 所属部分 | 核心知识点 | 难度 | |------|---------|-----------|------| | 练习 1 | 随堂 | 多态概念、三前提、父类引用指向子类对象 | ⭐ | | 练习 2 | 随堂 | 多态成员访问特点(编译看左运行看右) | ⭐⭐ | | 练习 3 | 随堂 | 向下转型、instanceof 运算符、调用子类特有方法 | ⭐⭐⭐ | | 练习 4 | 随堂 | 多态判断、多态与普通继承的区别 | ⭐⭐ | | 进阶 1 | 进阶 | 多态数组 + instanceof + 向下转型 + 员工管理综合 | ⭐⭐⭐⭐ | | 进阶 2 | 进阶 | 接口多态 + Usb 设备数组 + instanceof + 开闭原则 | ⭐⭐⭐⭐⭐ | | 作业 1 | 课后 | 接口默认方法/静态方法、通知系统扩展 | ⭐⭐⭐ | | 作业 2 | 课后 | 模板方法设计模式(接口默认方法实现)、数据备份系统 | ⭐⭐⭐⭐ |