第 04 章 继承与多态
思考与练习
继承基本概念
1. 查询资料,编写一个三角形类继承自 Shape 类。
答:
class Triangle extends Shape {
private double side1;
private double side2;
private double side3;
public Triangle() {
}
public Triangle(int id, int x, int y, double side1, double side2, double side3){
super(id, x, y);
= side1;
= side2;
= side3;
}
public double getSide1() {
return side1;
}
public void setSide1(double side1) {
= side1;
}
public double getSide2() {
return side2;
}
public void setSide2(double side2) {
= side2;
}
public double getSide3() {
return side3;
}
public void setSide3(double side3) {
= side3;
}
public double calculatePerimeter() {
return side1 + side2 + side3;
}
public double calculateArea() {
double s = calculatePerimeter() / 2;
return (s * (s - side1) * (s - side2) * (s - side3));
}
}
2. 查看 Object 类的源代码,观察其 clone 方法与 toString、equals 方法有何不同。
答:Object 的 clone 是 protected native 方法,需要子类继承后才能使用。toString 与 equals 方
法是 public 方法,其他类可直接使用。
3. 使用 IDE 尝试为 Circle 类、Shape 类自动生成 toString()方法,hashCode()与 equals()方法。
比较自动生成的方法与 Object 中相应的方法的内部实现代码有何不同?
4. this 与 super 关键字有何不同?
答:
this 关键字代表当前对象的引用,用于引用当前对象的实例变量、方法或构造函数。
super 关键字代表父类的引用,用于访问父类的实例变量、方法或构造函数。
5. 父类被子类的覆盖的方法、被隐藏的属性是否就不存在了?怎么调用这些方法、访问这
些属性?子类能访问父类的所有属性吗?
答:被子类覆盖的方法和被隐藏的属性并不会被删除,它们仍然存在于父类中。可以通过
super 关键字来调用被覆盖的方法和访问被隐藏的属性。
子类可以访问父类的非私有属性和方法,但无法直接访问父类的私有属性。通过父类的公有
或受保护的方法来间接访问父类的私有属性是一种常见的做法。
6. 拓展:通过英文的搜索引擎搜索在编程中 Foo 的含义。
答:在编程中,"Foo"通常被用作一个占位符或虚拟的类名、方法名或变量名,用于表示一
个通用的概念或示例。它是一种惯例命名,常用于示例代码或文档中,没有特殊含义,只是
用来表示一个抽象的概念。它已经成为编程文化和社区的一部分,被广泛用于代码示例和编
程练习中。
继承与多态
1. 执行完 Object s1 = "abc";后,能否使用 s1 直接调用 String 的 substring 方法?为什么?
答:不能。因为 s1 被声明为 Object 类型,而其无 substring 方法。需要先将其强制转化为 String
才可使用。
2. 希望设计一个无所不包的容器类,内部使用数组来装任何类型的数据。这个数组应该是
什么类型的?这个数组可以存放基本数据类型吗?
答:Object 类型数组。不能直接存放基本数据类型,但可以他存放基本数据类型对应的包装
类对象。
3. 编写一个通用方法,将任意类型对象的 toString 返回的值的@之后的部分显示出来。
答:
public static String getStringPartAfterAt(Object o) {
if (o == null)
return null;
String str = ();
// 查找 '@' 字符的位置
int atIndex = ('@');
// 确保 '@' 字符存在
if (atIndex != -1) {
// 获取 '@' 字符之后的部分
String afterAtStr = (atIndex + 1);
// 返回截取后的字符串
return afterAtStr;
}
return "";
}
4. 设计一个三角形类继承自 Shape 类。改写代码 4-4 的 中的代码来测试所
编写的三角形类。说一说,为什么 Shape 的静态方法 sumArea 没有变动,也能处理新编写
的三角形类?为 Shape 编写一个方法可以将任意 Shape 类型对象的周长加总求和。
答:因为 sumArea(Shape[] shapes)是面向 Shape 编程,三角形类继承自 Shape 类,所以可以
处理。
5. 以 、Shape 类及其子类 Circle、Rectangle 为例,说明哪里用到了多态技术?
继承与多态的关系是什么?
答:以代码 4-4 为例。3-6 行用 Shape 类型数组存储各 Shape 的子类对象(父类类型变量指
向子类类型对象)。而 方法是面向 Shape 编程(面向父类编程)。继承提供
了多态的基础,通过继承和方法重写,可以实现多态性(比如 Shape 的 getArea 在不同子类
对象中有不同的实现),使代码更灵活、可扩展和易维护。
类型转换与 instanceof 运算符
1. 执行完 Object s1 = "123";后,能否使用(String)s1 将其转换为 String 类型?如果使用
(Integer)s1 将其转换为 Integer 类型,是否会产生编译错误?运行时会提示什么错误信息?
答:可以。产生 ClassCastException
2. 什么是向上转型与向下转型?对一个类型进行向上转型与向下转型是否总是安全的?
答:
向上转型是指将一个子类的实例赋值给一个父类类型的引用变量。这样做可以让父类引用指
向子类对象,实现多态性。
向下转型是指将一个父类类型的引用变量转换为子类类型的引用变量。这样做需要强制类型
转换,以便访问子类特有的属性和方法。
对一个类型进行向上转型通常是安全的,因为子类对象可以被当做父类对象来使用。然而,
向下转型并不总是安全的,因为在运行时如果引用变量指向的对象不是目标类型的实例,会
导致 ClassCastException 异常。因此,在进行向下转型时应该先使用 instanceof 运算符进行
类型检查,确保转型安全。
3. 建立一个 Object 数组,里面可以存储各种各样的对象,也可能包含 null,编写一个方法,
将其中的 String 对象找出来,并调用其 substring 方法。
答:
public static void processStringObjects(Object[] array) {
for (Object obj : array) {
if (obj instanceof String) {
String str = (String) obj;
((1));
}
}
}
访问控制符
1. 对于代码 4-1 的 Shape 类,其 getArea()方法应该使用 public 还是 protected 或者包私有
访问控制符修饰?为什么?
答:
getArea()方法应该使用 protected 访问控制符修饰。使用 protected 修饰符可以让子类访问该
方法,从而允许子类重写 getArea()方法来计算特定形状的面积。同时,protected 修饰符也限
制了只有当前类和其子类可以访问该方法,保证了封装性和安全性。
如果使用 public 修饰符,虽然可以被任何类访问,但可能会破坏封装性,因为 getArea()方
法可能是为了特定子类而设计的。
2. 如果想在形状管理系统所在的包中定义一个常量圆周率,值为 ,且仅在该包中使用。
这个常量应该放在哪一个类中?应该使用什么访问控制符修饰?
答:
public class Shape {
static final double PI = ;
}
3. 在 IDE 中查看类的源代码可以使用其大纲(Outline)功能。查看 类的
源代码,找到其 digits 属性,观察其被哪种访问控制符修饰。并观察 IDE 中不同类型成员
在 Outline 中的图标各是什么?
答:无访问修饰符。在 IDEA 中,Outlie 对应的是 Structure(使用 Alt+7 打开)
4. 在 IDE 中想要查找 Integer 类中的 digits 属性都被哪些类调用。尝试使用 Eclipse 的“Open
Call Hierarchy”功能或 IDEA 的“Find Usage”功能找到 digits 属性在哪些类中被调用。
抽象类及抽象方法:abstract
1. 根据本节的代码片段,编写 Rectangle 类继承自 AbstractShape 类,使得本节内的测试代
码片段内的代码可以正常运行。
答:
public class Rectangle extends AbstractShape {
private int width;
private int height;
// 省略掉 setter/getter 方法与部分构造方法
public Rectangle(int width, int height) {
= width;
= height;
}
@Override
public double getArea() {
return width * height;
}
@Override
public double getPerimeter() {
return 2 * (width + height);
}
}
2. 在 IDE 中使用 new AbstractShape()尝试创建抽象类的实例,看看有何错误提示?
答:Error: Cannot instantiate the type AbstractShape
3. Object 类是抽象类吗?使用什么代码可以快速证明。
答:不是。Object e = new Object();
4. 为本节中的形状相关的类定义一个 showAreaPerimeter 方法,可以显示形状相关类的面
积与周长。该方法应该定义在哪个类中比较好?是声明为抽象方法还是非抽象方法?尝试编
写出这样的方法。
答:可定义在 AbstractShape 类中,非抽象方法。
public void showAreaPerimeter() {
("Area: " + getArea());
("Perimeter: " + getPerimeter());
}
5. 为本节中的形状相关的类定义一个 showInfo 方法,只需显示 AbstractShape 的子类的特有
属性。该方法应该定义在哪个类中比较好?是声明为抽象方法还是非抽象方法?尝试编写出
这样的方法。
答:定义在 AbstractShape,作为抽象方法。
public abstract void showInfo2();
然后在子类中覆盖该方法。
Java 类库代码分析与使用:Number 与 Random
1. 在 IDE 中查看 ()方法的源代码。看看其是如何实现的?
2. 尝试使用 Random 类随机生成大小写字母及数字的序列。
答:
public static String generateRandomSequence(int length) {
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int type = (3); // 0: uppercase letter, 1: lowercase letter, 2: digit
switch (type) {
case 0:
((char) ((26) + 'A')); // Uppercase letter
break;
case 1:
((char) ((26) + 'a')); // Lowercase letter
break;
case 2:
((10)); // Digit
break;
}
}
return ();
}
3. 尝试使用 Random 类编写一个密码生成器。可以指定长度、指定出现的字符或者不出现
的字符。
答:
import ;
public class PasswordGenerator {
public static String generatePassword(int length, String includeChars, String excludeChars) {
Random random = new Random();
StringBuilder sb = new StringBuilder();
String allChars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
if (includeChars != null && !()) {
allChars += includeChars;
}
if (excludeChars != null && !()) {
allChars = ("[" + excludeChars + "]", "");
}
for (int i = 0; i < length; i++) {
int randomIndex = (());
((randomIndex));
}
return ();
}
public static void main(String[] args) {
int length = 12;
String includeChars = "!@#$";
String excludeChars = "oO01lI";
String password = generatePassword(length, includeChars, excludeChars);
(password);
}
}
4. Random 类中的 int nextInt(int bounds)方法生成的伪随机数是均匀分布的。尝试编写程序
进行验证。
答:
import ;
public class RandomDistributionChecker {
public static void main(String[] args) {
Random random = new Random();
int bounds = 10;
int[] count = new int[bounds];
int totalNumbers = 10000;
for (int i = 0; i < totalNumbers; i++) {
int randomNumber = (bounds);
count[randomNumber]++;
}
for (int i = 0; i < bounds; i++) {
double percentage = (double) count[i] / totalNumbers * 100;
("Number " + i + " appeared " + count[i] + " times, percentage: "
+ percentage + "%");
}
}
}
5. 怎么快速地在 IDE 中查看 Random 中的 next(int bits)方法被哪些方法所调用?
答:
Eclipse, 双击选中 next 方法,右键点击选择 Open Call Hierarchy,或者选中后按 Ctrl+Alt+H。
IDEA,双击选中 next 方法,右键点击选择 Find Usage,或者选中后按 Alt+F7,然后选择 Call
Hierarchy。
6. 进阶:编写一个方法,可以生成期望值为 a、标准差为 b 的正态分布的随机数值。
答:
import ;
public class NormalDistributionGenerator {
public static double generateNormalDistribution(double mean, double stdDev) {
Random random = new Random();
double u1 = ();
double u2 = ();
double z = (-2 * (u1)) * (2 * * u2);
return mean + stdDev * z;
}
public static void main(String[] args) {
double mean = ;
double stdDev = ;
double randomValue = generateNormalDistribution(mean, stdDev);
(randomValue);
}
}
判断题
1. (错 )子类可以使用 this 访问父类定义的所有成员。
2. (对 )子类 Circle 继承自 Shape 类,可以说“Circle is-a Shape”。
3. (对 )在 Java 中任意类的对象,都可放入 Object 类型的变量。
4. (错 )对于两个引用类型的对象,可以使用==直接判断两个对象内部的值是否相等。
5. (对 )针对父类编写的代码,也可适用于其子类,这是多态性的一种表现。
6. (错 )父类型变量可以存储其子类类型对象,并能调用该对象的所有方法。
7. (错 )父类有属性 x,其子类也有属性 x,那么父类的 x 就没有了。
8. (错 )子类覆盖父类同名方法,父类该方法的代码就无法再访问了。
9. (错 )动态绑定是 Java 中多态实现的重要技术基础,其发生在编译阶段。
10. (对 )使用包与访问控制符可以控制对类与成员的访问。
11. (错 )protected 常用于修饰方法。被 protected 修饰的方法,仅能被本包中的类访问。
选择题
1. 在 Java 中,以下哪个关键字使得类不能派生出子类?(D )。
A. protected B. public C. private D. final
2. 类 Cat 是类 Animal 的子类,在 Cat 的构造方法中有一句 super(),该语句表达了什么含义?
(C )
A. 调用类 Cat 中定义的 super()方法。
B. 调用类 Animal 中定义的 super()方法。
C. 调用类 Animal 的构造方法。
D. 语法错误。
3. Assistant 类继承自 Teacher 类,Teacher 类继承自 Person 类,在 Java 源代码中声明如下,
Person p1 = new Person ();
Person p2 = new Teacher ();
Person p3 = new Assistan ();
以下说法哪个正确? (D )
A. 只有第 1 行能通过编译。
B. 第 1 行和第 2 行能通过编译,第 3 行编译出错。
C. 所有行都能通过编译,但第 2 和第 3 行运行时出错。
D. 所有行编译和运行都正确。
4. 在 Java 中,所有类的根类是?( )B
A.
B.
C.
D.
5. Java 语言中,以下关于覆盖与重载说法正确的是?( )A
A. 覆盖只有发生在父类与子类之间,而重载可以发生在同一个类中
B. 覆盖方法可以不同名,而重载方法必须同名
C. final 修饰的方法可以被覆盖,但不能被重载
D. 覆盖与重载是同一回事
6.以下对抽象类的描述正确的是( )C
A. 抽象类没有构造方法
B. 抽象类必须提供抽象方法
C. 有抽象方法的类一定是抽象类
D. 抽象类可以通过 new 关键字直接实例化
7. 父类的引用指向自己子类的对象是()的一种体现形式。C
A. 重载 B.覆盖 C.多态 D. 继承
8. 如下在一个 Java 抽象类中对抽象方法的声明哪个是正确的?( )C
A. public static abstract void m1();
B. public void abstract m1();
C. public abstract void m1();
D. public abstract void m1() {}
9. 以下论述错误的是?( )A
A. 编写 Car 类,不编写 toString 方法,执行 Car 的 toString 方法会出错
B. Object 类是所有类的父类
C. Java 中所有的类中都有 toString 方法
D. 因为 String is-a Object,所以 Object o = new String();不会出错
10. 阅读如下代码
class Foo {
String toString() {
return "I am foo.";
}
}
public class Main {
public static void main(String[] args) {
Foo x = new Foo();
(());
}
}
以下描述正确的是( )D
A. 显示 I am foo
B. 显示 Foo@39ba5a14
C. 发生 ClassNotFoundException
D. 编译失败
编程题
1. 继承、多态基础练习题:
创建 Person 类,仅包含两个 private 属性 String name、int age。创建 Person 两个实
例 p1, p2,直接输出其 toString 方法、并对 equals 方法进行测试;
覆盖 Person 类的 toString 方法,将返回字符串格式改为“name=yyy, age=zz”,然后进
行测试;
覆盖 Person 类的 equals 方法,当 name 相同且 age 相同时就返回 true,否则返回
false,然后进行测试;
答:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
= name;
= age;
}
public String toString() {
return "name=" + name + ", age=" + age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != ()) return false;
Person person = (Person) obj;
return age == && (name, );
}
}
// 测试代码
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Bob", 25);
(());
(());
((p2));
编写 Student 类(属性:学号、专业)与 Teacher(属性:工号、薪水)类均继承自
Person 类。覆盖 Student 类与 Teacher 类的 toString 方法,让其不仅能输出父类属性
值,也能输出自身属性值;覆盖 Student 的 equals 方法,当父类的 equals 返回 true
且学号相同才返回 true;覆盖 Teacher 的 equals 方法,当父类的 equals 返回 true 且
工号相同才返回 true,然后进行测试;
答:
public class Student extends Person {
private String studentId;
private String major;
public Student(String name, int age, String studentId, String major) {
super(name, age);
= studentId;
= major;
}
@Override
public String toString() {
return () + ", studentId=" + studentId + ", major=" + major;
}
@Override
public boolean equals(Object obj) {
if (!(obj)) return false;
Student student = (Student) obj;
return (studentId, );
}
}
public class Teacher extends Person {
private String employeeId;
private double salary;
public Teacher(String name, int age, String employeeId, double salary) {
super(name, age);
= employeeId;
= salary;
}
@Override
public String toString() {
return () + ", employeeId=" + employeeId + ", salary=" + salary;
}
@Override
public boolean equals(Object obj) {
if (!(obj)) return false;
Teacher teacher = (Teacher) obj;
return (employeeId, );
}
}
// 测试
Student s1 = new Student("沙和尚", 30, "S100", "Computer Science");
Teacher t1 = new Teacher("猪八戒", 25, "T200", 5000);
(());
(());
((new Student("沙和尚", 30, "S100", "Computer Science")));
((new Teacher("猪八戒", 25, "T200", 5000)));
可选:现有航海、计算机与外国语三个专业。设计一个枚举类 Major。使用 Major 类
替代 Student 类的专业属性,然后进行测试;
答:
public enum Major {
NAUTICAL, COMPUTER_SCIENCE, FOREIGN_LANGUAGES;
}
// 在 Student 类中添加
private Major major;
public Student(String name, int age, String studentId, Major major) {
super(name, age);
= studentId;
= major;
}
// 测试
Student s2 = new Student("伏羲", 20, "S101", _SCIENCE);
(());
将 Person 类改为抽象类,为其添加抽象的 showInfo 方法,然后在其子类(Student、
Teacher)中覆盖该方法,用来展示 name、age 及子类的特有属性,然后进行测试;
答:
public abstract class Person {
public abstract void showInfo();
}
// 在 Student 与 Teacher 中覆盖 showInfo 方法
@Override
public void showInfo() {
(());
}
创建一个Person数组,既包含Student实例又包含Teacher实例。编写方法 findByName,
可在数组中找到 name 属性包含指定字符串的所有实例,然后进行测试;
答:
Person[] people = {s1, t1, new Student(" 顺 风 耳 ", 22, "S102",
_LANGUAGES), new Teacher("Eve", 29, "T201", 4500)};
public static List<Person> findByName(Person[] people, String nameSubstring) {
List<Person> found = new ArrayList<>();
for (Person person : people) {
if (().contains(nameSubstring)) {
(person);
}
}
return found;
}
// 测试
List<Person> foundPeople = findByName(people, "顺风耳");
for (Person p : foundPeople) {
(());
}
编写一个方法,可以统计并输出 Person 数组中 Teacher 实例对象的平均工资,还能
统计并输出每个专业中的学生数量,然后进行测试;
答:
public static double averageSalary(Person[] people) {
double totalSalary = 0;
int count = 0;
for (Person person : people) {
if (person instanceof Teacher) {
Teacher teacher = (Teacher) person;
totalSalary += ;
count++;
}
}
return count == 0 ? 0 : totalSalary / count;
}
public static int[] countStudentsByMajor(Person[] people) {
int[] majorCounts = new int[().length]; // 枚举 Major 的长度定义数组大小
for (Person person : people) {
if (person instanceof Student) {
Student student = (Student) person;
majorCounts[()]++; // 使用枚举的 ordinal 作为数组索引
}
}
return majorCounts;
}
// 测试
("Average Salary: " + averageSalary(people));
int[] majorCounts = countStudentsByMajor(people);
Major[] majors = ();
for (int i = 0; i < ; i++) {
(majors[i] + ": " + majorCounts[i]);
}
尝试编写方法 Person generatePerson(),可随机生成 Student 与 Teacher 实例。实例的
name、age、学号、专业、工号、薪水均可随机生成。并进行测试。
答:
public static Person generatePerson() {
Random rand = new Random();
String[] names = {"唐僧", "孙悟空", "沙和尚", "猪八戒"};
Major[] majors = ();
if (()) {
// 生成学生
return new Student(
names[()],
(48) + 18, // 年龄在 18 到 65 之间
"S" + (1000),
majors[()]
);
} else {
// 生成老师
return new Teacher(
names[()],
(48) + 18, // 年龄在 18 到 65 之间
"T" + (1000),
() * 5000 // 薪水最高可达 5000 元
);
}
}
// 测试
Person randomPerson = generatePerson();
(());
();
2. 尝试编写一个任意对象生成器。可以在指定的几个类中,如 String、Integer、Object、
任意自定义的类等,随机返回某个类的对象实例。
答:
import ;
public class RandomObjectGenerator {
public static Object generateRandomObject() {
Random random = new Random();
int choice = (4); // 生成 0 到 3 的随机数,对应四个类
switch (choice) {
case 0:
return generateRandomString();
case 1:
return generateRandomInteger();
case 2:
return new Object();
case 3:
return new Person(generateRandomString(), (100)); // 假设
Person 有一个 String 和 int 的构造器
default:
return null;
}
}
private static String generateRandomString() {
String characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
int length = (10) + 5; // 生成长度为 5 到 15 的字符串
for (int i = 0; i < length; i++) {
(((())));
}
return ();
}
private static Integer generateRandomInteger() {
Random random = new Random();
return ();
}
public static void main(String[] args) {
// 测试生成器
for (int i = 0; i < 5; i++) {
Object obj = generateRandomObject();
(().getSimpleName() + ": " + ());
}
}
}
3. 面向对象综合练习:以书中第三章的的购物车案例代码为基础。将商品类改为抽象类
(包含商品名称、价格、商品简单描述及必要的方法);添加 Book 类(代表书本商品,
包含作者、ISBN 属性)与 CellPhone 类(代表手机商品,包含品牌、屏幕尺寸属性)。
功能与要求如下:
功能 1:希望在展示商品信息的时候,除了展示基本信息外还可针对不同类型的商
品展示各自特有的信息。注意:使用多态,不要针对每种子类型编写一个方法;
答:
public abstract class Commodity {
private String name;
private double price;
private String description;
public Commodity(String name, double price, String description) {
= name;
= price;
= description;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public String getDescription() {
return description;
}
public abstract void display();
}
public class Book extends Commodity {
private String author;
private String isbn;
public Book(String name, double price, String description, String author, String isbn)
{
super(name, price, description);
= author;
= isbn;
}
@Override
public void display() {
("Name: %s, Price: %f, Description: %s, Author: %s,
ISBN: %s%n",
getName(), getPrice(), getDescription(), author, isbn);
}
}
public class CellPhone extends Commodity {
private String brand;
private double screenSize;
public CellPhone(String name, double price, String description, String brand, double
screenSize) {
super(name, price, description);
= brand;
= screenSize;
}
@Override
public void display() {
("Name: %s, Price: %f, Description: %s, Brand: %s, Screen
Size: %f inches%n",
getName(), getPrice(), getDescription(), brand, screenSize);
}
}
功能 2:为自己的购物车系统添加商城类,形成“商城-购物车”系统。商城类主要用
来对商品初始化、新建、搜索、添加
功能 3:在商城类中添加快速统计每类商品数量的方法;
功能 4:在商城类中添加按照在商城类中价格进行升序或降序排列输出的方法;
答:
public class Mall {
private Commodity[] commodities;
private int count;
private int capacity;
public Mall(int capacity) {
= new Commodity[capacity];
= 0;
= capacity;
}
public void addCommodity(Commodity commodity) {
if (count < capacity) {
commodities[count++] = commodity;
}
}
public Commodity searchByName(String name) {
for (int i = 0; i < count; i++) {
if (commodities[i].getName().equals(name)) {
return commodities[i];
}
}
return null;
}
public void sortByPrice(boolean ascending) {
(commodities, 0, count, (c1, c2) -> {
return ascending ? ((), ()) :
((), ());
});
}
public int countByType(Class<? extends Commodity> type) {
int countType = 0;
for (int i = 0; i < count; i++) {
if ((commodities[i])) {
countType++;
}
}
return countType;
}
}
功能 5(测试):对 Book 类商品可按照作者姓名排序。对 CellPhone 类商品可按照
屏幕尺寸排序;
功能 6(进阶):尝试为其增加打折系统。支持两种不同的打折方式,金额满减与
直接折扣,这种折扣是针对购物车中的所有商品起作用。思考:是直接改写商品类
还是增加相关类才能更灵活的实现该功能。
答:
public abstract class DiscountStrategy {
public abstract double applyDiscount(double total);
}
public class PercentageDiscount extends DiscountStrategy {
private double percentage;
public PercentageDiscount(double percentage) {
= percentage;
}
@Override
public double applyDiscount(double total) {
return total * (1 - percentage / 100);
}
}
public class ThresholdDiscount extends DiscountStrategy {
private double threshold;
private double discount;
public ThresholdDiscount(double threshold, double discount) {
= threshold;
= discount;
}
@Override
public double applyDiscount(double total) {
return total >= threshold ? total - discount : total;
}
}
这里使用了抽象类,但实际上使用接口更好。