设计模式

Thank the flame for its light, but do not forget the lampholder
standing in the shade with constancy of patience.

谢谢火焰给你光明,但是不要忘了那执灯的人,他是坚忍地站在黑暗当中呢。

设计模式

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

什么是 GOF?

在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。

四位作者合称 GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。

  • 对接口编程而不是对实现编程。
  • 优先使用对象组合而不是继承。

模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。

创建型模式:这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

  • 工厂模式(Factory Pattern)
  • 抽象工厂模式(Abstract Factory Pattern)
  • 单例模式(Singleton Pattern)
  • 建造者模式(Builder Pattern)
  • 原型模式(Prototype Pattern)

结构型模式:这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

  • 适配器模式(Adapter Pattern)
  • 桥接模式(Bridge Pattern)
  • 过滤器模式(Filter、Criteria Pattern)
  • 组合模式(Composite Pattern)
  • 装饰器模式(Decorator Pattern)
  • 外观模式(Facade Pattern)
  • 享元模式(Flyweight Pattern)
  • 代理模式(Proxy Pattern)

行为型模式:这些设计模式特别关注对象之间的通信。

  • 责任链模式(Chain of Responsibility Pattern)
  • 命令模式(Command Pattern)
  • 解释器模式(Interpreter Pattern)
  • 迭代器模式(Iterator Pattern)
  • 中介者模式(Mediator Pattern)
  • 备忘录模式(Memento Pattern)
  • 观察者模式(Observer Pattern)
  • 状态模式(State Pattern)
  • 空对象模式(Null Object Pattern)
  • 策略模式(Strategy Pattern)
  • 模板模式(Template Pattern)
  • 访问者模式(Visitor Pattern)

设计模式的六大原则

1、开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则(Liskov Substitution Principle)

里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

3、依赖倒转原则(Dependence Inversion Principle)

这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

5、迪米特法则,又称最少知道原则(Demeter Principle)

最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

满足原则

  • 开闭原则
  • 依赖倒转原则
  • 迪米特法则

实质

  1. 实例化对象不能new,用工厂方法代替
  2. 将选择实现类,创建对象统一管理和控制,从而将调度这跟我们实现类解耦

意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

主要解决:主要解决接口选择的问题。

何时使用:我们明确地计划不同条件下创建不同实例时。

如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。

关键代码:创建过程在其子类执行。

优点:

  1. 一个调用者想创建一个对象,只要知道其名称就可以了。
  2. 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  3. 屏蔽产品的具体实现,调用者只关心产品的接口。

缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。

注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

实例

Car接口

1
2
3
4
package com.bobo.factory.method;
public interface Car {
void name();
}

TSL实现类

1
2
3
4
5
6
7
package com.bobo.factory.method;
public class TSL implements Car {
@Override
public void name() {
System.out.println("TSL");
}
}

WL实现类

1
2
3
4
5
6
7
package com.bobo.factory.method;
public class WL implements Car {
@Override
public void name() {
System.out.println("WL");
}
}

CarFactory接口

1
2
3
4
5
package com.bobo.factory.method;
//工厂方法模式
public interface CarFactory {
Car getCar();
}

TSLFactory工厂实现类

1
2
3
4
5
6
7
8
package com.bobo.factory.method;

public class TSLFactory implements CarFactory {
@Override
public Car getCar() {
return new TSL();
}
}

WLFactory工厂实现类

1
2
3
4
5
6
7
8
package com.bobo.factory.method;

public class WLFactory implements CarFactory{
@Override
public Car getCar() {
return new WL();
}
}

Consumer实现类

1
2
3
4
5
6
7
8
9
10
package com.bobo.factory.method;
public class Consumer {
public static void main(String[] args) {
//使用工厂创建
Car car = new WLFactory().getCar();
Car car1 = new TSLFactory().getCar();
car.name();
car1.name();
}
}

输出

1
2
WL
TSL

单例模式

单例设计模式(Singleton Design Pattern):一个类只允许创建一个对象(或者叫实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

  1. 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
  2. 考虑对象创建时的线程安全问题;
  3. 考虑是否支持延迟加载;
  4. 考虑 getInstance() 性能是否高(是否加锁)。
  1. 饿汉式

    1
    2
    3
    4
    5
    6
    7
    public class Singleton1 {
    private static Singleton1 INSTANCE = new Singleton1();
    public Singleton1() {}
    public static Singleton1 getInstance(){
    return INSTANCE;
    }
    }

    不支持延迟加载

    线程安全

  2. 懒汉式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Singleton2 {
    private static Singleton2 INSTANCE;
    public Singleton2(){}
    public static synchronized Singleton2 getInstance(){
    if (INSTANCE == null){
    INSTANCE = new Singleton2();
    }
    return INSTANCE;
    }
    }

    支持延迟加载

    并发度低

  3. 双重检测

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Singleton3 {
    private static volatile Singleton3 INSTANCE;
    public Singleton3() {}
    public static Singleton3 getInstance(){
    if (INSTANCE == null){
    synchronized (Singleton3.class){
    if (INSTANCE == null){
    INSTANCE = new Singleton3();
    }
    }
    }
    return INSTANCE;
    }
    }

    解决了懒汉式并发度低的问题

  4. 静态内部类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Singleton4 {
    public Singleton4() {}
    private static class SingletonStatic{
    private static final Singleton4 INSTANCE = new Singleton4();
    }
    public static Singleton4 getInstance(){
    return SingletonStatic.INSTANCE;
    }
    }

    既保证了线程安全,又能做到延迟加载

  5. 枚举

    1
    2
    3
    public enum Singleton5 {
    INSTANCE;
    }

    保证了实例创建的线程安全性和实例的唯一性

适配器模式

适配器模式的英文翻译是 Adapter Design Pattern。顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。

ITarget 表示要转化成的接口定义。Adaptee 是一组不兼容 ITarget 接口定义的接口,Adaptor 将 Adaptee 转化成一组符合 ITarget 接口定义的接口。

  1. 类适配器使用继承关系来实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public interface ITarget { 
    void f1();
    void f2();
    void fc();
    }
    public class Adaptee {
    public void fa() { //... }
    public void fb() { //... }
    public void fc() { //... }
    }

    public class Adaptor extends Adaptee implements ITarget {
    public void f1() {
    super.fa();
    }
    public void f2() { //...重新实现f2()... }
    // 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点
    }
  2. 对象适配器使用组合关系来实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public interface ITarget {
    void f1();
    void f2();
    void fc();
    }
    public class Adaptee {
    public void fa() { //... }
    public void fb() { //... }
    public void fc() { //... }
    }

    public class Adaptor implements ITarget {
    private Adaptee adaptee;
    public Adaptor(Adaptee adaptee) {
    this.adaptee = adaptee;
    }
    public void f1() {
    adaptee.fa(); //委托给Adaptee
    }
    public void f2() {
    //...重新实现f2()...
    }
    public void fc() {
    adaptee.fc();
    }
    }

使用判断标准:一个是 Adaptee 接口的个数,另一个是 Adaptee 和 ITarget 的契合程度。

  1. 如果 Adaptee 接口并不多,那两种实现方式都可以。
  2. 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都相同,那我们推荐使用类适配器,因为 Adaptor 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。
  3. 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。
查看评论