大部分讲解设计模式的书或者文章,都是从代码层面来说解设计模式,看的时候都懂,可是到真正用的时候,仍是理不清、想不明。算法
本文尝试从架构层面来聊一聊设计模式。经过将使用设计模式的代码和不使用设计模式的代码分别放到架构中,来看看设计模式对架构所产生的影响。设计模式
通常讲解设计模式的套路是:markdown
以策略模式为例:数据结构
意图:定义一系列的算法,把它们一个个封装起来, 而且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。架构
适用性:ide
类结构:函数
示例代码:oop
public class Context {
//持有一个具体策略的对象
private Strategy strategy;
/**
* 构造函数,传入一个具体策略对象
* @param strategy 具体策略对象
*/
public Context(Strategy strategy){
this.strategy = strategy;
}
/**
* 策略方法
*/
public void invoke(){
strategy.doInvoke();
}
}
public interface Strategy {
/**
* 策略方法
*/
public void doInvoke();
}
public class StrategyA implements Strategy {
@Override
public void doInvoke() {
System.out.println("InvokeA");
}
}
public class StrategyB implements Strategy {
@Override
public void doInvoke() {
System.out.println("InvokeB");
}
}
复制代码
从上面的讲解,你能理解策略模式吗?你是否有以下的一些疑问?this
产生这些疑问的缘由,是咱们在孤立的看设计模式,而没有把设计模式放到实际的场景中。编码
当咱们将其放到实际项目中时,咱们实际是须要一个客户端来组装和调用这个设计模式的,以下图所示:
public class Client {
public static void main(String[] args) {
Strategy strategy;
if("A".equals(args[0])) {
strategy = new StrategyA();
} else {
strategy = new StrategyB();
}
Context context = new Context(strategy);
context.invoke();
}
}
复制代码
做为比较,这里也给出直接使用ifelse时的结构和代码:
public class Client {
public static void main(String[] args) {
Context context = new Context(args[0]);
context.invoke();
}
}
public class Context {
public void invoke(String type) {
if("A".equals(type)) {
System.out.println("InvokeA");
} else if("B".equals(type)) {
System.out.println("InvokeB");
}
}
}
复制代码
乍看之下,使用ifelse更加的简单明了,不过别急,下面咱们来对比一下两种实现方式的区别,来具体看看设计模式所带来的优点。
首先,使用策略模式使得架构的边界与使用ifelse编码方式的架构的边界不一样。策略模式将代码分红了三部分,这里称为:
而ifelse将代码分红了两部分:
在ifelse实现中,「逻辑流程」和「逻辑实现」是硬编码在一块儿的,明显的紧耦合。而策略模式将「逻辑流程」和「逻辑实现」拆分开,对其进行了解耦。
解耦后,「逻辑流程」和「逻辑实现」就能够独立的进化,而不会相互影响。
假设如今要调整业务流程。对于策略模式来讲,须要修改的是「逻辑层」;而对于ifelse来讲,须要修改的也是「逻辑层」。
假设如今要新增一个策略。对于策略模式来讲,须要修改的是「实现层」;而对于ifelse来讲,须要修改的仍是「逻辑层」。
在软件开发中,有一个原则叫单一职责原则,它不只仅是针对类或方法的,它也适用于包、模块甚至子系统。
对应到这里,你会发现,ifelse的实现方式违背了单一职责原则。使用ifelse实现,使得逻辑层的职责不单一了。当业务流程须要调整时,须要调整逻辑层的代码;当具体的业务逻辑实现须要调整时,也须要调整逻辑层。
而策略模式将业务流程和具体的业务逻辑拆分到了不一样的层内,使得每一层的职责相对的单一,也就能够独立的进化。
咱们从新来观察一下策略模式的架构图,再对照上面的调用代码,你有没有发现缺乏了点什么?
在Client中,咱们要根据参数断定来实例化了StategyA或StategyB对象。也就是说,「调用层」使用了「实现层」的代码,实际调用逻辑应该是这样的:
能够看到,Client与StategyA和StategyB是强依赖的。这会致使两个问题:
咱们先来解决「对象分散」的问题,下一节来解决「稳定层依赖不稳定层」的问题!
对于「对象分散」的问题来讲,建立型的设计模式基本能解决这个问题,对应到这里,能够直接使用工厂方法!
使用了工厂方法后,构建代码被限制在了工厂方法内部,当策略对象的构造逻辑调整时,咱们只须要调整对应的工厂方法就能够了。
如今「调用层」只和「实现层」的StategyFactoryImpl有直接的关系,解决了「对象分散」的问题。可是即便只依赖一个类,调用层依然和实现层是强依赖关系。
该如何解决这个问题呢?咱们须要依赖倒置。通常方法是使用接口,例如这里的「逻辑层」和「实现层」就是经过接口来实现了依赖倒置:「逻辑层」并不强依赖于「实现层」的任何一个类。箭头方向都是从「实现层」指向「逻辑层」的,因此称为依赖倒置
可是对于「调用层」来讲,此方法并不适用,由于它须要实例化具体的对象。那咱们该如何处理呢?
相信你已经想到了,就是咱们一直在用的IOC!经过注入的方式,使得依赖倒置!咱们能够直接替换掉工厂方法。
能够看到,经过依赖注入,使得「调用层」和「实现层」都依赖于「逻辑层」。因为「逻辑层」也是相对较稳定的,因此「调用层」也就不会频繁的变化,如今须要变化的只有「实现层」了。
最后一个区别就是设计模式使得逻辑显化。什么意思呢?
当你使用ifelse的时候,实际上你须要深刻到具体的ifelse代码,你才能知道它的具体逻辑是什么。
对于使用设计模式的代码来讲,咱们回过头来看上面的架构图,从这张图你就能看出来对应的逻辑了:
至于具体的Strategy逻辑是什么样子的,你能够经过类名或方法名来将其显化出来!
本文经过将使用设计模式的代码和不使用设计模式的代码分别放到架构中,对比设计模式对架构所产生的影响: