模板方法模式
模板方法模式(Template Method Pattern)是一种在软件工程中常用的设计模式,属于行为型模式。它的核心思想是在一个抽象类中定义一个操作中的算法的骨架(即一个模板),而将一些步骤的实现延迟到子类中。这样,可以在不改变算法结构的情况下,重新定义算法中的某些特定步骤。
关键特点
- 抽象类定义模板:模板方法设定了算法的骨架。通常是一个具体方法,负责调用一系列抽象方法。
- 具体实现在子类:子类实现抽象方法,提供算法的具体行为。
- 控制反转:在父类中控制流程,子类实现细节,形成控制反转。
- 代码复用:将共同的代码移到单一位置,减少代码重复。
结构组成
- 抽象类(Abstract Class):定义模板方法和抽象操作。
- 具体类(Concrete Class):实现抽象类定义的抽象方法,以完成特定子类的详细步骤。
使用场景
- 当多个类有共同的方法,且逻辑相似时。
- 代码中存在多个类,它们仅在某些方法的实现上稍有不同。
- 重构代码,将相同的代码移到一个共同的父类中。
优点
- 提高代码复用性。
- 提高扩展性,因为算法的框架已经定义在抽象类中。
- 实现了反向控制,由父类控制算法流程。
缺点
- 导致类的数目增加。
- 可能导致系统更加复杂。
接下来我们仿照JDBCTemplate编写一个模板。
要创建一个类似JdbcTemplate的简化版本,我们首先需要理解JdbcTemplate在Spring框架中的主要功能。JdbcTemplate主要用于简化JDBC操作,管理资源(如连接、语句和结果集)的打开和关闭,以及提供一个模板方法来执行SQL查询和更新操作。
以下是一个基础的模仿JdbcTemplate的实现。这个示例将包括几个关键组件:
- 一个抽象的SimpleJdbcTemplate类:这个类将定义执行SQL操作的基本模板方法。
- 资源管理方法:用于管理数据库连接。
- 执行SQL的方法:这些方法将执行实际的SQL操作。
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public abstract class SimpleJdbcTemplate {// 假设有一个方法来获取数据库连接protected abstract Connection getConnection() throws SQLException;// 执行查询的模板方法public <T> T query(String sql, RowMapper<T> rowMapper, Object... args) throws SQLException {try (Connection conn = getConnection();PreparedStatement ps = conn.prepareStatement(sql)) {int paramIndex = 1;for (Object arg : args) {ps.setObject(paramIndex++, arg);}try (ResultSet rs = ps.executeQuery()) {if (rs.next()) {return rowMapper.mapRow(rs);}return null;}}}// 定义RowMapper接口public interface RowMapper<T> {T mapRow(ResultSet rs) throws SQLException;}
}
SimpleJdbcTemplate提供了一个query方法,这是一个模板方法,它接收SQL语句、RowMapper和参数,执行查询,并返回映射的结果。RowMapper是一个接口,需要用户实现,用于将ResultSet映射到想要的对象类型。
观察者模式
"观察者模式"是一种设计模式,用于软件开发中。在这种模式下,一个对象(称为“主题”)维护一个依赖对象列表(称为“观察者”),并在状态变化时自动通知它们。这种模式通常用于实现分布式事件处理系统,用于在对象之间传递消息或信息,而无需对象之间具有紧密的耦合关系。
观察者模式包括以下主要组件:
- 主题(Subject):维护观察者列表的对象。当其内部状态发生变化时,它会向观察者发送通知。
- 观察者(Observer):依赖于主题的对象。它们“观察”主题并在主题状态改变时接收更新。
- 具体主题(Concrete Subject):实现主题接口的类。它包含观察者想要知道的数据。
- 具体观察者(Concrete Observer):实现观察者接口的类。它定义了在接收到主题更新时应采取的行动。
最常见的观察者模式的使用就是:配置中心。
配置中心的配置变更导致本地配置自动更新是观察者模式的一个典型应用场景。在这个场景中:
- 配置中心扮演着**主题(Subject)**的角色。它维护着一个或多个配置项,这些配置项可能会随着时间或某些条件的改变而发生变化。
- 本地系统或服务则作为观察者(Observer),注册到配置中心以便于接收配置更新的通知。
当配置中心的配置发生变化时,它会通知所有注册的观察者(即本地系统或服务),使它们能够相应地更新自己的配置。这样的机制确保了配置的一致性和实时性,同时降低了系统组件之间的耦合度。
例如,在微服务架构中,各个微服务可能依赖于一个共享的配置中心来管理其配置。当配置中心的配置更新时,所有依赖的微服务会自动获取最新配置,从而保证了系统的灵活性和可维护性。
这里简单描写一个配置中心的代码:
package blossom.project.designmode.observe;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;// 观察者接口
interface Observer {void update(Map<String, String> config);
}// 配置中心类(主题)
class ConfigCenter {private List<Observer> observers = new ArrayList<>();private Map<String, String> config = new HashMap<>();// 注册观察者public void registerObserver(Observer observer) {observers.add(observer);}// 移除观察者public void removeObserver(Observer observer) {observers.remove(observer);}// 通知所有观察者配置已更新public void notifyObservers() {for (Observer observer : observers) {observer.update(new HashMap<>(config));}}// 设置配置项并通知观察者public void setConfig(String key, String value) {config.put(key, value);notifyObservers();}
}// 具体观察者实现
class Service implements Observer {private String name;public Service(String name) {this.name = name;}@Overridepublic void update(Map<String, String> config) {System.out.println("Service " + name + " received config update: " + config);}
}
package blossom.project.designmode.observe;import java.util.Random;class ConfigChangeSimulator implements Runnable {private ConfigCenter configCenter;private Random random = new Random();public ConfigChangeSimulator(ConfigCenter configCenter) {this.configCenter = configCenter;}@Overridepublic void run() {try {while (true) {// 模拟配置变更String key = "key" + random.nextInt(10);String value = "value" + random.nextInt(100);configCenter.setConfig(key, value);// 休眠一段时间以模拟配置更新间隔Thread.sleep(3000);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}public class ObserverPatternExample {public static void main(String[] args) {ConfigCenter configCenter = new ConfigCenter();Service serviceA = new Service("A");Service serviceB = new Service("B");configCenter.registerObserver(serviceA);configCenter.registerObserver(serviceB);// 创建并启动配置变更模拟器ConfigChangeSimulator simulator = new ConfigChangeSimulator(configCenter);new Thread(simulator).start();}
}
当配置中心配置发生变更的时候就会让本地的配置进行更新。
策略模式
策略模式是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法独立于使用它的客户端而变化,即使客户端可以在运行时改变其算法。这种模式涉及到三个角色:
- Context(上下文):维护对策略对象的引用。
- Strategy(策略):策略类的接口,定义了具体策略应实现的方法。
- ConcreteStrategy(具体策略):实现策略接口的类,提供具体的算法实现。
优点
- 封装性好:每个策略都被封装在自己的类中,易于维护和替换。
- 扩展性强:可以方便地引入新的策略而不影响既有代码。
- 避免多重条件选择语句:策略模式提供了替代多重条件选择语句(如if-else或switch-case)的解决方案。
- 客户端与算法解耦:策略模式让客户端独立于算法的实现。
缺点
- 客户端需要了解所有策略:客户端要选择合适的策略,需要了解每一个策略的不同。
- 增加对象数量:每种策略都会产生一个新类,随着策略的增加,类的数量也随之增加。
使用场景
- 多种类似算法的情况:当一个问题有多种解决方式时,每种方式可以封装为一个策略。
- 算法需要在运行时切换:如果你需要在运行时改变算法或行为,策略模式提供了一种优雅的方式来实现这一点。
- 避免多重条件转移语句:当一个操作有多个条件分支时,可以使用策略模式代替多重条件转移语句。
// 策略接口
interface SortingStrategy {void sort(List<Integer> list);
}// 具体策略实现之一
class BubbleSortStrategy implements SortingStrategy {@Overridepublic void sort(List<Integer> list) {System.out.println("Sorting using bubble sort");// 实现冒泡排序}
}// 具体策略实现之二
class QuickSortStrategy implements SortingStrategy {@Overridepublic void sort(List<Integer> list) {System.out.println("Sorting using quick sort");// 实现快速排序}
}// 上下文角色
class SortedList {private SortingStrategy strategy;public void setSortingStrategy(SortingStrategy strategy) {this.strategy = strategy;}public void sort(List<Integer> list) {strategy.sort(list);}
}// 使用策略模式
public class StrategyPatternDemo {public static void main(String[] args) {SortedList list = new SortedList();list.setSortingStrategy(new BubbleSortStrategy());list.sort(new ArrayList<>(Arrays.asList(1, 3, 2)));list.setSortingStrategy(new QuickSortStrategy());list.sort(new ArrayList<>(Arrays.asList(1, 3, 2)));}
}
在这个例子中,SortedList充当上下文,可以设置不同的排序策略(BubbleSortStrategy或QuickSortStrategy),并使用这些策略对列表进行排序。这样,排序算法可以在运行时动态改变,且SortedList与具体的排序算法解耦。
但是更加合理的实现方式是使用单例模式和策略模式。
在工厂中为每种登录策略创建单一实例,并在请求时返回这些实例:
class LoginStrategyFactory {private static final LoginStrategyFactory instance = new LoginStrategyFactory();private Map<String, LoginStrategy> strategies;private LoginStrategyFactory() {strategies = new HashMap<>();strategies.put("usernamePassword", new UsernamePasswordLoginStrategy());strategies.put("wechat", new WeChatLoginStrategy());// 初始化其他策略}public static LoginStrategyFactory getInstance() {return instance;}public LoginStrategy getLoginStrategy(String type) {if (!strategies.containsKey(type)) {throw new IllegalArgumentException("Unknown login type: " + type);}return strategies.get(type);}
}
在这个工厂中,我们使用一个Map来存储每种类型的登录策略。工厂在初始化时就创建了所有类型的策略实例。当请求一个特定类型的策略时,工厂返回预先创建的实例,而不是每次都创建新的实例。
登录控制器(Controller)登录操作:
@RestController
@RequestMapping("/auth")
public class LoginController {@PostMapping("/login")public ResponseEntity<?> login(@RequestParam String type, @RequestBody Map<String, String> credentials) {LoginStrategyFactory factory = LoginStrategyFactory.getInstance();LoginStrategy strategy = factory.getLoginStrategy(type);strategy.login(credentials.get("username"), credentials.get("password"));return ResponseEntity.ok().body("Login successful using " + type);}
}
策略模式在源码中的使用还是比较常见的,比如我们的Comparator,Arrays的sort,TreeMap的排序底层都用到了策略模式的思想。
同时,在我们的spring中,我们知道spring可以读取多个位置的配置文件,比如resource,jvm,environment等。同时,如果我们配置了nacos配置中心,也可以从配置中心进行配置读取。
策略模式是一个非常常用的模式,我们的实际编码过程可能没有意识到我们用到了这种模式。
责任链模式
责任链模式是一种行为型设计模式,它使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。在这个模式中,这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
工作机制
- 链条构建:责任链模式中的每个对象都包含对下一个对象的引用。这形成了一条链。
- 请求传递:一个请求从链的一端开始,沿着链传递,直到一个对象处理该请求为止。
- 处理或转发:每个对象在接收到请求后,要么亲自处理它,要么转发给链上的下一个对象。
角色
- 处理器(Handler):定义处理请求的接口,并实现链中的下一个处理器的引用。
- 具体处理器(Concrete Handler):处理它所负责的请求,可以访问它的后继者(即链上的下一个处理器),如果可以处理当前请求,就处理;否则将该请求转发给后继者。
- 客户端(Client):启动请求并发送到链上。
优点
- 降低耦合度:它将请求的发送者和接收者解耦。
- 增强给定对象指派职责的灵活性:可以动态地添加或修改处理一个请求的结构。
- 增强扩展性:可以根据需要增加新的请求处理类,满足开闭原则。
缺点
- 请求不保证被接收:一个请求可能最终未被任何接收端处理。
- 性能考虑:在某些情况下,一个请求可能需要遍历整个链,造成一定的性能损耗。
- 调试困难:由于责任链的运行时特性,有时可能不容易观察和调试运行路径。
使用场景
- 当多个对象可以处理同一个请求,但具体由哪个对象处理则在运行时动态决定时。
- 当你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求时。
- 当需要动态设置一组对象处理请求时。
我们最创建的责任链的使用场景就是网关。
在Spring Cloud Gateway中,使用责任链模式使得处理HTTP请求的过程变得非常灵活和强大。开发者可以根据具体需求选择合适的过滤器,并定义它们在请求处理过程中的顺序。这种模式也使得添加新的过滤器或修改现有过滤器链变得简单,从而可以轻松地扩展和维护整个网关的功能。
- 链式结构:在Spring Cloud Gateway中,过滤器被组织成一个链式结构,请求会依次经过这些过滤器。
- 分散处理:每个过滤器负责处理请求的一部分,这与责任链模式中的处理器(Handler)角色相似。例如,一个过滤器可能负责身份验证,另一个负责日志记录,再另一个负责请求重定向等。
- 动态组合:过滤器可以根据需要动态地添加到链中,提供了高度的灵活性和可配置性。
- 自主决策:每个过滤器可以自主决定如何处理传递给它的请求,或者将请求传递给链中的下一个过滤器。
我们知道,如果当前的过滤器需要处理当前请求,就会执行实际的处理方法,如果不需要进行处理,就直接return chain.doFilter()方法直接将当前请求传送到下一个过滤器去进行处理,这就是责任链模式,我需要对你进行处理我就对你进行处理,如果我不需要,那么我就不管你直接放你过去。
并且我们也知道,我们可以手动的添加和减少过滤器,也就是可插拔。
我的网关项目中的过滤器链就是用到责任链模式。
点击访问【BlossomGateway网关项目中的责任链】
这里还是简单的写一个责任链模式的代码:
要实现一个类似网关的责任链模式,其中过滤器可以从头到尾处理请求,然后再从尾到头逆向处理,我们需要定义一个过滤器接口,每个具体的过滤器实现这个接口。此外,我们还需要一个责任链类来管理这些过滤器。在这个实现中,我们将使用两个指针(或索引),一个用于正向遍历,一个用于反向遍历。
以下是实现的步骤和代码:
- 定义过滤器接口
interface Filter {void doFilter(Request request, Response response, FilterChain chain);
}
- 定义请求和响应类
class Request {// 请求的数据和方法
}class Response {// 响应的数据和方法
}
- 实现责任链(过滤器链)
class FilterChain {private List<Filter> filters = new ArrayList<>();private int index = 0; // 正向过滤器索引private int reverseIndex; // 反向过滤器索引public FilterChain addFilter(Filter filter) {filters.add(filter);reverseIndex = filters.size() - 1; // 初始化反向索引return this;}public void doFilter(Request request, Response response) {if (index < filters.size()) {Filter filter = filters.get(index++);filter.doFilter(request, response, this);} else if (reverseIndex >= 0) {Filter filter = filters.get(reverseIndex--);filter.doFilter(request, response, this);}}
}
- 实现具体过滤器
class AuthenticationFilter implements Filter {@Overridepublic void doFilter(Request request, Response response, FilterChain chain) {System.out.println("Authentication check");chain.doFilter(request, response); // 继续调用链中的下一个过滤器}
}class LoggingFilter implements Filter {@Overridepublic void doFilter(Request request, Response response, FilterChain chain) {System.out.println("Logging request");chain.doFilter(request, response); // 继续调用链中的下一个过滤器}
}
- 使用责任链
public class Gateway {public static void main(String[] args) {FilterChain chain = new FilterChain();chain.addFilter(new AuthenticationFilter()).addFilter(new LoggingFilter());Request request = new Request();Response response = new Response();chain.doFilter(request, response);}
}
在这个示例中,FilterChain类维护了一个过滤器列表,并实现了doFilter方法,该方法负责依次调用过滤器。正向遍历时,index用于跟踪当前过滤器的位置;当所有过滤器处理完毕后,reverseIndex用于从尾部开始反向遍历过滤器。每个具体的过滤器(如AuthenticationFilter和LoggingFilter)在执行完自己的逻辑后,通过调用**chain.doFilter(request, response)**将控制权传递给链中的下一个过滤器。
这种设计使得请求可以按顺序经过所有的过滤器,并在处理完所有过滤器后,按相反的顺序返回。这在一些需要在请求处理前后执行不同逻辑的场景(如安全检查、日志记录、资源清理等)中非常有用。
状态模式
状态模式是一种行为设计模式,它允许一个对象在其内部状态改变时改变它的行为。这种模式将每个状态的行为封装到对应的状态类中,使得对象在运行时可以看似改变其类。
核心思想:
- 在状态模式中,类的行为是基于它的状态改变的。
- 这种模式涉及到实现一个状态接口和实现该接口的一组状态类。
- 上下文类包含一个指向当前状态对象的引用,通过这个引用可以调用具体状态类的方法。
组件:
- Context(上下文):定义了客户端感兴趣的接口,并且保留了一个对当前状态实例的引用。
- State(状态):一个接口或抽象类,用来封装与上下文的一个特定状态相关的行为。
- Concrete States(具体状态):实现 State 接口的类,每个类封装了与 Context 的一个特定状态相关的行为。
假设有一个文档处理应用,文档可以处于不同的状态(草稿、审阅、发布):
javaCopy code
// 状态接口
interface DocumentState {void publish();void render();
}// 具体状态:草稿
class Draft implements DocumentState {@Overridepublic void publish() {System.out.println("Draft published, now in review state.");}@Overridepublic void render() {System.out.println("Render draft document.");}
}// 具体状态:审阅
class Review implements DocumentState {@Overridepublic void publish() {System.out.println("Review completed, now document is published.");}@Overridepublic void render() {System.out.println("Render document for review.");}
}// 上下文类
class Document {private DocumentState state;public Document() {this.state = new Draft(); // 初始状态为草稿}public void setState(DocumentState state) {this.state = state;}public void publish() {state.publish();if (state instanceof Draft) {setState(new Review());} else if (state instanceof Review) {setState(new Published());}}public void render() {state.render();}
}
实现思路说明:
- DocumentState 接口:定义了不同状态下的行为(如 publish、render)。
- 具体状态类(Draft、Review):实现 DocumentState 接口,提供了特定状态下的行为实现。
- 上下文类(Document):维护一个当前状态的引用,并委托状态对象执行具体的行为。
优点:
- 封装了状态转换逻辑:状态模式将状态的转换逻辑封装在状态对象内部,使得状态转换更加明确和集中。
- 易于扩展:可以通过添加新的状态类来增加新的状态和行为,符合开闭原则。
- 消除庞大的条件语句:状态模式通过将行为封装在状态对象中,避免了在上下文中使用大量条件语句。
缺点:
- 增加类的数量:每个状态都需要一个具体的类来实现,这可能会导致类的数量增加。
- 依赖于状态:状态模式的行为依赖于当前的状态,如果状态管理不当,可能会导致逻辑错误或不一致的行为。
使用场景:
- 当一个对象的行为依赖于它的状态,并且它必须在运行时根据状态改变它的行为时。
- 当一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。状态模式把这些条件分支移入它们各自的状态类中。
状态模式(State Pattern)与策略模式(Strategy Pattern)在结构上非常相似,这可能是我们感觉它们很像的原因。尽管这两种模式在结构上相似,但它们的应用场景和目的存在显著差异。
相似性
- 相同的结构:在状态模式和策略模式中,都有一个上下文(Context)类,它持有一个指向抽象接口(State 或 Strategy)的引用,并且通过该引用调用具体实现类(Concrete States 或 Concrete Strategies)的方法。
- 替代继承:两者都通过组合的方式替代了继承,允许动态改变行为。
差异性
- 目的不同:
- 状态模式关注于一个对象内部状态的变化,使得对象的行为随着其内部状态的改变而改变。它主要处理对象在多种状态下的不同行为。
- 策略模式主要是定义一系列算法或策略,并使它们可以互换使用。策略模式允许客户端根据不同情况选择不同的算法或策略。
- 状态的自管理:
- 在状态模式中,状态通常是自我管理的,意味着状态转换可以在状态类内部控制和管理。
- 在策略模式中,策略的选择通常由客户端控制,而不是由策略类自己决定。
- 应用场景:
- 使用状态模式时,通常是在一个对象的行为依赖于其状态,并且它需要在运行时根据状态改变其行为。
- 使用策略模式时,是为了定义一组算法,把它们封装起来,并使它们可以互相替换。
实例对比
考虑一个文本编辑器的例子:
- 在状态模式中,文本编辑器可能有多种状态(如插入、删除、选择等),每种状态下编辑器的行为(如键盘输入的处理)会有所不同。
- 在策略模式中,文本编辑器可能允许用户选择不同的文本布局算法(如左对齐、右对齐、居中对齐等),每种布局算法定义了文本如何布局,但并不依赖于编辑器的状态。
总的来说,尽管状态模式和策略模式在结构上相似,它们的应用场景和目标却有所不同。状态模式侧重于对象状态的管理和基于状态的行为变化,而策略模式侧重于定义一系列可互换的算法或策略。