美文网首页
设计模式之禅(二) —— 单例、工厂、模板

设计模式之禅(二) —— 单例、工厂、模板

作者: 若琳丶 | 来源:发表于2020-12-24 22:11 被阅读0次

一、单例模式

单例是面试当中最常见的一种设计模式,但是我们在应用中一般都是借助 Spring 指定生成单例还是多例对象,所以手写的情况不多。此处列出几种单例模式的写法。

饿汉式

public class SingleTonTestApp {
    private static SingleTonTestApp singleTon = new SingleTonTestApp();
    private SingleTonTestApp() {
    }
    public static SingleTonTestApp getInstance() {
        return singleTon;
    }
}
  • 优点:不会存在线程安全问题
  • 缺点:如果构造方法中存在过多的处理,会导致该类加载的时候特别慢,因此可能会引起性能问题。如果仅仅进行了类的加载,但没有进行类的应用,会造成比较大的资源浪费

总结
如果一个类,构造函数中的处理不繁杂,并且该类的单例实例一定会应用到,那么就选择饿汉式。

懒汉式

public class SingletonTestApp {
    private static volatile SingletonTestApp singleton = null;
    private SingletonTestApp() {
    }
    public static SingletonTestApp getSingleton() {
        if (singleton == null) {
            synchronized (SingletonTestApp.class) {
                if (singleton == null) {
                    singleton = new SingletonTestApp();
                }
            }
        }
        return singleton;
    }
}

解析
1、 最内层的if语句最容易理解:如果singleton为空,则进行实例化
2、 由于多线程并发存在实例化多个singleton的问题,所以要在最内层if外加一个同步代码块,以保证同一时刻只能有一条线程进行if判断,防止多条线程同时进行实例化
3、 最外层if根本目的在于提高性能:如果singleton不为空,则直接返回singleton。就算是多条线程同时进行singleton是否为空的判断也是没有问题的,这样就无需进入同步代码块排队、实例化,直接返回不为空的singleton。

枚举式

public class SingletonTestApp {
    private SingletonTestApp(){}
    //对外提供一个工厂方法获取实例对象,因为只能通过类名调用,所以用static修饰
    public static SingletonTestApp getInstance() {
        return SingletonEnum.INSTANCE.getSingleton();
    }

    /**
     * 枚举必须是私有的
     * ps:因为枚举类一开始就是有枚举实例(INSTANCE)的,所以里面的方法和成员都不用static去修饰
     */
    private enum SingletonEnum {
        //首先初始化一个枚举实例,目的是为了可以使用这个实例来创建需要单例的对象
        INSTANCE;

        //先声明单例对象的引用,节约资源
        private SingletonTestApp singleton;

        //JVM会保证单例的构造方法只会执行一次,从JVM层面上保证了线程安全
        SingletonEnum() {
            singleton = new SingletonTestApp();
        }

        //返回单例实例对象
        SingletonTestApp getSingleton() {
            return singleton;
        }
    }
}

解析
1、枚举的构造方法从JVM层面保证了线程安全
2、类加载的时候不会去执行枚举的构造函数,而是在调用getInstance的时候,先去实例化枚举对象(走枚举的构造函数),在调用枚举的getSinleton方法。属于懒加载,节约资源

单例模式的拓展

对象除了单例和多例外,还有一种是“有限的多例”。算是单例模式的一种拓展。它要求一个类只能产生两三个对象。


image.png
package com.baowen.nginx.structure.singleton;

import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

public class Emperor {

    /**
     * 定义最多能产生的实例数量
     */
    private static int maxNumOfEmperor = 2;
    /**
     * 每个皇帝都有名字, 使用一个ArrayList来容纳, 每个对象的私有属性
     */
    private static List<String> nameList = new CopyOnWriteArrayList<>();
    /**
     * 定义一个列表, 容纳所有的皇帝实例
     */
    private static List<Emperor> emperorList = new CopyOnWriteArrayList<Emperor>();
    /**
     * 当前皇帝序列号
     */
    private static int countNumOfEmperor = 0;

    //产生所有的对象
    static {
        for (int i = 0; i < maxNumOfEmperor; i++) {
            emperorList.add(new Emperor("皇" + (i + 1) + "帝"));
        }
    }

    private Emperor() {
    }

    /**
     * 传入皇帝名称, 建立一个皇帝对象
     *
     * @param name
     */
    private Emperor(String name) {
        nameList.add(name);
    }

    /**
     * 随机获得一个皇帝对象
     *
     * @return
     */
    public static Emperor getInstance() {
        Random random = new Random();
        //随机拉出一个皇帝, 只要是个精神领袖就成
        countNumOfEmperor = random.nextInt(maxNumOfEmperor);
        return emperorList.get(countNumOfEmperor);
    }

    /**
     * 皇帝发话了
     */
    public static void say() {
        System.out.println(nameList.get(countNumOfEmperor));
    }
}

此处为了保证线程安全,可以使用 来替换书中的 ArrayList 和 Vector,Vector 也可以用,不过从原理上讲效率比较低。

二、工厂方法模式

2.1.1 书中案例

image.png
/* 人类接口 */
public interface Human {
    //每个人种的皮肤都有相应的颜色
    public void getColor();
    //人类会说话
    public void talk();
}

/* 黑种人 */
public class BlackHuman implements Human {
    public void getColor(){
        System.out.println("黑色人种的皮肤颜色是黑色的! ");
    }
    public void talk() {
        System.out.println("黑人会说话, 一般人听不懂。 ");
    }
}

/* 黄种人 */
public class YellowHuman implements Human {
    public void getColor(){
        System.out.println("黄色人种的皮肤颜色是黄色的! ");
    }
    public void talk() {
        System.out.println("黄色人种会说话, 一般说的都是双字节。 ");
    }
}

/* 白种人 */
public class WhiteHuman implements Human {
    public void getColor(){
        System.out.println("白色人种的皮肤颜色是白色的! ");
    }
    public void talk() {
        System.out.println("白色人种会说话, 一般都是但是单字节。 ");
    }
}

/* ------------------------------------------------------------------*/

/* 抽象工厂 */
public abstract class AbstractHumanFactory {
    public abstract <T extends Human> T createHuman(Class<T> c);
}

/* ------------------------------------------------------------------*/
/* 客户端业务类 */
public class HumanFactory extends AbstractHumanFactory {
    public <T extends Human> T createHuman(Class<T> c){
        //定义一个生产的人种
        Human human = null;
        try {
            //产生一个人种
            human = (T)Class.forName(c.getName()).newInstance();
        } catch (Exception e) {
            System.out.println("人种生成错误! ");
        }
        return (T)human;
    }
}

在业务流程中,通过指定需要生成的实现类的类型,工厂就可以生成对应的对象,业务中拿来即用。

2.2 工厂方法的定义

image.png
/* 抽象产品 */
public abstract class Product {
    //产品类的公共方法
    public void method1(){
        //业务逻辑处理
    }
    //抽象方法
    public abstract void method2();
}

/* 具体产品1 */
public class ConcreteProduct1 extends Product {
    public void method2() {
        //业务逻辑处理
    }
}

/* 具体产品2 */
public class ConcreteProduct2 extends Product {
    public void method2() {
        //业务逻辑处理
    }
}

/* 抽象工厂 */
public abstract class Creator {
    /*
    * 创建一个产品对象, 其输入参数类型可以自行设置
    * 通常为String、 Enum、 Class等, 当然也可以为空
    */
    public abstract <T extends Product> T createProduct(Class<T> c);
}

/* 具体工厂 */
public class ConcreteCreator extends Creator {
    public <T extends Product> T createProduct(Class<T> c){
        Product product=null;
        try {
            product = (Product)Class.forName(c.getName()).newInstance();
        } catch (Exception e) {
            //异常处理
        }
        return (T)product;
    }
}

/* 客户端业务类 */
public class Client {
    public static void main(String[] args) {
        Creator creator = new ConcreteCreator();
        Product product = creator.createProduct(ConcreteProduct1.class);
        /*
        * 继续业务处理
        */
    }
}

工厂方法的优点

  • 良好的封装性,客户端对于具体产品的构建过程毫无感知(如:创造一个人时,如何构造内脏和四肢),降低了耦合性。
  • 拓展性好。增加产品时,只需要创建新的产品和新的工厂就可以,对之前的产品逻辑无需做修改。

工厂方法的本质

  • 工厂方法是 new 一个对象的替代品

源码中出现工厂方法的理由

  • 保证一定的封装性,降低耦合

三、抽象工厂

当一个产品在创建时,需要指定多个特点,需要在多个维度进行构建,类似于下图,则用普通的工厂模式就无法满足需求了。

3.1 书中案例

image.png

对此,需要重新定义类图:


image.png
/* -------------------------------第一层人类抽象------------------------------------ */
/* 定义人类接口 */
public interface Human {
    //每个人种都有相应的颜色
    public void getColor();
    //人类会说话
    public void talk();
    //每个人都有性别
    public void getSex();
}

/* -------------------------------第二层人类抽象------------------------------------ */

/* 抽象白种人 */
public abstract class AbstractWhiteHuman implements Human {
    //白色人种的皮肤颜色是白色的
    public void getColor(){
        System.out.println("白色人种的皮肤颜色是白色的! ");
    }
    //白色人种讲话
    public void talk() {
        System.out.println("白色人种会说话, 一般说的都是单字节。 ");
    }
}

/* 抽象黑种人 */
public abstract class AbstractBlackHuman implements Human {
    public void getColor(){
        System.out.println("黑色人种的皮肤颜色是黑色的! ");
    }
    public void talk() {
        System.out.println("黑人会说话, 一般人听不懂。 ");
    }
}

/* 抽象黄种人 */
public abstract class AbstractYellowHuman implements Human {
    public void getColor(){
        System.out.println("黄色人种的皮肤颜色是黄色的! ");
    }
    public void talk() {
        System.out.println("黄色人种会说话, 一般说的都是双字节。 ");
    }
}

/* ----------------人类实现类(具体分男女,此处只举一个例子,黑种人和白种人为类似的代码)-------------- */

/* 黄种女人 */
public class FemaleYellowHuman extends AbstractYellowHuman {
    //黄人女性
    public void getSex() {
        System.out.println("黄人女性");
    }
}

/* 黄种男人 */
public class MaleYellowHuman extends AbstractYellowHuman {
    //黄人男性
    public void getSex() {
        System.out.println("黄人男性");
    }
}

/* -------------------------------定义工厂------------------------------------ */
/* 抽象工厂 */
public interface HumanFactory {
    //制造一个黄色人种
    public Human createYellowHuman();
    //制造一个白色人种
    public Human createWhiteHuman();
    //制造一个黑色人种
    public Human createBlackHuman();
}

/* 女性工厂 */
public class FemaleFactory implements HumanFactory {
    //生产出黑人女性
    public Human createBlackHuman() {
        return new FemaleBlackHuman();
    }
    //生产出白人女性
    public Human createWhiteHuman() {
        return new FemaleWhiteHuman();
    }
    //生产出黄人女性
    public Human createYellowHuman() {
        return new FemaleYellowHuman();
    }
}

/* 男性工厂 */
public class MaleFactory implements HumanFactory {
    //生产出黑人男性
    public Human createBlackHuman() {
        return new MaleBlackHuman();
    }
    //生产出白人男性
    public Human createWhiteHuman() {
        return new MaleWhiteHuman();
    }
    //生产出黄人男性
    public Human createYellowHuman() {
        return new MaleYellowHuman();
    }
}

/* 客户端业务类 */
public class NvWa {
    public static void main(String[] args) {
        //第一条生产线, 男性生产线
        HumanFactory maleHumanFactory = new MaleFactory();
        //第二条生产线, 女性生产线
        HumanFactory femaleHumanFactory = new FemaleFactory();
        //生产线建立完毕, 开始生产人了:
        Human maleYellowHuman = maleHumanFactory.createYellowHuman();
        Human femaleYellowHuman = femaleHumanFactory.createYellowHuman();
        System.out.println("---生产一个黄色女性---");
        femaleYellowHuman.getColor();
        femaleYellowHuman.talk();
        femaleYellowHuman.getSex();
        System.out.println("\n---生产一个黄色男性---");
        maleYellowHuman.getColor();
        maleYellowHuman.talk();
        maleYellowHuman.getSex();
        /*
        * ......
        * 后面继续创建
        */
    }
}

3.2 抽象工厂的定义

image.png
/*----------------------------------产品A的抽象和具体定义------------------------------------*/
/* 抽象产品 */
public abstract class AbstractProductA {
    //每个产品共有的方法
    public void shareMethod(){}
    //每个产品相同方法, 不同实现
    public abstract void doSomething();
}

public class ProductA1 extends AbstractProductA {
    public void doSomething() {
        System.out.println("产品A1的实现方法");
    }
}

public class ProductA2 extends AbstractProductA {
    public void doSomething() {
        System.out.println("产品A2的实现方法");
    }
}

/*----------------------------------产品B的抽象和具体定义------------------------------------*/
public abstract class AbstractProductB {
    //每个产品共有的方法
    public void shareMethod(){}
    //每个产品相同方法, 不同实现
    public abstract void doSomething();
}

public class ProductB1 extends AbstractProductB {
    public void doSomething() {
        System.out.println("产品A1的实现方法");
    }
}

public class ProductB2 extends AbstractProductB {
    public void doSomething() {
        System.out.println("产品A2的实现方法");
    }
}

/*----------------------------------工厂的抽象和具体定义------------------------------------*/
public abstract class AbstractCreator {
    //创建A产品家族
    public abstract AbstractProductA createProductA();
    //创建B产品家族
    public abstract AbstractProductB createProductB();
}

public class Creator1 extends AbstractCreator {
    //只生产产品等级为1的A产品
    public AbstractProductA createProductA() {
        return new ProductA1();
    }
    //只生产产品等级为1的B产品
    public AbstractProductB createProductB() {
        return new ProductB1();
    }
}

public class Creator2 extends AbstractCreator {
    //只生产产品等级为2的A产品
    public AbstractProductA createProductA() {
        return new ProductA2();
    }
    //只生产产品等级为2的B产品
    public AbstractProductB createProductB() {
        return new ProductB2();
    }
}


public class Client {
    public static void main(String[] args) {
        //定义出两个工厂
        AbstractCreator creator1 = new Creator1();
        AbstractCreator creator2 = new Creator2();
        //产生A1对象
        AbstractProductA a1 = creator1.createProductA();
        //产生A2对象
        AbstractProductA a2 = creator2.createProductA();
        //产生B1对象
        AbstractProductB b1 = creator1.createProductB();
        //产生B2对象
        AbstractProductB b2 = creator2.createProductB();
        /*
        * 然后在这里就可以为所欲为了...
        */
    }
}

注意:
以上代码,以及书中“女娲造人”的案例中,都有使用到抽象类。使用抽象类的目的,主要是为了针对实现类可以共享的方法(shareMethod()方法,以及getColor()、talk() 方法),如果没有需要复用的方法,直接用接口也可以。

四、模板方法模式

模板方法比较好理解,这里直接上定义:

4.1 定义

image.png
/* 定义模板类 */
public abstract class AbstractClass {
    //基本方法
    protected abstract void doSomething();
    //基本方法
    protected abstract void doAnything();
    //钩子方法
    protected boolean isNeedDo() {
        return true;
    }
    //模板方法
    public void templateMethod(){
        /*
        * 调用基本方法, 完成相关的逻辑
        */
        this.doAnything();
        if (this.isNeedDo()) {
          this.doSomething();
        }
    }
}

/* 定义子类 */
public class ConcreteClass1 extends AbstractClass {
    //实现基本方法
    protected void doAnything() {
        //业务逻辑处理
    }
    protected void doSomething() {
        //业务逻辑处理
    }
}
public class ConcreteClass2 extends AbstractClass {//实现基本方法
    protected void doAnything() {
        //业务逻辑处理
    }
    protected void doSomething() {
        //业务逻辑处理
    }
    protected  abstract boolean isNeedDo() {
          return false;
    }
}

/* 客户端业务类 */
public class Client {
    public static void main(String[] args) {
        AbstractClass class1 = new ConcreteClass1();
        AbstractClass class2 = new ConcreteClass2();
        //调用模板方法
        class1.templateMethod();
        class2.templateMethod();
    }
}
  • 抽象模板中的基本方法尽量设计为protected类型, 符合迪米特法则, 不需要暴露的属性或方法尽量不要设置为protected类型。 实现类若非必要, 尽量不要扩大父类中的访问权限
  • 外界条件改变, 影响到模板方法的执行。 在我们的抽象类中isNeedTo的返回值就是影响了模板方法的执行结果, 该方法就叫做钩子方法(Hook Method) 。 有了钩子方法模板方法模式才算完美

优点:

  • 封装不变部分, 扩展可变部分
  • 提取公共部分代码, 便于维护
  • 行为由父类控制, 子类实现

相关文章

  • 设计模式

    常用的设计模式有,单例设计模式、观察者设计模式、工厂设计模式、装饰设计模式、代理设计模式,模板设计模式等等。 单例...

  • 设计模式之禅(二) —— 单例、工厂、模板

    一、单例模式 单例是面试当中最常见的一种设计模式,但是我们在应用中一般都是借助 Spring 指定生成单例还是多例...

  • 设计模式系列-抽象工厂模式

    JAVA设计模式系列: 单例模式 观察者模式 模板方法模式 简单工厂模式 抽象工厂模式 抽象工厂模式 定义 抽象工...

  • 设计模式一、单例模式

    系列传送门设计模式一、单例模式设计模式二、简单工厂模式设计模式三、工厂模式设计模式四、抽象工厂模式 简单单例(推荐...

  • 设计模式系列-简单工厂模式

    JAVA设计模式系列: 单例模式 观察者模式 模板方法模式 简单工厂模式 定义 简单工厂模式又叫做静态工厂方法模式...

  • 设计模式四、抽象工厂模式

    系列传送门设计模式一、单例模式设计模式二、简单工厂模式设计模式三、工厂模式设计模式四、抽象工厂模式 抽象工厂模式 ...

  • 设计模式三、工厂模式

    系列传送门设计模式一、单例模式设计模式二、简单工厂模式设计模式三、工厂模式设计模式四、抽象工厂模式 工厂模式 在一...

  • php 工厂模式

    承接上篇php模式设计之 单例模式,(虽然好像关系不大)。今天讲述第二种基础的模式设计——工厂模式。 那么何为工厂...

  • Tomcat中设计模式

    1 Tomcat中设计模式 在Tomcat中用了很多设计模式,如模板模式、工厂模式和单例模式等一些常用的设计模式,...

  • 设计模式

    常见的设计模式有哪些? 常见的工厂模式,代理模式,模板方法模式,责任链模式,单例模式,包装设计模式,策略模式等。手...

网友评论

      本文标题:设计模式之禅(二) —— 单例、工厂、模板

      本文链接:https://www.haomeiwen.com/subject/slsvnktx.html