如何理解这6种常见设计模式?

简介: 设计模式能够帮助我们优化代码结构,让代码更优雅灵活。有哪些常见的设计模式?如何合理运用?本文分享作者对工厂模式、单例模式、装饰模式、策略模式、代理模式和观察者模式的理解,介绍每种模式的模式结构、优缺点、适用场景、注意实现及代码实现Q @ L + U : _ L 4

如何理解这6种常见设计模式?

一 前言

最近在改N , Z b ]造一些历史的代码,发现一个很明显的特点,大部分代码是记叙H y E 9 | D @ 6 `文,按照事件的发展过程将故事平铺直叙的讲解出来。

这种方式的好处是比较符合人类的思维习惯,一条主线讲到底,代码阅读起来没有太大难度,只要顺着藤就能摸到瓜,但是缺点也很明显,一旦故事线中需要插入一些新的元素,比如:加入一个新的人物角色、新的时间线,都会需要大量更改故事线以配合这个新元素的融入,甚} B X : [ S e至对原有文章造成破坏性的影响。

为了解决这个问题,人们总结出了很多种文章结构,例如:总-分结构,并列结q | D x构,总-分-总结构等等,有了这些结构,在加入新元素的时候,甚至不必考虑新元素与原故事情e [ %节的关联性,直接单拉一个分支故事线独立去讲就好了,只要能够在整体故, I G : s A事结束前,z 3 3 : ^与汇聚到主线故事就可以了(是不是很像git?)。

在软件开发领域,也有很多这样的非常有用的实践总结,我们称之为设计模式。对于] R e = ` t K设计模式,大家都不陌生,随便找个人,估计都能讲出N个设计模式来,但是除了这些设计模式M w r B的概念,很多人不知道如何灵活运用这些设计模式T [ | ]。所以借这篇文章和大家共同学习设计模式的思想。

理解设计o D I M模式

我尽量用最通俗易懂的示例和语言来讲述我理解的P ! 5 1 p E O设计模式,希望} Q . ! m 4 - j能对大家有所帮助。

另外也无需精通所有的设计模式,只要能够融汇贯通常见的设计模式,就能让你的代码变得优雅。就像程咬金只会三板斧,但是熟练度无人能及,照样能横行天下。

1 工厂模式(Factory)y u . O v A 4

简单工厂(Simple Factory)

小明追妹子的时候,请她喝了不少咖啡,她爱喝卡布奇诺,每次去咖啡店,只要跟服务员说“来杯卡布奇诺”就行了,虽然各家的口味有些不同,但是不管是星爸爸还是Costa,都能够提供卡布奇诺这种咖啡。这里的星爸爸和Costa就是生产咖啡的工厂。

(1)简单工厂模式结构

简单工厂模式包含如下角色:

  • Factory:工厂角色-负责实现创建所有实例的内部逻辑.
  • Product:抽象产品角色-是所创建的所有对象的父类,负责描述所有实例所共有的公共接口。
  • ConcreteProduct:具体产品角色-是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。

} ^ T i V = p X k构图:

如何理解这6种常见设计模式?

时序图:

如何理解这6种常见设计模式?

(2)优缺点

  • 优点:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。
  • 缺点:是当产品修改时,工厂类也要做相应的修改。

工厂方法(Factory Method)

以前经常带老婆去优衣库(简单工厂)买衣服,就那么多款式,逛的次数多了,她就烦Z z Q了。后来我改变策略,带老婆去逛商场(抽象工厂),商场里有各式品牌的店铺,不用我管,她自己就能逛上一整天。 区别于简单工厂,核心工厂类(商场)不再负责所有产品的创建,而是将具体创建的工作交给子类(服装店)去做,成为一个抽象工厂角色: | m z u $,仅负责给出具体工厂类必须实现的接口(门店),而不接触哪一个产品类应当被实例化这种细节。

(1)工厂方法模式结构

工厂方法模式包含如下角色:

  • Product:抽象产品
  • ConcreteProduct:具体产品
  • Factory:抽象工厂
  • Conc6 s ! X G Q 6 mreteFactory:具体工厂

结构图:

如何理解这6种常见设计模式?

时序图:

如何理解这6种常见设计模式?

工厂模式总结

(1)适用场景

输出的产品是标准品,谁来做都可以。

(2)举例

常见的数据库连接工厂,SqlSessionFactory,产品是一个数据库连接,至于是oracle提供的,还是mysql提t - e 0 T ~ i m W供的,我并不需要关心,因为都能让我通过sql来操u ] R / E [ l作数据。

(3x 8 P c n)注意事项

项目初期,软件结构和需求s v =; E Q }没有稳定下来时,不建议使用此模式,因为其劣势也很明显,增加了代码的复杂度,增加了调用层次,增加了内存负担。所以要注意防止模式的滥用。

(4)简单实a x O [ u a e ^

package FactoryMethod;

public class FactoryPattern

{

public static void main(String[] args)

{

Fac~ v ~ P )tory facu r 4 k w ctory = new ConcreteFactoryA();

Product product =| O h B p c 6 d f5 E [ ~ Wactory.createProduct();

product.use();

}

}

//抽象产品:提供了产品的接口

interface Product

{

public voidT o F = C use;

}

//具体产品A:实现抽象产品中的抽象方法

class Conc% ~ 4 f * z # NreteProductA imZ * $ 8 L ~ Tplements Prodt ? k G ) j r nuct

{

public void use()

{

System.out.println("具体产品A显示...");

}

}

//具体产品B:实现抽象产品中的抽象方法

class ConcretePrs S m * d b . } |oductB implements Product

{

publi[ ~ n 1c void use()

{

System.out.println("具体产品B显示...");

}

}

//抽象工厂:提供了厂品的生成方法

interfacp + ] @ h a ue Factory

{

public Product createProduct();

}

//具体工厂A:实现了厂品的生成方法

class ConcreteFactoryA implements AbstractFactory

{

publi] # R 5 ;c Product createProduct()

{

System.out.println("具体工厂A生成-->具体产品A.");

return new ConcreteProductA();

}

}

//具体工厂B:实现了厂品的生成方法

class ConcreteFaA n o ~ | 5ctoryB implements AbstractFactb E 4 l N & ( Z 6ory

{

public Product createProduct()

{

System.S n Zout.println("具体工厂B生成--U % 8 s P t ,>具体产品B.");

return new ConcreteProductB();

}

}

2 单例模式(Singleton)

韦小宝有7个老婆,但是每个都只有他这一个^ p ~ 8 c d老公,他的所有老婆叫老公时,指的都是他,他就是一个单例。

单例模式结构

单例模式包含如下角色:

  • Singleton:单例

结构图:

如何理解这6种常见设计模式?

时序图:

如何理解这6种常见设计模式?

优缺点

  • 优点:全局只有一个实例,便于统一控制,同W L a e时减少了系统资源开销。
  • 缺点:没有抽象层,扩6 4 A ` G T g展困难。

应用场景

适合需要做全局统一控制的场景,例如:全局唯一的编码生成器。

注意事项

只对外提供p G t m公共的getInstance方法,不提供任何公共构造函数。

简单实现

public class Singleton

{

private static volatile Singleton instance=null; //保证 instance 在所有线程中同步

private Singleton(){} //private 避免类在外部被实例化

public static synchronized Singleton getInstance()

{

//getInstance 方法前加同步

if(instU Z ~ 3 / , f |ance == null)

{

instance = new Singleton();

}

return instance;

}

}

3 装饰模式(Decorator)

大学毕业,想要送给室友一个有纪念意义的礼物,就找到一张大家的合照,在上面写上“永远的兄弟!”,然后拿去礼品店装b p M Z 8 l了个相框,再包上礼盒。这里的我和礼品= M + i q F店都是装饰器,都没有改变照片本身o i Y n f j _,却都让照片变得更适合作为礼物送人。

装饰模式结构

装饰模式包含如下角色:

  • Component:抽象构件
  • ConcreteComponent:具体构件
  • Decorator:抽 5 n { v d ) L l象装饰类
  • ConcreteDecorator:具体装饰类

结构图:

如何理解这6种常见设计模式?

时序图:

如何理解这6种常见设计模式?

优缺点o h X 0

  • 优点:比继承更加灵活(继承是耦合度很大的静态关系),可以动态的为对象增加职责,可以通过使用不同的装饰器组合为对象扩展N个新功能,而不会影响到对象本身。
  • n 6 +点:当一个对象的装饰器过多时,会产生很多的装饰类小对象和装饰组合策略,增加系统复杂度,增加代码的阅读理解成本。

适用场景

  • 适合需要(通过配置,如:diamond)来动态增减对象功能的场景。
  • 适合一个对象需要N种功能排列组合的场景(如果用继承,会使子f B V类数量爆炸式增长)

注意事项

  • D g U I个装饰类的接口必须与被装饰类的接口保持相同,对于D P m客户端来说无论是装饰之前的对象还是装饰之后q k J | 7 a的对象都可以一致对待。
  • 尽量保持具体构件类Component作为一个“轻”类,也就是说不要把太多的逻辑和状态M L ` )放在具体构件类中,可以通过装饰类。

简单实现

package dec= k & T 2 3orator;

public class DecoratorPattern

{

public static void main(String[] arb F | L q { igs)

{

Component component = new ConI , RcreteComponent();

coG S ` t 0 f + 8mponent.I J 7 g ? @ S : roperation();

System.out.println("---------------------------------");

Component decorator = new ConcreteDecorator(component);

decor8 Y ? 8 e u k Iator.operation();

}

}

//抽象构件角色

interface Component

{

public void operation();

}

//具体构件角色

class ConcreteCompone y ent implements Com$ U l # N v 9ponent + i R x I S j r

{

public ConcreteComponent()

{

System.out.printQ } ^ 2 D 1 N T Wln("创建具体构件角色& 9 x t ;");

}

public void operation()

{

System.out.println("调用具体构件角色的方法operation()");

}

}

//抽象装饰角色

class Det & ; L & p ecorator impl7 o E q zem5 } A I & i y wents Component

{

prip 6 q B x ; D Lvate Component component;

public Decorator(Componentx U D ; Z d component)

{

this.componenth N R V # S !=component;

}

public void operation()

{

com1 z ~ aponM f Z ,ent.operation();

}

}

//具体装饰角色

cla^ , 4 d (ss ConcreteDecorator extends Decorator

{

public ConcreteDecorator(Component component)

{

super(component);

}

public void operation()

{

super.operation();

addBehavior();

}

public void addBehavior()

{

System.out.println("` t - F r X p .为具体构件角色增加额外的功能addBehavior()");

}

}

4 策略模式(Strategy)

男生追妹子时,一般都会用到这种模式,常见的策略有这些:约会吃饭;看电影;看演唱会;逛街;去旅行……,虽然做的事情不同,但可以相互替换,唯一的目标都是捕获妹子的芳心。

策略模式结构{ N v a @

  • Context: 环境类
  • Strategy: 抽象策略类
  • ConcreteStrategy: 具体策略类

结构图:

如何理解这6种常见设计模式?

e ! p w A I C ^序图:

如何理解这6种常见设计模式?

优缺点

  • 优点:策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为。干掉复杂难看的if-else。
  • 缺点:调用时,必须提前知道都有哪些策略模式类,才能自行决定M ; Z | f当前场景该使用何种策F m P略。

试用场景

一个系统需要动态地在几种可替换算法中选择一种。不希望使用@ 2 0 A者关心算法细节,将具体算法封装进策略类中。

注意事项

一定要在策7 G % % L x | |略类的注释中说明该策略的用S t 0 g x ) j e途和Y } C ] Y l D适用场景。

简单实现

package strategy;

public class Stratem + ! 2 IgyPattern

{

public static void main(String[] args)

{

Context context = new ConE ; 7 m T N 1 2text();

Strategy strategyA = new ConcreteStratk $ k [ { &egyA();

context.setStrategy(strategyA);

context.algorithm();

Sysk 8 `tem.out.5 w 9 4println("l ) - f 8 + 2-----------------")[ U K W J k ] C L;

Strategy st] j d V %rategyB = new ConcreP z uteStrategy6 } % l # _B();

context.setStrategy(strategyB);

context.algorithm();

}

}

//抽象策略类

interface Strategy

{

public void algorithm(); //策略方法

}

//d l d * p具体策略类A6 B f A : 1

class ConcreteStrategyA implements Strategy

{

public void algorithm()

{

System.e $ 2 # m u ! v 8out.println("具体策略A的策略方法被访问!")F | -;

}

}

//具体策略类B

class ConcreteStrategyB implements Strategy

{

publiD H i Ic void algorithm()

{

System.out.println("具体策略B的策略方法被访问!");

}

}

//环境类

class Context

{

private Strategy strategy;

public Strn + U ] J H /ategy getStrategy| o b i C k()

{

return strategy;

}

public void setStrateg7 q &y(Strategy strategy)

{

this.strategy} O C ! ~ G Z U=strategy;

}

public void algorh - ) D fithm()

{

strategy.algorithm();

}

}

5 代理模式(Proxy)

淘宝店客服总是会收到非常多的重复问题,例如:y * + 9 t r } j有没有现货?什么时候发货?发什么快递?大量回答重复性的问题太烦了,于是就出现了小蜜机器人,他来帮客服回答那些已知的问题,当碰到小蜜无法解答的问题时,才会转到人工客服。这里的小蜜机器人就是客服的代理。

代理模式结构

代理模式包含如下角色:L , C B % Q 6 v q

  • Subject: 抽象主题角色
  • Proxy: 代理主题角色
  • RealSubject: 真实主题角色

结构图:

如何理解这6种常见设计模式?

时序图:

如何理解这6种常见设计模式?

优缺点

  • 优点:代理可V 5 h V 1 / D以协调调用方与被调用方,降低了 P b系统的耦合度。根据代理类型和场景的不同,可以起到控制安全性、减小系统开销等作用。
  • H G ! b G ? l Q $点:增加了一层代理处理,增加了系统的复杂度,同时可能会降低系统的相应速度。

试用场景

理论上可以代理任何对象,常见的代理模式有:

  • 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地X H D 0 r址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。
  • 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  • Copy-on-Write代理:它是虚q o x D a f x c拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行r N ` e k v +。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理e ` | U R l Z可以让这个操作延迟,只有对象被用到的时候才被克隆。
  • 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 缓冲(Cache)代理:为某一个目标操作的结果提l h S d K W C 6供临时的存储空间,以便多个客户端可以共享这些结果。
  • X ; G ) q r 8 A火墙(w [ E . I a Q V wFirewall)代理:保护目标不让恶意用户接近。
  • 同步化(b L r . mSynchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
  • 智能引用(Smart Reference)代理:当x q w D Z h s一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。

简单实现

package prot Z + Z Dxy;

public cl- p # F Bass ProxyPatterA o ; F T =n

{

public static void main(String[] args)

{

Proxy proxy = new Proxy();

proxy.request();

}

}

//抽象主题

interface Subject

{

void reqI b l ^ E Duest();

}

//真实主题

class RealSubject implements Subject

{

public void request()

{

SW } V q jystem.out.println("访问真实主题方法...");

}

}

//代理

class Proxy implements Subject

{

private RealSubject realSubj3 M C # Rect;

pu0 5 = q }blic void request()

{

if (realSubject==null)

{

rE & f l - g s w JealSubject=new RealSubject();

}

preRequest();

realSubjectz X O V 8 1 u ; `.request();

after9 X ` BRequest();

}

public void preRequest()

{

System.out.println("访问真实主题之前的预处理。");

}

public void afterRequest()

{

Syst- x @ = * n | s `em.out.println("访问真实主题之后的后续处理。");

}

}

6 观察者模式(Observer)x , 6 } X & X -

出差在外,想了解孩子在家的情况,这时候只要加入“相亲相爱一家人”群,老爸老妈会经常把孩子的照片和视频发到群里,你要做的就是作为一个观察者,刷一刷群里的信E c m |息就能够了解一切了。

观察者模式结构

观察者模式包含如下角色:

  • Subject:目标
  • ConcreteSubject:具体目标
  • Observer:观察者
  • ConcreteObserver:具体观察者U d g

结构图:

如何理解这6种常见设计模式?

时序图:

如何理解这6种常见设计模式?

优缺点

  • c - z r . Z = A点:将复杂的串行处理逻辑变为= Z T [ q G单元化的独立处理逻辑,被n @ = ( p , y 0观察者只是按照自己的逻辑发出消息,不用关心谁来消费消息,每个观察者只处理自己关心的内容。逻辑相互隔离带来简单清爽的代码结构。
  • 缺点:观察者较多时,可能会花费一定的开销来发消息,但这个消息可能仅一个观察者消费。

适用场景

适用于一对多的的业务场景,一个对象发生变更,会触发N个对象做相应处理的场景。例如:订单调度通知,任Z } c y E务状态变化等。

注意事项

避免观察者与被观察者_ ) d { ! R c % F之间形成循环依赖,可能会因此导致系统崩溃。

简单实现

package observer;

import java.util.*;

public class ObserverPattern

{

public static void main(String[] args)

{

Subject su3 ^ V ( q C /bject = ne8 g u Gw ConcreteSubject();

ObserveZ ^ d kr obsA = new Co3 4 : p 7 `ncreteObserverA();

Observer obsb = new ConcreteObserverB();

subject.add(obsA);

subject.add(obsB);

subject.setState(0);

}

}

//抽象目标

abstract class Subjec} k : c o = ` mt

{

protected List<Observer> observerList = new ArrayList<Observer>();

//增加观察者方法

public void add! * g W H(Observer observer)

{

obseh ? @ G Orvers.add(obserZ n 1 U Xver);

}

//删除观察者方法

public void remove(Observer observer)

{

observers.remove(observer);

}

public abstract void notify(); //通知观察者方法

}~ T D L V }

//具体目标

class ConcreteSubject extends SubjecK H /t

{

private Integer state;

public void setState(Integer state){

this.state = state;

// 状态改变通知观察者W C p %

notify();

}

public void notify()

{

System.out.println("具体目标状态发生改变...");

System.e s a % u @out.println("--------------");

for(Observer obs:observers)

{

obs.process4 2 M P v / P J();

}

}

}

//抽象观察者

interface Observer

{

void process(); //具体的处理

}

//具体观察者A

class CoH ! ) ;ncreteObserverA implements ObservE @ + $ M n ] Ler

{

public void process()

{

System.out.println("具体观察者A处理!");

}

}

//具体观察者B

class ConcreteObserverB implements Observer

{

publiT N c ( 3 , 3 O Kc void process()

{

System.out.println("具体观察者B处理!");

}

}

作者:茶什!

本文为阿里云原创内容,未经允许不得转载。