摘要:
1、通俗易懂,适合小白
2、仅做面试复习用,部分来源网络,博文免费,知识无价,侵权请联系!
1. 什么是设计模式?
设计模式是在软件开发过程中,针对反复出现的问题所总结归纳出的通用解决方案。它就像是建筑领域里的经典建筑结构,能帮助开发者更高效、更合理地组织代码,提升软件的可维护性、可扩展性和可复用性 。
2. 为什么要使用设计模式?
可维护性:设计模式使代码结构更清晰,模块职责更明确,便于理解和修改代码。
可扩展性:能轻松应对需求变化,在不破坏原有系统结构的前提下添加新功能。
可复用性:模式中的代码结构和逻辑可在不同项目或模块中重复使用,减少重复开发。
3. 设计模式有多少种,都有哪些设计模式?
设计模式一般分为三种类型,共 23 种:
创建型模式(5 种):单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
结构型模式(7 种):代理模式、适配器模式、装饰器模式、桥接模式、组合模式、外观模式、享元模式。
行为型模式(11 种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式 。
4. 设计模式的六大原则是什么?
单一职责原则(SRP):一个类应该只负责一项职责,避免一个类承担过多功能。
里氏替换原则(LSP):所有引用基类的地方必须能透明地使用其子类对象,子类可扩展父类功能,但不能改变父类原有功能的语义。
依赖倒置原则(DIP):高层模块不应该依赖低层模块,二者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
接口隔离原则(ISP):客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。
迪米特法则(LoD):一个对象应该对其他对象保持最少的了解,降低对象之间的耦合度。
开闭原则(OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭 。
5. 什么是高内聚、低耦合?
高内聚:指一个模块内的各个元素之间联系紧密,完成单一且明确的功能。比如一个用户信息处理模块,只专注于用户信息的增删改查等相关操作,模块内代码围绕这一核心功能紧密协作。
低耦合:模块与模块之间相互依赖程度低,一个模块的修改不会或很少影响其他模块。例如用户模块和订单模块,二者相对独立,用户模块的业务逻辑变化基本不影响订单模块 。
6. 什么是单例模式?
单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。比如在数据库连接管理中,整个应用程序只需一个数据库连接实例,通过单例模式可以保证在任何地方获取到的都是同一个数据库连接对象 。
7. 单例模式中俄汉式和懒汉式有什么区别?
- 饿汉式:类加载时就创建实例,线程安全。因为在类初始化阶段实例就已创建,不存在多线程并发创建的问题。例如:
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
- 懒汉式:在第一次调用获取实例方法时才创建实例。但在多线程环境下若不做同步处理会出现多个实例的问题,所以通常需要加锁保证线程安全。比如:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
8. 单例模式都有哪些应用场景?
日志记录器:整个应用程序只需一个日志记录器实例,用于统一记录日志信息。
配置管理器:管理应用程序的配置信息,保证各处获取的配置一致。
数据库连接池:维护一个数据库连接池实例,供多个模块复用数据库连接 。
9. 什么是代理模式?
代理模式是一种结构型设计模式,为其他对象提供一种代理以控制对这个对象的访问。代理对象和目标对象实现相同接口,客户端通过代理对象间接访问目标对象。比如在网络访问中,代理服务器可以代替客户端去访问目标网站,在访问前可以进行权限检查、缓存处理等操作 。
10. Java 中代理模式如何实现静态代理?
静态代理需要代理类和目标类实现相同接口,在代理类中持有目标类对象,通过构造函数传入。示例代码如下:
// 定义接口
interface Subject {
void request();
}
// 目标类
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject handling request");
}
}
// 代理类
class Proxy implements Subject {
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("Proxy pre - processing");
realSubject.request();
System.out.println("Proxy post - processing");
}
}
使用时:
RealSubject realSubject = new RealSubject();
Proxy proxy = new Proxy(realSubject);
proxy.request();
11. Java 中代理模式如何实现动态代理?
Java 动态代理通过 java.lang.reflect.Proxy
和 InvocationHandler
接口实现。步骤如下:
定义目标接口和目标类。
实现
InvocationHandler
接口,在invoke
方法中编写代理逻辑,处理目标方法调用。使用
Proxy.newProxyInstance
方法创建代理对象。示例代码:
// 目标接口
interface Subject {
void request();
}
// 目标类
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject handling request");
}
}
// 实现InvocationHandler
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object\[] args) throws Throwable {
System.out.println("Dynamic proxy pre - processing");
Object result = method.invoke(target, args);
System.out.println("Dynamic proxy post - processing");
return result;
}
}
// 创建动态代理对象
Subject realSubject = new RealSubject();
Subject proxy = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
new MyInvocationHandler(realSubject));
proxy.request();
12. Java 中什么是解释器模式?
解释器模式是一种行为型设计模式,给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。在 Java 中,比如表达式解析场景,通过定义文法规则和解释器来解析并计算表达式的值 。
13. Java 中如何实现解释器模式?
定义抽象表达式类,声明解释方法
interpret
。实现终结符表达式类和非终结符表达式类,分别对应文法中的终结符和非终结符,实现
interpret
方法。客户端调用解释器进行表达式解释。示例代码(简单算术表达式解释):
// 抽象表达式
abstract class Expression {
public abstract int interpret();
}
// 终结符表达式(数字)
class NumberExpression extends Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret() {
return number;
}
}
// 非终结符表达式(加法)
class AddExpression extends Expression {
private Expression left;
private Expression right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}
// 客户端调用
Expression expression = new AddExpression(new NumberExpression(3), new NumberExpression(4));
int result = expression.interpret();
System.out.println("Result: " + result);
14. Java 中什么是替换法则(LSP)?
里氏替换原则(LSP)规定,所有引用基类的地方必须能透明地使用其子类对象。子类必须能够完全替代父类,且在替换后程序的正确性和行为不能被改变。子类可以扩展父类功能,但不能改变父类方法的前置条件和后置条件 。
15. Java 中为什么不允许从静态方法中访问非静态变量?
静态方法属于类本身,在类加载时就存在,不依赖于类的实例。而非静态变量属于类的实例,只有在创建对象后才存在。如果允许静态方法访问非静态变量,就会出现静态方法执行时非静态变量可能还未初始化的情况,导致逻辑错误和内存问题 。
16. 微服务架构的六种常用设计模式是什么?
服务发现模式:解决微服务实例的注册与发现问题,如使用 Eureka、Consul 等组件,让客户端能找到服务实例的地址。
断路器模式:防止服务调用中出现级联故障,当目标服务调用失败率达到一定阈值时,断路器自动断开,不再调用目标服务,避免消耗过多资源。
负载均衡模式:将请求均匀分配到多个服务实例上,提高系统的可用性和性能,常见的有 Nginx 负载均衡、Ribbon 负载均衡等。
API 网关模式:作为微服务的统一入口,负责请求路由、认证授权、流量控制等,对客户端隐藏内部微服务细节 。
事件溯源模式:通过记录系统所有状态变化的事件,按顺序回放事件来重建系统状态,常用于需要追踪历史操作和审计的场景。
命令查询职责分离(CQRS)模式:将读操作(查询)和写操作(命令)分离,分别设计不同的接口和逻辑,提高系统的可扩展性和性能 。
17. Java 中单例模式有什么优缺点?
优点
资源利用率高:避免频繁创建和销毁对象,像数据库连接单例,减少连接创建开销。
全局访问方便:提供全局唯一访问点,便于在不同模块获取同一实例。
缺点
扩展性差:不利于继承和扩展,修改单例类可能影响整个应用。
不利于测试:单例实例可能在测试前就已创建,难以进行单元测试隔离 。
线程安全问题(懒汉式):如果不处理好,懒汉式在多线程环境下可能创建多个实例 。
18. Java 中单例模式使用时有哪些注意事项?
线程安全:如果是懒汉式单例,要确保在多线程环境下的线程安全,可通过同步锁或静态内部类等方式实现。
防止反射攻击:重写单例类的构造函数,防止通过反射创建多个实例。
序列化和反序列化:如果单例类实现了
Serializable
接口,要处理好序列化和反序列化过程,保证反序列化后还是同一个实例 。
19. Java 中单例模式如何防止反射漏洞攻击?
在单例类的构造函数中添加判断逻辑,当发现已经创建过实例时,抛出异常阻止通过反射创建新实例。示例代码:
public class Singleton {
private static Singleton instance;
private Singleton() {
if (instance != null) {
throw new RuntimeException("Singleton instance already exists");
}
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
20. 什么是工厂模式?
工厂模式是一种创建型设计模式,它提供了一种创建对象的方式,将对象的创建和使用分离。通过工厂类来负责创建对象,调用者只需要关心如何使用对象,而不需要了解对象的创建细节 。
21. Java 中工厂模式有什么优势?
解耦创建和使用:使代码的依赖关系更清晰,创建对象的逻辑集中在工厂类,使用方无需知道创建细节。
提高可维护性和可扩展性:如果创建对象的逻辑发生变化,只需要修改工厂类,不影响使用对象的其他代码。新增对象类型时,只需在工厂类中添加相应创建逻辑 。
便于代码复用:工厂类可以被多个地方复用,提高代码复用率 。
22. 说说你理解的 Spring 中工厂模式?
在 Spring 框架中,工厂模式广泛应用于 Bean 的创建。比如 BeanFactory
就是一个工厂,它负责根据配置信息创建和管理 Bean 实例。开发者通过配置文件或注解定义 Bean 的信息,BeanFactory
按照这些信息来创建 Bean,将对象的创建和使用分离,同时 Spring 还提供了 FactoryBean
接口,允许开发者自定义 Bean 的创建逻辑,进一步增强了创建对象的灵活性 。
23. 为什么 Spring IOC 要使用工厂模式创建 Bean?
实现解耦:将 Bean 的创建逻辑从使用它的代码中分离出来,降低组件之间的耦合度。例如一个业务组件依赖其他服务组件,通过 IOC 容器(基于工厂模式)创建和注入服务组件,业务组件无需关心服务组件的创建细节。
便于管理和配置:IOC 容器可以统一管理 Bean 的生命周期、作用域等,通过配置文件或注解就能灵活配置 Bean 的创建方式、依赖关系等,方便应用程序的维护和扩展 。
支持依赖注入:工厂模式为依赖注入提供了基础,能方便地将依赖的对象注入到目标对象中,实现对象之间的协作 。
24. Java 中工厂模式分为哪几大类?
Java 中工厂模式主要分为三类:
简单工厂模式:简单工厂不是一种标准的设计模式,它是工厂方法模式的简化。有一个工厂类,根据传入的参数决定创建哪个具体产品类的实例。
工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类 。
25. Java 中什么是简单工厂模式?
简单工厂模式有一个工厂类,它根据传入的参数来决定创建哪个具体产品类的实例。工厂类有一个创建对象的方法,该方法通常是静态的,通过条件判断(如 switch - case
语句)根据不同参数创建不同产品对象。例如创建不同类型的图形对象:
// 产品接口
interface Shape {
void draw();
}
// 具体产品类:圆形
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
// 具体产品类:矩形
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
// 简单工厂类
class ShapeFactory {
public static Shape createShape(String type) {
if ("circle".equalsIgnoreCase(type)) {
return new Circle();
} else if ("rectangle".equalsIgnoreCase(type)) {
return new Rectangle();
}
return null;
}
}
使用时:
Shape circle = ShapeFactory.createShape("circle");
circle.draw();
26. Java 中简单工厂模式有什么优缺点?
优点
实现简单:工厂类集中了对象创建逻辑,使用方只需传入参数就能获取对象,代码简洁易懂。
解耦创建和使用:将对象创建和使用分离,提高了代码的可维护性。
缺点
不符合开闭原则:当需要新增产品类型时,需要修改工厂类的创建逻辑(如添加
switch - case
分支),违反了开闭原则。职责过重:工厂类承担了所有对象的创建逻辑,当产品类型过多时,工厂类会变得复杂难以维护 。
27. Java 中什么是工厂方法模式?
工厂方法模式定义一个用于创建对象的接口(抽象工厂方法),让子类决定实例化哪一个类。抽象工厂类声明抽象的工厂方法,具体工厂子类实现该方法来创建具体产品对象。比如:
// 产品接口
interface Product {
void operation();
}
// 具体产品类A
class ConcreteProductA implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductA operation");
}
}
// 具体产品类B
class ConcreteProductB implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductB operation");
}
}
27. Java 中什么是工厂方法模式?
答案:
工厂方法模式定义一个创建对象的接口,但让子类决定实例化哪个类。它包含:
- 抽象工厂:声明工厂方法
- 具体工厂:实现工厂方法,创建具体产品
- 抽象产品:定义产品接口
- 具体产品:实现产品接口
28. Java 中工厂方法模式有什么应用场景?
应用场景:
- 创建对象需要大量重复代码
- 客户端不关心具体产品类
- 需要灵活扩展系统时
- 框架设计,如 Spring 的 BeanFactory
29. Java 中什么是抽象工厂模式?
答案:
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它包含:
- 抽象工厂:声明一组创建产品的方法
- 具体工厂:实现创建具体产品的方法
- 抽象产品:定义产品接口
- 具体产品:实现产品接口
30. Java 中抽象工厂模式有什么应用场景?
应用场景:
- 系统需要创建一系列相关产品
- 需要保证产品之间的兼容性
- 产品族扩展困难,如更换数据库
- GUI 库中不同风格的控件创建
31. Java 中代理模式有什么应用场景?
应用场景:
- 远程代理:如 RPC 调用
- 虚拟代理:延迟加载大对象
- 保护代理:控制访问权限
- 智能引用:添加额外操作如引用计数
- AOP 实现:日志、事务等横切关注点
32. Java 中代理模式有几种分类?
分类:
- 静态代理
- 动态代理:
- JDK 动态代理
- CGLIB 动态代理
33. Java 中三种代理模式有什么区别?
区别:
- 静态代理:手动编写代理类,灵活性差
- JDK 动态代理:基于接口,使用反射机制
- CGLIB 动态代理:基于继承,可以代理普通类
34. Java 中代理模式如何实现 CGLIB 动态代理?
实现步骤:
- 添加 CGLIB 依赖
- 创建 MethodInterceptor 实现类
- 使用 Enhancer 创建代理对象
- 设置超类和回调
示例代码:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class);
enhancer.setCallback(new MethodInterceptorImpl());
TargetClass proxy = (TargetClass) enhancer.create();
35. Java 中什么是建造者模式?
答案:
建造者模式将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。它包含:
- 产品:最终构建的复杂对象
- 抽象建造者:定义构建步骤
- 具体建造者:实现构建步骤
- 指挥者:控制构建过程
36. Java 中建造者模式有什么使用场景?
使用场景:
- 创建复杂对象,对象包含多个部分
- 需要创建的对象有不同的表示
- 对象创建过程需要统一控制
- 避免重叠构造器问题
37. Java 中如何实现建造者模式?
实现方式:
- 定义产品类
- 创建抽象建造者接口
- 实现具体建造者类
- 创建指挥者类(可选)
- 客户端使用建造者创建对象
38. Java 中什么是模板方法模式?
答案:
模板方法模式定义一个操作中的算法骨架,而将一些步骤延迟到子类中实现。它包含:
- 抽象类:定义模板方法和基本方法
- 具体子类:实现基本方法
39. Java 中什么时候使用模板方法模式?
使用时机:
- 多个子类有共同行为时
- 需要控制子类扩展点
- 重要、复杂的算法需要固定流程
- 重构时提取公共代码
40. Java 中模板方法模式有什么应用场景?
应用场景:
- 框架设计:如 Spring 的 JdbcTemplate
- 算法固定流程:如排序算法
- 业务流程控制
- 代码复用场景
41. Java 中如何实现模板方法模式?
答案:
- 创建抽象类定义模板方法和基本方法
- 模板方法使用final修饰防止子类重写
- 基本方法可以是抽象方法或具体方法
- 子类继承抽象类并实现抽象方法
- 客户端调用模板方法执行算法流程
示例代码:
abstract class AbstractClass {
// 模板方法
public final void templateMethod() {
primitiveOperation1();
primitiveOperation2();
}
// 基本方法1
protected abstract void primitiveOperation1();
// 基本方法2
protected abstract void primitiveOperation2();
}
42. Java 中什么是外观模式?
答案:
外观模式(Facade Pattern)为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。它:
- 隐藏系统的复杂性
- 提供简化的接口
- 是结构型设计模式
- 也被称为门面模式
43. Java 中外观模式有什么使用场景?
使用场景:
- 为复杂子系统提供简单接口
- 客户端与多个子系统之间存在很大依赖性时
- 需要构建层次化系统结构时
- 系统需要分层设计时
- 减少客户端与子系统的耦合
44. Java 中如何实现外观模式?
实现步骤:
- 创建外观类统一管理子系统
- 外观类包含对子系统对象的引用
- 外观类提供简化的方法调用
- 客户端只与外观类交互
示例代码:
class SubSystemA {
public void operationA() {
System.out.println("子系统A操作");
}
}
class Facade {
private SubSystemA systemA = new SubSystemA();
public void simpleMethod() {
systemA.operationA();
}
}
45. Java 中外观模式有什么优势?
优势:
- 减少系统依赖
- 提高灵活性
- 提高安全性(隐藏内部细节)
- 简化客户端调用
- 符合迪米特法则(最少知道原则)
46. Java 中什么是原型模式?
答案:
原型模式(Prototype Pattern)用于创建重复对象,通过克隆已有对象来创建新对象。它:
- 实现Cloneable接口
- 重写Object的clone()方法
- 分为浅拷贝和深拷贝
- 属于创建型设计模式
47. Java 中原型模式有什么应用场景?
应用场景:
- 创建对象成本较高时(如初始化时间长)
- 需要避免使用new创建对象时
- 系统需要保存对象状态时
- 需要动态加载类时
- 对象结构复杂但有相似性时
48. Java 中原型模式有哪些使用方式?
使用方式:
- 浅拷贝:基本数据类型拷贝值,引用类型拷贝引用
- 深拷贝:所有属性都完全拷贝
- 通过实现Cloneable接口
- 通过序列化实现深拷贝
- 使用第三方库如Apache Commons Lang
49. Java 中原型模式如何实现浅拷贝?
实现方法:
- 实现Cloneable接口
- 重写clone()方法
- 调用super.clone()
示例代码:
class ShallowCopy implements Cloneable {
private int value;
private Date date;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); // 浅拷贝
}
}
50. Java 中原型模式如何实现深拷贝?
实现方法:
- 手动实现:逐个复制引用对象的属性
- 序列化方式:通过对象流实现
- 使用工具类:如BeanUtils.copyProperties()
- 实现Cloneable接口并重写clone()
示例代码(序列化方式):
class DeepCopy implements Serializable {
private int value;
private Date date;
public DeepCopy deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (DeepCopy) ois.readObject();
}
}
51. Java 中什么是策略模式?
策略模式是一种行为型设计模式,它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。在 Java 中,通常是定义一个策略接口,不同的具体策略类实现该接口,从而实现不同的算法逻辑。这样做可以让使用算法的客户端在运行时动态选择不同的策略,而无需修改客户端代码本身。
52. Java 中策略模式有什么应用场景?
电商促销:在电商系统中,不同的促销活动(如满减、打折、赠品等)可以用策略模式实现。系统根据不同的活动规则选择对应的促销策略,方便活动的添加、修改和替换。
支付方式:支付模块中,多种支付方式(如微信支付、支付宝支付、银行卡支付等)可以分别封装成不同策略,客户端根据用户选择调用相应支付策略。
排序算法:在需要进行排序的场景中,不同的排序算法(冒泡排序、快速排序、归并排序等)可以实现为策略,根据实际需求(数据规模、数据特点等)选择合适的排序策略。
53. Java 中策略模式有什么优缺点?
优点
可维护性和可扩展性高:每个策略算法独立封装,新增或修改算法只需在对应的策略类中操作,不影响其他部分。
复用性好:具体策略类可以在不同场景下被复用。
符合开闭原则:可以在不修改原有代码的情况下,添加新的策略。
缺点
策略类数量可能过多:当算法较多时,会产生大量的策略类,增加代码管理难度。
客户端需要了解策略细节:客户端需要知道有哪些策略可供选择,增加了使用成本。
54. Java 中如何实现策略模式?
- 定义策略接口,声明算法方法。例如:
interface Strategy {
int execute(int num1, int num2);
}
- 实现具体策略类。比如加法策略和乘法策略:
class AdditionStrategy implements Strategy {
@Override
public int execute(int num1, int num2) {
return num1 + num2;
}
}
class MultiplicationStrategy implements Strategy {
@Override
public int execute(int num1, int num2) {
return num1 \* num2;
}
}
- 创建上下文类,持有策略对象,并提供设置策略和执行策略的方法:
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.execute(num1, num2);
}
}
- 客户端使用:
Context context = new Context(new AdditionStrategy());
int result = context.executeStrategy(3, 4);
System.out.println(result);
context.setStrategy(new MultiplicationStrategy());
result = context.executeStrategy(3, 4);
System.out.println(result);
55. Java 中什么是观察者模式?
观察者模式是一种行为型设计模式,它定义了对象之间的一对多依赖关系,当一个对象(被观察者)的状态发生改变时,所有依赖它的对象(观察者)都会得到通知并自动更新。在 Java 中,被观察者通常维护一个观察者列表,当自身状态改变时,遍历列表通知所有观察者。
56. Java 中实现观察者模式有哪两种方式?
**基于 JDK 内置的
java.util.Observer
和 **java.util.Observable
:Observable
类是被观察者的基类,它提供了添加、删除观察者以及通知观察者的方法。Observer
接口是观察者需要实现的接口,其中update
方法用于接收被观察者的通知并进行处理。自定义实现:不依赖 JDK 内置类,自己定义被观察者接口和实现类、观察者接口和实现类。被观察者接口中定义注册、注销观察者以及通知观察者的方法,观察者接口定义接收通知的方法。
57. Java 中观察者模式有什么应用场景?
事件监听:在图形界面编程中,按钮点击、窗口关闭等事件可以用观察者模式实现。组件作为被观察者,监听器作为观察者,当组件状态改变(如按钮被点击)时通知监听器。
消息推送:消息中心作为被观察者,订阅者作为观察者。当有新消息产生时,消息中心通知所有订阅者。
股市行情系统:股票价格作为被观察者,股民客户端作为观察者。当股票价格变动时,通知股民客户端更新行情。
58. Java 中如何实现观察者模式?
基于 JDK 内置的实现方式:
- 被观察者继承
Observable
类:
import java.util.Observable;
class StockPrice extends Observable {
private double price;
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
setChanged();
notifyObservers(price);
}
}
- 观察者实现
Observer
接口:
import java.util.Observable;
import java.util.Observer;
class Investor implements Observer {
@Override
public void update(Observable o, Object arg) {
if (o instanceof StockPrice) {
double newPrice = (double) arg;
System.out.println("Stock price updated to: " + newPrice);
}
}
}
- 客户端使用:
StockPrice stockPrice = new StockPrice();
Investor investor = new Investor();
stockPrice.addObserver(investor);
stockPrice.setPrice(100.0);
自定义实现方式:
- 定义被观察者接口:
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(Object arg);
}
- 实现被观察者类:
class WeatherStation implements Subject {
private java.util.ArrayList\<Observer> observers = new java.util.ArrayList<>();
private String weather;
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(Object arg) {
for (Observer observer : observers) {
observer.update(arg);
}
}
public void setWeather(String weather) {
this.weather = weather;
notifyObservers(weather);
}
}
- 定义观察者接口:
interface Observer {
void update(Object arg);
}
- 实现观察者类:
class Citizen implements Observer {
@Override
public void update(Object arg) {
if (arg instanceof String) {
String weather = (String) arg;
System.out.println("Weather updated: " + weather);
}
}
}
- 客户端使用:
WeatherStation weatherStation = new WeatherStation();
Citizen citizen = new Citizen();
weatherStation.registerObserver(citizen);
weatherStation.setWeather("Sunny");
59. Java 中解释器模式有什么优点?
可扩展性好:如果需要支持新的文法规则,只需要新增对应的终结符或非终结符表达式类,符合开闭原则。
语法分析和解释分离:将语言的语法定义和解释执行过程分离,使得代码结构清晰,便于理解和维护。
重用性高:表达式类可以在不同的解释场景中复用,只要符合相同的文法规则。
60. Java 中什么是适配器模式?
适配器模式是一种结构型设计模式,它将一个类的接口转换成客户希望的另一个接口,使原本由于接口不兼容而不能一起工作的那些类可以一起工作。在 Java 中,通常有类适配器和对象适配器两种实现方式,分别通过继承和组合的方式来实现接口转换。
61. Java 中适配器模式有什么应用场景?
第三方库整合:当引入第三方库时,如果第三方库的接口与现有系统接口不兼容,可使用适配器模式进行适配。比如一个支付系统要接入新的支付渠道,该渠道接口与现有系统接口不同,通过适配器模式可让新渠道融入现有系统。
旧系统升级:在对旧系统进行升级改造时,不改变旧系统代码的情况下,使用适配器模式适配新的接口要求,使旧系统能与新系统协同工作。
不同数据库操作:不同数据库的操作接口可能不同,使用适配器模式可以将不同数据库的操作接口适配成统一的操作接口,方便上层应用调用。
62. Java 中适配器模式有什么优缺点?
优点
提高兼容性:解决了不兼容接口之间的问题,使原本不能协同工作的类可以一起工作。
增强可维护性和可扩展性:将适配逻辑封装在适配器类中,不影响原有类的代码,后续如果接口变化,只需修改适配器类。
符合开闭原则:在不修改原有代码的基础上,通过添加适配器类实现功能扩展。
缺点
增加代码复杂度:引入了额外的适配器类,使代码结构变得复杂,增加了理解和维护成本。
过多使用可能导致混乱:如果系统中大量使用适配器模式,会使系统的接口关系变得混乱,难以理清。
63. Java 中实现适配器模式有几种方式?
类适配器:通过继承需要适配的类,同时实现目标接口来完成适配。它利用了继承的特性,在 Java 中一个类只能继承一个父类,这是类适配器的限制之一。
对象适配器:通过在适配器类中组合一个被适配类的对象,然后实现目标接口,在接口方法实现中调用被适配类对象的方法,实现接口转换。对象适配器更符合组合优于继承的原则,使用更为灵活。
64. Java 中如何实现类的适配器模式?
假设存在一个被适配类 Adaptee
,它有一个方法 specificRequest
,目标接口 Target
有一个方法 request
,实现类适配器如下:
// 被适配类
class Adaptee {
public void specificRequest() {
System.out.println("Adaptee specific request");
}
}
// 目标接口
interface Target {
void request();
}
// 类适配器
class ClassAdapter extends Adaptee implements Target {
@Override
public void request() {
specificRequest();
}
}
客户端使用:
Target adapter = new ClassAdapter();
adapter.request();
65. Java 中如何实现对象的适配器模式?
还是以上面的 Adaptee
和 Target
为例,实现对象适配器:
// 被适配类
class Adaptee {
public void specificRequest() {
System.out.println("Adaptee specific request");
}
}
// 目标接口
interface Target {
void request();
}
// 对象适配器
class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
客户端使用:
Adaptee adaptee = new Adaptee();
Target adapter = new ObjectAdapter(adaptee);
adapter.request();
66. Java 中什么是装饰模式?
装饰模式是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。在 Java 中,通常是定义一个抽象组件类,具体组件类实现该抽象类,装饰类也继承或实现该抽象组件类,并且在装饰类中持有一个具体组件对象,通过调用具体组件对象的方法并添加额外逻辑来实现功能增强。
67. 装饰模式和适配器模式有什么区别?
目的不同:装饰模式主要是为了增强对象的功能,在不改变对象接口的前提下动态添加功能;而适配器模式是为了让不兼容的接口能够协同工作,重点在于接口转换。
结构不同:装饰模式中装饰类和被装饰类通常有共同的抽象基类或接口;适配器模式中适配器类和被适配类没有必然的继承或接口实现关系,适配器类主要是为了适配接口。
使用场景不同:当需要在运行时动态地给对象添加功能时,使用装饰模式;当需要整合不同接口的类时,使用适配器模式。
68. 装饰模式和代理模式有什么区别?
目的不同:装饰模式侧重于增强对象功能,在原有功能基础上添加新功能;代理模式主要是控制对目标对象的访问,比如权限控制、延迟加载等。
对客户端的透明性不同:装饰模式对客户端透明,客户端并不知道自己调用的是装饰后的对象还是原始对象;代理模式客户端知道自己使用的是代理对象。
功能扩展方式不同:装饰模式通过层层嵌套装饰类来逐步增强功能;代理模式中代理类通常是集中管理对目标对象的访问控制逻辑。
69. Java 中如何实现装饰模式?
- 定义抽象组件:
abstract class Beverage {
protected String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
- 定义具体组件:
class Coffee extends Beverage {
public Coffee() {
description = "Coffee"
}
@Override
public double cost() {
return 1.0;
}
}
- 定义抽象装饰类,继承抽象组件类:
abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
- 定义具体装饰类:
class Milk extends CondimentDecorator {
private Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
@Override
public double cost() {
return 0.5 + beverage.cost();
}
}
- 客户端使用:
Beverage coffee = new Coffee();
coffee = new Milk(coffee);
System.out.println(coffee.getDescription() + " costs \$" + coffee.cost());
70. Java 中装饰模式有什么优缺点?
优点
灵活性高:可以在运行时动态地添加或去除功能,通过组合不同的装饰类来满足不同的需求。
符合开闭原则:在不修改原有类代码的基础上,通过新增装饰类来扩展功能。
可维护性好:各个装饰类的功能单一,便于理解和维护,且功能的添加和修改都集中在装饰类中。
缺点
多层装饰可能导致复杂度增加:当装饰层次过多时,代码的可读性和调试难度会增加,难以理清对象的实际功能组合情况。
增加类的数量:每一个具体的装饰功能都需要一个装饰类,会导致类的数量增多,增加代码管理成本。
71. Java 中装饰模式有什么应用场景?
图形界面组件:在 Swing 或 JavaFX 中,为组件添加边框、背景色、提示信息等功能,可以使用装饰模式。例如给按钮添加提示信息装饰,给文本框添加边框装饰等。
IO 流处理:Java 的 IO 流中广泛使用了装饰模式,如
BufferedInputStream
可以装饰InputStream
,为其添加缓冲功能;DataInputStream
可以装饰InputStream
,为其添加读取基本数据类型的功能。游戏角色属性增强:在游戏开发中,给游戏角色动态添加属性(如攻击力增强、防御力增强等),可以通过装饰模式实现。
72. 抽象工厂模式和原型模式有什么区别?
创建方式不同:抽象工厂模式通过工厂类创建一系列相关或相互依赖的对象,通常是根据不同的产品族来创建对象;原型模式是通过复制已有的对象实例来创建新对象,通过实现
Cloneable
接口并覆盖clone
方法来实现对象克隆。应用场景不同:抽象工厂模式适用于创建一系列相关对象的场景,比如创建不同操作系统下的图形界面组件(按钮、文本框等);原型模式适用于创建重复对象且对象创建成本较高的场景,如创建大量相似的游戏角色。
依赖关系不同:抽象工厂模式中工厂类和产品类之间存在依赖关系,工厂类负责创建产品类对象;原型模式中主要是对象自身的复制,不涉及复杂的工厂类与产品类的依赖关系。