[TOC]
创建型模式包括工厂模式、建造者模式和单例模式。其中工厂模式又分为:
* 简单工厂模式(又称 静态工厂方法模式)
* 工厂方法模式(又称 工厂模式、虚拟构造器模式、多态工厂模式)
* 抽象工厂模式(又称 Kit 模式)
# 1 简单工厂模式
## 1.1 模式定义
简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
## 1.2 模式结构
简单工厂模式包含如下角色:
* Factory:工厂角色
负责实现创建所有实例的内部逻辑
* Product:抽象产品角色
是所创建的所有对象的父类,负责描述所有实例所共有的公共接口
* ConcreteProduct:具体产品角色
具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。
## 1.3 代码示例
```java
public static final Fruit getFruit(String name) {
if (name.equals("apple")) {
return new Apple();
} else if (name.equals("banana")) {
return new Banana();
} else if (name.equals("grape")) {
return new Grape();
} else {
return null;
}
}
```
## 1.4 总结
* 创建型模式对类的实例化过程进行了抽象,能够将对象的创建与对象的使用过程分离。
* 简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。
* 简单工厂模式最大的优点在于实现对象的创建和对象的使用分离,将对象的创建交给专门的工厂类负责,但是其最大的缺点在于工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,而且产品较多时,工厂方法代码将会非常复杂。
* 简单工厂模式适用情况包括:工厂类负责创建的对象比较少;客户端只知道传入工厂类的参数,对于如何创建对象不关心。
# 2 工厂方法模式
## 2.1 模式定义
工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
## 2.2 模式结构
工厂方法模式包含如下角色:
* Product:抽象产品
定义产品的接口,产品对象的共同父类或者接口
* ConcreteProduct:具体产品
实现抽象产品接口
* Factory:抽象工厂
声明工厂方法
* ConcreteFactory:具体工厂
抽象工厂类的子类,实现抽象工厂定义的工厂方法
## 2.3 代码示例
```java
interface FruitFactory {
Fruit createFruit();
}
public class AppleFactory implements FruitFactory {
@Override
public Fruit createFruit() {
return new Apple();
}
}
public class BananaFactory implements FruitFactory {
@Override
public Fruit createFruit() {
return new Banana();
}
}
public class GrapeFactory implements FruitFactory {
@Override
public Fruit createFruit() {
return new Grape();
}
}
```
## 2.4 总结
* 工厂方法模式包含四个角色:抽象产品是定义产品的接口,是工厂方法模式所创建对象的超类型,即产品对象的共同父类或接口;具体产品实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,它们之间往往一一对应;抽象工厂中声明了工厂方法,用于返回一个产品,它是工厂方法模式的核心,任何在模式中创建对象的工厂类都必须实现该接口;具体工厂是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户调用,返回一个具体产品类的实例。
* 工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。
* 工厂方法模式的主要优点是增加新的产品类时无须修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性;其缺点在于增加新产品的同时需要增加新的工厂,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性。
* 工厂方法模式适用情况包括:一个类不知道它所需要的对象的类;一个类通过其子类来指定创建哪个对象;将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定。
# 3 抽象工厂模式
## 3.1 模式定义
抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为 Kit 模式,属于对象创建型模式。
## 3.2 模式结构
抽象工厂模式包含如下角色:
* AbstractFactory:抽象工厂
用于声明生成抽象产品的方法
* ConcreteFactory:具体工厂
实现了抽象工厂声明的生成抽象产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中
* AbstractProduct:抽象产品
为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法
* Product:具体产品
定义具体工厂生产的具体产品对象,实现抽象产品接口中定义的业务方法。
## 3.3 代码示例
```java
// 抽象工厂
public interface ComputerCompany {
Keyboard createKeyboard();
Mouse createMouse();
}
// 具体工厂
public class AppleCompany implements ComputerCompany {
@Override
public Keyboard createKeyboard() {
return new AppleKeyboard();
}
@Override
public Mouse createMouse() {
return new AppleMouse();
}
}
public class DellCompany implements ComputerCompany {
@Override
public Keyboard createKeyboard() {
return new DellKeyboard();
}
@Override
public Mouse createMouse() {
return new DellMouse();
}
}
// 抽象产品
public abstract Keyboard {
public abstract void print();
}
public abstract Mouse {
public abstract void scroll();
}
// 具体产品
public class AppleKeyboard extends Keyboard {
@Override
public void print() {
}
}
public class DellKeyboard extends Keyboard {
@Override
public void print() {
}
}
public class AppleMouse extends Mouse {
@Override
public void scroll() {
}
}
public class DellMouse extends Mouse {
@Override
public void scroll() {
}
}
```
## 3.4 总结
* Keyboard 和 AppleKeyboard、Dellkeyboard 的关系为父类、子类的关系,它们在一个产品等级结构中。AppleKeyboard 和 AppleMouse 则属于同一个产品族。
* 抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构。
* 抽象工厂模式的主要优点是隔离了具体类的生成,使得客户并不需要知道什么被创建,而且每次可以通过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便;
* 主要缺点在于增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。
* 抽象工厂模式适用情况包括:一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节;系统中有多于一个的产品族,而每次只使用其中某一产品族;属于同一个产品族的产品将在一起使用;系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
# 4 工厂模式的退化
当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;
当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。
# 5 建造者模式
## 5.1 模式定义
造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式。
## 5.2 模式结构
* Product:产品角色
被构建的复杂对象,包含多个组成部件
* Builder:抽象建造者
为创建一个产品对象的各个部件指定抽象接口
* ConcreteBuilder:具体建造者
实现了抽象建造者接口,实现各个部件的构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象
* Director:指挥者
负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其 construct() 建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造
## 5.3 代码示例
```java
// 产品角色
public class Phone {
private String mBrand;
private String mOS;
private String mMemory;
// getter and setter ...
}
// 抽象建造者
public abstract class PhoneBuilder {
protected Phone mPhone = new Phone();
public abstract void buildBrand();
public abstract void buildOS();
public abstract void buildMemory();
public Phone createPhone() {
return mPhone;
}
}
// 具体建造者
public class ApplePhoneBuilder extends PhoneBuilder {
@Override
public void buildBrand() {
mPhone.setBrand("Apple");
}
@Override
public void buildOS() {
mPhone.setOS("iOS");
}
@Override
public void buildMemory() {
mPhone.setMemory("1G");
}
}
public class NexusPhoneBuilder extends PhoneBuilder {
@Override
public void buildBrand() {
mPhone.setBrand("Nexus");
}
@Override
public void buildOS() {
mPhone.setOS("Android");
}
@Override
public void buildMemory() {
mPhone.setMemory("3G");
}
}
// 指挥者
public class PhoneDirector {
private PhoneBuilder mPhoneBuilder;
public void setPhoneBuilder(PhoneBuilder phoneBuilder) {
mPhoneBuilder = phoneBuilder;
}
public Phone construct() {
mPhoneBuilder.buildBrand();
mPhoneBuilder.buildMemory();
mPhoneBuilder.buildOS();
return mPhoneBuilder.createPhone();
}
}
// 使用
ApplePhoneBuilder applePhoneBuilder = new ApplePhoneBuilder();
NexusPhoneBuilder nexusPhoneBuilder = new NexusPhoneBuilder();
PhoneDirector director = new PhoneDirector();
director.setPhoneBuilder(applePhoneBuilder);
Phone iPhone = director.construct();
director.setPhoneBuilder(nexusPhoneBuilder);
Phone Nexus = director.construct();
```
## 5.4 总结
* 在建造者模式的结构中引入了一个指挥者类,该类的作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。
* 建造者模式的主要优点在于客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象,每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,符合“开闭原则”,还可以更加精细地控制产品的创建过程;
* 其主要缺点在于由于建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,因此其使用范围受到一定的限制,如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
* 建造者模式适用情况包括:需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性;需要生成的产品对象的属性相互依赖,需要指定其生成顺序;对象的创建过程独立于创建该对象的类;隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同类型的产品。
# 6 单例模式
## 6.1 模式定义
单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。
## 6.2 模式结构
* Singleton:单例
在单例类的内部实现只生成一个实例,同时它提供一个静态的工厂方法,让客户可以使用它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有。
## 6.3 代码示例
```java
public class Singleton {
private static volatile Singleton sInstance;
public static synchronized Singleton getInstance() {
if (sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
private Singleton() {}
}
```
## 6.4 总结
* 单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例类拥有一个私有构造函数,确保用户无法通过 new 关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法。该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。
* 单例模式的主要优点在于提供了对唯一实例的受控访问并可以节约系统资源;其主要缺点在于因为缺少抽象层而难以扩展,且单例类职责过重。
* 单例模式适用情况包括:系统只需要一个实例对象;客户调用类的单个实例只允许使用一个公共访问点。
## 6.5 几种单例模式的写法
1、懒汉式(线程不安全)
```java
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
懒汉式写法使用了懒加载模式,也就是在使用类实例(调用getInstance方法)时,才会进行初始化。但却存在一个问题,当多个线程并行调用getInstance方法时就会产生多个实例。
2、懒汉式(线程安全)
```java
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
上面的写法解决了多个线程并行调用创建多个实例的问题,但是并不高效。因为每次只能有一个线程调用getInstance方法使用实例,但实际上只有第一次调用创建时需要加锁,后面并不需要加锁。
3、双重校验锁
```java
public class Singleton {
// 声明成 volatile
private volatile static Singleton instance;
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
```
是一种使用同步块加锁的方法,首先在同步块外检查一次,如果instance!=null直接返回实例;如果instance==null就进入同步块内,再次检查instance是否为空,因为可能会有多个线程一起进入同步块外部的if,为空则创建实例。
但是由于`instance = new Singleton();`并非是一个原子操作,创建实例的过程如下:
a、给instance分配内存区域
b、调用Singleton的构造函数来初始化成员变量
c、将instance指向分配的内存空间
但在JVM的及时编译器中存在指令重排序的优化,步骤b和c的执行顺序并不能保证,可能为a-b-c的顺序,也可能a-c-b的顺序执行。如果是a-c-b的执行顺序的话,在c执行完成后,b执行前,假设其他线程拿到instance实例后会发现并未初始化而报错。
我们在这里将instance变量声明为volatile的,volatile关键字有两个作用:一是保证内存可见性(线程之间),二是禁止指令重排优化。此处主要是用到volatile的第二个特性,在volatile变量的赋值操作后会有一个内存屏障,读操作不会被重排到内存屏障前。也就是读操作一定会在对volatile变量的写操作执行完成后才会进行。
注:Java5以前的volatile依然无法禁止指令重排优化。
4、饿汉式
```java
public class Singleton{
//类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
```
这种写法在类第一次加载到内存中时就会进行初始化实例,是线程安全的。但是某些场景下无法使用,比如需要先调用类的某个方法设置参数给类,然后根据参数再创建实例。
5、静态内部类写法
```java
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
```
SingletonHolder是私有的,除了getInstance没有方法可以访问它,因此是懒加载;同时JVM本身的机制保证了线程安全问题;读取实例的时候不会进行同步,没有性能上的缺陷;也不依赖JDK版本。
6、枚举Enum
```java
public enum EasySingleton{
INSTANCE;
}
```
可以通过EasySingleton.INSTANCE直接来访问实例
## 参考文章
[如何正确地写出单例模式](http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/)
[volatile关键字及其作用](https://blog.csdn.net/u010255818/article/details/65633033)
- 导读
- Java知识
- Java基本程序设计结构
- 【基础知识】Java基础
- 【源码分析】Okio
- 【源码分析】深入理解i++和++i
- 【专题分析】JVM与GC
- 【面试清单】Java基本程序设计结构
- 对象与类
- 【基础知识】对象与类
- 【专题分析】Java类加载过程
- 【面试清单】对象与类
- 泛型
- 【基础知识】泛型
- 【面试清单】泛型
- 集合
- 【基础知识】集合
- 【源码分析】SparseArray
- 【面试清单】集合
- 多线程
- 【基础知识】多线程
- 【源码分析】ThreadPoolExecutor源码分析
- 【专题分析】volatile关键字
- 【面试清单】多线程
- Java新特性
- 【专题分析】Lambda表达式
- 【专题分析】注解
- 【面试清单】Java新特性
- Effective Java笔记
- Android知识
- Activity
- 【基础知识】Activity
- 【专题分析】运行时权限
- 【专题分析】使用Intent打开三方应用
- 【源码分析】Activity的工作过程
- 【面试清单】Activity
- 架构组件
- 【专题分析】MVC、MVP与MVVM
- 【专题分析】数据绑定
- 【面试清单】架构组件
- 界面
- 【专题分析】自定义View
- 【专题分析】ImageView的ScaleType属性
- 【专题分析】ConstraintLayout 使用
- 【专题分析】搞懂点九图
- 【专题分析】Adapter
- 【源码分析】LayoutInflater
- 【源码分析】ViewStub
- 【源码分析】View三大流程
- 【源码分析】触摸事件分发机制
- 【源码分析】按键事件分发机制
- 【源码分析】Android窗口机制
- 【面试清单】界面
- 动画和过渡
- 【基础知识】动画和过渡
- 【面试清单】动画和过渡
- 图片和图形
- 【专题分析】图片加载
- 【面试清单】图片和图形
- 后台任务
- 应用数据和文件
- 基于网络的内容
- 多线程与多进程
- 【基础知识】多线程与多进程
- 【源码分析】Handler
- 【源码分析】AsyncTask
- 【专题分析】Service
- 【源码分析】Parcelable
- 【专题分析】Binder
- 【源码分析】Messenger
- 【面试清单】多线程与多进程
- 应用优化
- 【专题分析】布局优化
- 【专题分析】绘制优化
- 【专题分析】内存优化
- 【专题分析】启动优化
- 【专题分析】电池优化
- 【专题分析】包大小优化
- 【面试清单】应用优化
- Android新特性
- 【专题分析】状态栏、ActionBar和导航栏
- 【专题分析】应用图标、通知栏适配
- 【专题分析】Android新版本重要变更
- 【专题分析】唯一标识符的最佳做法
- 开源库源码分析
- 【源码分析】BaseRecyclerViewAdapterHelper
- 【源码分析】ButterKnife
- 【源码分析】Dagger2
- 【源码分析】EventBus3(一)
- 【源码分析】EventBus3(二)
- 【源码分析】Glide
- 【源码分析】OkHttp
- 【源码分析】Retrofit
- 其他知识
- Flutter
- 原生开发与跨平台开发
- 整体归纳
- 状态及状态管理
- 零碎知识点
- 添加Flutter到现有应用
- Git知识
- Git命令
- .gitignore文件
- 设计模式
- 创建型模式
- 结构型模式
- 行为型模式
- RxJava
- 基础
- Linux知识
- 环境变量
- Linux命令
- ADB命令
- 算法
- 常见数据结构及实现
- 数组
- 排序算法
- 链表
- 二叉树
- 栈和队列
- 算法时间复杂度
- 常见算法思想
- 其他技术
- 正则表达式
- 编码格式
- HTTP与HTTPS
- 【面试清单】其他知识
- 开发归纳
- Android零碎问题
- 其他零碎问题
- 开发思路