漫谈设计模式之创建型模式

Christopher Alexander说过:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样你就能一次又一次地使用该方案而不必做重复劳动。”

一般而言,一个模式有四个基本要素:模式名称、问题、解决方案、效果。

总体来说,设计模式分为三大类:创建型模式、结构型模式、行为型模式。其中创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建、组合和表示它的那些对象。一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象。创建型模式共五种:抽象工厂模式、建造者模式、工厂方法模式、原型模式、单例模式。

一、 抽象工厂模式

1.1 目的
抽象工厂模式是提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

1.2 适用性
(1) 一个系统要独立于它的产品的创建、组合和表示时;
(2) 一个系统要由多个产品系列中的一个来配置时;
(3) 当你强调一系列相关的产品对象的设计以便进行联合使用时;
(4) 当你提供一个产品类库,而只想显示它们的接口而不是实现时。

1.3 结构和实现


漫谈设计模式之创建型模式_第1张图片
抽象工厂模式
public interface Sender {  
    public void Send();  
}  

//两个实现类:
public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}  

public class SmsSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
}  

//两个工厂类:
public class SendMailFactory implements Provider {  
    @Override  
    public Sender produce(){  
        return new MailSender();  
    }  
}  

public class SendSmsFactory implements Provider{    
    @Override  
    public Sender produce() {  
        return new SmsSender();  
    }  
}  

//接口:
public interface Provider {  
    public Sender produce();  
}  

//测试类
public class Test {  
    public static void main(String[] args) {  
        Provider provider = new SendMailFactory();  
        Sender sender = provider.produce();  
        sender.Send();  
    }  
}  

1.4 优缺点
(1) 分离了具体的类。如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好。
(2) 使得易于交换产品系列。一个具体工厂类在一个应用中仅出现一次。
(3) 有利于产品的一致性。当一个系列中的产品对象被设计成一起工作时,一个应用只能使用同一个系列中的对象。
(4) 难以支持新种类的产品。难以扩展抽象工厂以生产新种类的产品。因为抽象工厂方法确定了可以被创建的产品集合,但是支持新种类的产品需要扩展该工厂接口,这涉及抽象工厂类及其所有子类的改变。

二、 建造者模式

2.1 目的
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后结合起来得到的。将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

2.2 适用性
(1) 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时;
(2) 当构造过程必须允许被构造的对象有不同的表示时。

2.3 代码实现

public class Builder {  
    private List list = new ArrayList();  
    public void produceMailSender(int count){  
        for(int i=0; i

2.4 优点
(1) 它使你可以改变一个产品的内部表示。Builder对象提供给导向器一个构造产品的抽象接口。该接口使得生成器可以隐藏这个产品的表示和内部结构,同时也隐藏了该产品是如何装配的。
(2) 它将构造代码和表示代码分开。Builder模式通过封装一个复杂对象的创建和表示方式提高了对象的模块性。客户不需要知道定义产品内部结构的类的所有信息。
(3) 它使你可对构造过程进行更精细的控制。

三、 工厂方法模式

3.1 目的
定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。

3.2 适用性
(1) 当一个类不知道它所必须创建的对象的类的时候;
(2) 当一个类希望由它的子类来指定它所创建的对象的时候。

3.3 结构与实现
(1) 普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。首先看下关系图:


漫谈设计模式之创建型模式_第2张图片
普通工厂模式
//共同接口:
public interface Sender {  
    public void Send();  
}  
//创建实现类:
public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}  

public class SmsSender implements Sender {  
  
    @Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
}  
//建工厂类:
public class SendFactory {  
    public Sender produce(String type) {  
        if ("mail".equals(type)) {  
            return new MailSender();  
        } else if ("sms".equals(type)) {  
            return new SmsSender();  
        } else {  
            System.out.println("请输入正确的类型!");  
            return null;  
        }  
    }  
}  
//测试:
public class FactoryTest {  
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produce("sms");  
        sender.Send();  
    }  
}  

(2)多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。关系图:


漫谈设计模式之创建型模式_第3张图片
多个工厂模式

将上面的代码做下修改,改动下SendFactory类就行,如下:

public class SendFactory {  
   public Sender produceMail(){  
        return new MailSender();  
    }  
      
    public Sender produceSms(){  
        return new SmsSender();  
    }  
}  
//测试类如下:
public class FactoryTest {  
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produceMail();  
        sender.Send();  
    }  
}  

(3)静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

public class SendFactory {      
    public static Sender produceMail(){  
        return new MailSender();  
    }  
      
    public static Sender produceSms(){  
        return new SmsSender();  
    }  
}  
public class FactoryTest {  
    public static void main(String[] args) {      
        Sender sender = SendFactory.produceMail();  
        sender.Send();  
    }  
}  

大多数情况下,我们会选用第三种——静态工厂方法模式。

四、 原型模式

4.1 目的
原始模型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。

4.2 适用性
(1) 当要实例化的类是运行时刻指定时,例如,通过动态装载;或者
(2) 为了避免创建一个与产品类层次平行的工厂类层次时;或者
(3) 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

4.3 代码实现

//创建一个原型类:
public class Prototype implements Cloneable {  
    public Object clone() throws CloneNotSupportedException {  
        Prototype proto = (Prototype) super.clone();  
        return proto;  
    }  
}  

一个原型类只需要实现Cloneable接口,覆写clone方法,重点是调用super.clone(),进而调用Object的clone()方法。结合对象的浅复制和深复制详细介绍一下:

  • 浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
  • 深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象。
//深浅复制的例子:
public class Prototype implements Cloneable, Serializable {  
    private static final long serialVersionUID = 1L;  
    private String string;  
    private SerializableObject obj;  
  
    /* 浅复制 */  
    public Object clone() throws CloneNotSupportedException {  
        Prototype proto = (Prototype) super.clone();  
        return proto;  
    }  
  
    /* 深复制 */  
    public Object deepClone() throws IOException, ClassNotFoundException {  
        /* 写入当前对象的二进制流 */  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(bos);  
        oos.writeObject(this);  
  
        /* 读出二进制流产生的新对象 */  
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  
        ObjectInputStream ois = new ObjectInputStream(bis);  
        return ois.readObject();  
    }  
  
    public String getString() {  
        return string;  
    }  
  
    public void setString(String string) {  
        this.string = string;  
    }  
  
    public SerializableObject getObj() {  
        return obj;  
    }  
  
    public void setObj(SerializableObject obj) {  
        this.obj = obj;  
    }  
  
}  
  
class SerializableObject implements Serializable {  
    private static final long serialVersionUID = 1L;  
}  

4.4 优点
(1) 运行时刻增加和删除产品。原型模式允许只通过客户注册原型实例就可以将一个新的具体产品类并入系统。
(2) 改变值以指定新对象。高度动态的系统允许你通过对象复合定义新的行为。
(3) 改变构造以指定新对象。许多应用由部件和子部件来创建对象。
(4) 减少子类的构造。
(5) 用类动态配置应用。

五、 单例模式

5.1 目的
保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例对象能保证在一个JVM中,该对象只有一个实例存在。单例模式具有以下几个特点:

1、单例类有且仅有一个实例。  
2、单例类必须自己创建自己的唯一实例。  
3、单例类必须给所有其他对象提供单一实例。

5.2 适用性
(1) 对唯一实例的受控访问。单例类封装它的唯一实例,所以它可以严格的控制客户怎么以及何时访问它。
(2) 缩小名空间。避免了那些存储唯一实例的全局变量污染名空间。
(3) 允许对操作和表示的精华。单例类可以有子类,而且用这个扩展类的实例来配置一个应用是很容易的。
(4) 允许可变数目的实例。
(5) 比类操作更灵活。

5.3 实现
单例分为两种:懒汉式单例、饿汉式单例。
5.3.1 懒汉单例
首先介绍下什么是延迟加载,延迟加载是程序真正需要使用的时才去创建实例,不用时不创建实例。依据延迟加载可以把懒汉单例分为以下几类。

(1) 同步延迟加载单例

public class Singleton {  
    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */  
    private static Singleton instance = null;  
  
    /* 私有构造方法,防止被实例化 */  
    private Singleton() {  
    }  
  
    /* 静态工程方法,创建实例 */  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

(2) 双重检测同步延迟加载单例

public class Singleton {  
  private volatile static Singleton instance = null;  
  private Singleton() {}  

  public static Singleton getInstance() {  
    if (instance == null) {  // 1
        synchronized (Singleton.class) {
         if (instance == null) {// 2
             instance = new Singleton();  // 3
          }  
       }  
    }  
    return instance;  
   }  
} 

双重检测同步延迟加载单例线程并非绝对安全;不能防止反序列化、反射产生新的实例。

线程不安全原因是Java的乱序执行、初始化对象需要时间。
对于语句3,JVM在执行时大致做了下述三件事:
a. 在内存中分配一块内存;
b. 调用构造方法;
c. 将内存的地址指向instance变量。(执行这一步后,instance != null)

如果按照abc的顺序执行也不会有什么问题。但由于Java乱序执行的机制,有可能在真实情况下执行顺序为acb。

假设t1、t2是两个线程。t1执行到1时,发现为null,于是执行到语句3,先执行a,再执行c,在还没有执行b时,时间片给了t2。这时,由于instance已经分配了地址空间,instance不为null了。所以t2在执行到语句1后直接return instance,获得了这个还没有被初始化的对象,然后在使用时就报错了。因为有可能得到了实例的正确引用,但却访问到其成员变量的不正确值。

(3) 内部类实现延迟加载单例

public class Singleton {  
   private Singleton() {}  
   public static class Holder {  
    // 这里的私有没有什么意义  
    /* private */static Singleton instance = new Singleton();  
   }  
   public static Singleton getInstance() {  
    // 外围类能直接访问内部类(不管是否是静态的)的私有变量  
    return Holder.instance;  
   }  
}

这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。

5.3.2 饿汉模式
(1) 非延迟加载单例,也称为饿汉模式

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

    public static Singleton getInstance() {  
         return instance;  
    }  
}

饿汉模式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。但是如果类创建时失败,则永远无法获得该类的实例化。

5.4 优点
(1)减少了类的频繁创建,特别是减少大型对象的创建,很大程度上节约了系统开销。
(2)减少使用new操作符,降低了系统内存的使用频率,减轻GC压力。
(3)保证系统核心服务独立控制整个系统操作流程,如果有多个实例的话,系统将出现混乱。如同一个军队出现了多个司令员同时指挥,肯定会乱成一团,所以系统的核心控制模块必须使用单例模式,才能保证系统正常运行。

你可能感兴趣的:(漫谈设计模式之创建型模式)