前言
设计模式算是高频的面试题了,基本在工作的前几年,只要想说设计,必定会谈到设计模式。我理解的原因是业务设计通常难以描述,普通CURD开发也很难有设计亮点,大部分情况下更是难以参与亮点。技术设计更是扯淡,没到一定深度,就是单纯的方案整合商罢了,更难说。因此设计模式相对于大部分人来说,是一个体现自己与众不同的银弹,在短时间内,能让面试官能GET到你想描述的东西,不至于出现各说各的情况。本文也是博主对设计模式的一些简单理解,聊一聊常见的问题,写得比较简略,后续会追加更新。博主现在工作9116,已经快不行了,坚持下更新,不能忘了自己的初衷。
设计模式有哪些?
一些常见的设计模式及其在Spring框架中的使用场景:
- 单例模式(Singleton Pattern):在Spring框架中,许多核心组件(如ApplicationContext)都是以单例模式创建和管理的。通过使用单例模式,可以确保在整个应用程序中只有一个实例,并提供全局访问点。
- 工厂模式(Factory Pattern):在Spring框架中,BeanFactory和ApplicationContext充当了工厂的角色,负责创建和管理对象实例。通过配置和使用不同类型的工厂,可以实现对象的灵活创建和解耦。
- 观察者模式(Observer Pattern):Spring框架中的事件机制就是基于观察者模式实现的。通过定义事件、监听器和发布者,可以实现模块之间的解耦和事件的触发与处理。
- 代理模式(Proxy Pattern):Spring框架中的AOP(面向切面编程)就是基于代理模式实现的。通过使用动态代理技术,可以在不修改原有代码的情况下,为目标对象添加额外的功能,如事务管理、日志记录等。
- 模板方法模式(Template Method Pattern):在Spring框架中,JdbcTemplate就是使用了模板方法模式。通过定义模板方法和钩子方法,可以将通用的数据库操作逻辑封装在模板类中,而将具体的实现延迟到子类中。
- 策略模式(Strategy Pattern):Spring框架中的BeanPostProcessor就是使用了策略模式。通过实现BeanPostProcessor接口,可以在Spring容器实例化和初始化Bean的过程中插入自定义的逻辑。
- 外观模式(Facade Pattern):在Spring框架中,ApplicationContext充当了外观角色,提供了统一的接口,隐藏了底层复杂的配置和实现细节,简化了系统的使用和调用。
- 责任链模式(Chain of Responsibility Pattern):该模式将请求的发送者和接收者解耦,通过将多个对象组成链条,依次处理请求,直到找到合适的处理者。这样可以避免请求发送者与接收者之间的直接耦合,提高代码的灵活性和可扩展性。在Java中,Servlet过滤器就是一种责任链模式的应用。
- 建造者模式(Builder Pattern):该模式通过将复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。建造者模式适用于创建复杂的对象,且对象的构建过程需要多个步骤或者可配置选项。在Java中,StringBuilder和DocumentBuilder就是建造者模式的应用。
- 原型模式(Prototype Pattern):该模式通过复制现有对象来创建新对象,而无需显式地使用new操作符。原型模式适用于创建成本较高的对象,或者对象的创建过程比较复杂的情况。在Java中,Object类的clone()方法就是原型模式的应用。
- 享元模式(Flyweight Pattern):该模式通过共享对象来减少内存使用和提高性能。它适用于存在大量相似对象的场景,通过共享相同的状态,减少了对象的数量和内存占用。在Java中,String常量池就是享元模式的应用。
- 桥接模式(Bridge Pattern):该模式将抽象部分与它的实现部分分离,使它们可以独立地变化。桥接模式适用于需要在多个维度上扩展的情况,通过将抽象和实现分离,可以灵活地组合不同的抽象和实现。在Java中,JDBC(Java Database Connectivity)就是桥接模式的应用。
- 组合模式(Composite Pattern):该模式将对象组织成树形结构,使得用户可以以相同的方式处理单个对象和组合对象。组合模式适用于处理具有层次结构的对象,通过统一的接口,简化了对单个对象和组合对象的操作。在Java中,AWT和Swing中的组件树就是组合模式的应用。
- 状态模式(State Pattern):该模式允许对象在内部状态改变时改变它的行为。通过将状态封装成独立的类,使得对象的行为可以根据状态的改变而变化,避免了大量的条件语句。在Java中,线程的不同状态(如新建、运行、等待等)就可以使用状态模式来实现。
策略模式和工厂模式的区别
arduino
public class PaymentContext {
private PaymentStrategy paymentStrategy;
public PaymentContext(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void doPayment(double amount) {
paymentStrategy.pay(amount);
}
}
public class Main {
public static void main(String[] args) {
// 使用策略模式选择支付策略
PaymentContext paymentContext = new PaymentContext(new CreditCardPaymentStrategy());
paymentContext.doPayment(100.0);
// 使用策略模式切换支付策略
paymentContext = new PaymentContext(new PayPalPaymentStrategy());
paymentContext.doPayment(200.0);
}
}
策略模式关注的是如何在运行时动态地选择算法或行为。它将一组算法封装到独立的策略类中,并使这些策略类可以互相替换。客户端代码可以根据需要选择合适的策略对象,而不必关心具体的实现细节。
typescript
public class ProductA extends Product {
public void use() {
System.out.println("Using Product A");
}
}
public class ProductB extends Product {
public void use() {
System.out.println("Using Product B");
}
}
public class ProductFactory {
public static Product createProduct(String type) {
if (type.equals("A")) {
return new ProductA();
} else if (type.equals("B")) {
return new ProductB();
}
return null;
}
}
public class Main {
public static void main(String[] args) {
// 使用工厂模式创建产品对象
Product productA = ProductFactory.createProduct("A");
productA.use();
Product productB = ProductFactory.createProduct("B");
productB.use();
}
}
工厂模式关注的是如何创建对象的过程。它提供了一个通用的接口或抽象类来创建对象,而不需要直接暴露对象的创建逻辑。客户端代码只需要通过工厂来获取所需的对象,而不需要关心对象的具体创建方式。
总结
策略模式合理利用多态特性,生成一个接口,传入不同的实现类来动态处理不同的逻辑。普通工厂模式,则是利用工厂类隐藏对象创建逻辑,通过输入不同的参数生成不同的对象。
装饰模式和代理模式的区别
java
// 抽象组件接口
interface Component {
void operation();
}
// 具体组件类
class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("执行具体组件的操作");
}
}
// 装饰器类
abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
// 具体装饰器类
class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
System.out.println("执行具体装饰器A的操作");
}
}
class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
System.out.println("执行具体装饰器B的操作");
}
}
// 使用装饰器模式
public class DecoratorPatternExample {
public static void main(String[] args) {
// 创建具体组件对象
Component component = new ConcreteComponent();
// 使用具体装饰器A装饰具体组件
Component decoratedComponentA = new ConcreteDecoratorA(component);
decoratedComponentA.operation();
System.out.println("==============");
// 使用具体装饰器B装饰具体组件
Component decoratedComponentB = new ConcreteDecoratorB(component);
decoratedComponentB.operation();
}
}
定义了一个抽象组件接口Component,具体组件类ConcreteComponent实现了该接口。然后,我们定义了一个抽象装饰器类Decorator,它也实现了Component接口并持有一个Component对象。具体装饰器类ConcreteDecoratorA和ConcreteDecoratorB分别继承了Decorator类,并在其operation()方法中添加了额外的操作。
typescript
// 抽象主题接口
interface Subject {
void request();
}
// 实际主题类
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("执行实际主题的请求");
}
}
// 代理类
class Proxy implements Subject {
private Subject realSubject;
public Proxy(Subject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
// 在实际主题对象的请求前后执行一些额外操作
System.out.println("执行代理的前置操作");
realSubject.request();
System.out.println("执行代理的后置操作");
}
}
// 使用代理模式
public class ProxyPatternExample {
public static void main(String[] args) {
// 创建实际主题对象
Subject realSubject = new RealSubject();
// 创建代理对象
Subject proxy = new Proxy(realSubject);
// 通过代理对象进行请求
proxy.request();
}
}
定义了一个抽象主题接口Subject,实际主题类RealSubject实现了该接口。然后,我们定义了一个代理类Proxy,它也实现了Subject接口并持有一个实际主题对象。在代理类的request()方法中,我们可以在实际主题对象的请求前后执行一些额外的操作。
总结
装饰器模式和代理模式都会对原有接口再次进行实现,但有所不同的是,装饰器在不修改原有代码,通过新增继承子类的方式拓展原有方法,从而在运行时动态地添加额外功能。而代理模式则是通过实现类获取到实际对象,直接控制其访问来进行拓展。
工作中是怎么使用的?
我在项目中主要用到了模板模式,怎么用的呢?我在做订单交付实时计算流的重构时,参考了Flink的Sink端,将整个Sink当成一个模板类,提供三个抽象方法,分别是数据准备、数据计算和最后的处理方法,还有一个普通流程方法,负责串联前面三个方法。
模板方式给我重构时带来了什么好处呢?首先是将原先一个大的计算流程拆分成十个小的计算流,分别用模板类套上,由此可以梳理出数据准备、数据计算和数据处理三块。第一个好处就是这样细分下来就很清晰明了,相比原先,我能知道各个模块有没有重复查询数据库或者接口的操作,如果有的话就合并重复的查询。第二个好处就是经过梳理后,需要入库处理的数据可以在最后阶段去做统一批量入库,避免计算中途频繁修改数据做成一个大事务。
总的来说,模板模式在这次重构过程中,起到的作用是辅助我在重构过程中清晰梳理了整个计算流程。同时梳理后的代码相对来说很好做性能优化,比如批量处理,多线程优化。
写在最后
我自己对于设计模式其实不太会用,尽管我自己写了不少框架方面的东西,一些SDK,但是也是无意间用到,目前用的最多的会是代理、门面、单例这样的基础款。因为框架大部分比较固化,没有提供足够的扩展性,所以我没有使用到一些框架设计中常用的设计模式,比如spring里用到的那些。
最近的状态很差啊,前几天有个同事加班加的不行了,去医院检查了,博主年轻倒还能扛,但是没加班费没调休到11点确实伤害太大了。各方面的压力都有吧,但是我还算乐天,之前情感上有些问题,可能是我冲动了,还好我朋友们一顿话聊,让我重拾冷静。有一个好对象着实不易,哈哈,我说话很直,有的人会喜欢,有的人会觉得我不会说话。我之前一直以为我改了,但是现在看来没有改,所以接下来,我会继续成长,做一个好人,自己好大家都好,希望大家身体健康,有自己喜欢的和喜欢自己的人,下一篇博客见!