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

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

作者: 若琳丶 | 来源:发表于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) 。 有了钩子方法模板方法模式才算完美

    优点:

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

    相关文章

      网友评论

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

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