《设计模式之行为型模式》系列,共包含以下文章:
- 行为型模式(一):模板方法模式、观察者模式
- 行为型模式(二):策略模式、命令模式
- 行为型模式(三):责任链模式、状态模式
- 行为型模式(四):备忘录模式、中介者模式
- 行为型模式(五):解释器模式、访问者模式、依赖注入
???? 如果您觉得这篇文章有用 ✔️ 的话,请给博主一个一键三连 ???????????? 吧 (点赞 ????、关注 ????、收藏 ????)!!!您的支持 ???????????? 将激励 ???? 博主输出更多优质内容!!!
行为型模式(五):解释器模式、访问者模式
- 9.解释器模式(Interpreter)
- 9.1 代码示例
- 9.1.1 定义表达式接口
- 9.1.2 实现具体表达式类
- 9.1.3 客户端
- 10.访问者模式(Visitor)
- 10.1 代码示例
- 10.1.1 元素接口
- 10.1.2 具体元素
- 10.1.3 访问者接口
- 10.1.4 具体访问者
- 10.1.5 对象结构
- 10.1.6 客户端
- 10.1.7 输出
- 11.依赖注入(Dependency Injection)
- 11.1 代码示例
- 11.1.1 构造器注入(Constructor Injection)
- 11.1.2 设值方法注入(Setter Injection)
- 11.1.3 接口注入
- 11.1.3.1 定义注入接口
- 11.1.3.2 实现注入接口
- 11.1.3.3 客户端
- 11.2 总结
9.解释器模式(Interpreter)
解释器模式(Interpreter
)是一种行为设计模式,它主要用于处理语言、表达式或命令的解析和执行。这种模式定义了如何构建一个解释器来解析特定的语句或命令,并执行相应的操作。下面是解释器模式的一些关键点:
- 文法定义:首先需要定义一个文法,这个文法描述了语言或表达式的结构。例如,一个简单的算术表达式文法可能包括加法、减法等操作。
-
表达式接口:定义一个抽象类或接口,所有具体的表达式类都实现这个接口。接口通常包含一个
interpret
方法,用于解释和执行表达式。 -
具体表达式类:实现表达式接口的具体类,每个类负责解析和执行特定类型的表达式。例如,
AddExpression
类负责处理加法操作。 - 上下文:一个上下文对象,用于存储解析过程中需要的信息,如变量值、中间结果等。
-
客户端:客户端代码将表达式组合成一个大的表达式树,然后调用
interpret
方法来解析和执行整个表达式。
9.1 代码示例
假设我们要解析和执行一个简单的算术表达式,如 1 + 2 * 3
。我们可以使用解释器模式来实现:
9.1.1 定义表达式接口
public interface Expression {
int interpret();
}
9.1.2 实现具体表达式类
public class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret() {
return number;
}
}
public class AddExpression implements Expression {
private Expression left, right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}
public class MultiplyExpression implements Expression {
private Expression left, right;
public MultiplyExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() * right.interpret();
}
}
9.1.3 客户端
public class Client {
public static void main(String[] args) {
Expression expression = new AddExpression(
new NumberExpression(1),
new MultiplyExpression(
new NumberExpression(2),
new NumberExpression(3)
)
);
int result = expression.interpret();
System.out.println("结果: " + result); // 输出: 结果: 7
}
}
-
expression
是一个AddExpression
对象。- 调用
AddExpression
的interpret
方法:-
left.interpret()
调用NumberExpression(1)
的interpret
方法,返回 1。 -
right.interpret()
调用MultiplyExpression(2, 3)
的interpret
方法:-
left.interpret()
调用NumberExpression(2)
的interpret
方法,返回 2。 -
right.interpret()
调用NumberExpression(3)
的interpret
方法,返回 3。 -
MultiplyExpression
的interpret
方法返回 2 * 3 = 6。
-
-
AddExpression
的interpret
方法返回 1 + 6 = 7。
-
- 调用
通过这种方式,解释器模式可以灵活地解析和执行复杂的表达式,同时保持代码的可扩展性和可维护性。
10.访问者模式(Visitor)
访问者模式(Visitor
)是一种行为设计模式,它允许你在不改变数据结构的情况下,为数据结构中的元素添加新的操作。这种模式特别适用于数据结构相对稳定,但需要在数据结构上定义很多操作的场景。以下是访问者模式的几个关键点:
-
元素(
Element
):定义一个接受访问者的方法accept(Visitor visitor)
,该方法会调用访问者的方法来访问元素。 -
访问者(
Visitor
):定义一系列访问方法,每个方法对应一种元素类型。访问方法通常命名为visit(Element element)
。 -
具体元素(
ConcreteElement
):实现accept
方法,该方法会调用访问者的一个访问方法。 -
具体访问者(
ConcreteVisitor
):实现访问者接口中的访问方法,对具体元素进行操作。 -
对象结构(
ObjectStructure
):可以是集合或其他数据结构,包含元素对象,并提供方法让访问者访问这些元素。
10.1 代码示例
假设你有一个文档编辑器,文档中包含不同类型的对象,如文本段落和图片。你希望在不修改这些对象的情况下,为它们添加新的操作,比如计算字数或生成缩略图。
10.1.1 元素接口
interface Element {
void accept(Visitor visitor);
}
10.1.2 具体元素
class Paragraph implements Element {
private String text;
public Paragraph(String text) {
this.text = text;
}
public String getText() {
return text;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Image implements Element {
private String url;
public Image(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
10.1.3 访问者接口
interface Visitor {
void visit(Paragraph paragraph);
void visit(Image image);
}
10.1.4 具体访问者
class WordCountVisitor implements Visitor {
private int wordCount = 0;
@Override
public void visit(Paragraph paragraph) {
String[] words = paragraph.getText().split("\\s+");
wordCount += words.length;
}
@Override
public void visit(Image image) {
// 图片不增加字数
}
public int getWordCount() {
return wordCount;
}
}
class ThumbnailGeneratorVisitor implements Visitor {
@Override
public void visit(Paragraph paragraph) {
// 文本段落不需要生成缩略图
}
@Override
public void visit(Image image) {
System.out.println("Generating thumbnail for image: " + image.getUrl());
}
}
10.1.5 对象结构
class Document {
private List<Element> elements = new ArrayList<>();
public void addElement(Element element) {
elements.add(element);
}
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
10.1.6 客户端
public class VisitorPatternDemo {
public static void main(String[] args) {
Document document = new Document();
document.addElement(new Paragraph("Hello, world!"));
document.addElement(new Image("http://example.com/image.jpg"));
WordCountVisitor wordCountVisitor = new WordCountVisitor();
document.accept(wordCountVisitor);
System.out.println("Total word count: " + wordCountVisitor.getWordCount());
ThumbnailGeneratorVisitor thumbnailGeneratorVisitor = new ThumbnailGeneratorVisitor();
document.accept(thumbnailGeneratorVisitor);
}
}
10.1.7 输出
Total word count: 2
Generating thumbnail for image: http://example.com/image.jpg
通过访问者模式,你可以在不修改文档元素的情况下,为它们添加新的操作,如计算字数和生成缩略图。
11.依赖注入(Dependency Injection)
依赖注入(Dependency Injection
)是一种设计模式,用于实现控制反转(Inversion of Control
,IoC
)。它的主要目的是减少代码之间的耦合,提高代码的可测试性和可维护性。通过依赖注入,对象的依赖关系由外部提供,而不是由对象自己创建或查找。
11.1 代码示例
假设有一个 Logger
接口和两个实现类 FileLogger
和 ConsoleLogger
,以及一个需要日志功能的 UserService
类。
11.1.1 构造器注入(Constructor Injection)
通过构造器传递依赖对象。
- 优点:依赖关系清晰,不可变性好。
- 缺点:构造器参数过多时,代码可读性下降。
// Logger 接口
interface Logger {
void log(String message);
}
// FileLogger 实现
class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("File: " + message);
}
}
// ConsoleLogger 实现
class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Console: " + message);
}
}
// UserService 类,通过构造器注入 Logger
class UserService {
private final Logger logger;
public UserService(Logger logger) {
this.logger = logger;
}
public void createUser(String name) {
logger.log("Creating user: " + name);
// 其他创建用户的逻辑
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Logger logger = new ConsoleLogger();
UserService userService = new UserService(logger);
userService.createUser("Alice");
}
}
11.1.2 设值方法注入(Setter Injection)
通过设值方法(setter
)传递依赖对象。
- 优点:灵活性高,便于修改依赖关系。
- 缺点:依赖关系不那么明显,对象可能处于不完整状态。
// Logger 接口
interface Logger {
void log(String message);
}
// FileLogger 实现
class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("File: " + message);
}
}
// ConsoleLogger 实现
class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Console: " + message);
}
}
// UserService 类,通过设值方法注入 Logger
class UserService {
private Logger logger;
public void setLogger(Logger logger) {
this.logger = logger;
}
public void createUser(String name) {
logger.log("Creating user: " + name);
// 其他创建用户的逻辑
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
UserService userService = new UserService();
Logger logger = new ConsoleLogger();
userService.setLogger(logger);
userService.createUser("Alice");
}
}
11.1.3 接口注入
通过接口方法传递依赖对象。
- 优点:灵活性高,适用于复杂的依赖关系。
- 缺点:实现复杂,使用较少。
11.1.3.1 定义注入接口
// Logger 接口
interface Logger {
void log(String message);
}
// FileLogger 实现
class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("File: " + message);
}
}
// ConsoleLogger 实现
class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Console: " + message);
}
}
// 注入接口
interface LoggerInjector {
void injectLogger(Logger logger);
}
11.1.3.2 实现注入接口
UserService
类需要实现 LoggerInjector
接口,并提供一个方法来注入 Logger
对象。
// UserService 类,实现 LoggerInjector 接口
class UserService implements LoggerInjector {
private Logger logger;
@Override
public void injectLogger(Logger logger) {
this.logger = logger;
}
public void createUser(String name) {
logger.log("Creating user: " + name);
// 其他创建用户的逻辑
}
}
11.1.3.3 客户端
public class Main {
public static void main(String[] args) {
// 创建 UserService 对象
UserService userService = new UserService();
// 创建 Logger 对象
Logger logger = new ConsoleLogger();
// 通过 LoggerInjector 接口注入 Logger 对象
((LoggerInjector) userService).injectLogger(logger);
// 使用 UserService
userService.createUser("Alice");
}
}
- 类型转换
-
userService
是一个UserService
类的实例。 -
UserService
类实现了LoggerInjector
接口,因此userService
也可以被视为LoggerInjector
类型的对象。 - 通过
((LoggerInjector) userService)
,我们将userService
强制转换为LoggerInjector
类型。
-
- 调用注入方法
-
LoggerInjector
接口定义了一个injectLogger
方法。 - 通过类型转换后,我们可以调用
injectLogger
方法,将Logger
对象注入到UserService
中。
-
11.2 总结
依赖注入是一种强大的设计模式,通过外部提供依赖关系,使得代码更加灵活、可测试和可维护。
- 降低耦合度:对象不再负责创建或查找其依赖,依赖关系由外部提供。
- 提高可测试性:可以通过注入不同的依赖来测试对象的行为。
- 提高可维护性:依赖关系明确,代码更易于理解和维护。
构造器注入、设值方法注入和接口注入是实现依赖注入的三种主要方式,每种方式都有其适用场景和优缺点。