设计模式与架构02 -- 单例模式,工厂模式

单例模式

  • 单例模式是最简单的设计模式之一,属于创建型模式,它提供了一种创建对象的最佳方式;
  • 单例模式只涉及到一个单一的类,该类负责创建自己的对象,同时确保只会创建唯一的一个实例对象;
单例模式的实现
  • 单例模式的实现分为两种:

    • 饿汉式:类加载就会导致该单实例对象被创建;
    • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会被创建;
  • 饿汉式单例模式 静态变量方式 案例代码如下:

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类图如下:
image.png
  • 代码实现如下:
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类图如下:
image.png
  • 代码实现如下:
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类图如下:
image.png
  • 代码实现如下:
//抽象产品类
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类图如下:
image.png
  • 代码实现如下:
//咖啡抽象类
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 keys = p.keySet();
            for (Object key: keys) {
                String className = p.getProperty((String) key);
                //通过反射技术创建对象
                Class clazz = Class.forName(className);
                Coffee coffee = (Coffee) clazz.newInstance();
                //将名称与对象 存储到容器中
                map.put((String) key,coffee);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static Coffee createCoffee(String name) {
        //通过名称 获取对象
        return map.get(name);
    }
}

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 用来存储创建的对象,新增一种对象时,只需要修改配置文件即可;

你可能感兴趣的:(设计模式与架构02 -- 单例模式,工厂模式)