单例模式
- 单例模式是最简单的设计模式之一,属于创建型模式,它提供了一种创建对象的最佳方式;
- 单例模式只涉及到一个单一的类,该类负责创建自己的对象,同时确保只会创建唯一的一个实例对象;
单例模式的实现
-
单例模式的实现分为两种:
- 饿汉式:类加载就会导致该单实例对象被创建;
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会被创建;
饿汉式单例模式 静态变量
方式 案例代码如下:
public class Singleton {
//1.私有构造方法
private Singleton() {}
//2.在本类中创建实例对象
private static Singleton instance = new Singleton();
//3.提供一个公共的访问方式,让外界访问
public static Singleton getInstance() {
return instance;
}
}
-
饿汉式单例模式 静态代码块
方式 案例代码如下:
public class Singleton {
//1.私有构造方法
private Singleton() {}
//2.定义静态成员变量
private static Singleton instance;
//3.在静态代码块中赋值
static {
instance = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
饿汉式单例模式,在类加载时就已经创建单实例对象,若没有使用单例类,就会造成内存的浪费;
懒汉式单例模式 - 线程不安全
方式
public class Singleton {
//1.私有构造方法
private Singleton() {}
//2.定义静态成员变量
private static Singleton instance;
//3.提供一个公共的访问方式,让外界访问
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
-
懒汉式单例模式 - 线程安全
方式
public class Singleton {
//1.私有构造方法
private Singleton() {}
//2.定义静态成员变量
private static Singleton instance;
//3.提供一个公共的访问方式,让外界访问
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
synchronized
:线程同步锁;懒汉式单例模式 - 双重检查锁
方式
public class Singleton {
//1.私有构造方法
private Singleton() {}
//2.定义静态成员变量
private static volatile Singleton instance;
//3.提供一个公共的访问方式,让外界访问
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
添加
volatile
关键字之后的双重检查锁模式,是一种比较好的单例实现模式,能够保证在多线程的情况下,线程安全也不会有性能问题;懒汉式单例模式 - 静态内部类
方式静态内部类单例模式中,实例有内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性,方法被调用时才会被加载,并初始化其静态属性,静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序;
public class Singleton {
//1.私有构造方法
private Singleton() {}
//2.定义一个静态内部类
private static class SingletonHandler {
//在内部类中声明并初始化外部类对象
private static Singleton INSTANCE = new Singleton();
}
//3.提供一个公共的访问方式,让外界访问
public static Singleton getInstance() {
return SingletonHandler.INSTANCE;
}
}
第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance方法,虚拟机加载SingletonHandler类并初始化INSTANCE,这样不仅能确保线程安全,也能保证Singleton实例的唯一性;
静态内部类单例模式时一种优秀的单例模式,是开源项目中比较常用的一种单例模式,在没有任何加锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费;
饿汉式 -- 枚举方式
枚举类实现单例模式是极力推荐的实现方式,因为枚举类型是线程安全的,并且只会加载一次,设计者充分利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所有单例实现中唯一不会被破坏的实现方式;
public enum Singleton {
INSTANCE;
}
-
序列化会破坏单例模式
,使用上面创建的单例类,创建单例对象,枚举方式除外,案例代码如下:
import java.io.Serializable;
public class Singleton implements Serializable {
//1.私有构造方法
private Singleton() {}
//2.定义一个静态内部类
private static class SingletonHandler {
//在内部类中声明并初始化外部类对象
private static Singleton INSTANCE = new Singleton();
}
//3.提供一个公共的访问方式,让外界访问
public static Singleton getInstance() {
return SingletonHandler.INSTANCE;
}
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//先写入文件
try {
writeObjectToFile();
} catch (Exception e) {
e.printStackTrace();
}
//两次读取文件的 获取的单例对象不同
try {
readObjectFromFile();
} catch (Exception e) {
e.printStackTrace();
}
try {
readObjectFromFile();
} catch (Exception e) {
e.printStackTrace();
}
}
//往文件中写数据
public static void writeObjectToFile() throws Exception {
Singleton singleton = Singleton.getInstance();
//创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/liyanyan33/Desktop/a.txt"));
//写对象
oos.writeObject(singleton);
//释放资源
oos.close();
}
//从文件中读数据
public static void readObjectFromFile() throws Exception {
//创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/liyanyan33/Desktop/a.txt"));
//读取对象
Singleton singleton = (Singleton)ois.readObject();
System.out.println(singleton);
//释放资源
ois.close();
}
}
- 解决方案:在单例类中实现
readResolve
方法,原理在于Singleton singleton = (Singleton)ois.readObject()
,底层会去判断调用readResolve
方法,改进之后的代码如下所示:
import java.io.Serializable;
public class Singleton implements Serializable {
//1.私有构造方法
private Singleton() {}
//2.定义一个静态内部类
private static class SingletonHandler {
//在内部类中声明并初始化外部类对象
private static Singleton INSTANCE = new Singleton();
}
//3.提供一个公共的访问方式,让外界访问
public static Singleton getInstance() {
return SingletonHandler.INSTANCE;
}
//当进行反序列化是 会自动调用改方法 将改方法的返回值直接返回
public Object readResolve() {
return SingletonHandler.INSTANCE;
}
}
-
反射会破坏单例模式
,使用上面创建的单例类(这里使用静态内部类的方式),创建单例对象,枚举方式除外,案例代码如下:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取Singleton类的字节码对象
Class clazz = Singleton.class;
try {
//获取无参构造方法
Constructor cons = clazz.getDeclaredConstructor();
//取消访问检查
cons.setAccessible(true);
//创建单例对象
Singleton instance1 = (Singleton) cons.newInstance();
Singleton instance2 = (Singleton) cons.newInstance();
//不等 说明反射破坏了单例模式
System.out.println(instance1 == instance2);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
- 解决方案:代码如下:
public class Singleton {
private static boolean flag = false;
//1.私有构造方法
private Singleton() {
//flag = true 表明非第一次访问
//flag = false 表明第一次访问
synchronized (Singleton.class) {
if (flag) {
throw new RuntimeException("不能创建多个对象");
}
flag = true;
}
}
//2.定义一个静态内部类
private static class SingletonHandler {
//在内部类中声明并初始化外部类对象
private static Singleton INSTANCE = new Singleton();
}
//3.提供一个公共的访问方式,让外界访问
public static Singleton getInstance() {
return SingletonHandler.INSTANCE;
}
}
- 系统类
RunTime
就是一个单例模式类,源码如下:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
- 其采用的是饿汉式 -- 静态成员变量 的单例模式;
工厂模式
- 首先我们通过一个实际案例,来慢慢引出工厂设计模式;
- 设计一个咖啡店点餐系统,包含咖啡类与咖啡店类,UML类图如下:
- 代码实现如下:
public abstract class Coffee {
public abstract String getName();
public void addMilk() {
System.out.println("加奶");
}
public void addSugar() {
System.out.println("加糖");
}
}
public class AmericanCoffee extends Coffee{
@Override
public String getName() {
return "美式咖啡";
}
}
public class LatteCoffee extends Coffee{
@Override
public String getName() {
return "拿铁咖啡";
}
}
public class CoffeeStore {
//点咖啡
public Coffee orderCoffee(String type) {
Coffee coffee = null;
if (type.equals("american")) {
coffee = new AmericanCoffee();
} else if (type.equals("latte")) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("对不起,您所点的咖啡没有!");
}
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CoffeeStore coffeeStore = new CoffeeStore();
coffeeStore.orderCoffee("latte");
}
}
- 上述实现了点咖啡的功能,现在我们需要新增一种
英式咖啡
,创建英式咖啡类继承自Coffee类,但还需要改CoffeeStore类的代码,这就违背了软件设计原则中的开闭原则
,如果我们使用工厂来生产对象,CoffeeStore类就只和工厂打交道就可以了,若要更换咖啡对象,直接在工厂里更换该咖啡对象即可,达到CoffeeStore类与具体咖啡对象解耦的目的,所以说工厂模式的最大优点是:解耦
;
简单工厂模式
- 简单工厂模式不是一种设计模式,不属于23种设计模式之一,其是一种编程习惯;
- 简单工厂模式包含的角色有:
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能;
- 具体产品:实现或者继承抽象产品的子类;
- 具体工厂:提供了创建产品的方法,调用者通过该方法来创建产品;
- 现在使用简单工厂模式对上面的案例进行改进,UML类图如下:
- 代码实现如下:
public abstract class Coffee {
public abstract String getName();
public void addMilk() {
System.out.println("加奶");
}
public void addSugar() {
System.out.println("加糖");
}
}
public class AmericanCoffee extends Coffee{
@Override
public String getName() {
return "美式咖啡";
}
}
public class LatteCoffee extends Coffee{
@Override
public String getName() {
return "拿铁咖啡";
}
}
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
Coffee coffee = null;
if (type.equals("american")) {
coffee = new AmericanCoffee();
} else if (type.equals("latte")) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("对不起,您所点的咖啡没有!");
}
return coffee;
}
}
public class CoffeeStore {
//点咖啡
public Coffee orderCoffee(String type) {
//创建工厂
SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
//通过工厂 获取咖啡
Coffee coffee = factory.createCoffee(type);
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CoffeeStore coffeeStore = new CoffeeStore();
coffeeStore.orderCoffee("latte");
}
}
-
简单工厂模式的优点:
- CoffeeStore类实现了与具体咖啡类的解耦;
- SimpleCoffeeFactory工厂类封装了创建对象的细节,将创建对象与业务逻辑(点咖啡)分离,这样以后就避免了修改客户( CoffeeStore类)代码,如果要新增具体产品,可直接修改SimpleCoffeeFactory工厂类,更容易扩展;
-
简单工厂模式的缺点:
- SimpleCoffeeFactory工厂类与具体咖啡类产生了耦合,如果后期新增一种咖啡,SimpleCoffeeFactory工厂类也需要修改代码,违背了开闭原则;
-
CoffeeStore类
与SimpleCoffeeFactory
工厂类产生耦合
, CoffeeStore类可以看成是SimpleCoffeeFactory工厂类的客户,可能会有其他的客户类;
在开发中,可以将简单工厂模式中创建对象的方法定义为静态方法,属于
静态工厂模式
,代码如下:
public class SimpleCoffeeFactory {
public static Coffee createCoffee(String type) {
Coffee coffee = null;
if (type.equals("american")) {
coffee = new AmericanCoffee();
} else if (type.equals("latte")) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("对不起,您所点的咖啡没有!");
}
return coffee;
}
}
public class CoffeeStore {
//点咖啡
public Coffee orderCoffee(String type) {
//通过工厂 获取咖啡
Coffee coffee = SimpleCoffeeFactory.createCoffee(type);
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
工厂方法模式
- 针对上述案例的缺点,可使用工厂方法模式完美解决,完全遵循开闭原则;
- 工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪个产品对象,工厂方法使一个产品类的实例化延迟到其工厂的子类;
- 工厂方法模式的角色:
- 抽象工厂:提供创建产品的接口,调用者通过它访问具体工厂的工厂方法创建产品;
- 具体工厂:主要是实现抽象工厂中抽象方法,完成具体产品的创建;
- 抽象产品:定义了产品的规范接口,描述了产品的主要特性和功能;
- 具体产品:实现了抽象产品定义的规范接口,由具体工厂来创建,它与具体工厂之间一一对应;
- 使用工厂方法模式对上述案例进行改进,UML类图如下:
- 代码实现如下:
//抽象产品类
public abstract class Coffee {
public abstract String getName();
public void addMilk() {
System.out.println("加奶");
}
public void addSugar() {
System.out.println("加糖");
}
}
//具体产品类
public class AmericanCoffee extends Coffee{
@Override
public String getName() {
return "美式咖啡0";
}
}
//具体产品类
public class LatteCoffee extends Coffee{
@Override
public String getName() {
return "拿铁咖啡";
}
}
//抽象工厂
public interface CoffeeFactory {
public Coffee createCoffee();
}
//具体工厂类
public class AmericanCoffeeFactory implements CoffeeFactory{
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
//具体工厂类
public class LatteCoffeeFactory implements CoffeeFactory{
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
}
//客户端
public class CoffeeStore {
//持有抽象工厂
private CoffeeFactory factory;
public void setFactory(CoffeeFactory factory) {
this.factory = factory;
}
public Coffee orderCoffee() {
Coffee coffee = factory.createCoffee();
coffee.addSugar();
coffee.addMilk();
return coffee;
}
}
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CoffeeStore store = new CoffeeStore();
//在使用是初始化具体的工厂
AmericanCoffeeFactory acf = new AmericanCoffeeFactory();
store.setFactory(acf);
store.orderCoffee();
}
}
- 现在再新增一款咖啡,只需要创建一个具体工厂类和具体产品类,不会修改抽象工厂类和抽象产品了,符合开闭原则;
- 工厂方法模式的优点:
- 客户只需要知道具体工厂 就能获取到想要的具体产品,无需知道产品创建的细节;
- 在系统新增产品时,只需要新增具体工厂类和具体产品类,无需对原工厂进行修改,满足开闭原则;
- 工厂方法模式的缺点:
- 每新增产品时, 都要新增具体工厂类和具体产品类,增加系统的复杂度;
抽象工厂模式
- 产品类型:具体产品的抽象,例如拿铁咖啡与美式咖啡都属于咖啡;
- 产品族:具体产品的抽象集合,例如咖啡与甜点构成一个产品族;
- 抽象工厂模式是工厂方法模式的升级版,工厂方法模式只生产一种类型的产品,而抽象工厂模式可生产多个不同类型的产品;
- 抽象工厂模式提供了一个 创建一组相关或相互依赖对象 的接口,且访问类无需指定具体产品类 就能获取到不同类型产品的模式结构;
- 抽象工厂模式的角色有:
- 抽象工厂:提供创建产品的接口,它包含多个创建产品的方法,可以创建多个不同类型的产品;
- 具体工厂:主要是实现抽象工厂中多个抽象方法,完成具体产品的创建;
- 抽象产品:定义了产品的规范接口,描述了产品的主要特性和功能,有多个抽象产品类;
- 具体产品:实现了抽象产品定义的规范接口,由具体工厂来创建,它与具体工厂之间是多对一的关系;
- 场景案例:现在咖啡店业务发生了变化,不仅要生产咖啡还要生产甜点,如提拉米苏,抹茶慕斯等,如果按照工厂方法模式进行设计,需要定义提拉米苏类,提拉米苏工厂类,抹茶慕斯类,抹茶慕斯工厂类以及甜品抽象类,出现
类爆炸
情况,其中拿铁咖啡与美式咖啡属于同一产品类型,提拉米苏与抹茶慕斯属于同一产品类型,拿铁咖啡与提拉米苏属于同一产品族假设属于(意大利风味),美式咖啡与抹茶慕斯属于同一产品族假设属于(美式风味),可采用抽象工厂模式,UML类图如下:
- 代码实现如下:
//咖啡抽象类
public abstract class Coffee {
public abstract String getName();
public void addMilk() {
System.out.println("加奶");
}
public void addSugar() {
System.out.println("加糖");
}
}
//具体产品类
public class AmericanCoffee extends Coffee{
@Override
public String getName() {
return "美式咖啡";
}
}
//具体产品类
public class LatteCoffee extends Coffee{
@Override
public String getName() {
return "拿铁咖啡";
}
}
//甜点抽象类
public abstract class Dessert {
abstract public void show();
}
//具体产品类
public class Trimisu extends Dessert{
@Override
public void show() {
System.out.println("提拉米苏");
}
}
//具体产品类
public class MachaMourse extends Dessert{
@Override
public void show() {
System.out.println("抹茶慕斯");
}
}
//抽象工厂类
public interface DessertFactory {
public Coffee createCoffee();
public Dessert createDessert();
}
//具体工厂类
public class AmericanDessertFactory implements DessertFactory{
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
@Override
public Dessert createDessert() {
return new MachaMourse();
}
}
//具体工厂类
public class ItalyDessertFactory implements DessertFactory{
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
@Override
public Dessert createDessert() {
return new Trimisu();
}
}
//客户端
public class CoffeeStore {
private DessertFactory factory;
public void setFactory(DessertFactory factory) {
this.factory = factory;
}
public Coffee orderCoffee() {
Coffee coffee = null;
coffee = factory.createCoffee();
coffee.addSugar();
coffee.addMilk();
return coffee;
}
public Dessert orderDessert() {
Dessert dessert = null;
dessert = factory.createDessert();
dessert.show();
return dessert;
}
}
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CoffeeStore store = new CoffeeStore();
AmericanDessertFactory factory = new AmericanDessertFactory();
store.setFactory(factory);
store.orderCoffee();
store.orderDessert();
}
}
- 如果要新增一个产品族,只需要再加一个对应的工厂类,不需要修改其他类;
- 抽象工厂模式的优点:
- 当产品族中的多个对象,被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象;
- 抽象工厂模式的缺点:
- 当产品族中需要新增一个新的产品时,所有的工厂类都需要进行修改;
- 抽象工厂模式的使用场景:
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,例如电器工厂中的电视机,洗衣机,空调等;
- 系统中有多个产品族,但每次只使用其中的某一产品族时;
- 系统中提供了产品的类库,且所有产品的接口都相同,客户端不依赖产品实例的创建细节与内部结构;
工厂模式的扩展
- 可以通过
简单工厂模式 + 配置文件
的方式解除工厂对象和产品对象的耦合
,在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可; - 第一步:定义配置文件,命名为
bean.properties
,文件内容如下:
american=com.example.sign.AmericanCoffee
latt=com.example.sign.LatteCoffee
- 代码编写:
public class SimpleCoffeeFactory {
//加载配置文件 获取配置文件中类名,并创建该对象进行存储
//1.定义容器 存储对象
private static HashMap map = new HashMap<>();
//2.加载配置文件
static {
Properties p = new Properties();
InputStream is = SimpleCoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
//调用p的load方法 进行配置文件加载
try {
p.load(is);
//从p集合中获取类名 创建对象
Set
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CoffeeStore store = new CoffeeStore();
Coffee coffee = store.orderCoffee("latte");
System.out.println(coffee.getName());
}
}
- 静态成员变量
map
用来存储创建的对象,新增一种对象时,只需要修改配置文件即可;