与对象创建有关,它包含单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。虽然简单工厂模式不是 Gof 提出的创建型设计模式,但是它对理解抽象工厂模式有帮助。
单例模式 定义 :
保证一个类仅有一个实例,而且自行实例化,并提供一个访问它的全局访问点。(即是定义,也是本质。本质即是控制实例数量。)
客户端(Client)通过单例类 Singleton 的 getInstance 方法来获取实例对象。
单例注意点:
构造函数私有
含有一个该类的静态私有对象
有一个静态的公有的函数用于创建或获取它本身的静态私有对象
线程同步
线程安全 :
代码所在进程中存在多个线程同时运行,可能会同时运行一段代码,如果每次运行的结果和单线程一样,而且其它变量值也和预期一样,就是线程安全的。 或者说:一个类或程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说不用考虑同步问题,那就是线程安全的。
计算机中使用场景 :
线程池,缓存,日志对象,打印机,显卡的驱动程序对象等多少具有资源管理器的功能。
单例模式的使用场景 :
在一个系统中,要求一个类有且仅有一个对象,它的具体使用场景如下:
整个项目需要一个共享访问点或共享数据。
创建一个对象需要消耗的资源过多,比如访问 I/O 或者数据库等资源。
工具类对象。
下面介绍几种形式的单例模式,至于选用则取决于项目本身情况:是否为复杂的并发环境,或者是否需要控制单例对象的资源消耗。
饿汉式 这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。这种方式基于类加载机制,避免了多线程的同步问题。在类加载的时候就完成实例化,没有达到懒加载的效果。如果从始至终未使用过这个实例,则会造成内存的浪费。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Singleton { private static final Singleton mSingleton = new Singleton (); private Singleton () {} public static Singleton getInstance () { return mSingleton; } }
懒汉式 线程不安全的写法:声明了一个静态对象,在用户第一次调用时初始化。这虽然节约了资源,但第一次加载时需要实例化,反应稍慢一些,而且在多线程时不能正常工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Singleton { private static Singleton mSingleton ; private Singleton () {} public static Singleton getInstance () { if (mSingleton == null ){ mSingleton = new Singleton (); } return mSingleton; } }
线程安全的写法:这种写法能够在多线程中很好地工作,但是每次调用 getInstance 方法时都需要进行同步。这会造成不必要的同步开销,而且大部分时候我们是用不到同步的。所以,不建议用这种模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Singleton { private static Singleton mSingleton ; private Singleton () {} public static synchronized Singleton getInstance () { if (mSingleton == null ){ mSingleton = new Singleton (); } return mSingleton; } }
饿汉式和懒汉式的区别 :
从名字来说,饿汉是类一旦加载,单例则初始化完成。而懒汉则比较懒,当调用getInstance时,才会去初始化实例。
线程安全: 饿汉式天生线程安全的,可直接用于多线程而不会出问题。 懒汉式本身是非线程安全的,可以有几种写法,如静态内部类的方式,double-lock的方式。在资源加载和性能方面有些区别。
资源加载和性能: 饿汉式在类创建同时就实例化一个静态对象出来,不管之后是否适用,都会占据一定内存。但相应,第一次调用更快,因为资源已经初始化。 懒汉式会延迟加载,第一次使用该单例时才会实例化对象出来。如果初始化时,做的工作比较多,性能上会有些延迟,之后和饿汉式一样。
双重检查模式(DCL) 这种写法在 getInstance 方法中对 Singleton 进行了两次判空,第一次是为了不必要的同步,第二次是在 Singleton 等于 null 的情况下才创建实例。在这里使用 volatile 会或多或少地影响性能,但考虑到程序的正确性,牺牲这点性能还是值得的。DCL 的优点是资源利用率高。第一次执行 getInstance 时单例对象才被实例化,效率高。其缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷。DCL 虽然在一定程度上解决了资源的消耗和多余的同步、线程安全等问题,但其还是在某些情况会出现失效的问题,也就是 DCL 失效。这里建议用静态内部类单例来替代 DCL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class Singleton { private volatile static Singleton mSingleton; private Singleton () {} public static Singleton getInstance () { if (mSingleton == null ){ synchronized (Singleton.class){ if (mSingleton == null ){ mSingleton = new Singleton (); } } } return mSingleton; } }
静态内部类 第一次加载 Singleton 类时并不会初始化 mSingleton,只有第一次调用 getInstance 方法时虚拟机加载 SingletonHolder 并初始化 mSingleton。这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。所以,推荐使用静态内部类单例模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class Singleton { private static class SingletonHolder { private static final Singleton mSingleton = new Singleton (); } private Singleton () {} public static Singleton getInstance () { return SingletonHolder.mSingleton; } }
kotlin:
1 2 3 4 5 6 7 8 9 class Singleton private constructor () { companion object { val instance = SingletonHolder.holder } private object SingletonHolder { val holder= Singleton() } }
枚举 枚举单例的优点就是简单,但是大部分应用开发很少用枚举,其可读性并不是很高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public enum Singleton { INSTANCE; public void whateverMethod () { } }
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。在上面的几种单例模式实现中,有一种情况下其会重新创建对象,那就是反序列化:将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。反序列化操作提供了 readResolve 方法,这个方法可以让开发人员控制对象的反序列化。在上述几个方法示例中,如果要杜绝单例对象被反序列化时重新生成对象,就必须加入如下方法:
1 2 3 private Object readResolve () throws ObjectStreamException{ return singleton; }
登记式单例(可忽略) 实际上维护了一组单例类实例,将这些实例存放再Map(登记薄)中,对于已经登记过的实例,Map则直接返回。否则,先登记,再返回。其实,内部实现还是饿汉式。
工厂模式 顾名思义,它会创建一些东西。确切地说,它会创建对象。工厂模式的用途是借助通用接口将逻辑与使用分开。
1 2 3 4 5 6 7 8 9 public interface Bread { String name () ; String calories () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Baguette implements Bread { @Override public String name () { return "Baguette" ; } @Override public String calories () { return " : 65 kcal" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Roll implements Bread { @Override public String name () { return "Roll" ; } @Override public String calories () { return " : 75 kcal" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class BreadFactory { public Bread getBread (String breadType) { if (breadType == "RAG" ){ return new Baguette (); }else if (breadType == "ROL" ){ return new Roll (); } return null ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class MainActivity : AppCompatActivity () { private val DEBUG_TAG = "tag" override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) val breadFactory = BreadFactory() val breadROL = breadFactory.getBread("ROL" ) Log.e( DEBUG_TAG, "${breadROL.name()} ${breadROL.calories()} " ) Log.e(DEBUG_TAG, "-----------------------------" ) val breadRAG = breadFactory.getBread("RAG" ) Log.e( DEBUG_TAG, "${breadRAG.name()} ${breadRAG.calories()} " ) } }
简单工厂模式 又叫做静态工厂方法模式,属于创建型设计模式,但是并不属于 23 种 GoF 设计模式之一。
定义 :这是由一个工厂对象决定创建出哪一种产品类的实例。
在简单工厂模式中有如下角色:
Factory:工厂类,这是简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
IProduct:抽象产品类,这是简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
Product:具体产品类,这是简单工厂模式的创建目标。
示例 :
用生产计算机来举例,假设有一个计算机的代工生厂商,它目前已经可以代工生产联想计算机了。随着业务的拓展,这个代工生厂商还要生产惠普和华硕的计算机。这样就需要用一个单独的类来专门生产计算机。
抽象产品类:创建一个计算机的抽象产品类,其有一个抽象方法用于启动计算机。
1 2 3 4 5 6 7 public abstract class Computer { public abstract void start () ; }
具体产品类:创建各个品牌的计算机。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class LenovoComputer extends Computer { @Override public void start () { System.out.println("联想计算机启动" ); } } public class HpComputer extends Computer { @Override public void start () { System.out.println("惠普计算机启动" ); } } public class AsusComputer extends Computer { @Override public void start () { System.out.println("华硕计算机启动" ); } }
工厂类:它提供了一个静态方法 createComputer 用来生产计算机。只需要传入自己想生产的计算机的品牌,它就会实例化相应品牌的计算机对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ComputerFactory { public static Computer createComputer (String type) { Computer mComputer = null ; switch (type){ case "lenovo" : mComputer = new LenovoComputer (); break ; case "hp" : mComputer = new HpComputer (); break ; case "ausu" : mComputer = new AsusComputer (); break ; } return mComputer; } }
客户端调用工厂类:传入 “hp” 生产出惠普计算机并调用该计算机对象的 start 方法。
1 2 3 4 5 public class CreateComputer { public static void main (String[] args) { ComputerFactory.createComputer("hp" ).start(); } }
使用场景和优缺点
使用场景:
工厂类负责创建的对象比较少。
客户只需知道传入工厂类的参数,而无需关心创建对象的逻辑。
优点:使用户根据参数获得对应的类的实例,避免了直接实例化类,降低了耦合性。
缺点:可实例化的类型在编译期间已经被确定。如果增加新类型,则需要修改工厂,这违背了开放封闭原则。简单工厂需要知道所有要生成的类型,其当子类过多或者子类层次过多时不适合使用。
工厂方法模式 定义:定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。
在工厂方法模式中有如下角色
Product:抽象产品类
ConcreteProduct:具体产品类,实现 Product 接口。
Factory:抽象工厂类,该方法返回一个 Product 类型的对象。
ConcreteFactory:具体工厂类,返回 ConcreteProduct 实例。
示例
工厂方法模式的抽象产品类与具体产品类的创建和简单工厂模式是一样的。
创建抽象工厂
1 2 3 4 public abstract class ComputerFactory { public abstract <T extends Computer > T createComputer (Class<T> clz) ; }
具体工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class GDComputerFactory extends ComputerFactory { @Override public <T extends Computer > T createComputer (Class<T> clz) { Computer computer = null ; String className = clz.getName(); try { computer = (Computer) Class.forName(className).newInstance(); }catch (Exception e){ e.printStackTrace(); } return (T) computer; } }
客户端调用
1 2 3 4 5 6 7 8 9 10 11 12 public class client { public static void main (String[] args) { ComputerFactory computerFactory = new GDComputerFactory (); LenovoComputer lenovoComputer = computerFactory.createComputer(LenovoComputer.class); lenovoComputer.start(); HpComputer hpComputer = computerFactory.createComputer(HpComputer.class); hpComputer.start(); AsusComputer asusComputer = computerFactory.createComputer(AsusComputer.class); asusComputer.start(); } }
工厂方法与简单工厂
对于简单工厂模式,其在工厂类中包含了必要的逻辑判断,根据不同的条件来动态实例化相关的类。对客户来说,这去除了与具体产品的依赖,但与此同时也带来一个问题,如果要增加产品,比如要生产苹果计算机,就需要在工厂类中添加一个 Case 分支条件,这违背了开放封闭原则,对修改也开放了。而工厂方法模式就没有违背这个开放封闭原则。如果需要生产苹果计算机,则无需修改工厂类,直接创建产品即可。
建造者模式 也被称为生成器模式,它是创建一个复杂对象的创建型模式,其将构建复杂对象的过程和它的部件解耦,使得构建过程和部件的表示分离开来。例如要 DIY 一台台式计算机,我们找到 DIY 商家,这时这台计算机的 CPU 、主板或者其它部件都是什么牌子、什么配置的,这些都可以根据需求来变化。对于这些部件的组装成计算机的过程是一样的,我们无需知道组装过程,只需要提供相关部件的牌子和配置就可以了。对于这种情况就可以采用建造者模式,将部件和组装过程分离,使得构建过程和部件都可以自由拓展,两者之间的耦合也降到最低。
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
在建造者模式中有如下角色:
Director:导演类,负责安排已有模块的顺序,然后通知 Builder 开始建造。
Builder:抽象 Builder 类,规范产品的组建,一般由子类实现。
ConcreteBuilder:具体建造者,实现抽象 Builder 类定义的所有方法,并且返回一个组建好的对象。
Product:产品类。
示例:以 DIY 组装计算机的例子来实现
创建产品类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public abstract class Computer { private String mCPU; private String mMainboard; private String mRam; public void setmCPU (String mCPU) { this .mCPU = mCPU; } public void setmMainboard (String mMainboard) { this .mMainboard = mMainboard; } public void setmRam (String mRam) { this .mRam = mRam; } }
创建 Builder 类规范产品的组建
1 2 3 4 5 6 7 8 9 10 public abstract class Builder { public abstract void buildCPU (String cpu) ; public abstract void buildMainboard (String mainboard) ; public abstract void buildRam (String ram) ; public abstract Computer create () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class MoonComputerBuilder extends Builder { private Computer mComputer = new Computer (); @Override public void buildCPU (String cpu) { mComputer.setmCPU(cpu); } @Override public void buildMainboard (String mainboard) { mComputer.setmMainboard(mainboard); } @Override public void buildRam (String ram) { mComputer.setmRam(ram); } @Override public Computer create () { return mComputer; } }
用导演类来统一组装过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Director { Builder mBuild = null ; public Director (Builder build) { this .mBuild = build; } public Computer CreateComputer (String cpu,String mainboard,String ram) { this .mBuild.buildMainboard(mainboard); this .mBuild.buildCPU(cpu); this .mBuild.buildRam(ram); return mBuild.create(); } }
客户端调用导演类
1 2 3 4 5 6 7 8 9 10 11 12 public class CreateComputer { public static void main (String[] args) { Builder mBuilder = new MoonComputerBuilder (); Director mDirector = new Director (mBuilder); mDirector.CreateComputer("i7-6700" ,"华擎玩家至尊" ,"三星 DDR4" ); } }
使用场景和优缺点
使用场景:
当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
相同的方法,不同的执行顺序,产生不同的事件结果时。
多个部件或零件都可以被装配到一个对象中,但是产生的运行结果又不相同时。
产品类非常复杂,或者产品类中的调用顺序不同而产生了不同的功能。
在创建一些复杂的对象时,这些对象的内部组成构件间的建造顺序是稳定的,但是对象的内部组成构件面临着复杂的变化。
优点:
使用建造者模式可以使客户端不必知道产品内部组成的细节。
具体的建造者类之间是相互独立的,容易扩展。
由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其它的模块产生任何影响。
缺点:
备注 参考资料:
Android 进阶之光