设计模式读书笔记

迷思 2018-04-16

这段时间学习了一下软件开发中的设计模式,这篇读书笔记就谈谈我对设计模式的理解。

设计模式不是一套api,而是一种可复用的、一般性的解决方式,类似于以前谈过的MSF,其官方定义如下:设计模式(design pattern)是软件开发人员在软件开发过程中面临的一般问题的解决方案。设计模式的提出本身是基于面向对象的语言的,没有了面向对象的继承与多态,所有设计模式都玩不转了。设计模式分为三大类,创建型、结构型、行为型,这次就主要谈谈创建型设计模式。

首先是最常见的工厂模式,官方的说法是:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。这句话真的很难懂,所以不如举个例子,到汽车厂提车,无论宝马、奥迪还是奥拓只要提就行了,不必管具体的制造细节。这样的说法感觉清晰了些,可以看出工厂模式提供了一定程度上的封装,但具体它要实现怎样的目的呢?放一段Java源码:

public interface Shape {
   void draw();
}

public class Rectangle implements Shape {

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

public class Square implements Shape {

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

public class Circle implements Shape {

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

public class ShapeFactory {

   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;
   }
}

这样一切都清晰了,用我自己的话说,工厂模式实现的是对一类产品族的封装。其实大家多少都用过工厂模式,当初学习C++虚基类以及Java接口的时候,肯定尝试过通过一个抽象类实现各种相应的实体类,这个过程其实即使工厂模式的核心,即基于接口与实现技术,对于C++也可以说是虚基类与继承技术。要说工厂模式比我们原来写的东西多了什么,就是最后的ShapeFactory类以及其getShape方法,我相信当初大部分人都不会使用这种写法,但仔细一下这种方式实现了对上层非常干净的封装,对于用户来说再也不需要想着new一个什么样的对象,需要的只是产生一个ShapeFactory对象,然后从中getShape得到想要的对象,即只要走进汽车工厂就可以取到各种各样的车。

有句题外话,就是我在看工厂模式的过程中突然模糊了Java中虚基类与接口的区别,查了查才想起来虚基类的部分函数是已经实现了的,子类只需实现那些没有实现的类,而接口的函数全部都是虚函数,实现类必须实现其中全部的函数。

下面看看单例模式,这种模式要求该类自己负责创建自己,且需确保只有单个对象被创建。为了确保该类不会被实例化,有种骚操作就是把构造函数设为 private ,这样如果该类被 new 则编译器报错,这种操作真的是长见识。具体的单例模式代码还分为懒汉模式和饿汉模式。

懒汉模式:

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

其中 synchronized 关键字是为了保证线程安全,即多线程访问时给对象上锁。从下可以看到饿汉模式不存在这种问题。

饿汉模式:

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }

这两者的区别就在于,懒汉模式的初始化在被调用时进行,而饿汉模式的初始化在类加载时就进行了,这带来的结果就是懒汉模式更节省内存,但是其本身存在线程不安全的问题,必须使用 sychronized 关键字,所以性能会比较低,一般在单例用的次数少且消耗资源大时使用。

单例模式这样就讲清楚了,但是带来的新的问题是类到底何时被初始化?我查了相关资料发现,JVM并没有规定类何时被加载(加载到JVM方法区和堆区),但严格规定了何时被初始化,其中最常见的两种情况就是 new 一个类或者调用其静态方法(还包括读取、设置静态字段等情况),一旦出现上述情况类就必须被初始化,而饿汉模式中的static成员instance在类加载时被赋值,同时 new 了自身,所以该类在加载时就初始化,懒汉模式就可以等到被调用再初始化。

可见,这么短短两段代码中蕴藏着那么多知识,充分体现了第一次提出规范单例模式的程序员的智慧。

最后再谈谈原型模式。原型模式的目的是创建重复的对象,比如说,如果类的初始化需要很多资源,不如只保存一份相应的对象。从这个角度看,原型模式很像是单例模式的扩展,可以看成是一个产品族的单例集合。看看如下java代码:

public abstract class Shape implements Cloneable {
   
   private String id;
   protected String type;
   
   abstract void draw();
   
   public String getType(){
      return type;
   }
   
   public String getId() {
      return id;
   }
   
   public void setId(String id) {
      this.id = id;
   }
   
   public Object clone() {
      Object clone = null;
      try {
         clone = super.clone();
      } catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }
      return clone;
   }
}

public class ShapeCache {
    
   private static Hashtable<String, Shape> shapeMap 
      = new Hashtable<String, Shape>();

   public static Shape getShape(String shapeId) {
      Shape cachedShape = shapeMap.get(shapeId);
      return (Shape) cachedShape.clone();
   }

   public static void loadCache() {
      Circle circle = new Circle();
      circle.setId("1");
      shapeMap.put(circle.getId(),circle);

      Square square = new Square();
      square.setId("2");
      shapeMap.put(square.getId(),square);

      Rectangle rectangle = new Rectangle();
      rectangle.setId("3");
      shapeMap.put(rectangle.getId(),rectangle);
   }
}

java中自带的Cloneable类可以很好地实现原型模式的目的。虽然上述代码有些工厂模式的感觉,但其实原型模式的目的与工厂模式完全不同。但是,原型模式最大的用处之一就是和工厂模式结合,如果把上述代码中的形状改为工厂,具体的形状改为不同的工厂,那么最后的 Cache 提供的就是不同工厂的克隆,这很好地实现了工厂的单例性。

本文描述了工厂模式、单例模式、原型模式三种常见的创建型设计模式,下次再来说说结构型设计模式。