avatar

设计模式

23种设计模式

  • 创建型模式:提供创建对象的机制, 增加已有代码的灵活性和可复用性。

    单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。

  • 结构型模式:介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。

    适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

  • 行为型模式:负责对象间的高效沟通和职责委派。

    模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。

模板模式

场景:客户到银行办理业务:

  1. 取号排队
  2. 办理具体现金/转账/企业/个人/理财业务
  3. 给银行工作人员评分

模板方法模式介绍:
它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现,这样,新的子类可以在不改变一个算法结构的前提下重新定义该算法的某些特定步骤

核心:处理步骤父类中定义好,具体实现延迟到子类中定义

什么时候用到模板方法模式:
实现一个算法时,整体步骤很固定,但是某些部分易变,易变部分可以抽象出来,供子类实现。

  1. 有多个子类共有的方法,且逻辑相同。
  2. 重要的、复杂的方法,可以考虑作为模板方法。

开发中常见的场景:非常频繁,各个框架、类库都有他的影子

  1. 数据库访问的封装
  2. Junit单元测试
  3. servlet中关于doGet/doPost方法调用
  4. spring中JDBCTemplate、HibernateTemplate等

优点:

  1. 封装不变部分,扩展可变部分。
  2. 提取公共代码,便于维护。
  3. 行为由父类控制,子类实现。

缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

责任链模式

定义:允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。类似if-else语句,但是不利于扩展,因此使用责任链代替if-else的过程。

场景:接力赛跑、大学中,奖学金审批、公司公文审批。

开发中常见的使用场景:

  1. Java中,异常机制就是一种责任链模式。一个try可以对应多个catch,当第一个catch不匹配类型,则自动跳到第二个catch。
  2. Javascript语言中,事件的冒泡和捕获机制。
  3. Servlet开发中,过滤器的链式处理。

意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

优点:

  1. 降低耦合度。它将请求的发送者和接收者解耦。
  2. 简化了对象。使得对象不需要知道链的结构。
  3. 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
  4. 增加新的请求处理类很方便。

缺点:

  1. 不能保证请求一定被接收。
  2. 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用
  3. 可能不容易观察运行时的特征,有碍于除错。

使用场景:
1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
3、可动态指定一组对象处理请求。

单例模式

何为单例模式:一个类只能有一个实例,并且在整个项目中都能访问这个实例。

在Spring中,bean可以被定义为两种模式:prototype(多例)和singleton(单例)

singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。
prototype(多例):对这个bean的每次请求都会创建一个新的bean实例,类似于new。

核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
应该在什么时候下使用单例模式?

举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。

适用场景:

  1. 需要生成唯一序列的环境
  2. 需要频繁实例化然后销毁的对象。
  3. 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  4. 频繁访问数据库或文件的对象。

JDK中的Runtime类就是使用的饿汉式单例!在Spring MVC框架中的controller 默认是单例模式的!

单例的优点:

  1. 在内存中只有一个对象,节省内存空间
  2. 避免频繁创建销毁对象,可以提高系统的性能。
  3. 避免对共享资源的多重占用
  4. 可以全局访问。

单例的缺点:

  1. 不适用于变化频繁的对象
  2. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出。
  3. 如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失。

实现单例模式的几种方法

  1. 饿汉式(静态变量)方法

    优点:写法简单,在类加载的时候就完成了实例化,同时也就避免了线程同步问题,因此线程安全
    缺点:由于是在类加载时就完成了实例化,没有达到懒加载的效果。如果一直没有使用过这个实例,就造成了内存的浪费!

  2. 饿汉式2(static静态代码块)其实和第一种饿汉式(静态变量)方法差不多,其优缺点一致!

    唯一不同的就是把创建单例对象的操作放进了static静态代码块中

  3. 懒汉式(线程不安全)的这种方式起到了懒加载的效果,但只能在单线程下使用。

    如果在多线程下,一个线程进入了if(singleton==null)判断语句块,还没执行产生实例的句子,另一个线程又进来了,这时会产生多个实例,所以不安全。
    结语:懒汉式(线程不安全)在实际开发中,不要使用这种方式!!存在潜在危险

  4. 懒汉式2(线程安全)方式

    优点:线程安全
    缺点:效率太低,每次调用getInstance方法都要进行同步
    结语:懒汉式2(线程安全)方式在开发中不推荐使用,主要是效率太低了*/

  5. 双重检查应用实例方式

    线程安全、延迟加载、效率较高

    结语:开发中推荐使用!

  6. 枚举方式的枚举:

    推荐写法,简单高效。充分利用枚举类的特性,只定义了一个实例,且枚举类是天然支持多线程的。
    借助JDK1.5中添加的枚举来实现单例模式优点:

    1. 不仅能避免多线程同步问题

    2. 还能防止反序列化重新创建新的对象

      枚举方式单例是由Effective java作者Josh Bloch提倡的,结语:推荐使用!

单例总结

1、饿汉式(静态变量)这种方式可以使用,但是没有达到 lazy loading 懒加载的效果会造成内存的浪费!开发中不建议使用。
2、饿汉式(static静态代码块)其实和第一种饿汉式(静态变量)方法差不多,其优缺点一致!唯一不同的就是把创建单例对象的操作放进了static静态代码块中
3、懒汉式(线程不安全)起到了懒加载的效果,但只能在单线程下使用。在实际开发中,不要使用这种方式!!!
4、懒汉式2(线程安全)方式线程安全但是效率太低,每次调用getInstance方法都要进行同步。所以在开发中不推荐使用。
5、懒汉式3同步代码块(线程安全)方式在开发中不使用 ,实际上这个设计有点搞笑哈哈。
6、双重检查应用实例方式,线程安全、延迟加载、效率较高。因此开发中推荐使用!
7、静态内部类单例方式线程安全、利用静态内部类特点实现延迟加载、效率高。 开发中推荐使用这种静态内部类单例方式!
8、借助JDK1.5中添加的枚举来实现单例模式不仅能避免多线程同步问题还能防止反序列化重新创建新的对象。枚举方式单例是由Effective java作者Josh Bloch提倡的,开发中推荐使用!

详情传送门

策略模式

实现方式

  1. 提供公共接口或抽象类,定义需要使用的策略方法。(策略抽象类)

  2. 多个实现的策略抽象类的实现类。(策略实现类)

  3. 环境类,对多个实现类的封装,提供接口类型的成员量,可以在客户端中切换。

  4. 客户端 调用环境类 进行不同策略的切换。

注:Jdk中的TreeSet和 TreeMap的排序功能就是使用了策略模式。

优点

  (1)策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。

  (2)使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。

缺点

  (1)策略模式只适用于客户端知道算法或行为的情况。

  (2)由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。

代理模式

静态代理

实现方式:

  1. 为真实类和代理类提供的公共接口或抽象类。(租房)

  2. 真实类,具体实现逻辑,实现或继承a。(房主向外租房)

  3. 代理类,实现或继承a,有对b的引用,调用真实类的具体实现。(中介)

  4. 客户端,调用代理类实现对真实类的调用。(租客租房)

动态代理

实现方式:

  1. 公共的接口(必须是接口,因为Proxy类的newproxyinstance方法的第二参数必须是个接口类型的Class)

  2. 多个真实类,具体实现的业务逻辑。

  3. 代理类,实现InvocationHandler接口,提供Object成员变量,和Set方法,便于客户端切换。

  4. 客户端,获得代理类的实例,为object实例赋值,调用Proxy.newproxyinstance方法在程序运行时生成继承公共接口的实例,调用相应方法,此时方法的执行由代理类实现的Invoke方法接管。

jdk动态代理使用的局限性

通过反射类Proxy和InvocationHandler回调接口实现的jdk动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方方式实现动态代理。

观察者模式

观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

实现方式

a) 角色抽象类(提供对观察者的添加,删除和通知功能)。

b) 角色具体类,实现a,维护一个c的集合(对角色抽象类的实现)。

c) 观察者抽象类(被角色通知后实现的方法)。

d) 观察者实现类,实现c(多个)。

注:JDK提供了对观察者模式的支持,使用Observable类和Observer接口

简单工厂模式

就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。

实现方式

  1. 抽象产品类(也可以是接口)

  2. 多个具体的产品类

  3. 工厂类(包括创建a的实例的方法)

优点

工厂类是整个模式的关键.包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象.通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的.明确了各自的职责和权利,有利于整个软件体系结构的优化。

缺点

由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;

文章作者: PanXiaoKang
文章链接: http://example.com/2020/05/02/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 向阳榆木
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论