单例模式(Singleton Pattern)确保一个类只有一个实例,并且提供一个全局的访问。

单例模式随处可见,比如线程池缓存对话框日志对象等,这些时候如果制造出多个实例,程序运行就会出现预期之外的情况。
这里可能有疑问,我用全局静态变量也能做到一个类只有一个实例,为什么要引入这样一个设计模式呢?原因其实很简单,全局静态变量会造成资源浪费:假设这个类非常消耗资源,程序在运行过程中,不是每一次都用到这个类,那就是极大的浪费。

类图

类图不是目的,仅仅帮助理解

单例模式

单例模式的类图很简单,只有一个类,有一个代表自己实例的instance变量,还有一个提供全局访问的静态方法getInstance()

以下的代码和思路是针对Java语言

单例类型

单例模式分为懒汉式和饿汉式,区别在于实例化单例对象的时机。

饿汉式

在饿汉式单例模式实现中,不管单例是否用到,都会实例化一个单例对象。典型的写法如下:

/**
 * 单例
 * Created by Carlton on 2016/11/21.
 */
class Singleton private constructor()
{
    companion object
    {
        private val instance = Singleton()
        fun instance() = instance
    }
}

因为Kotlin和Java在静态语法上的不一致,后面的代码都用Java来实现方便理解

/**
 * 单例模式
 * Created by Carlton on 2016/11/21.
 */
public class Singleton
{
    private Singleton()
    {

    }

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

饿汉式非常简单,也不会出现资源占用之外的其他问题,就不多说。

饱汉式、常规方法

饱汉式也就是常规实现方式比较复杂,原因是我们用到类实例的时候才会去实例化,这中间会出现各种各样的情况。

介绍了两种实现方式,接下来我们实现一个常规的单例模式:

/**
 * 单例模式
 * Created by Carlton on 2016/11/21.
 */
public class Singleton
{
    private Singleton()
    {

    }

    private static Singleton instance = null;
    public static Singleton instance()
    {
        if(instance == null)
        {
            // 1
            instance = new Singleton();
        }
        return instance;
    }
}

在客户端获取单例的时候,检查对象是否是null如果是,则实例化一个,如果不是则直接返回已有的对象,如果在单线程的情况下,确实如此,现在如果,两个或者两个以上的线程就有问题了:
– 如果两个线程都到1这个位置
– 那么现在的情况就是if (instance == null)判断的时候两个线程都通过了
– 这个时候instance会实例化两次,这两个线程拿到的不是同一个实例

怎么解决这个问题呢?不慌解决,先看看一下双重验证和volatile

双重检查和volatile

如果加上线程锁,好像问题就解决了,先看看加线程锁怎么写:

/**
 * 单例模式
 * Created by Carlton on 2016/11/21.
 */
public class Singleton
{
    private Singleton()
    {

    }

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

现在看看多线程的情况程序会出现什么问题:
– 如果有两个线程都到了1
– 因为同步锁的原因,只有一个线程可以先进入到2
– 当第一个线程进入3实例化一个instance后,第二个线程进入判断的时候,就不会进入3
这就是双重验证,在C/C++中,这样做是没有问题的,但是:双重检查对Java语言编译器不成立!原因在于,Java编译器中,Singleton类的初始化与instance变量赋值的顺序不可预料,如果一个线程在没有同步化的条件下读取instance引用,并调用这个对象的方法的话,可能会发现对象的初始化过程还没有完成,从而造成崩溃。

可能有人会觉得volatile可以解决问题,修改变量申明:

private static volatile Singleton instance = null;

先看看volatile是什么?

volatile变量具有synchronized的可见性特性,但是不具备原子特性。这就是说线程能够自动发现volatile变量的最新值。volatile变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。

通过这个描述知道volatile是一个轻量级的线程同步,之前出现的问题在于线程没有同步化的条件下读取instance,现在加上volatile问题就解决了。但是:JDK1.5之前,这样使用双重检查还是有问题。

Java中如何正确的实现单例模式

说了这么多,如何才能正确实现单例模式呢?
* 使用饿汉式
* JDK1.5以后使用带volatile修饰的双重检查
* 同步锁加到方法上:

/**
 * 单例模式
 * Created by Carlton on 2016/11/21.
 */
public class Singleton
{
        private static volatile Singleton instance = null;
        private Singleton()
        {
        }
        public static synchronized Singleton instance()
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
        return instance;
        }
}

多说几句,如果把同步锁加到方法上面,代表这个方法同一时间只有一个线程能够进入方法,这个时候后面的线程进入就会正常的直接返回instance实例。

总结

单例模式在思路上是很简单的模式,也就不提供例子,单例模式还有很多单例模式的变种,但是核心没变:一个类只有一个实例;这个实例由自己来实例化;单例模式没有提供公共的构造函数,所以其他类不能对其实例化。需要注意的是,这个模式的复杂点在于实现方式,如何才能保证在各种情况下只有一个类实例才是关键点。

设计模式笔记本 【传送门】

不登高山,不知天之高也;不临深溪,不知地之厚也
感谢指点、交流、喜欢

建造者模式(Builder Pattern)简化了构建复杂对象的过程,除了简化复杂的对象的构建过程,最核心的思想是化整为零、按需分配。

先说如何简化构建过程,建造者模式就像是Google的模块化手机,通过各个零件的定制来完成一部手机,比如,我可以装两个摄像头,或者把添加多一块电池,只是换个模块就能完成。

化整为零、按需分配说的是如果要实现一个多变的对象,把这个多变对象的整体,分解成一小块一小块的,然后组装起来,还能按照需求决定哪些需要定制,怎么定制,比如,Android或者Ios中常用的DialogNotification等。

类图

类图不是目的,仅仅帮助理解

建造者模式

IBuilder是一个建造者接口,规范了建造的内容,可以有很多实现,比如,BuilderABuilderB,有一个重点就是化整为零的产品和Builder 之间是组合关系,有了建造者和产品我们就可以通过建造者来定制产品了,这时候Director的作用就是规定构造产品的顺序或者一些固定的其他默认属性,比如构建的时候依赖顺序必须先是A后是B,那么可以通过Director来控制,这里的Direcotr抽象工厂模式有点像。

聚合关系:对象和对象之间是整体部分的关系,部分的生命周期可以超越整体,这是弱关系。
组合关系:对象和对象之间是整体部分的关系,部分的生命周期不能超越整体,这是强关系。

实例

先看看实例的类图。

类图不是目的,仅仅帮助理解

实例类图
还是以手机为例,规定一个手机产品的创建流程,用于流水线生产,手机的生产需要生产:主板、CPU、内存、屏幕、外壳。现在要实现一部手机,可以这样做:

fun main(args: Array<String>)
{
    val nexus5 = Phone("Google")
    nexus5.cpu = "Google CPU"
    nexus5.ram = "Google RAM"
    nexus5.screen = "Google Screen"
    nexus5.motherboard = "Google Motherboard"
    nexus5.view = "Google Nexus5 View"
    println(nexus5)
}
// Phone
/**
 * 手机
 * Created by Carlton on 2016/11/15.
 */
class Phone(val name: String)
{
    /**
     * cpu
     */
    var cpu: String? = null
    /**
     * 内存
     */
    var ram: String? = null
    /**
     * 屏幕
     */
    var screen: String? = null
    /**
     * 主板
     */
    var motherboard: String? = null
    /**
     * 外观
     */
    var view: String? = null

    override fun toString(): String
    {
        return "Phone(name='$name', cpu=$cpu, ram=$ram, screen=$screen, motherboard=$motherboard, view=$view)"
    }
}

这样,创建了一只Nexus5的手机,如果现在需要一只Nexus6手机怎么做呢?重新创建一个Phone实例,然后给属性赋值。如果Nexus6手机的CPURAMNexus5一样,那赋值的代码就重复了,不方便重用了。如果还需要一个苹果6手机,又得重新去实例化Phone对象,如果再建造一个苹果7,CPU主板都一样,就会重复做很次这些操作,关键问题还不在这里,关键问题是暴露了产品的具体信息,这样产品类就变得极其不稳定,后期修改产品类的时候很难维护,因为很多地方在修改属性,如果使用建造者包装一次,客户端就不知道产品内部的具体信息(只有建造者知道,这样就控制了产品类出现的次数),后面修改产品类的时候就比较轻松。
还有一个问题是如果对构造顺序有严格的要求,比如必须先建主板才能建cpu那么,上面这种方式就不能控制。建造者的实现:

fun main(args: Array<String>)
{
    // 首先创建一个Google手机的建造者创建一个nexus5手机
    val googleBuilder = GoogleBuilder()
    googleBuilder.buildCpu("Google CPU")
    googleBuilder.buildMotherboard("Google Motherboard")
    googleBuilder.buildRam("Google RAM")
    googleBuilder.buildScreen("Google Screen")
    googleBuilder.buildView("Google View")
    val director = Director(googleBuilder)
    val nexus5 = director.build()
    println(nexus5)

    // 现在创建一个nexus6的手机,还是用nexus5的建造者,屏幕和外观不一样
    googleBuilder.buildScreen("Google Big Screen")
    googleBuilder.buildView("Google Big View")
    println(Director(googleBuilder).build())
}

// 打印
Phone(name='Google', cpu=Google CPU, ram=Google RAM, screen=Google Screen, motherboard=Google Motherboard, view=Google View)
Phone(name='Google', cpu=Google CPU, ram=Google RAM, screen=Google Big Screen, motherboard=Google Motherboard, view=Google Big View)

/**
 * 手机Builder
 * Created by Carlton on 2016/11/15.
 */
interface IPhoneBuilder
{
    /**
     * 定制CPU
     */
    fun buildCpu(cpu: String?)

    /**
     * 定制内容
     */
    fun buildRam(ram: String?)

    /**
     * 定制屏幕
     */
    fun buildScreen(screen: String?)

    /**
     * 定制主板
     */
    fun buildMotherboard(motherboard: String?)

    /**
     * 定制视图
     */
    fun buildView(view: String?)

    /**
     * 创建
     */
    fun create(): Phone
}

// Google手机建造者
/**
 * 谷歌手机建造者
 * Created by Carlton on 2016/11/15.
 */
class GoogleBuilder : IPhoneBuilder
{
    override fun create(): Phone
    {
        return phone
    }

    val phone = Phone("Google")
    override fun buildCpu(cpu: String?)
    {
        phone.cpu = cpu
    }

    override fun buildRam(ram: String?)
    {
        phone.ram = ram
    }

    override fun buildScreen(screen: String?)
    {
        phone.screen = screen
    }

    override fun buildMotherboard(motherboard: String?)
    {
        phone.motherboard = motherboard
    }

    override fun buildView(view: String?)
    {
        phone.view = view
    }
}
// 组装者
/**
 * 组装者
 * Created by Carlton on 2016/11/16.
 */
class Director(val builder: IPhoneBuilder)
{
    /**
     * 顺序建造
     */
    fun build(cpu: String, ram: String, motherboard: String, screen: String, view: String): Phone
    {
        builder.buildMotherboard(motherboard)
        builder.buildCpu(cpu)
        builder.buildRam(ram)
        builder.buildScreen(screen)
        builder.buildView(view)
        return builder.create()
    }

    /**
     * 建造
     */
    fun build(): Phone
    {
       return builder.create()
    }
}

首先,客户端创建了一个Google手机的建造者,并且分别建造了各个部件,然后拿到组装者去组装,组装的时候就可以按照一定的顺序来组装,或者在组装的时候做一些其他事情,接来下让建造者修改了其中两个部件屏幕和外观,然后造了一个新手机。这样做可以轻易的替换建造者,而其他部分代码不用修改来控制建造过程。
总结一下,建造者(IBuilder)可以隐藏具体的产品建造过程,产品的消费者只需要拿到完整的产品,组装者(Director)可以控制产品组装的流程,具体的产品的创造和实例化客户端根本不关心。建造者也提供了很强的扩展性,通过替换建造者或者修改某一个建造者,就能在背后影响产品的创造过程,而客户端也就是消费者并不知道,建造者把业务需求表现的差异化实现封装到了IBuilderDirector

和工厂模式的区别

和工厂模式一样都是输入创建类型的设计模式,封装创建过程给消费者,从类图上可以看出来和抽象工厂模式很像,但是,之前说过,类图只是参考,学习设计模式主要是学习其思路,在思路上抽象工厂模式是直接创建一个产品,及时的就把产品创造出来了,而建造者模式是先准备和定制产品属性,最后通过build()或者create()来创建一个产品。建造者的创建过程可以由客户端来控制,在创建过程上比抽象工厂模式更加灵活,在概念上抽象工厂模式创建的是一个产品族,是一类整体,建造者模式中产品过程则是独立的个体。

建造者的变种

建造者的核心在想在于创建产品,由很小的一些块组成整体产品。所以一般情况下不需要使用标准的建造者格式,大多数时候建造顺序不重要,这样只需要一个Builder类,连接口都可以不使用,这种情况在很多地方都应用,举个例子:

Java中的java.util.Calendar.Builder,是一个针对日历实例的建造者

Calendar
可以看到这里面有很多set方法,这些方法就是在定制这个产品,你会发现不需要有任何的顺序或者必须要调用,一个产品匹配一个建造者,主要作用是简化了实例过程,因为需要设置的属性太多了!最后使用build方法生产一个Calendar实例,这是典型的使用方式。

设计模式不要局限于形式,而在于思想

几个其他的例子

用几个简单的例子来加深一下理解。

StringBuilder

Java中如果要对String进行操作尽量使用StringBuilder,原因是String的连接等操作会产生新的实例,它是一个不可变的对象,比如,String str = "abc" + "bcd";这里内存中会产生3个对象,"abc"、"bcd"都是一个String对象,然后连接后把引用给到str
那么,StringBuilder在这里有什么作用呢?StringBuilder里面是一个char数组,如果用StringBuilder来做应该是这个样子:

val stringBuilder  = StringBuilder()
stringBuilder.append("abc")
stringBuilder.append("bcd")
val str = stringBuilder.toString()

同样的都是拼接"acb"和"bcd"如果使用StringBuilder的话我们只会在toString()的时候创建一个String,不是每一次都去创建好一个产品,然后做操作,这样就提高了性能:

@Override
public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

这里的toString()build()、create()是一个意思。用建造者模式,我们可以对产品各个情况先组建好,这里就是对字符各种操作先操作完成,最后一次输出完整的字符串对象。

GsonBuilder

Gson我相信都用过,是一个Json转对象,对象转Json的库,和FastJson一样,前者是Google的,后者是阿里的。先看一段代码:

……
public final class GsonBuilder 
{
    private Excluder excluder;
    private LongSerializationPolicy longSerializationPolicy;
    private FieldNamingStrategy fieldNamingPolicy;
    private final Map<Type, InstanceCreator<?>> instanceCreators;
    private final List<TypeAdapterFactory> factories;
    private final List<TypeAdapterFactory> hierarchyFactories;
    private boolean serializeNulls;
    private String datePattern;
    private int dateStyle;
    private int timeStyle;
    private boolean complexMapKeySerialization;
    private boolean serializeSpecialFloatingPointValues;
    private boolean escapeHtmlChars;
    private boolean prettyPrinting;
    private boolean generateNonExecutableJson;
……

这里有很多很多很多的属性,这些属性关系到对Json的解析和处理方式,我们不可能每次解析Json的时候都去赋值这么多属性,所以看看使用建造者模式如何规避这个问题,GsonBuilder在实例化的时候预先了一些默认值:

public GsonBuilder() 
{
    this.excluder = Excluder.DEFAULT;
    this.longSerializationPolicy = LongSerializationPolicy.DEFAULT;
    this.fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
    this.instanceCreators = new HashMap();
    this.factories = new ArrayList();
    this.hierarchyFactories = new ArrayList();
    this.dateStyle = 2;
    this.timeStyle = 2;
    this.escapeHtmlChars = true;
}

然后也提供了一些方法来修改部分属性,也就是建造者方法,这样可以通过GsonBuilder来建造一个Gson实例,而不用过多的去关注建造过程。当所有的属性都准备完成后,一次性输出产品:

public Gson create()
{
    ArrayList factories = new ArrayList();
    factories.addAll(this.factories);
    Collections.reverse(factories);
    factories.addAll(this.hierarchyFactories);
    this.addTypeAdaptersForDate(this.datePattern, this.dateStyle, this.timeStyle, factories);
    return new Gson(this.excluder, this.fieldNamingPolicy, this.instanceCreators, this.serializeNulls, this.complexMapKeySerialization, this.generateNonExecutableJson, this.escapeHtmlChars, this.prettyPrinting, this.serializeSpecialFloatingPointValues, this.longSerializationPolicy, factories);
}

Android中也有很多建造者的应用比如:android.support.v4.app.NotificationCompat.Builder、android.support.v7.app.AlertDialog.Builder,为什么使用建造者,建造者又有哪些缺点,通过这些实例自己能够去理解才是最重要的。

总结

建造者模式和抽象工厂类似都是封装了产品的建造过程,区别是建造者模式是构建完后一次性输出完整的产品,抽象工厂创建实例的时候,直接就输出了完成的产品,相比之下,建造者可以定制和控制构建过程,建造者也简化了产品的创建过程。

设计模式笔记本 【传送门】

不登高山,不知天之高也;不临深溪,不知地之厚也
感谢指点、交流、喜欢