实验报告三 类和对象
一、实验目的及要求
-
实验目的:在面向抽象编程的基础上,掌握使用接口进行程序设计的核心思想是使用接口回调,明白接口也可以体现程序设计的“开-闭”原则,即对扩展开放,对修改关闭。
-
实验要求:深刻理解面向接口编程和“开-闭”原则,要求会定义接口变量,该接口变量存放了实现该接口的类的对象的引用,从而接口变量就可以回调类实现的接口方法。
-
上机实验内容:定义接口,声明若干个abstract方法;创建若干实现接口的类,在类中重写抽象方法、定义若干个参数为接口类型的方法,通过实现接口的类的对象回调类重写的接口方法,最后定义主类来实现相应功能,最后完成实验报告。
二、实验环境
-
硬件要求:计算机一台
-
软件要求:Windows操作系统,使用Java语言,集成开发环境不限,建议使用如Eclipse、MyEclipse或IntelliJ IDEA等。
三、实验内容
- 定义一个名为IAnimal的接口,并声明两个abstract方法,分别为eat()和sleep()。创建两个实现IAnimal接口的类,名为Dog和Cat,在这两个类中重写接口中的抽象方法。此外,定义一个名为ZooKeeper的类,包含一个IAnimal类型的成员变量,以及一个名为careForAnimal()的方法,该方法通过调用接口变量的eat()和sleep()方法来照顾动物。
-
设计思路:
(1)定义接口:首先定义一个名为IAnimal的接口,其中包含两个抽象方法eat()和sleep()。这个接口将为所有动物类提供一个公共的行为模板。
(2)实现接口:创建两个类Dog和Cat,都实现IAnimal接口,并都需要实现eat()和sleep()方法。这两个类对IAnimal接口中定义的方法提供具体的实现。
(3)创建ZooKeeper类:在ZooKeeper类中,包含一个IAnimal类型的成员变量,这样ZooKeeper就可以接管任何实现IAnimal接口的类的实例。ZooKeeper类中还包含一个名为careForAnimal()的方法,该方法通过调用IAnimal接口变量的eat()和sleep()方法来照顾动物。
(4)实现“开-闭”原则:在上述设计中,如果想要添加一个新的动物类,只需要创建一个实现IAnimal接口的一个类,然后在ZooKeeper类中的IAnimal类型变量可以存放这个类的实例。ZooKeeper类无需做任何修改就可以照顾新的动物,这就体现“开-闭”原则。
-
设计类图:
图1 题目一类图
-
实验步骤:
(1)定义IAnimal接口,包括eat()和sleep()两个抽象方法。
(2)创建Dog类和Cat类,这两个类都实现IAnimal接口,并重写eat()和sleep()方法。
(3)设计并实现ZooKeeper类,包含一个IAnimal类型的成员变量,以及一个名为careForAnimal()的方法,该方法通过调用接口变量的eat()和sleep()方法来照顾动物。
(4)在主方法中,创建Dog类和Cat类的实例,并将这些实例传递给ZooKeeper类的对象,通过调用ZooKeeper对象的careForAnimal()方法来照顾这些动物。
(5)执行程序,观察并分析结果。
- 定义一个名为IDriveable的接口,并声明两个abstract方法,分别为start()和stop()。创建两个实现IDriveable接口的类,分别为Car和Bike,在这两个类中重写接口中的抽象方法。此外,在Car类中,再定义一个参数为IDriveable类型的方法,代表这辆车可以拖拉其他可驾驶的物体。最后在主类中实例化Car对象,通过接口变量调用拖拉方法。
-
设计思路:
(1)定义接口:首先定义一个名为IDriveable的接口,其中包含两个抽象方法start()和stop()。这个接口将为所有可驾驶物体提供一个公共的行为模板。
(2)实现接口:接着创建两个类Car和Bike,它们都实现IDriveable接口,这意味着它们都要实现start()和stop()方法。这两个类对IDriveable接口中定义的方法提供具体的实现。
(3)接口回调:在Car类中,定义一个参数为IDriveable类型的方法tow(),表示这辆车可以拖拉其他可驾驶的物体。在这个方法中,调用IDriveable接口的start()和stop()方法,实现接口回调。
-
设计类图:
图2 题目二类图
-
实验步骤:
(1)定义IDriveable接口,包括start()和stop()两个抽象方法。
(2)创建Car类和Bike类,这两个类都要实现IDriveable接口,并重写start()和stop()方法。
(3)在Car类中,定义一个参数为IDriveable类型的tow()方法,表示这辆车可以拖拉其他可驾驶的物体。
(4)在主方法中,创建Car类和Bike类的实例,并将Bike类的实例传递给Car类的对象,通过调用Car对象的tow()方法实现拖拉功能。
(5)执行程序,观察并分析结果。在结果中,应该能看到,当Car对象调用tow()方法时,实际上是通过接口回调,调用Bike对象的start()和stop()方法。
- 定义一个名为IStrategy的接口,并在此接口中声明一个abstract方法,execute()。创建两个实现IStrategy接口的类,分别为ConcreteStrategyA和ConcreteStrategyB,在这两个类中重写execute()方法。定义一个名为Context的类,包含一个IStrategy类型的成员变量和一个名为executeStrategy()的方法,该方法通过接口变量调用execute()方法。
-
设计思路:
(1)定义接口:首先定义一个名为IStrategy的接口,其中包含一个抽象方法execute()。这个接口将为所有策略提供一个公共的行为模板。
(2)实现接口:然后创建两个类ConcreteStrategyA和ConcreteStrategyB,它们都实现IStrategy接口,这意味着它们都要实现execute()方法。这两个类对IStrategy接口中定义的方法提供具体的实现。
(3)创建Context类:在Context类中,包含一个IStrategy类型的成员变量,这样Context就可以接管任何实现IStrategy接口的类的实例。Context类中还包含一个名为executeStrategy()的方法,该方法通过调用IStrategy接口变量的execute()方法来执行策略。
(4)实现“开-闭”原则:在上述设计中,如果想要添加一个新的策略类,例如ConcreteStrategyC,只需要创建一个实现IStrategy接口的ConcreteStrategyC类,然后在Context类中的IStrategy类型变量可以存放ConcreteStrategyC类的实例。Context类无需做任何修改就可以执行新的策略,这就体现“开-闭”原则。
-
设计类图:
图3 题目三类图
-
实验步骤:
(1)定义IStrategy接口,包括execute()抽象方法。
(2)创建ConcreteStrategyA类和ConcreteStrategyB类,这两个类都要实现IStrategy接口,并重写execute()方法。
(3)设计并实现Context类,包含一个IStrategy类型的成员变量,以及一个名为executeStrategy()的方法,该方法通过调用接口变量的execute()方法来执行策略。
(4)在主方法中,创建ConcreteStrategyA类和ConcreteStrategyB类的实例,并将这些实例传递给Context类的对象,通过调用Context对象的executeStrategy()方法来执行策略。
(5)执行程序,观察并分析结果。
- 定义一个函数接口IGreeting,它声明一个接受String参数并返回void的方法sayHello。接下来,创建一个名为Greeting的类,包含一个接受IGreeting类型参数的方法greet,这个方法通过函数接口变量调用sayHello方法。在主类中,使用Lambda表达式创建一个IGreeting对象,并将其传递给Greeting类的greet方法。这个Lambda表达式接受一个字符串参数,并打印一条欢迎消息。
-
设计思路:
(1)定义函数接口:首先定义一个名为IGreeting的函数接口,其中包含一个接受String参数并返回void的方法sayHello。
(2)创建类:然后创建一个名为Greeting的类,它包含一个接受IGreeting类型参数的方法greet。在这个方法中,通过函数接口变量调用sayHello方法。这样就可以通过传入不同的IGreeting实例来改变greet方法的行为。
(3)使用Lambda表达式:在主类中,使用Lambda表达式创建一个IGreeting对象,然后将其传递给Greeting类的greet方法。这个Lambda表达式接受一个字符串参数,并打印一条欢迎消息。
-
设计类图:
图4 题目四类图
-
实验步骤:
(1)定义函数接口IGreeting,包括接受String参数并返回void的sayHello方法。
(2)创建Greeting类,包含一个接受IGreeting类型参数的方法greet。在这个方法中,通过函数接口变量调用sayHello方法。
(3)在主类中,使用Lambda表达式创建一个IGreeting对象,然后将其传递给Greeting类的greet方法。
(4)执行程序,观察并分析结果。在结果中,当主类调用Greeting类的greet方法时,实际上是通过函数接口和Lambda表达式,打印出一条欢迎消息。
##四、实验结果与分析
- 题目一
实验结果:
图5 题目一输出结果
实验结果分析:
实验通过定义IAnimal接口及Dog、Cat两个实现类来展示接口回调。ZooKeeper类的careForAnimal()方法通过IAnimal接口来调用具体实现类的eat()和sleep()方法,实现对动物的照顾。这体现“开-闭”原则:即添加新的动物类(实现IAnimal接口)时,ZooKeeper类无需修改即可照顾新动物。经检验,实验结果符合预期。
- 题目二
- 实验结果:
图6 题目二输出结果
- 实验结果分析:
本实验创建了一个简单的驾驶模型,其中包括一个IDriveable接口、两个实现这个接口的类(Car和Bike)以及一个主类。在此方法中,它首先打印出正在拖拉的信息,然后调用被拖物体的start和stop方法,表示启动和停止被拖拉的物体。在主类中,创建一个Car对象和一个Bike对象,然后让汽车启动、拖拉自行车,最后停止汽车。经检验,实验结果符合预期。
- 题目三
- 实验结果:
图7 题目三输出结果
- 实验结果分析:
在本实验中,在主类中,创建 ConcreteStrategyA 和 ConcreteStrategyB 的对象,并创建一个 Context 对象,初始策略为 ConcreteStrategyA。然后调用 executeStrategy 方法执行策略,接着更改策略为 ConcreteStrategyB,再次调用 executeStrategy 方法执行策略。经检验,实验结果符合预期。
- 题目四
- 实验结果:
图8 题目四输出结果
- 实验结果分析:
本实验成功地通过Lambda表达式实现匿名函数,并通过函数接口传递给 greet 方法。经检验,实验结果符合预期。
-
实验中遇到的问题:
(1)定义接口时没有正确声明抽象方法,在定义IAnimal接口时,eat()和sleep()方法没有加abstract关键字。
(2)实现接口类没有正确重写抽象方法,Dog类和Cat类实现IAnimal接口后,没有重写eat()和sleep()方法。
(3)接口类型变量无法调用具体方法,通过IAnimal接口变量无法调用eat()和sleep()方法。
(4)类定义错误,没有实现接口,定义Dog类时没有添加implements IAnimal。
-
问题解决的方法:
(1)正确定义接口时,接口方法需要加abstract关键字。
(2)类实现接口后需要重写接口中的所有抽象方法,否则该类的对象无法被当成接口使用。
(3)由于接口变量保存的是实现类对象的引用,但其类型是接口类型,所以无法直接调用实现类中的具体方法。需要强制类型转换为实现类后再调用。
(4)当类需要实现某个接口时,在类定义时需要使用implements关键字明确指定实现的接口,否则该类不会被视为实现了该接口。
##五、附源程序
1.题目1:
Main.java
// 定义一个动物接口,包含吃和睡觉的操作
interface IAnimal {
void eat();
void sleep();
}
// 实现Animal接口的Dog类
class Dog implements IAnimal {
// 重写eat方法
public void eat() {
System.out.println("狗正在吃饭..."); // 打印狗正在吃饭的信息
}
// 重写sleep方法
public void sleep() {
System.out.println("狗正在睡觉..."); // 打印狗正在睡觉的信息
}
}
// 实现IAnimal接口的Cat类
class Cat implements IAnimal {
// 重写eat方法
public void eat() {
System.out.println("猫正在吃饭..."); // 打印猫正在吃饭的信息
}
// 重写sleep方法
public void sleep() {
System.out.println("猫正在睡觉..."); // 打印猫正在睡觉的信息
}
}
// 定义一个动物园管理员类,用于照顾动物
class ZooKeeper {
private IAnimal animal; // 定义一个Animal类型的私有变量
// ZooKeeper的构造方法,需要传入一个Animal类型的变量
public ZooKeeper(IAnimal animal) {
this.animal = animal; // 将传入的animal赋值给类的animal变量
}
// 照顾动物的方法,包括动物的吃和睡觉的操作
public void careForAnimal() {
animal.eat(); // 动物吃东西
animal.sleep(); // 动物睡觉
}
}
public class Main {
public static void main(String[] args) {
IAnimal dog = new Dog(); // 创建一个Dog类型的动物
IAnimal cat = new Cat(); // 创建一个Cat类型的动物
ZooKeeper zooKeeper = new ZooKeeper(dog); // 创建一个ZooKeeper对象,照顾的动物为dog
System.out.println("动物园管理员正在照顾狗:");
zooKeeper.careForAnimal(); // 动物园管理员照顾狗
zooKeeper = new ZooKeeper(cat); // 创建一个新的ZooKeeper对象,照顾的动物为cat
System.out.println("\n动物园管理员正在照顾猫:");
zooKeeper.careForAnimal(); // 动物园管理员照顾猫
}
}
- 题目2:
Main.java
// 定义一个可驾驶的接口,包含启动和停止的操作
interface IDriveable {
void start();
void stop();
}
// 实现IDriveable接口的Car类
class Car implements IDriveable {
// 重写start方法
public void start() {
System.out.println("汽车正在启动..."); // 打印汽车正在启动的信息
}
// 重写stop方法
public void stop() {
System.out.println("汽车正在停止..."); // 打印汽车正在停止的信息
}
// 定义一个参数为IDriveable类型的方法,表示汽车可以拖着其他可以驾驶的物体
public void tow(IDriveable driveable) {
System.out.println("汽车正在拖拉另一个可驾驶的物体..."); // 打印汽车正在拖拉的信息
driveable.start(); // 启动被拖拉的物体
driveable.stop(); // 停止被拖拉的物体
}
}
// 实现IDriveable接口的Bike类
class Bike implements IDriveable {
// 重写start方法
public void start() {
System.out.println("自行车正在启动..."); // 打印自行车正在启动的信息
}
// 重写stop方法
public void stop() {
System.out.println("自行车正在停止..."); // 打印自行车正在停止的信息
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car(); // 创建一个Car对象
Bike bike = new Bike(); // 创建一个Bike对象
car.start(); // 启动汽车
car.tow(bike); // 汽车拖拉自行车
car.stop(); // 停止汽车
}
}
- 题目3:
Main.java
// 定义一个策略接口,包含一个执行方法
interface IStrategy {
void execute();
}
// 实现IStrategy接口的ConcreteStrategyA类,代表策略A
class ConcreteStrategyA implements IStrategy {
// 重写execute方法
public void execute() {
System.out.println("执行策略A..."); // 打印执行策略A的信息
}
}
// 实现IStrategy接口的ConcreteStrategyB类,代表策略B
class ConcreteStrategyB implements IStrategy {
// 重写execute方法
public void execute() {
System.out.println("执行策略B..."); // 打印执行策略B的信息
}
}
// 定义一个上下文类,用于使用策略
class Context {
private IStrategy strategy; // 定义一个IStrategy类型的私有变量
// Context的构造方法
public Context(IStrategy strategy) {
this.strategy = strategy; // 将传入的strategy赋值给类的strategy变量
}
// 设置策略的方法
public void setStrategy(IStrategy strategy) {
this.strategy = strategy; // 将传入的strategy赋值给类的strategy变量
}
// 执行策略的方法
public void executeStrategy() {
strategy.execute(); // 调用传入策略的execute方法
}
}
public class Main {
public static void main(String[] args) {
IStrategy strategyA = new ConcreteStrategyA(); // 创建一个ConcreteStrategyA对象
IStrategy strategyB = new ConcreteStrategyB(); // 创建一个ConcreteStrategyB对象
Context context = new Context(strategyA); // 创建一个Context对象,初始策略为strategyA
context.executeStrategy(); // 执行策略
context.setStrategy(strategyB); // 更改策略为strategyB
context.executeStrategy(); // 执行策略
}
}
- 题目4:
Main.java
// 定义一个函数接口,有一个String类型参数的方法
interface IGreeting {
void sayHello(String name);
}
// Greeting类,有一个greet方法,这个方法接受一个IGreeting接口和一个String类型的参数
class Greeting {
public void greet(IGreeting greeting, String name) {
greeting.sayHello(name); // 调用sayHello方法
}
}
public class Main {
public static void main(String[] args) {
Greeting greeting = new Greeting(); // 创建一个Greeting对象
// 使用Lambda表达式创建一个IGreeting对象
IGreeting helloGreeting = name -> System.out.println("Hello, " + name + "!");
// 调用greet方法
greeting.greet(helloGreeting, "World");
}
}