Spring中的设计模式
# 设计原则
设计模式中有七大原则,它们分别是:
- 开闭原则(Open Closed Principle,OCP)
- 单一职责原则(Single Responsibility Principle, SRP)
- 里氏代换原则(Liskov Substitution Principle,LSP)
- 依赖倒转原则(Dependency Inversion Principle,DIP)
- 接口隔离原则(Interface Segregation Principle,ISP)
- 合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
- 最少知识原则(Least Knowledge Principle,LKP)或者迪米特法则(Law of Demeter,LOD)¹。
这些原则的目的是降低对象之间的耦合,增加程序的可复用性、可扩展性和可维护性
- 开闭原则:比如您有一个生产电脑的工厂类,根据输入的类型,生产出不同的电脑。如果您需要添加新的电脑产品,那么修改工厂类的方法会违反开闭原则,因为您修改了已有的代码。正确的做法应该是将工厂类抽象成接口,让具体的工厂类去实现它,生产它们公司相应的产品。
- 单一职责原则:比如您有一个动物类,它会呼吸空气。如果您需要添加新的功能,比如鱼需要呼吸水,那么修改动物类的方法会违反单一职责原则,因为您让一个类负责了两个不同的职责。正确的做法应该是将动物类分解为陆生动物类和水生动物类。
- 里氏代换原则:比如您有一个书籍类,它有一个获取内容的方法。如果您需要添加新的功能,比如漫画类,它也有一个获取内容的方法,但是返回的是图片而不是文字。那么使用漫画类替换书籍类会违反里氏代换原则,因为您改变了父类方法预期的行为。正确的做法应该是将书籍类和漫画类分别继承一个抽象的媒体类。
- 依赖倒转原则:比如您有一个业务逻辑类,它需要操作数据库。如果您直接依赖于具体的数据库操作类,那么当数据库发生变化时,您需要修改业务逻辑类的代码。这样会违反依赖倒转原则,因为您让高层模块依赖于低层模块。正确的做法应该是让业务逻辑类依赖于一个抽象的数据库操作接口,让具体的数据库操作类去实现它。
- 接口隔离原则:比如您有一个动物接口,它定义了吃、喝、跑、飞等方法。如果您让狗实现这个接口,那么狗就必须实现飞这个方法,即使它不会飞。这样会违反接口隔离原则,因为您让一个类依赖于它不需要的方法。正确的做法应该是将动物接口分解为多个更小更专业的接口。
- 合成/聚合复用原则:比如您有一个汽车类和一个轮胎类。如果您让汽车继承轮胎,那么当轮胎发生变化时,汽车也需要跟着变化。这样会违反合成/聚合复用原则,因为您使用了继承来复用轮胎的功能。正确的做法应该是让汽车包含轮胎作为它的属性。
- 最少知识原则:比如您有一个顾客类和一个商店类。如果顾客想要买东西,他就需要知道商店里有什么商品、商品有什么价格、商店有什么优惠活动等等。这样会违反最少知识原则,因为您让一个对象知道了太多其他对象的细节。正确的做法应该是让顾客只知道商店有一个购买方法,让商店内部处理其他的逻辑。
# 设计模式
- 创建型模式:工厂方法模式、单例模式
- 结构型模式:适配器模式、装饰器模式、代理模式
- 行为型模式:观察者模式、策略模式、模板方法模式
# 工厂方法模式
Spring框架中的工厂方法模式体现在它使用BeanFactory和ApplicationContext来创建对象。这些工厂类可以根据配置文件或注解来创建和管理Bean对象。例如,下面是一个简单的例子,它演示了如何使用ApplicationContext来创建一个Bean对象:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
2
3
4
5
6
7
8
9
10
在上面的例子中,我们使用ClassPathXmlApplicationContext来创建一个ApplicationContext实例,然后使用getBean方法从容器中获取一个名为"helloWorld"的Bean对象。这个Bean对象是根据Beans.xml配置文件中定义的Bean信息来创建的。
# 代理模式
Spring框架中的代理模式主要体现在它的AOP(面向切面编程)功能。Spring AOP使用代理模式来创建目标对象的代理,以便在目标方法执行前后添加额外的逻辑。Spring AOP可以使用JDK动态代理或CGLIB来创建代理对象。如果目标对象实现了接口,Spring AOP会使用JDK动态代理;否则,它会使用CGLIB来创建代理对象¹³。
例如,下面是一个简单的例子,它演示了如何使用Spring AOP来为目标方法添加前置通知:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspect {
@Before("execution(* com.example.MyClass.myMethod(..))")
public void beforeMyMethod() {
System.out.println("Before myMethod");
}
}
2
3
4
5
6
7
8
9
10
在上面的例子中,我们定义了一个切面类MyAspect,并在其中定义了一个前置通知方法beforeMyMethod。这个方法使用@Before注解来指定它应该在哪些方法执行之前执行。在这个例子中,我们指定beforeMyMethod应该在com.example.MyClass类中的myMethod方法执行之前执行。
# 单例模式
其中单例模式
# 装饰器模式
Spring框架中使用了装饰器模式来动态地给某些类增加一些额外的功能。在Spring中,使用装饰器模式的类通常具有两个典型的特征:一个是类名中含有Wrapper,另一个是类名中含有Decorator。
例如,Spring框架中的BufferedInputStream
类就是一个装饰器类,它继承自FilterInputStream
并实现了InputStream
接口。它的作用是为另一个输入流添加缓冲功能,以提高读取性能。
# 适配器模式
Spring框架中使用了适配器模式来将不同类型的处理器(如Controller、HttpRequestHandler或者Servlet等)适配到统一的请求处理流程中。在Spring MVC中,这个功能是由HandlerAdapter
接口及其实现类来完成的¹³。
例如,下面是一个简单的例子,它演示了如何使用HandlerAdapter
接口来定义一个自定义的适配器:
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof MyHandler;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
MyHandler myHandler = (MyHandler) handler;
myHandler.handleRequest(request, response);
return null;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在上面的例子中,我们定义了一个自定义的HandlerAdapter
实现类MyHandlerAdapter
。这个类实现了HandlerAdapter
接口中的三个方法:supports
、handle
和getLastModified
。其中,supports
方法用于判断这个适配器是否支持给定的处理器类型;handle
方法用于处理请求并返回一个ModelAndView对象;而getLastModified
方法用于返回资源的最后修改时间。
# 观察者模式
Spring框架中使用了观察者模式来实现事件驱动模型。在Spring中,观察者模式由三部分组成:事件(ApplicationEvent
)、事件发布者(ApplicationEventPublisher
)和事件订阅者(ApplicationListener
)。
例如,下面是一个简单的例子,它演示了如何在Spring中使用观察者模式来定义一个自定义事件:
import org.springframework.context.ApplicationEvent;
public class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
}
2
3
4
5
6
7
在Spring中,您可以通过实现ApplicationListener
接口或使用@EventListener
注解来监听自定义事件。
例如,下面是一个简单的例子,它演示了如何使用ApplicationListener
接口来监听一个自定义事件:
import org.springframework.context.ApplicationListener;
public class MyEventListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
// 处理事件
}
}
2
3
4
5
6
7
8
在上面的例子中,我们定义了一个自定义的事件监听器类MyEventListener
,它实现了ApplicationListener
接口。这个类重写了onApplicationEvent
方法,用于处理MyEvent
类型的事件。
另外,您也可以使用@EventListener
注解来监听自定义事件,如下所示:
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class MyEventListener {
@EventListener
public void handleMyEvent(MyEvent event) {
// 处理事件
}
}
2
3
4
5
6
7
8
9
10
在上面的例子中,我们定义了一个名为MyEventListener
的类,并在其中定义了一个名为handleMyEvent
的方法。这个方法使用了@EventListener
注解来指定它应该监听哪种类型的事件。在这个例子中,我们指定这个方法应该监听MyEvent
类型的事件。
# 策略模式
Spring框架中使用了策略模式来支持不同的算法或行为的选择。在Spring中,您可以使用依赖注入特性来注入一个接口的多个实现,从而实现策略模式的选择。
例如,下面是一个简单的例子,它演示了如何在Spring中使用策略模式来定义一个策略接口及其多个实现:
public interface MyStrategy {
void doSomething();
}
@Component
public class MyStrategyA implements MyStrategy {
@Override
public void doSomething() {
// 实现A
}
}
@Component
public class MyStrategyB implements MyStrategy {
@Override
public void doSomething() {
// 实现B
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在上面的例子中,我们定义了一个策略接口MyStrategy
,以及两个实现类MyStrategyA
和MyStrategyB
。这两个实现类都使用了@Component
注解,因此它们会被Spring容器自动扫描并注册为Bean。
# 模板方法模式
Spring框架中使用了模板方法模式来封装不变的部分,扩展可变的部分。在Spring中,模板方法模式被广泛应用,例如JdbcTemplate
、RedisTemplate
和RestTemplate
等都使用了模板方法模式。
例如,下面是一个简单的例子,它演示了如何在Spring中使用模板方法模式来定义一个抽象类及其子类:
public abstract class MyAbstractClass {
public final void templateMethod() {
// 调用抽象方法
abstractMethod();
// 调用具体方法
concreteMethod();
}
protected abstract void abstractMethod();
private void concreteMethod() {
// 具体实现
}
}
public class MyClass extends MyAbstractClass {
@Override
protected void abstractMethod() {
// 实现抽象方法
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在上面的例子中,我们定义了一个抽象类MyAbstractClass
,它包含一个模板方法templateMethod
。这个方法调用了一个抽象方法abstractMethod
和一个具体方法concreteMethod
。我们还定义了一个子类MyClass
,它继承自MyAbstractClass
并重写了抽象方法abstractMethod
。