设计模式是指在特定环境下为解决某一个通用软件设计问题提供一套定制的解决方法,该方法描述了对象和类之间的相互作用。对于面向对象开发而言,其特点是可维护、可复用、可扩展、灵活性高,设计模式的出现就是为了保持良好的对象与类的结构,使面向对象的开发具有上述的特点,从而提供高内聚、低耦合的程序范式。
设计原则
在描述设计模式之前,首先需要讲述的是设计模式的几大原则,这些原则实际上就是构建不同设计模式的基准,遵从相应的设计原则可以有效地实现更灵活、更具扩展性和易维护的软件系统。但是并不是每一条设计原则都必须要满足,而是根据实际场景选择合适的设计原则进行权衡和取舍。
设计模式基于的六大原则:
- 开闭原则:对于一个软件实体如类、模块和函数应该对修改封闭,对扩展开放(装饰者模式、策略模式、适配器模式、观察者模式等)
- 单一职责原则:一个类制作一件事,一个类应该只有一个引起它修改的原因
- 里氏替换原则:子类可以完全替换父类,也就是说在使用继承的时候应当把抽象层放在程序设计的高层,并且保持稳定,程序的细节变化由低层的实现层来完成(通过抽象类实现代码复用,也就是模板模式)
- 依赖倒置原则:细节应该依赖于抽象,抽象不应该依赖于细节。将抽象层放在程序设计的高层,并且保持稳定,使程序的细节变化由底层的实现层来实现
- 接口隔离原则:客户端不应该依赖它不需要的接口,如果一个接口在实现时,部分方法由于冗余被客户端实现,则应该将接口进行拆分,让实现类只需要依赖自己需要的接口方法(接口是需要进行拆分的)
- 迪米特原则:最少知道原则,一个类不应该知道自己操作的类的细节
设计模式分类
创建型模式
创建型模式主要用于创建对象,提供了五种创建模型:
- 工厂方法模式
- 抽象工厂模式
- 建造者模式
- 原型模式
-
结构性模式
该类设计模式主要用于处理类和对象的组合,提供了七种结构性模式:
适配器模式
- 桥接模式
- 组合模式
- 装饰者模式
- 外观模式
- 享元模式
-
行为性模式
该类设计模式主要用于描述类或者对象是如何进行交互和怎样分配职责的:
责任链模式
- 命令模式
- 解释器模式
- 迭代器模式
- 中介者模式
- 备忘录模式
- 观察者模式
- 状态模式
- 策略模式
- 模板方法模式
典型设计模式详细介绍
工厂方法模式
工厂方法模式区别于简单工厂模式而言,核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅仅负责给出具体工厂类需要实现的接口,并不会接触到具体某一个产品类被实例化。适用场景:标准化产品输出;例子:数据库连接工厂SqlSessionFactory,不需要知道到底是mysql还是oracle提供的;但是在项目初期不建议使用该模式,因为增加了代码的复杂度;
1、工厂方法模式结构包含的角色:
- Product 抽象产品接口
- ConcreteProduct 具体产品的实现类
- Factory 抽象工厂
- ConcreteFactory 具体工厂
结构图:
时序图:
简单实现:
public class FactoryPattern {
public static void main(String[] args) {
Facotry fac = new ConcreteFactoryA();
Product pro = fac.createProduct();
pro.use();
}
}
// 抽象产品:提供了产品的接口
interface Product {
public void use();
}
// 具体产品A:实现抽象产品中的抽象方法
class ConcreteProductA implements Product {
public void use() {
System.out.println("具体产品展示A");
}
}
// 具体产品B:实现抽象产品中的抽象方法
class ConcreteProductB implements Product {
public void use() {
System.out.println("具体产品展示A");
}
}
// 抽象工厂:提供了产品的生成方法
interface Factory {
public Product createProduct();
}
// 具体工厂A:实现了产品的生成方法
class ConcreteFactoryA implements Factory {
public Product createProduct() {
return new ConcreteProductA();
}
}
// 具体工厂B:实现了产品的生成方法
class ConcreteFactoryB implements Factory {
public Product createProduct() {
return new ConcreteProductB();
}
}
单例模式
单例模式就是创建某一个对象只创建一次,再次使用时还是这个对象。有点在于全局只有一个实例,便于进行控制,并且减少了系统开销,但缺点是没有抽象层,扩展起来比较困难。适用场景:适合需要做全局统一控制的场景;
结构图:
时序图:
简单实现:
public class SingleTonPattern {
public static void main(String[] args) {
SingleTon sin = new SingleTon();
}
}
// 双重锁校验的方式:
class SingleTon {
// 有三点:为什么要双重校验,保证多线程的情况下也能创建单例
// 加入volitile的目的是防止出现指令重排序(分配内存空间、初始化对象、对象头指针头建立指向)
private static volatile SingleTon instance = null;
private SingleTon(){}
public static SingleTon getInstance() {
if (instance == null) {
synchronized(SingleTon.class) {
if (instance == null) {
instance = new SingleTon();
}
}
}
return instance;
}
}
// 静态内部类
class SingleTon {
private SingleTon(){}
private static class SingleTonHolder {
private static staticSingleTon instance = new SingleTon();
}
public static SingleTon getInstance() {
return SingleTonHolder.instance;
}
}
// 枚举
public enum SingleTon {
INSTANCE;
public void xx(){}
}
装饰者模式
装饰者模式比继承更加灵活,可以动态的为对象则更加职责,通过使用不同的装饰者组合为对象扩展多个功能,但不会影响到对象本身。但是问题在于某一个对象的装饰器过多后,会产生很多的装饰类小对象和装饰组合策略,增加系统复杂度,增加代码的阅读成本。
使用场景:适合需要来动态增减对象功能的场景,以及适合一个对象需要N种功能排列组合的场景。
注意事项:装饰类的接口必须与被装饰类的接口保持相同,因为对于客户端来说无论是装饰之前的对象还是装饰过后的对象都可以一致对待;尽量保持具体构建类比较轻,可以通过装饰类放入逻辑状态以及具体的构建类。
装饰模式的结构:
- Component :抽象组件接口
- ConcreteComponent:具体构建
- Decorator: 抽象装饰类
- ConcreteDecorator : 具体装饰类
结构图:
时序图:
简单实现:
public class DecoratorPattern {
public static void main(String[] args) {
Component component = new ConcreteComponent();
component.operation();
Component decorator = new ConcreteDecorator(component);
decorator.operation();
}
}
// 抽象构建模式
interface Component {
public void operation();
}
// 具体构建模式
class ConcreteComponent implements Component {
public ConcreteComponent() {
System.out.println("创建具体构建角色");
}
public void operation() {
System.out.println("调用具体构建角色的方法operation");
}
}
// 抽象装饰角色
class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}
// 具体装饰角色
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation();
addBehavior();
}
public void addBehavior() {
System.out.println("为具体构建角色增加额外的功能");
}
}
观察者模式
观察者模式的优势在于将复杂的串行处理逻辑变为单元化的独立处理逻辑,被观察者知识按照自己的逻辑发出消息,不用关心谁来消费消息,每一个观者者只处理自己关心的内容,逻辑相互隔离带来简单清爽的代码结构;但是缺点是当观察者较多时,可能会花费一定的开销来发送消息,但是这个消息可能只有一个观察者来消费。
适用场景:一对多的业务场景,当一个对象发生变更时,会触发N个对象做相应处理的场景,比如订单调度以及任务状态变化等;
观察者模式结构:
- Subject 目标
- ConcreteSubject 具体目标
- Observer 观察者
- ConcreteObserver 具体观察者
结构图:
序列图:
简单实现:
public class ObserverPattern {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Observer a = new ConcreteObserver();
Observer b = new ConcreteObserver();
subject.add(a);
subject.add(b);
subkect.setState(0);
}
}
abstract class Subject {
protected List<Observer> observerList = new ArrayList<>();
public void add(Observer observer) {
observerList.add(observer);
}
public void remoce(Observer observer) {
observerList.remove(observer);
}
public abstract void notify();
}
class ConcreteSubject extends Subject {
private Integer state;
public void setState(Integer state) {
this.state = state;
// 当状态发生改变时,需要通知到其他观察者
notify();
}
public void notify() {
System.out.println("具体目标状态发生改变...");
System.out.println("--------------");
for (Observer obs:observerList) {
obs.process();
}
}
}
interface Observer (
void process();
)
class ConcreteObserverA implements Observer {
public void process() {
System.out.println("具体观察者A处理!");
}
}
class ConcreteObserverB implements Observer {
public void process() {
System.out.println("具体观察者B处理!");
}
}
策略模式(与SPI之间的差距)
策略模式的优势在于对开闭原则提供的完美支持,用户可以在不修改原有系统的基础上选择算法或者行为,避免出现复杂难看的if else语句;
缺点:必须提前知道存在哪些策略模式类,才能自行觉得当前场景应该使用何种策略;
适用场景:一个系统需要动态地在集中可以替换的算法种随机选择一种,不希望使用者关心算法细节,将具体算法封装在策略类中;
策略模式结构 :
- Context 环境类
- Strategy 抽象策略类
- ConcreteStrategy具体策略类
结构图:
流程图:
简单实现:
public class StrategyPattern {
public static void main(String[] args) {
Context context = new Context();
Strategy strategy = new ConcreteStrategy();
context.setStrategy(strategy);
context.algorithm();
}
}
interface Strategy {
public void algorithm();
}
class ConcreteStrategyA implements Strategy {
public void algorithm() {
System.out.print("策略A");
}
}
class ConcreteStrategyB implements Strategy {
public void algorithm() {
System.out.print("策略B");
}
}
class Context {
private Strategy strategy;
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void algorithm() {
this.strategy.algorithm();
}
}
代理模式
代理模式的优势在于可以协调调用方以及被调用方,降低了系统的耦合程度。根据代理类型和场景的不同,可以起到控制安全性、减小系统开销的作用。缺点是增加了一层代理处理,提高了系统的复杂度。
适用场景:
- 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。
- 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
- Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
- 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
- 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
- 防火墙(Firewall)代理:保护目标不让恶意用户接近。
- 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
- 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。
代理模式的角色:
- Subject 抽象主题角色
- Proxy代理
- RealSubject 真实主题角色
结构图:
时序图:
简单实现:
public class ProxyPattern {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.request();
}
}
//抽象主题
interface Subject {
void request();
}
//真实主题
class RealSubject implements Subject {
public void request() {
System.out.println("访问真实主题方法...");
}
}
//代理
class Proxy implements Subject {
private RealSubject realSubject;
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.request();
afterRequest();
}
public void preRequest() {
System.out.println("访问真实主题之前的预处理。");
}
public void afterRequest() {
System.out.println("访问真实主题之后的后续处理。");
}
}