Browse Source

feat: 新增20260724多态与接口进阶练习题——8道练习题覆盖多态基础、转型、instanceof、接口默认方法/静态方法、模板方法设计模式

WanJL 18 hours ago
parent
commit
1730f15d5f
2 changed files with 745 additions and 0 deletions
  1. 1 0
      README.md
  2. 744 0
      课堂练习题/20260724-多态与接口进阶练习.md

+ 1 - 0
README.md

@@ -61,4 +61,5 @@
 | 2026-07-21 | [构造方法练习](./课堂练习题/20260721-构造方法练习.md) | 构造方法概念、构造方法重载、private 构造方法、this() 构造方法间调用、构造方法+对象数组+排序+Random 综合 |
 | 2026-07-22 | [继承练习](./课堂练习题/20260722-继承练习.md) | 继承概念与格式、多层继承、super() 调用父类构造方法、方法重写、is...a 关系判断、继承+构造方法+对象数组+排序+多态综合 |
 | 2026-07-23 | [抽象类与接口练习](./课堂练习题/20260723-抽象类与接口练习.md) | 抽象类定义与特点、抽象方法重写、抽象类计算周长与面积(Shape体系)、抽象类+对象数组+排序+多态综合、模板方法设计模式;接口入门(Playable播放器接口、Charger充电规范继承+实现、SmartPhone实现多接口) |
+| 2026-07-24 | [多态与接口进阶练习](./课堂练习题/20260724-多态与接口进阶练习.md) | 多态概念与三前提、多态成员访问特点(编译看左运行看右)、向上转型/向下转型、instanceof运算符、多态+对象数组+排序综合(员工绩效管理系统)、接口多态+Usb设备管理器、接口默认方法/静态方法(消息通知系统扩展)、模板方法设计模式(数据备份系统) |
 

+ 744 - 0
课堂练习题/20260724-多态与接口进阶练习.md

@@ -0,0 +1,744 @@
+# 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 | 课后 | 模板方法设计模式(接口默认方法实现)、数据备份系统 | ⭐⭐⭐⭐ |