学习设计模式(更新时间-2022年11月1日)

145 阅读14分钟

工厂模式

  工厂模式分为:简单工厂模式(Simple Factory)、工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)。


简单工厂模式

  重新来回顾一下简单工厂,简单的工厂模式也可以当做是一个静态的工厂,用一个工厂对象创建同一类的对象实例。

  首先我们可以有这么一个场景,比如有小米、华为两个品牌的电视机。两个电视机假设具有播放的功能,那么我播放时候就需要new相关的类,如果有多个相同类似的品牌电视机,就需要不断的实例化,这时候我们就可以抽象出一个电视类,他具有播放功能,新建一个TV接口类。

package cn.jianfreespace;


/**
 * @author Jianfreespace
 * 抽象电视类
 */
public interface TV {


    public void play();
}

  新建小米电视机类、华为电视机类型,实现TV类的方法

package cn.jianfreespace;


/**
 * 小米电视
 */
public class XiaomiTV implements TV {

    @Override
    public void play() {
        System.out.println("播放小米电视机");
    }
}

package cn.jianfreespace;


/**
 * 华为电视机
 */
public class HuweiTV implements TV{
    @Override
    public void play() {
        System.out.println("播放华为电视机");
    }
}

  这时候就一个有一个类来统一管理不同品牌机的实例化。方便解耦,因为在不同场景下,可以A类中通过new的方式实例化了类B,那么A类与B类之间就存在耦合。这个时候如果修改了B类中的代码,那么A类中也需要重新进行修改,简单的几个关联可能工作量不大,但如果多个类依赖那么改动时候就容易出现许多问题。所以就需要用到工厂来统一管理类创建的过程,让调用者不用去关系类创建的过程,只管使用。

package cn.jianfreespace;

public class TvFactory {



    public static TV playTV(String tvName) {

        if ("xiaomi".equals(tvName)) {

            return new XiaomiTV();
        } else if ("huawei".equals(tvName)) {
            return new HuweiTV();
        } else {

            return null;
        }
    }

}

  代码新建了一个TVfactory工厂,提供一个静态方法playTv,返回的是TV类,是XiaomiTV、HuaweiTV的父类,统一管理创建的过程,用户只需要输入创建的品牌名字即可获取实例化后的对象。

package cn.jianfreespace;

public class Main {


    public static void main(String[] args) {
        TV tv;

        tv = TvFactory.playTV("huawei");
        if (tv == null) {
            System.out.println("没有找到该电视机种类");
            return;
        }

        tv.play();



    }
}

  根据输入参数的不同获取不同实例对象。

工厂方法模式

  工厂方法模式是对简单工厂模式中产品的具有化,这样就不必去更改工厂类,如果我有非常多的品牌机型,按照简单工厂模式那么我需要对playTv方法进行修改,无限增加相关的语句,这样会显得非常杂乱。工厂方法模式抽象化一个工厂,相当于总管理一样,然后其中的各个产品的工厂实现这个抽象的工厂,并且对对应的产品进行生产,相当于需要对小米电视进行操作则直接操作小米工厂即可,而如果需要修改产品的相关业务逻辑直接在原有产品类中修改,不用再去修改工厂类。

  新建一个接口工厂,并且具有生产电视的方法,同时新建小米工厂、华为工厂实现TVFactory。而对应的每一个工厂能够具体实现所属的产品,解决了简单工厂模式下需要多产品进行添加操作。

package cn.jianfreespace;


/**
 * @author Jianfreespace
 * 电视机工厂
 */
public interface TVFactory {


    public TV produceTV();
}

package cn.jianfreespace;

public class XiaomiFactory implements TVFactory{


    @Override
    public TV produceTV() {
        System.out.println("生产小米工厂");
        return new XiaomiTV();
    }
}

package cn.jianfreespace;

public class HuaweiFactory implements TVFactory{


    @Override
    public TV produceTV() {
        System.out.println("生产华为电视");
        return new HuaweiTV();
    }
}

  使用反射加载工厂类,实例化一个工厂对象。可以只操作对应工厂就可以实际生产相关产品。

package cn.jianfreespace;





public class Main {
    public static void main(String[] args) {
        try {
            // 初始化抽象产品
            TV tv;
            TVFactory tvFactory;

            Class catClass = Class.forName("cn.jianfreespace.HuaweiFactory");
            Object xiaomiObj = catClass.newInstance();
            tv = (TV) catClass.getMethod("produceTV").invoke(xiaomiObj,null);
            tv.play();

        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

}

抽象工厂模式

  抽象工厂模式属于创建型模式的一种,与工厂方法不同的是抽象工厂针对的是一组产品的生产,是一个产品族。可以理解为在工厂方法模式上新增了一个抽象工厂,抽象了两种不同的产品,然后下属的的产品工厂只是接收总工厂发出的命令来选择不同的工厂进行生产相同的产品族。具体在代码的体现可以理解为,先新建两个抽象产品,比如电视机、电冰箱产品。定义每个产品的抽象工厂。


package cn.jianfreespace;


/**
 * 电视机抽象产品
 */
public interface Television {


    public void play();
}

package cn.jianfreespace;

/**
 * 冰箱-抽象产品类型
 */
public interface Refrigerator {


    /**
     *
     */
    public void adjustTemperature();

}

// 抽象工厂
package cn.jianfreespace.factory;

import cn.jianfreespace.Television;

/**
 * 电视机工厂
 */
public interface TVFactory {


    Television productTv();
}

package cn.jianfreespace.factory;

import cn.jianfreespace.Refrigerator;

/**
 * 电冰箱工厂
 */
public interface RefrigeratorFactory {


    Refrigerator produRefrigerator();
}


  实现具体的产品、小米电视机、小米电冰箱、华为电视机、华为电冰箱。新建四个类分别实现抽象产品类

package cn.jianfreespace;


/**
 * @author Jianfreespace
 */
public class XiaomiTelevision implements Television {

    @Override
    public void play() {
        System.out.println("小米电视机播放中");
    }
}

package cn.jianfreespace;


/**
 * @author Jianfreespace
 * 小米 冰箱具体产品
 */
public class XiaomiRefrigerator implements Refrigerator {

    @Override
    public void adjustTemperature() {
        System.out.println("小米冰箱温度调节中");
    }
}


package cn.jianfreespace;


/**
 * @author Jianfreespace
 */
public class HuaweiTelevision implements Television {


    @Override
    public void play() {
        System.out.println("华为电视机播放中");
    }
}

package cn.jianfreespace;

public class HuaweiRefrigerator implements Refrigerator {

    @Override
    public void adjustTemperature() {
        System.out.println("华为冰箱温度调节中");
    }
}


  实现每个产品工厂生产具体的产品,新建四个工厂,实现电视工厂、冰箱工厂,分别生产小米电视、小米电冰箱、华为电视、华为电冰箱四个具体工厂。

package cn.jianfreespace.factory.impl;

import cn.jianfreespace.Television;
import cn.jianfreespace.XiaomiTelevision;
import cn.jianfreespace.factory.TVFactory;


/**
 * 小米电视机工厂
 */
public class XiaomiTvFacetory implements TVFactory {

    @Override
    public Television productTv() {
        return new XiaomiTelevision();
    }
}


package cn.jianfreespace.factory.impl;

import cn.jianfreespace.Refrigerator;
import cn.jianfreespace.XiaomiRefrigerator;
import cn.jianfreespace.factory.RefrigeratorFactory;

/**
 * 小米电冰箱工厂
 */
public class XiaomiRefrigeratorFactory implements RefrigeratorFactory {

    @Override
    public Refrigerator produRefrigerator() {
        return new XiaomiRefrigerator();
    }
}

package cn.jianfreespace.factory.impl;

import cn.jianfreespace.HuaweiTelevision;
import cn.jianfreespace.Television;
import cn.jianfreespace.factory.TVFactory;

/**
 * 华为电视机工厂
 */
public class HuweiTvFactory implements TVFactory {

    @Override
    public Television productTv() {
        return new HuaweiTelevision();
    }
}

package cn.jianfreespace.factory.impl;

import cn.jianfreespace.HuaweiRefrigerator;
import cn.jianfreespace.Refrigerator;
import cn.jianfreespace.factory.RefrigeratorFactory;

/**
 * 华为电冰箱工厂
 */
public class HuweiRefrigeratorFactory implements RefrigeratorFactory {

    @Override
    public Refrigerator produRefrigerator() {
        return new HuaweiRefrigerator();
    }
}



  新建一个抽象产品工厂、整合每个产品族下的产品,具有两个方法,生产电视、生产冰箱

package cn.jianfreespace.factory;


import cn.jianfreespace.Refrigerator;
import cn.jianfreespace.Television;

/**
 * @author Jianfreespace
 * 抽象工厂-电器工厂类
 */
public interface EFactory {



    /**
     * 抽象生产电视机
     * @return
     */
    public Television produceTelevision();

    /**
     * 抽象生产电冰箱
     */
    public Refrigerator produceRefrigerator();
}

  新建小米总工厂、华为总工厂实现总工厂方法,分别生产对应品牌下的产品。

package cn.jianfreespace.factory.impl;

import cn.jianfreespace.Refrigerator;
import cn.jianfreespace.Television;
import cn.jianfreespace.XiaomiRefrigerator;
import cn.jianfreespace.XiaomiTelevision;
import cn.jianfreespace.factory.EFactory;

public class XiaomiFactory implements EFactory {

    @Override
    public Television produceTelevision() {
        System.out.println("生产小米电视机");
        return new XiaomiTvFacetory().productTv();
    }

    @Override
    public Refrigerator produceRefrigerator() {
        System.out.println("生产小米冰箱");
        return new XiaomiRefrigeratorFactory().produRefrigerator();
    }
}

package cn.jianfreespace.factory.impl;

import cn.jianfreespace.HuaweiRefrigerator;
import cn.jianfreespace.HuaweiTelevision;
import cn.jianfreespace.Refrigerator;
import cn.jianfreespace.Television;
import cn.jianfreespace.factory.EFactory;

public class HuaweiFactory implements EFactory {

    @Override
    public Television produceTelevision() {
        System.out.println("生产华为电视机");
        return new HuweiTvFactory().productTv();
    }

    @Override
    public Refrigerator produceRefrigerator() {
        System.out.println("生产华为冰箱");
        return new HuweiRefrigeratorFactory().produRefrigerator();
    }


}


  只需要通过实例化小米工厂或华为工厂就可以获得产品相关方法的实例。具体实例化如下:

package cn.jianfreespace;

import cn.jianfreespace.factory.EFactory;
import cn.jianfreespace.factory.impl.HuaweiFactory;

public class Main {


    public static void main(String[] args) {
        try {
            EFactory eFactory;
            Television tv;
            Refrigerator refrigerator;

            Class catClass = Class.forName("cn.jianfreespace.factory.impl.HuaweiFactory");

            Object XiaomiObj = catClass.newInstance();
            tv = (HuaweiTelevision)catClass.getMethod("produceTelevision").invoke(XiaomiObj,null);
            tv.play();

            //----------
            HuaweiFactory huaweiFactory = new HuaweiFactory();



        }catch (Exception e) {
            System.out.println(e);
        }

    }



}

  可以基于反射,或者new进行实例化,获取该工厂方法,获得产品的相关实例,而不用关心创建过程。实现了完整的抽象工厂逻辑。

  抽象工厂的优点:可以将产品的生产分离出来,易于改变产品系列,利于产品的一致性,一次生产一系列产品。不利于新增产品,若需要新增产品则需要新增相对应的抽象工厂及工厂实现类都需要增加对应的方法。

单例模式

  设计模式总共分为创建型模式、结构型模式、行为型模式。而单例模式属于创建型模式。单例模式解释是,由类来管理自身的唯一对象,并且这个类提供了访问该对象的方式,可以直接访问,不需要实例化类,如使用关键字 new。其使用场景多是在:文件系统的资源管理器,比如应用的日志等方面,可以不必要多创建对象消耗资源。

  单例模式总共学习了以下几种: 懒汉模式、饿汉模式、线程安全下的单例模式、静态内部类的单例模式、枚举类单例模式。

懒汉模式

  首先新建一个SingletonLazy类,定义了一个静态变量,类型为类。单例模式需要隐藏构造函数,所以需要把构造函数使用private默认为私有。并且给予一个静态方法提供访问该类的唯一实例化对象。懒汉模式下,一个对象的默认null,在调用initialization时会首先判断静态变量是否已被创建完成,若未进行创建,则会新建一个实例兵赋值到静态变量中,然后在返回该实例。此模式为线程不安全,若在多线程下,若多个线程同时访问静态方法,可能会产生类被多次创建的情况。如线程1调用方法,判断为null准备实例时,线程2也进入方法判断为null,那么两个线程将会都实例化次对象。所以懒汉模式为线程不安全。

  值得注意的是,在initialization方法加上synchronize同步锁,可保证线程安全,但因为是在方法上加,所以在多线程访问该方法时会产生阻塞,因此在性能方法会有损耗。

package cn.jianfreespace;


/**
 * 单例模式-懒汉模式
 */
public class SingletonLazy {

    private static SingletonLazy singletonLazy = null;

    /**
     * 私有化构造方法
     */
    private SingletonLazy(){};

    /**
     * 实例化
     */
    public static SingletonLazy initialization() {
        if (singletonLazy == null) {
            singletonLazy = new SingletonLazy();
        }

        return singletonLazy;
    }

}

饿汉模式

  饿汉模式下沿用了懒汉模式的基本框架。有所改变的是,在新建静态变量时,直接实例化了该类,此模式下是线程安全的,没有多线程下带来的多实例问题,但同时也没有延时加载带来的节约资源的好处。

package cn.jianfreespace;

/**
 * 单例模式-饿汉模式
 */
public class SingletonHungry {

    private static SingletonHungry singletonHungry = new SingletonHungry();


    /**
     * 私有化构造函数
     */
    private SingletonHungry(){};

    /**
     * 静态初始方法
     */
    public static SingletonHungry initializeation() {

        return singletonHungry;
    }

}

线程安全下的单例模式

  懒汉模式下线程不安全,所以需要提供一个同步锁来保证该方法只能被一个进程所访问到,具体的实现是使用synchronize关键字。懒汉模式下在方法上加入同步锁可保证线程安全,但会造成性能上的开销,所以提供第二种的优化,使用synchronize在代码段中使用。

  使用同步锁锁实例化的核心代码,可以有效减少部分阻塞。 如果判断变量不为空就不实例化该对象了,若未空,则进入同步锁代码,会先对类进行上锁,保证其他线程无法访问该方法,然后在进行一次判空,这里判空,解释说是onlyOneInstance有可能是null,多个线程依然可以进入到,所以需要在内部进行再次判断。关于使用volatile关键字的原因,解释说是JVM中的指令重排机制,在onlyOneInstance = new SingletonSynchronized();需要执行的步骤有三步,

  • 1.分配内存
  • 2.初始化对象
  • 3.将onlyOneInstance指向分配的地址

  所以在JVM指令重排机制的时,可能会重排为 1>3 >2,在多线程环境下访问onlyOneInstance时,有可能会得到一个未被初始化的实例,而valatile关键字可以阻止JVM的指令重排,从而保证多线程环境下的正常运行。

package cn.jianfreespace;


/**
 * 单例模式-线程安全-双重校验锁
 */
public class SingletonSynchronized {

    private volatile static SingletonSynchronized onlyOneInstance = null;


    /**
     * 构造函数-私有
     */
    private SingletonSynchronized(){};

    /**
     * 初始化实例
     */
    public static SingletonSynchronized getInstance() {
        if (onlyOneInstance == null) {
            synchronized (SingletonSynchronized.class) {
                if (onlyOneInstance == null) {
                    onlyOneInstance = new SingletonSynchronized();
                }
            }
        }

        return onlyOneInstance;
    }
}

静态内部类下的单例模式

  单例模式下,可实现一个静态内部类,静态内部类具有JVM保持的线程安全性问题,同时也具备了延迟加载的好处。在新建SingletonStatic时,在类内部新建一个静态类SigletonHolder,并且在该类中定义一个静态不可修改的变量,并实例化该外部类。然后SingletonStatic使用initialization方法可直接调用SigletonHolder.singletonStatic得到该类的实例,保证其唯一性。

package cn.jianfreespace;


/**
 * 单例模式-静态内部类
 */
public class SingletonStatic {


    /**
     * 构造函数-私有方法
     */
    private SingletonStatic(){};

    /**
     * 静态内部类
     */
    private static class SigletonHolder {
        private static final SingletonStatic singletonStatic  = new SingletonStatic();
    }

    /**
     * 初始化实例化
     */
    public static SingletonStatic initialization() {

        return SigletonHolder.singletonStatic;
    }



}

枚举实现单例模式

  枚举类解释说是单例的最佳实践,原因在于实现比较简单,而且免得复杂的序列化或反射攻击的时候能够防止实例化多次,然而我并不知道什么复杂的序列化,类似与log4j的那个序列化漏洞攻击吧。引用阿里技术论坛的解释为:对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。

  通过代码可以了解到,枚举类其实符合单例模式的特点:构造函数私有,由类来管理创建对象的过程,并且枚举类在编译后不可被继承,添加了final修饰符,不能通过反射、反序列化等被攻击。

package cn.jianfreespace;


/**
 * 单例模式-枚举类
 */
enum SingletonEnum {

    RED{
        @Override
        public void showColor() {
            System.out.println("红色");

        }
    },
    BLUE{
        @Override
        public void showColor() {
            System.out.println("蓝色");

        }
    };


    /**
     * 定义一个抽象方法
     *
     * @return
     */
    public abstract void showColor();

}

建造者模式

  在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,相同的创建过程可创建不同的对象。每个具体建造者相关独立,与其他建造者无关,其他建造者的创建不会影响其他建造者。增加新的具体建造者无需改变原有的类库代码。

  熟悉一下建造者模式,例子:KFC点餐。KFC的套餐是各式各样的,套餐A与B、C都能够自己不同的产品食物。而点餐的人只需要关注选择哪个套餐,而不是关注这个套餐是如何组成的。

  首先新建一个套餐类,套餐类的属性有食物、饮料。不同的食物组成不同的套餐。

package cn.jianfreespace;

/**
 * 套餐类
 */
public class Meal {

    private String food;
    private String drink;

    public String getFood() {
        return food;
    }

    public void setFood(String food) {
        this.food = food;
    }

    public String getDrink() {
        return drink;
    }

    public void setDrink(String drink) {
        this.drink = drink;
    }
}

  需要提供一个套餐建造者,用以控制套餐的生产。建造者可以具体提供不同的套餐类。所以需要对建造者抽象化,并且运行其他建造者继承。抽象类提供创建食物、创建饮料等,并提供该套餐的返回实例。

package cn.jianfreespace;


/**
 * 创建组合类
 */
public abstract  class MealBuilder {

    protected Meal meal = new Meal();

    public abstract void buildFood();

    public abstract void buildDrink();

    public Meal getMeal() {
        return meal;
    }
}

package cn.jianfreespace;


/**
 * 套餐B
 */
public class SubMealBuildB  extends MealBuilder{

    @Override
    public void buildFood() {
        meal.setFood("一个鸡肉卷");
    }

    @Override
    public void buildDrink() {
        meal.setDrink("一杯果汁");
    }
}


  创建一个套餐A、一个套餐B,并继承建造抽象类兵实现父类方法,就可以拥有不同的套餐。

package cn.jianfreespace;


/**
 * 套餐A
 */
public class SubMealBuildA extends MealBuilder {


    @Override
    public void buildFood() {
        meal.setFood("一个鸡腿堡");

    }

    @Override
    public void buildDrink() {
        meal.setDrink("一杯可乐");
    }


}

  提供一个指挥者来控制套餐的创建与否。测试代码main,实例化指挥者类,并初始化参数传入创造的套餐类,若传入B套餐则会自动生产该套餐下的食物,其生成的过程不必关心。

package cn.jianfreespace;


/**
 * 指挥者
 */
public class KFCWaiter {

    private MealBuilder mealBuilder;

    public void setMealBuilder(MealBuilder mealBuilder) {
        this.mealBuilder = mealBuilder;
    }

    public Meal construct() {
        mealBuilder.buildFood();
        mealBuilder.buildDrink();
        return mealBuilder.getMeal();
    }
}

package cn.jianfreespace;

public class Main {
    public static void main(String[] args) {


        KFCWaiter kfc = new KFCWaiter();
        kfc.setMealBuilder(new SubMealBuildB());
        Meal meal = kfc.construct();
        System.out.println(meal.getFood());
        System.out.println(meal.getDrink());

    }
}