详解设计模式(一)创建型

前言: 模式起源于建筑业,最早由美国的Alexander博士提出。Alexander给出了模式的经典定义:每个模式都描述了一个在我们的环境中不断出现的问题,然

前言:

模式起源于建筑业,最早由美国的Alexander博士提出。Alexander给出了模式的经典定义:每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心,通过这种方式,我们可以无数次地重用那些已有的解决方案,无须再重复相同的工作。

这个定义也可以简单地用一句话表示:A pattern is a solution to a problem in a context.

GoF将模式的概念引入软件工程领域,这标志着软件模式的诞生。Surely,软件模式并非仅限于设计模式,还包括架构模式、分析模式和过程模式等,而研究最深入的是设计模式。

1995年,GoF出版了神作 Design Patterns : Elements of Reusable Objected-Oriented SoftWare 

这本书的出版意味着设计模式正式成为软件工程领域一个重要的研究分支。书中包含的23种经典设计模式,至今仍然是广大软件工程师学习的典范。

 

 

GoF设计模式根据其目的可分为创建型、结构型和行为型三类。

今天我们讲解的主题是三大类中最容易理解的创建型(Creational)模式,下面将对6种创建型模式进行详细介绍(其中简单工厂模式不属于GoF,但是考虑其应用较为频繁,且是学习其它创建型模式的基础,这里将一并介绍)

一、简单工厂模式   Simple Factory Pattern

1. 定义

简单工厂模式,又称为静态工厂方法(Static Factory Method)模式,属于类创建型模式。在简单工厂模式中,可以根据参数不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

2. 模式结构

(1)角色

Factory、Product、ConcreteProduct

(2)类图

 

 

              注:虚线加空心菱形表示接口实现,虚线加箭头则表示依赖关系。下面两个是依赖,别看错。

 

3. 模式分析

工厂类中的工厂方法,一般都是static修饰的,所以叫静态工厂方法模式。也正因为此,工厂角色无法形成基于继承的体系结构。

4. 模式应用

(1) java.text.DateFormat

public final static DateFormat getDateInstance();

public final static DateFormat getDateInstance(int style);

public final static DateFormat getDateInstance(int style, Localelocale);

(2) javax.crypto.KeyGenerator和java.security.KeyPairGenerator

//获取不同加密算法的密钥生成器
KeyGenerator keyGen  = KeyGenerator.getInstance("DESede");
//创建密码器
Cipher cp = Cipher.getInstance("DESede");

(3) LoggerFactory

 public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
        return logger;
 }

 

5. 扩展

有时候抽象产品类同时也是其子类的工厂;

有时候工厂类、抽象产品类、具体产品类三个角色可以合并,比如jdk中的KeyGenerator和Cipher;

6. 优缺点

优点:实现对象的创建和对象的使用分离,将对象的创建完全交给专门的工厂类负责;

缺点:工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,而且产品较多时,工厂方法代码将会非常复杂。

 

二、工厂方法模式   Factory Method Pattern

1. 定义

工厂方法模式又称为工厂模式,也叫虚拟构造器模式或者多态工厂模式,它属于类创建型模式。

2. 模式结构

工厂模式解决了简单工厂模式不够灵活、不符合开闭原则的缺点,下面是工厂模式的类图:

 工厂模式包含如下角色:Product、ConcreteProduct、Factory、ConcreteFactory

从角色我们可以明显看出工厂方法模式和简单工厂模式的区别,没错,工厂方法模式多了一个抽象工厂类。简单工厂模式的核心在一个具体工厂类里,而工厂方法模式的核心是一个抽象工厂类,这样就可以把具体创建工作交给子类去做,从而可以轻松添加新的产品工厂。

3. 模式分析

工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。在工厂方法模式中,工厂类与产品类往往具有平行的等级结构,它们之间一一对应。

4. 模式应用

(1)java.util.Collection接口中为所有的java集合类定义了一个iterator()方法,该方法用来返回可以遍历集合的迭代器对象。而具体的Java集合类可以通过实现该iterator方法返回具体的迭代器对象。这里的iterator()方法是一个典型的工厂方法。

抽象产品类:Iterator

具体产品类:Itr

抽象工厂类:Collection

具体工厂类:ArrayList

Iterator<E> iterator();//Collection接口中iterator()方法
/**
具体产品类,是ArrayList的内部类
**/
private class Itr implements Iterator<E> {...};

/**
ArrayList中的具体工厂方法
**/
public Iterator<E> iterator() {
    return new Itr();
}

5. 扩展

(1)使用多个工厂方法

在抽象工厂角色中可以定义多个工厂方法,让具体工厂角色实现这些不同的工厂方法,这些工厂方法可以包含不同的业务逻辑,以满足对不同的产品对象的需求。

(2)产品对象的重复使用

工厂方法总是调用产品类的构造函数以创建一个新的产品实例,然后将这个实例提供给客户端。而在实际情形中,工厂方法所作的事情可以相当复杂,一个常见的复杂逻辑就是重复使用产品对象。工厂对象将已经创建过的产品保存到一个集合(如数组、List等)中,然后根据客户对产品的请求,对集合进行查询。如果没有满足要求的产品对象,就直接将该产品返回客户端;如果集合中没有这样的产品对象,那么就创建一个新的满足要求的产品对象,然后将这个对象增加到集合中,再返回给客户端。这就是后面将要学习的享元模式的设计思想。

6. 优缺点

优点:增加新的产品类时无须修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性;

缺点:增加新产品的同时需要增加新的工厂,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性。

三、抽象工厂 Abstract Factory Pattern

1. 定义

提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。

2. 模式结构

抽象工厂模式结构较为复杂,下面给出类图:

 抽象工厂模式角色:AbstractFactory、ConcreteFactory、AbstractProduct、ConcreteProduct

从角色组成看和工厂方法模式有些相似,但是仔细观察类图,还是能看出很多不同。首先,工厂方法模式中的工厂类只有一个工厂方法,但是抽象工厂模式中的工厂则不止一个工厂方法。如果仅仅如此,那么可能只是我们上面所讲的工厂方法模式的普通扩展。但是这里每一个具体工厂所生产的产品A、B、C、D...构成了一个产品族,且工厂1生产出的产品A1和工厂2生产出的产品A2属于同一种产品(实现同一个抽象产品接口)。

简单点说,就是这里有一个”种族“的概念,一个产品有两个维度,分别是”族“和”种“。不同的Factory生产不同的产品族,但这些产品又同时有属于自己”种“。一个抽象产品类和实现它的具体产品类,也被称作一个产品等级结构。

ps:抽象工厂模式是工厂模式的泛化版,工厂模式是一种特殊的抽象工厂模式。

3. 模式分析

(1)抽象工厂类的典型代码

public abstract class AbstractFactory

{

    public abstract AbstractProductA createProductA();//比如A产品是手机

    public abstract AbstractProductB createProductB();//比如B产品是笔记本电脑

}

(2)每一个具体工厂类的典型代码

public class ConcreteFactory1 extends AbstractFactory //工厂1可以是小米品牌工厂
{
    public AbstractProductA createProductA()
    {
        return new ConcreteProductA1();//生产小米手机
    }
     
     public AbstractProductB createProductB()
     {
        return new ConcreteProductB1();//生产小米笔记本电脑
     }
}

4. 模式应用

(1)JDBC的Connection是一个典型的抽象工厂

抽象工厂类:Connection

具体工厂类:com.mysql.cj.jdbc.JdbcConnection,oracle.jdbc.OracleConnection

抽象产品类:  Statement、PreparedStatement、CallableStatement(这三者其实也是依次实现的关系,我们忽略这个)

具体产品类:  JdbcStatement、JdbcPreparedStatement、JdbcCallableStatement、OracleStatement、OraclePreparedStatement、OracleCallableStatement

5. 扩展

(1)当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;

(2)当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化为简单工厂模式。

6. 优缺点

新增产品族很容易,只需要新增具体工厂即可。但是新增一个产品等级结构却比较麻烦,需要去改动每一个工厂类的代码。在这里我们可以清楚地看到,开闭原则的支持发生了倾斜。

四、建造者模式 Builder Pattern

1. 定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式属于对象创建模式。

3. 模式结构

(1)角色

Builder、ConcreteBuilder、Product、Direct

(2)类图

3. 模式分析

指挥者类的作用(1)隔离了客户与生产过程(2)负责控制产品的生成过程

4. 模式应用

 JavaMail中的Message和MimeMessage等类均可看成退化的建造者模式的应用。

5. 扩展

(1)建造者模式的简化:如果只有一个具体建造者,可以省略抽象建造者角色;如果抽象建造者已经被省略掉,那么还可以省略指挥者角色。

(2)建造者模式和抽象工厂模式的比较

与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关产品,这些产品分别位于不同的产品等级结构,不同等级结构的产品在同一个工厂中构成一个产品族。在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象。

6. 优缺点

主要优点在于客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象,每一个具体建造者相对独立,因此可以方便地替换具体建造者或者增加新的具体建造者,符合开闭原则,还可以更加精准地控制产品地创建过程;其主要缺点是由于建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,因此其使用范围受到一定的限制,如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

五、原型 Prototype Pattern

1. 定义

原型模式是一种对象创建型模式,用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。

2. 模式结构

(1)角色

Prototype、ConcretePrototype、Client

(2)类图

3. 模式分析

4. 模式应用

java中Object对象的clone方法,注意深克隆和浅克隆的区别。HashMap的clone默认是浅复制,即新的HashMap对象和旧的有相同的key、value,但是会和旧HashMap的值相互影响。

5. 扩展

6. 优缺点

六、单例 Singleton Pattern

1. 定义

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。又名单件模式或单态模式。

2. 模式结构

 

3. 模式应用

Runtime、ServletContext、ServletContextConfig、ApplicationContext(饿汉式)

4. 扩展

(1)饿汉式

public class EagerSingleton
{
    private static final EagerSingleton instance = new EagerSingleton();
        
    private EagerSingleton(){}
          
public static EagerSingleton get Instance() { return instance; } }

(2)懒汉式

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

(3)双重检查锁

懒汉式可能会出现线程安全问题,我们可以使用双重检查锁来解决:

public class LazyDoubleCheckSingleton {
      private  volatile static LazyDoubleCheckSingleton lazy = null;

      private LazyDoubleCheckSingleton(){}
    
      public static LazyDoubleCheckSingleton getInstance(){
  
             if(lazy==null){
     
                   synchronized (LazyDoubleCheckSingleton.class){

                           if(lazy==null){

                                    lazy = new LazyDoubleCheckSingleton();
                            
                           }
                   }
             }
             return lazy;
       }
}

(4)内部类(防反射破坏版)

public class LazyInnerClassSingleton {
    private LazyInnerClassSingleton(){
        if(LazyHolder.LAZY != null){
            throw new RuntimeException("不允许创建多个实例");
        }
    }
    
    public static final LazyInnerClassSingleton getInstance(){
        return LazyHolder.LAZY;
    }
    
    private static class LazyHolder{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

(5)注册式单例模式

枚举式(防反射、序列化破坏)

Effective java 推荐使用

public enum EnumSingleton {
    INSTANCE;
    private Object data;
    public Object getData() {
        return data;
    }
    public void setData(Object data){
        this.data = data;
    }
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

容器式(适用于实例非常多的情况,非线程安全)

public class ContainerSingleton {
    private ContainerSingleton(){}
    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
    public static Object getBean(String className){
        synchronized (ioc){
            if(!ioc.containsKey(className)){
                Object obj = null;
                try{
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            } else {
                return ioc.get(className);
            }
        }
    }
}

(6)ThreadLocal

public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };
    
    private ThreadLocalSingleton(){};
    
    public static ThreadLocalSingleton getInstance(){
        return threadLocalInstance.get();
    }
}

5. 优缺点

单例模式的主要优点在于提供了对唯一实例的受控访问并可以节约系统资源;其主要缺点在于因为缺少抽象层而难以扩展,且单例类职责过重。