当先锋百科网

首页 1 2 3 4 5 6 7

从用途来说,常用设计模式分类:

  1. 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  2. 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  3. 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

这里我们用图来整体描述一下
这里写图片描述

其实还有J2EE 模式等其他模式,这里我们暂时就不细说了。今天我们就来说一下创建型模式。

一、工厂方法模式(Factory Method)

工厂方法模式又叫虚拟构造(Virtual Constructor)或者多态工厂(Polymorphic Factory)模式。工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法把实例化推迟给子类。

工厂方法分为三种:

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

1.2 工厂方法模式

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

public interface ISendFactory {
    public Sender produce(); 
}
public class MailFactory implements ISendFactory {  

    public Sender produce() {  
        return new MailSender();  
    }  
} 
public class SmsFactory implements ISendFactory {  

    public Sender produce() {  
        return new SmsSender();  
    }  
}   

1.3 静态工厂模式

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

public class SendFactory {  

    public static Sender produceMail(){  
        return new MailSender();  
    }  

    public static Sender produceSms(){  
        return new SmsSender();  
    }  
}  

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

二、抽象工厂模式(Abstract Factory)

使用工厂方法模式足以应付我们可能遇到的大部分业务需求。但是当产品种类非常多时,就会出现大量的与之对应的工厂类,这不是我们所希望的,所以在这种情况下使用简单工厂模式与工厂方法模式相结合的方式来减少工厂类:即对于产品树上类似的种类(一般是树的叶子中互为兄弟的)使用简单工厂模式来实现。
当然特殊的情况,就要特殊对待了:对于系统中存在不同的产品树,而且产品树上存在产品族(位于不同产品等级结构中功能相关联的产品组成的家族)。那么这种情况下就可能可以使用抽象工厂模式了。

抽象工厂模式就是提供一个接口,用来创建相关或者依赖对象的家族,而不需要指定确定的类。
这里写图片描述

小结:

简单工厂 : 用来生产同一等级结构中的任意产品。(对于增加新的产品,无能为力)
工厂方法 :用来生产同一等级结构中的固定产品。(支持增加任意产品)
抽象工厂 :用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)

三、单例模式(Singleton)

单例模式就是确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例模式又分为饿汉式和懒汉式。

懒汉式单例模式

public class Singleton {  

    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */  
    private static Singleton instance = null;  

    /* 私有构造方法,防止被实例化 */  
    private Singleton() {  
    }  

    /* 静态工程方法,创建实例 */  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    public Object readResolve() {  
        return instance;  
    }  
}  

这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对getInstance方法加synchronized关键字,如下:

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

但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:

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

似乎解决了之前提到的问题,将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的,看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:

  1. A、B线程同时进入了第一个if判断
  2. A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
  3. 由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
  4. B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
  5. 此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

饿汉式单例模式

private static class SingletonFactory{           
    private static Singleton instance = new Singleton();      

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

实际情况是,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式:

public class Singleton {  

    /* 私有构造方法,防止被实例化 */  
    private Singleton() {  
    }  

    /* 此处使用一个内部类来维护单例 */  
    private static class SingletonFactory {  
        private static Singleton instance = new Singleton();  
    }  

    /* 获取实例 */  
    public static Singleton getInstance() {  
        return SingletonFactory.instance;  
    }  

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
    public Object readResolve() {  
        return getInstance();  
    }  
}  

其实说它完美,也不一定,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实现方法。也有人这样实现:因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized关键字,也是可以的:

public class SingletonTest {  

    private static SingletonTest instance = null;  

    private SingletonTest() {  
    }  

    private static synchronized void syncInit() {  
        if (instance == null) {  
            instance = new SingletonTest();  
        }  
    }  

    public static SingletonTest getInstance() {  
        if (instance == null) {  
            syncInit();  
        }  
        return instance;  
    }  
}  

考虑性能的话,整个程序只需创建一次实例,所以性能也不会有什么影响。

补充:采用”影子实例”的办法为单例对象的属性同步更新

public class SingletonTest {  

    private static SingletonTest instance = null;  
    private Vector properties = null;  

    public Vector getProperties() {  
        return properties;  
    }  

    private SingletonTest() {  
    }  

    private static synchronized void syncInit() {  
        if (instance == null) {  
            instance = new SingletonTest();  
        }  
    }  

    public static SingletonTest getInstance() {  
        if (instance == null) {  
            syncInit();  
        }  
        return instance;  
    }  

    public void updateProperties() {  
        SingletonTest shadow = new SingletonTest();  
        properties = shadow.getProperties();  
    }  
} 

通过单例模式的学习告诉我们:

  1. 单例模式理解起来简单,但是具体实现起来还是有一定的难度。
  2. synchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)。

采用类的静态方法,实现单例模式的效果,也是可行的,此处二者有什么不同?
首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)
其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。
再次,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。
最后一点,单例类比较灵活,毕竟从实现上只是一个普通的Java类,只要满足单例的基本需求,你可以在里面随心所欲的实现一些其它功能,但是静态类不行。从上面这些概括中,基本可以看出二者的区别,但是,从另一方面讲,我们上面最后实现的那个单例模式,内部就是用一个静态类来实现的,所以,二者有很大的关联,只是我们考虑问题的层面不同罢了。两种思想的结合,才能造就出完美的解决方案,就像HashMap采用数组+链表来实现一样,其实生活中很多事情都是这样,单用不同的方法来处理问题,总是有优点也有缺点,最完美的方法是,结合各个方法的优点,才能最好的解决问题!

四、建造者模式(Builder)

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

建造者模式通常包括下面几个角色:

  1. Builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建。
  2. ConcreteBuilder:实现Builder接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例。
  3. Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

这里我们用创建西门子冰箱为例
这里写图片描述
西门子导演者

/**
 * 西门子导演者
* @ClassName: SlementsDirectors
* @Description: 这里导演者指导建造者以一定的方式生产西门子冰箱
* @author CrystalCao
* @date 2017年8月21日 下午5:35:08
*
 */
public class SlementsDirectors {

    WasherBuilder washerBuilder;

    //为导演者配置一个建造者
    public SlementsDirectors(WasherBuilder washerBuilder) {
        this.washerBuilder = washerBuilder;
    }

    //按照一定的方式或者规则建造冰箱
    public void contruct() {
        washerBuilder.buildeWasherSkeleton();
        washerBuilder.buildeWasherEngine();
        washerBuilder.buildeWasherWheels();
        washerBuilder.buildeWasherBody();
    }
}

具体建造者

/**
 * 具体建造者
* @ClassName: SlementsWasherBuilder
* @Description: 只改变自己的内部实现(把西门子就压缩机换成新的压缩机)
* @author CrystalCao
* @date 2017年8月21日 下午5:45:52
*
 */
public class SlementsWasherBuilder implements WasherBuilder {

    SlementsWasher slementsWasher = new SlementsWasher();

    @Override
    public void buildeWasherSkeleton() {
        slementsWasher.setWasherSkeleton("冰箱框架");
    }

    @Override
    public void buildeWasherEngine() {
        Compressor washerEngine = new Compressor("新型压缩机");
        slementsWasher.setWasherEngine(washerEngine);
    }

    @Override
    public void buildeWasherWheels() {
        slementsWasher.setWasherWheels("冰箱轮子");
    }

    @Override
    public void buildeWasherBody() {
        slementsWasher.setWasherDoor("冰箱门");
        slementsWasher.setWasherColor("颜色");
    }

    public SlementsWasher retrieveWasher() {
        return slementsWasher;
    }
}

产品

package com.crystal;

//产品:西门子冰箱
public class SlementsWasher {

    private String washerSkeleton;
    private Compressor washerEngine;
    private String washerWheels;
    private String washerDoor;
    private String washerColor;

    public SlementsWasher() {}

    public void setWasherSkeleton(String washerSkeleton) {
        this.washerSkeleton = washerSkeleton;
    }

    public void setWasherEngine(Compressor washerEngine) {
        this.washerEngine = washerEngine;
    }

    public void setWasherWheels(String washerWheels) {
        this.washerWheels = washerWheels;
    }

    public void setWasherDoor(String washerDoor) {
        this.washerDoor = washerDoor;
    }

    public void setWasherColor(String washerColor) {
        this.washerColor = washerColor;
    }

    @Override
    public String toString() {
        return "SlementsWasher [washerSkeleton=" + washerSkeleton
                + ", washerEngine=" + washerEngine + ", washerWheels="
                + washerWheels + ", washerDoor=" + washerDoor
                + ", washerColor=" + washerColor + "]";
    }
}

构件

/**
 * 
* @ClassName: Compressor
* @Description: 一个部件对象(压缩机)冰箱的核心
* @author CrystalCao
* @date 2017年8月21日 下午5:40:02
*
 */
public class Compressor {

    private String name;

    public Compressor(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Compressor [name=" + name + "]";
    }
}

客户端

public class Client {

    public static void main(String[] args) {
        SlementsWasherBuilder washerBuilder = new SlementsWasherBuilder();
        SlementsDirectors director = new SlementsDirectors(washerBuilder);
        director.contruct();
        SlementsWasher washer = washerBuilder.retrieveWasher();
        System.out.println(washer);
    }
}

建造者模式将很多功能集成到一个类里,这个类可以创造出比较复杂的东西。所以与工程模式的区别就是:工厂模式关注的是创建单个产品,而建造者模式则关注创建符合对象,多个部分。因此,是选择工厂模式还是建造者模式,依实际情况而定。

五、原型模式(prototype)

原型模式是指“用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。”其思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。

public class Prototype implements Cloneable {  

    public Object clone() throws CloneNotSupportedException {  
        Prototype proto = (Prototype) super.clone();  
        return proto;  
    }  
}  

很简单,一个原型类,只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在Object类中,clone()是native的,具体怎么实现,我会在另一篇文章中,关于解读Java中本地方法的调用,此处不再深究。在这儿,我将结合对象的浅复制和深复制来说一下,首先需要了解对象深、浅复制的概念:

浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。

此处,写一个深浅复制的例子:

public class Prototype implements Cloneable, Serializable {  

    private static final long serialVersionUID = L;  
    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 = L;  
}  

要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象。