定义
- 组合模式(Composite Pattern)是一种结构型设计模式
- 它允许你将对象组合成树形结构以表示部分 - 整体层次结构
- 组合模式使得用户对单个对象和组合对象的使用具有一致性。
以下是组合模式的主要特点和定义:
一、主要角色
-
Component(抽象构件) :
- 定义了组合中对象的接口,用于规范叶子节点和容器节点的公共操作,使得客户端可以以统一的方式处理叶子节点和容器节点。
- 通常包含一些通用的方法,如添加子节点、删除子节点、获取子节点等操作的声明。
-
Leaf(叶子构件) :
- 代表组合中的叶子节点,是最基本的、不能再包含其他子节点的对象。
- 实现了 Component 接口,提供具体的业务逻辑。
-
Composite(容器构件) :
- 代表组合中的容器节点,包含子节点,可以对这些子节点进行管理。
- 实现了 Component 接口,除了实现自身的业务逻辑外,还负责管理子节点,如添加、删除、遍历子节点等操作。
二、使用场景
- 当你需要表示对象的部分 - 整体层次结构时,组合模式可以很方便地构建这样的树形结构。例如,文件系统中的目录和文件、图形用户界面中的容器和组件等。
- 当客户端希望以统一的方式处理单个对象和组合对象时,组合模式可以提供一致的接口,使得客户端无需区分处理的是叶子节点还是容器节点。
业务
类图
Folder持有-components : List属性,可以添加任意Component组件,组合成目标对象
classDiagram
class Component {
+display() : void
}
class File {
-name : String
+File(String)
+display() : void
}
class Folder {
-name : String
-components : List<Component>
+Folder(String)
+addComponent(Component) : void
+removeComponent(Component) : void
+display() : void
}
File --|> Component
Folder --|> Component
代码
用 Java 实现的组合模式的示例,假设我们有一个文件系统的模拟场景,包括文件夹和文件。
首先定义抽象构件 Component:
public abstract class Component {
public abstract void display();
}
然后实现叶子构件 Leaf(文件):
public class File extends Component {
private String name;
public File(String name) {
this.name = name;
}
@Override
public void display() {
System.out.println("文件:" + name);
}
}
接着实现容器构件 Composite(文件夹):
import java.util.ArrayList;
import java.util.List;
public class Folder extends Component {
private String name;
private List<Component> components;
public Folder(String name) {
this.name = name;
components = new ArrayList<>();
}
public void addComponent(Component component) {
components.add(component);
}
public void removeComponent(Component component) {
components.remove(component);
}
@Override
public void display() {
System.out.println("文件夹:" + name);
for (Component component : components) {
component.display();
}
}
}
最后使用这些类:
public class Main {
public static void main(String[] args) {
Folder rootFolder = new Folder("根文件夹");
File file1 = new File("文件 1.txt");
File file2 = new File("文件 2.txt");
Folder subFolder = new Folder("子文件夹");
File subFile = new File("子文件.txt");
rootFolder.addComponent(file1);
rootFolder.addComponent(file2);
rootFolder.addComponent(subFolder);
subFolder.addComponent(subFile);
rootFolder.display();
}
}
在这个示例中,Component 是抽象构件,定义了所有组件的通用接口 display()。File 类是叶子构件,代表文件,实现了 display() 方法来显示文件的名称。Folder 类是容器构件,代表文件夹,它可以包含其他组件(文件或文件夹),实现了 display() 方法来显示文件夹的名称,并遍历显示其包含的所有组件。这样,客户端可以以统一的方式处理文件和文件夹,无需区分它们的具体类型。
在框架中的使用
spring
一、配置类与 Bean 的管理
Spring 的配置类可以看作是一个容器构件,其中的 @Bean 方法创建的各个 Bean 对象是叶子构件。配置类负责将多个 Bean 组合在一起,形成一个完整的应用程序配置。
例如:
@Configuration
public class AppConfig {
@Bean
public ServiceA serviceA() {
return new ServiceAImpl();
}
@Bean
public ServiceB serviceB() {
return new ServiceBImpl();
}
@Bean
public CompositeService compositeService() {
return new CompositeServiceImpl(serviceA(), serviceB());
}
}
这里,CompositeService 依赖于 ServiceA 和 ServiceB,通过配置类将它们组合在一起,形成一个更复杂的服务。
二、Spring Security 中的过滤器链
Spring Security 使用过滤器链来处理请求的安全认证和授权。过滤器链是一个由多个过滤器组成的结构,其中每个过滤器可以看作是叶子构件,而过滤器链本身可以看作是容器构件。
例如:
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
public class SecurityConfig {
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.addFilterBefore(new CustomFilter1(), UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(new CustomFilter2(), CustomFilter1.class);
return http.build();
}
}
这里,通过 addFilterBefore 方法将多个过滤器组合成一个过滤器链,对请求进行依次处理,体现了组合模式的思想。
mybatis
在 MyBatis 中也能体现出组合模式的使用,主要体现在动态 SQL 的构建上。
MyBatis 的动态 SQL 允许根据不同的条件拼接 SQL 语句片段,就像组合不同的部分来构建一个完整的 SQL 语句。例如,使用 <if>、<choose>、<when>、<otherwise> 等标签来构建动态条件查询。
以下是一个示例:
<select id="findUsersByConditions" resultType="User">
SELECT * FROM users
WHERE 1 = 1
<if test="username!= null">
AND username = #{username}
</if>
<if test="age!= null">
AND age = #{age}
</if>
</select>
在这个例子中可以把整个动态 SQL 片段看作是一个组合结构。
<select> 标签和其中的固定部分(如 SELECT * FROM users WHERE 1 = 1)可以看作是容器构件,
而 <if> 标签内部的条件片段(如 AND username = #{username})可以看作是叶子构件。根据不同的输入条件,这些片段会被组合在一起,形成一个完整的 SQL 查询语句。
这样的设计使得 MyBatis 能够灵活地构建各种复杂的 SQL 语句,而无需为每一种可能的条件组合编写单独的 SQL 语句,提高了代码的可维护性和可扩展性。
总结
优点
- 简化客户端代码:客户端可以以统一的方式处理叶子节点和容器节点,无需区分它们的具体类型,提高了代码的可维护性和可扩展性。
- 易于扩展:可以方便地增加新的叶子节点或容器节点,而不会影响现有的代码结构。
- 更好地表示层次结构:清晰地表示了对象的部分 - 整体层次关系,使得代码结构更加直观。
缺点
- 实现较为复杂:组合模式需要定义多个类,并且需要处理好叶子节点和容器节点的关系,实现起来相对复杂。
- 可能会导致过度设计:如果层次结构比较简单,使用组合模式可能会使设计过于复杂,反而增加了代码的维护难度。