Java笔试面试题AI答之设计模式(4)
文章目录
- 16. 简述什么是观察者模式?
- 基本概念
- 主要特点
- 实现方式
- 应用场景
- 优缺点
- 17. 请列举观察者模式应用场景 ?
- 18. 请用Java代码实现观察者模式的案例 ?
- 19. 什么是装饰模式?
- 定义与特点
- 结构与角色
- 工作原理
- 优点
- 应用场景
- 示例
- 20. 请用Java代码实现装饰者模式的案例 ?
16. 简述什么是观察者模式?
观察者模式(Observer Pattern),也被称为模型-视图(Model-View)模式、源-收听者(Listener)模式或从属者模式,是软件设计模式中的一种行为型模式。该模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并被自动更新。以下是观察者模式的详细解释:
基本概念
- 观察者(Observer):等待被通知的对象,它持有对一个或多个主题(Subject)的引用,以便在主题状态发生变化时得到更新。
- 主题(Subject):也被称为被观察者,当它的状态发生变化时,会主动通知所有注册的观察者对象,使得它们能够自动更新自己。
主要特点
- 松耦合:主题和观察者之间通过抽象接口进行交互,使得它们可以独立演化而不影响彼此。
- 一对多关系:一个主题可以有多个观察者,并且它们之间没有直接联系。
- 可扩展性:可以随时增加新的观察者或删除现有观察者,无需修改主题或观察者的代码。
- 实时性:实现了实时更新机制,当主题状态改变时能够即刻通知相关观察者。
实现方式
观察者模式通常包含以下几个角色:
- 抽象主题(Subject):定义了一个用于保存观察者对象的集合以及一个用于增加、删除和通知观察者的接口。
- 具体主题(Concrete Subject):实现了抽象主题定义的接口,用于管理具体的观察者对象集合,并在需要时向观察者发送通知。
- 抽象观察者(Observer):定义了一个更新接口,该接口用于响应主题状态的变化。
- 具体观察者(Concrete Observer):实现了抽象观察者接口,并在接收到主题的通知时执行相应的操作。
应用场景
观察者模式在许多应用中都有广泛的应用,特别是当存在对象之间的一对多关系,并且需要实时通知和更新时。以下是一些典型的应用场景:
- 消息发布/订阅系统:观察者模式可以用于构建消息发布/订阅系统,其中消息发布者充当主题(被观察者),而订阅者则充当观察者。
- 图形用户界面(GUI):在图形用户界面中,用户界面的各个部分(如按钮、文本框等)可以作为观察者,而它们所依赖的数据或状态则可以作为主题。当数据或状态发生变化时,界面元素会自动更新以反映这些变化。
- 业务对象之间的交互:在业务系统中,不同业务对象之间可能存在复杂的依赖关系。通过使用观察者模式,可以将这些依赖关系解耦,使得当某个业务对象的状态发生变化时,能够自动通知并更新依赖它的其他业务对象。
优缺点
- 优点:
- 降低了对象之间的耦合度,提高了系统的灵活性和可扩展性。
- 实现了对象之间的动态关联,可以在运行时动态地添加或删除观察者。
- 简化了对象之间的通信机制,使得系统更加易于理解和维护。
- 缺点:
- 如果一个主题通知了太多的观察者,可能会导致系统性能下降。
- 如果观察者和主题之间存在循环依赖关系,可能会导致系统崩溃。
- 在某些情况下,观察者可能无法感知到其他已注册的新类型或特定类型的观察者。
总的来说,观察者模式是一种非常有用的设计模式,它能够帮助我们解决对象之间的一对多依赖关系问题,并提高系统的灵活性和可扩展性。然而,在使用时也需要注意其潜在的缺点和限制。
17. 请列举观察者模式应用场景 ?
观察者模式(Observer Pattern)是一种设计模式,主要用于当一个对象的状态发生变化时,能够自动通知多个依赖于它的对象并使它们同步更新。它是一种行为型模式,常用于实现发布-订阅机制。以下是观察者模式的一些主要应用场景:
-
图形用户界面(GUI)事件监听:
- 在图形用户界面编程中,按钮、文本框等控件的事件处理通常使用观察者模式。控件(如按钮)作为被观察者,当用户进行某些操作时(如点击按钮),会通知观察者(即事件处理器或监听器)来处理相应的事件。例如,当用户点击按钮时,按钮对象会通知所有注册的监听器,然后调用这些监听器的处理方法来响应点击事件。
-
数据模型与视图同步:
- 在模型-视图-控制器(MVC)架构中,观察者模式常用于数据模型和视图之间的更新同步。当模型中的数据发生变化时,所有依赖该模型的视图都会自动更新显示。例如,在图形化的应用程序中,如果数据模型发生改变,表格视图和图表视图等都会同步更新以反映最新数据。
-
发布-订阅系统:
- 观察者模式是发布-订阅系统的基础。在这种系统中,发布者(被观察者)发布消息,订阅者(观察者)订阅感兴趣的消息类型。当某个消息类型被发布时,订阅者会收到通知并执行相应的处理逻辑。这种模式常见于消息中间件系统,如Kafka或RabbitMQ,它们允许不同的服务订阅某个主题,当该主题发布消息时,所有订阅者都会接收消息并处理。
-
股票价格监控:
- 在金融系统中,股票价格可能频繁变动,多个不同的系统(如显示价格的终端、交易系统等)都依赖这些变化。观察者模式可以让股票价格的变化自动通知所有依赖该数据的系统。当股票价格更新时,所有注册的投资者或系统会收到通知,并相应调整其交易策略或显示界面。
-
缓存系统的失效通知:
- 在一些分布式系统中,当某个缓存中的数据失效时,观察者模式可以用于通知相关系统或服务进行缓存更新或重新加载数据。例如,当数据库中的数据被更新时,缓存层会收到通知,标记缓存数据失效,以确保下一次请求能获取到最新的数据。
-
社交媒体的通知机制:
- 在社交媒体平台中,当用户发布新动态时,所有关注者都应及时收到通知。这里的用户是被观察者,关注者是观察者。用户发布新内容时,平台会通知所有关注者。例如,在微博、推特等平台中,当某个用户发布新动态时,所有关注者都会在时间线上看到该动态。
-
日志系统:
- 在某些复杂的系统中,日志的记录可能需要发送到多个地方,如文件、数据库、监控系统等。观察者模式可以用于实现这样的需求,其中日志记录器充当被观察者,而观察者可以是日志分析器、报警系统等。当日志发生变化时,观察者将收到通知并执行相应的操作,如生成报告、发送警报等。
-
定时任务或调度器:
- 一些定时任务系统需要在指定的时间通知多个任务执行。观察者模式可以很好地支持这一功能。调度器作为被观察者,任务作为观察者,调度器到达指定时间后通知所有观察者执行任务。例如,定时任务调度器可以通知多个系统组件执行某些例行维护任务。
-
游戏中的状态变化通知:
- 在游戏开发中,某些对象的状态变化可能影响多个其他对象。例如,当游戏角色的生命值发生变化时,UI界面需要更新生命值显示,敌对角色可能需要调整攻击策略,音效系统可能需要播放不同的音效。观察者模式可以确保这些变化能够自动通知并同步更新到所有相关的对象。
综上所述,观察者模式在需要通知多个对象进行同步更新的场合具有广泛的应用价值,它能够提高系统的扩展性和可维护性。
18. 请用Java代码实现观察者模式的案例 ?
观察者模式(Observer Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
以下是一个使用Java实现的简单观察者模式案例:
首先,定义一个主题接口(Subject),它包含一个注册观察者、删除观察者以及通知观察者的方法:
import java.util.ArrayList;
import java.util.List;
public interface Subject {
// 注册观察者
void registerObserver(Observer o);
// 删除观察者
void removeObserver(Observer o);
// 通知所有观察者
void notifyObservers();
}
然后,定义一个观察者接口(Observer),它包含一个更新方法,用于在接收到通知时执行:
public interface Observer {
// 接收通知时调用的更新方法
void update(String message);
}
接下来,实现具体的主题类(ConcreteSubject),它实现了Subject接口,并维护了一个观察者列表:
public class ConcreteSubject implements Subject {
private List<Observer> observers;
private String subjectState;
public ConcreteSubject() {
this.observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
if (!observers.contains(o)) {
observers.add(o);
}
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(subjectState);
}
}
// 用于设置主题状态并通知所有观察者
public void setState(String state) {
this.subjectState = state;
notifyObservers();
}
// Getter和Setter(如果需要)
public String getState() {
return subjectState;
}
}
最后,实现具体的观察者类(ConcreteObserver),它实现了Observer接口:
public class ConcreteObserver implements Observer {
private String name;
private String observerState;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
observerState = message;
readObserverState();
}
private void readObserverState() {
System.out.println(name + " received state: " + observerState);
}
}
现在,我们可以使用这些类来演示观察者模式:
public class ObserverPatternDemo {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
Observer observer1 = new ConcreteObserver("Observer 1");
Observer observer2 = new ConcreteObserver("Observer 2");
subject.registerObserver(observer1);
subject.registerObserver(observer2);
subject.setState("Hello World!");
// 如果需要,可以删除某个观察者
// subject.removeObserver(observer1);
// subject.setState("Another message");
}
}
在这个例子中,ConcreteSubject
类维护了一个观察者列表,并在其状态改变时通过调用 notifyObservers()
方法来通知所有已注册的观察者。每个 ConcreteObserver
对象在接收到通知时都会更新自己的状态,并通过 readObserverState()
方法输出接收到的消息。
19. 什么是装饰模式?
装饰模式是一种常见的设计模式,主要用于在不改变原有类结构的情况下,动态地给对象增加新的功能或职责。这种模式通过创建一个包装对象(即装饰器)来包裹真实的对象,从而达到扩展对象功能的目的。以下是装饰模式的详细简述:
定义与特点
- 定义:装饰模式(Decorator Pattern)动态地给一个对象增加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
- 特点:
- 多用组合,少用继承:通过对象间的组合关系来实现功能的扩展,而不是通过继承关系。
- 开闭原则:对扩展开放,对修改关闭。可以在不修改原有类代码的情况下,通过增加新的装饰器类来扩展功能。
- 透明性:装饰对象与真实对象有相同的接口,这样客户端就可以以相同的方式与它们交互,无需知道它们是真实对象还是装饰对象。
结构与角色
装饰模式包含以下几个角色:
- 抽象构件(Component)角色:定义一个对象的接口,可以给这些对象动态地添加一些职责。
- 具体构件(Concrete Component)角色:定义了一个具体的对象,也可以给这个对象添加一些职责。
- 装饰(Decorator)角色:持有一个构件(Component)对象的引用,并定义一个与抽象构件接口一致的接口。
- 具体装饰(Concrete Decorator)角色:负责给构件对象“贴上”一些新的职责。
工作原理
- 装饰模式通过创建一个包装对象(即装饰器)来包裹真实对象。
- 装饰器对象与真实对象有相同的接口,客户端可以通过这个接口与它们进行交互。
- 装饰器对象可以在转发请求给真实对象之前或之后增加一些新的功能或行为。
优点
- 灵活性:可以在不修改原有类代码的情况下,通过增加新的装饰器类来扩展功能。
- 扩展性:遵循开闭原则,对扩展开放,对修改关闭。
- 减少子类数量:通过组合关系替代继承关系,减少了子类的数量,降低了系统的复杂度。
应用场景
- 当需要给某个对象动态地增加一些职责时。
- 当不能使用继承的方式来扩展对象的功能时(例如,类已经被定义为final,或者出于设计上的考虑不希望产生大量的子类)。
- 当一个类的功能比较复杂,而且需要经常扩展时,可以使用装饰模式来简化类的设计。
示例
假设有一个简单的图形界面程序,其中有一个按钮(Button)类。现在需要给这个按钮增加一些新的样式或功能,比如边框、颜色等。使用装饰模式,可以创建一个或多个装饰器类(如BorderDecorator、ColorDecorator等),每个装饰器类都负责给按钮增加一种特定的样式或功能。这样,就可以通过组合不同的装饰器来创建具有多种样式和功能的按钮对象,而无需修改原始的按钮类代码。
20. 请用Java代码实现装饰者模式的案例 ?
装饰者模式(Decorator Pattern)是一种用于动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更为灵活。这种模式创建了一个装饰者类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
下面是一个使用Java实现的装饰者模式案例,假设我们有一个咖啡类(Coffee),我们可以使用不同的调料(如奶精、摩卡等)来装饰它,增加它的口味。
首先,定义一个咖啡接口(Coffee)和一个具体的咖啡类(如Espresso)实现这个接口:
interface Coffee {
double cost();
String getDescription();
}
class Espresso implements Coffee {
@Override
public double cost() {
return 1.99;
}
@Override
public String getDescription() {
return "Espresso";
}
}
接下来,定义一个装饰者抽象类,它实现了Coffee接口,并持有一个Coffee对象的引用,用于调用实际咖啡的方法:
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee decoratedCoffee) {
this.decoratedCoffee = decoratedCoffee;
}
@Override
public double cost() {
return decoratedCoffee.cost();
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
现在,我们可以创建具体的装饰者类,比如Mocha
和Milk
,它们扩展了CoffeeDecorator
类,并添加了额外的功能(如增加成本、修改描述):
class Mocha extends CoffeeDecorator {
public Mocha(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public double cost() {
return 0.20 + super.cost(); // 假设加摩卡需要额外0.20
}
@Override
public String getDescription() {
return super.getDescription() + ", Mocha";
}
}
class Milk extends CoffeeDecorator {
public Milk(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public double cost() {
return 0.10 + super.cost(); // 假设加奶需要额外0.10
}
@Override
public String getDescription() {
return super.getDescription() + ", Milk";
}
}
最后,我们可以使用这些类来创建一个具有多种口味的咖啡,并查看其成本和描述:
public class DecoratorPatternDemo {
public static void main(String[] args) {
Coffee espresso = new Espresso();
System.out.println(espresso.getDescription() + " $" + espresso.cost());
Coffee espressoMocha = new Mocha(espresso);
System.out.println(espressoMocha.getDescription() + " $" + espressoMocha.cost());
Coffee espressoMochaMilk = new Milk(espressoMocha);
System.out.println(espressoMochaMilk.getDescription() + " $" + espressoMochaMilk.cost());
}
}
输出将会是:
Espresso $1.99
Espresso, Mocha $2.19
Espresso, Mocha, Milk $2.29
这样,我们就通过装饰者模式在运行时动态地给咖啡添加了不同的调料,而无需修改咖啡类或创建新的咖啡子类。
答案来自文心一言,仅供参考