设计模式

简介

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

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。

设计模式的分类

根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。

总体来讲设计模式分为三大类:

  • 创建型模式,共五种:工厂模式,抽象工厂模式,单例模式,建造者模式,原型模式。

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

  • 结构型模式,共七种:适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。

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

  • 行为型模式,共十一种:策略模式,模板方法模式,观察者模式,迭代子模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式。

这些设计模式关注对象之间的通信。

设计模式的六大原则

  1. 开闭原则(Open Close Principle)。对扩张开发,对修改关闭。
  2. 里氏代换原则(Liskov Substitution Principle)。任何基类可以出现的地方,子类一定可以出现。
  3. 依赖倒转原则(Dependence Inversion Principle)。针对接口编程,依赖于抽象而不依赖于具体。
  4. 接口隔离原则(Interface Segregation Principle)。使用多个隔离的接口优于使用单个接口。降低类之间的耦合度。
  5. 迪米特法则(Demeter Principle),又称最少知道原则。一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
  6. 合成复用原则(Composite Reuse Principle)。尽量使用合成/聚合的方式,而不是使用继承。

具体设计模式

一 工厂模式

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

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

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

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

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

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

优点: 1. 一个调用者想创建一个对象,只要知道其名称即可。

2. 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可。

3. 屏蔽产品的具体实现,调用者只关心产品接口。

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

使用场景: 1. 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。

2. 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 

3. 设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。

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

实现:

步骤1: 创建一个接口 Shape.java


public interface Shape {
    void draw();
}

步骤2: 创建实现接口的实体类 Rectangle.java

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("inside rectangle :: draw() method.");
    }
}

Square.java

public class Square implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

Circle.java

public class Circle implements Shape {
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

步骤3: 创建一个工厂,生成基于给定信息的实体类的对象 ShapeFactory.java

public class ShapeFactory {   
   //使用 getShape 方法获取形状类型的对象
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
}

步骤4: 使用该工厂,通过传递类型信息来获取实体类的对象 FactoryPatternDemo.java

public class FactoryPatternDemo {
 
   public static void main(String[] args) {
      ShapeFactory shapeFactory = new ShapeFactory();
 
      //获取 Circle 的对象,并调用它的 draw 方法
      Shape shape1 = shapeFactory.getShape("CIRCLE");
 
      //调用 Circle 的 draw 方法
      shape1.draw();
 
      //获取 Rectangle 的对象,并调用它的 draw 方法
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
 
      //调用 Rectangle 的 draw 方法
      shape2.draw();
 
      //获取 Square 的对象,并调用它的 draw 方法
      Shape shape3 = shapeFactory.getShape("SQUARE");
 
      //调用 Square 的 draw 方法
      shape3.draw();
   }
}

步骤5: 执行程序,输出结果:

Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.

二 抽象工厂模式

抽象工厂模式是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显示指定他们的类。每个生成的工厂都能按照工厂模式提供对象。

意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

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

何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。

如何解决:在一个产品族里面,定义多个产品。

关键代码:在一个工厂里聚合多个同类产品。

优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。

使用场景:

  1. QQ 换皮肤,一整套一起换。

  2. 生成不同操作系统的程序。

注意事项:产品族难扩展,产品等级易扩展。

实现:

步骤1: 为形状创建一个接口

Shape.java

public interface Shape {
   void draw();
}

步骤2: 创建实现接口的实体类。

Rectangle.java

public class Rectangle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

Square.java

public class Square implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

Circle.java

public class Circle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

步骤3: 为颜色创建一个接口。

Color.java

public interface Color {
   void fill();
}

步骤4: 创建实现接口的实体类。

Red.java

public class Red implements Color {
 
   @Override
   public void fill() {
      System.out.println("Inside Red::fill() method.");
   }
}

Green.java

public class Green implements Color {
 
   @Override
   public void fill() {
      System.out.println("Inside Green::fill() method.");
   }
}

Blue.java

public class Blue implements Color {
 
   @Override
   public void fill() {
      System.out.println("Inside Blue::fill() method.");
   }
}

步骤5: 为 Color 和 Shape 对象创建抽象类来获取工厂。

AbstractFactory.java

public abstract class AbstractFactory {
   public abstract Color getColor(String color);
   public abstract Shape getShape(String shape) ;
}

步骤6: 创建扩展了 AbstractFactory 的工厂类,基于给定的信息生成实体类的对象。

ShapeFactory.java

public class ShapeFactory extends AbstractFactory {
    
   @Override
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
   
   @Override
   public Color getColor(String color) {
      return null;
   }
}

ColorFactory.java

public class ColorFactory extends AbstractFactory {
    
   @Override
   public Shape getShape(String shapeType){
      return null;
   }
   
   @Override
   public Color getColor(String color) {
      if(color == null){
         return null;
      }        
      if(color.equalsIgnoreCase("RED")){
         return new Red();
      } else if(color.equalsIgnoreCase("GREEN")){
         return new Green();
      } else if(color.equalsIgnoreCase("BLUE")){
         return new Blue();
      }
      return null;
   }
}

步骤7: 创建一个工厂创造器/生成器类,通过传递形状或颜色信息来获取工厂。

FactoryProducer.java

public class FactoryProducer {
   public static AbstractFactory getFactory(String choice){
      if(choice.equalsIgnoreCase("SHAPE")){
         return new ShapeFactory();
      } else if(choice.equalsIgnoreCase("COLOR")){
         return new ColorFactory();
      }
      return null;
   }
}

步骤8:使用 FactoryProducer 来获取 AbstractFactory,通过传递类型信息来获取实体类的对象。

AbstractFactoryPatternDemo.java

public class AbstractFactoryPatternDemo {
   public static void main(String[] args) {
 
      //获取形状工厂
      AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
 
      //获取形状为 Circle 的对象
      Shape shape1 = shapeFactory.getShape("CIRCLE");
 
      //调用 Circle 的 draw 方法
      shape1.draw();
 
      //获取形状为 Rectangle 的对象
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
 
      //调用 Rectangle 的 draw 方法
      shape2.draw();
      
      //获取形状为 Square 的对象
      Shape shape3 = shapeFactory.getShape("SQUARE");
 
      //调用 Square 的 draw 方法
      shape3.draw();
 
      //获取颜色工厂
      AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
 
      //获取颜色为 Red 的对象
      Color color1 = colorFactory.getColor("RED");
 
      //调用 Red 的 fill 方法
      color1.fill();
 
      //获取颜色为 Green 的对象
      Color color2 = colorFactory.getColor("Green");
 
      //调用 Green 的 fill 方法
      color2.fill();
 
      //获取颜色为 Blue 的对象
      Color color3 = colorFactory.getColor("BLUE");
 
      //调用 Blue 的 fill 方法
      color3.fill();
   }
}

步骤9:

执行程序,输出结果:

Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.
Inside Red::fill() method.
Inside Green::fill() method.
Inside Blue::fill() method.

三 单例模式

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  1. 单例类只能有一个实例。

  2. 单例类必须自己创建自己的唯一实例。

  3. 单例类必须给其他所有对象提供这一实例。

意图: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决: 一个全局使用的类频繁地创建与销毁。

何时使用: 当你想控制实例数目,节省系统资源的时候。

如何解决: 判断系统是否已经有这个实例,如果有则返回,如果没有则创建。

关键代码: 构造函数是私有的。

优点:

  1. 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。

  2. 避免对资源的多重占用。(比如写文件操作)

缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景:

  1. 要求生产唯一序列号。

  2. WEB中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。

  3. 创建的一个对象需要消耗的资源过多,比如I/O与数据库连接等。

实现:

步骤1: 创建一个 Singleton 类

SingleObject.java

public class SingleObject {
 
   //创建 SingleObject 的一个对象
   private static SingleObject instance = new SingleObject();
 
   //让构造函数为 private,这样该类就不会被实例化
   private SingleObject(){}
 
   //获取唯一可用的对象
   public static SingleObject getInstance(){
      return instance;
   }
 
   public void showMessage(){
      System.out.println("Hello World!");
   }
}

步骤2: 从 singleton 类获取唯一的对象

SingletonPatternDemo.java

public class SingletonPatternDemo {
   public static void main(String[] args) {
 
      //不合法的构造函数
      //编译时错误:构造函数 SingleObject() 是不可见的
      //SingleObject object = new SingleObject();
 
      //获取唯一可用的对象
      SingleObject object = SingleObject.getInstance();
 
      //显示消息
      object.showMessage();
   }
}

步骤3: 执行程序,输出结果:

Hello World!

单例模式的几种实现方式

  1. 懒汉模式,线程不安全。
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}
  1. 懒汉模式,线程安全。影响效率。
public class Singleton {
   private static Singleton instance;
   private Singleton (){

   }
   public static synchronized Singleton getInstance() {
      if (instance == null) {
         instance = new Singleton();
      }
      return instance;
   }

}
  1. 饿汉模式,线程不安全。
public class Singleton {
   private static Singleton instance = new Singleton();
   private Singleton () {}
   public static Singleton getInstance() {
      return instance;
   }
}
  1. 双检锁/双重校验锁,线程安全,较高性能。
public class Singleton {
   private volatile static Singleton singleton;
   private Singleton() {}
   public static Singleton getSingleton() {
      if (singleton == null) {
         synchronized (Singleton.class){
            if (singleton == null){
               singleton = new Singleton();
            }
         }
      }
      return singleton;
   }
}
  1. 登记式/静态内部类。线程安全。
public class Singleton {
   private static class SingletonHolder {
      private static final Singleton INSTANCE = new Singleton();
   }
   private Singleton () {}
   public static final Singletonn getInstance() {
      return SingletonHolder.INSTANCE;
   }
}
  1. 枚举,线程安全

是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。 不能通过 reflection attack 来调用私有构造方法。

public enum Singleton {
   INSTANCE;
   public void whateverMethod() {

   }
}

经验之谈:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

四 策略模式

在策略模式中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为模式。在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的context对象。策略对象改变context对象的执行算法。

意图: 定义一系列的算法,把他们一个个封装起来,并使他们可以相互替换。

主要解决: 在有多种算法相识的情况下,使用 if else 所带来的复杂和难以维护。

何时使用: 一个系统有许多类,而区分他们的只是他们直接的行为。

如何解决: 将这些算法封装成一个一个的类,任意地替换。

关键代码: 实现同一个接口。

优点:

  1. 算法可以自由切换。

  2. 避免使用多重条件判断。

  3. 扩展性良好。

缺点:

  1. 策略类会增多。

  2. 所有策略类都需要对外暴露。

使用场景:

  1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

  2. 一个系统需要动态地在几种算法中选择一种。

  3. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

实现:

步骤 1: 创建一个接口。

Strategy.java

public interface Strategy {
   public int doOperation(int num1, int num2);
}

步骤 2: 创建实现接口的实体类。

OperationAdd.java

public class OperationAdd implements Strategy {
   @Override
   public int doOperation(int num1, int num2){
      return num1 + num2;
   }
}

OperationSubstract.java

public class OperationSubstract implements Strategy {
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}

OperationMultiply.java

public class OperationMultiply implements Strategy {
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2 ;
   }
}

步骤 3: 创建 Context 类。

Context.java

public classs Context {
   private Strategy strategy;

   public Context(Strategy strategy) {
      this.strategy = strategy;
   }
   public int executeStrategy(int num1, int num2) {
      return strategy.doOperation(num1,num2);
   }
}

步骤 4: 使用 Context 来查看当它改变策略 Strategy 时的行为变化。

StrategyPatternDemo.java

public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationSubstract());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationMultiply());    
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

步骤 5: 执行程序,输出结果:

10 + 5 = 15
10 - 5 = 5
10 * 5 = 50

五 命令模式

是一种数据驱动的设计模式,属于行为模型。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适对象,并把命令传给相应的对象,该对象执行命令。

意图: 将一个请求封装成一个对象,从而可以用不同的请求对客户进行参数化。

主要解决: 在软件系统中,行为请求者与行为实现者通常是一种紧耦合关系,但某些场合比如需要对行为进行记录,撤销或者重做,事务处理时,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将行为请求者与行为实现者解耦,将一组行为抽象为对象,可以实现二者之间的松耦合。

如何解决: 通过调用者调用接受者执行命令,顺序:调用者->接受者->命令。

关键代码: 定义三个角:1.received真正的命令执行对象 2.command 3.invoker使用命令对象的入口

优点: 1. 降低了系统耦合度。 2. 新的命令可以很容易地添加到系统中去。

缺点: 使用命令模式可能会导致某些系统有过多的具体命令类。

使用场景: 认为是命令的地方都可以使用命令模式。

注意事项: 系统需要支持命令的撤销和恢复操作。

实现:

步骤1: 创建一个命令接口。

Order.java

public interface Order {
   void execute();
}

步骤2: 创建一个请求类。

Stock.java

public class Stock {
   private String name = "ABC";
   private int quantity = 10;

   public void buy() {
      System.out.println("Stock [ name: " + name + ",quantity: " + quantity + "] bought");
   }

   public void sell() {
      System.out.println("Stock [ name: " + name + ",quantity: " + quantity + "] sold");
   }
}

步骤3: 创建实习那Order接口的实体类。

BuyStock.java

public class BuyStock implements Order {
   private Stock abcStock;

   public BuyStock(Stock abcStock){
      this.abcStock = abcStock;
   }

   public void execute() {
      abcStock.buy();
   }
}

SellStock.java

public class SellStock implements Order {
   private Stock abcStock;

   public SellStock(Stock abcStock){
      this.abcStock = abcStock;
   }
 
   public void execute() {
      abcStock.sell();
}

步骤4: 创建命令调用类。

Broker.java

public calss Broker {
   private List<Order> orderList = new ArrayList<Order>(); 
 
   public void takeOrder(Order order){
      orderList.add(order);      
   }
 
   public void placeOrders(){
      for (Order order : orderList) {
         order.execute();
      }
      orderList.clear();
   }
}

步骤 5: 使用 Broker 类来接受并执行命令。

CommandPatternDemo.java

public class CommandPatternDemo {
   public static void main(String[] args) {
      Stock abcStock = new Stock();
 
      BuyStock buyStockOrder = new BuyStock(abcStock);
      SellStock sellStockOrder = new SellStock(abcStock);
 
      Broker broker = new Broker();
      broker.takeOrder(buyStockOrder);
      broker.takeOrder(sellStockOrder);
 
      broker.placeOrders();
   }
}

步骤 6: 执行程序,输出结果:

Stock [ name: ABC, quantity: 10 ] bought
Stock [ name: ABC, quantity: 10 ] sold

六 迭代器模式

七 装饰模式

八 观察者模式

九 组合模式

十 适配器模式

十一 桥接模式

十二 责任链模式