设计模式之创建型模式

169 阅读13分钟

设计模式(Design pattern),提供了在软件开发过程中面临的一些问题的最佳解决方案,是Java开发者必修的一门课程。本文主要介绍设计模式中的5种创建型模式

一.单例模式

单例模式,顾名思义就是只有一个实例,并且她自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。下面我们来看下有哪几种实现方式吧。

1.饿汉式

在程序启动时就直接构造对象 并给予公共的获取方式

代码如下

/**
 * 饿汉式单例模式
 */
public class Hungry {

    //私有的不可变的静态对象
    private final static Hungry HUNGRY = new Hungry();
    //私有化构造器
    private Hungry() {
    }
    //公共的获取对象方法
    public static Hungry getHungry(){
        return  HUNGRY;
    }
}

饿汉式的缺点

在因为程序启动会就创建对象 会造成内存的浪费

2.基础的懒汉式(单线程可用)

因为饿汉式回浪费内存 所以饿汉式进行改造 在获取对象时 判断对象是否存在 如果不存在 则创建后获取

/**
 * 懒汉式的单例模式
 */
public class Lazy {
    //私有的静态的对象
    private static Lazy lazy;
    //私有的构造器
    private Lazy(){}
    //公开的获取对象的方法
    public static Lazy getLazy1(){
        if (lazy == null){
            lazy = new Lazy();
        }
        return lazy;
    }
}

使用如下方法测试

public static void test1(){
        Lazy lazy1 = getLazy1();
        Lazy lazy2 = getLazy1();
        System.out.println(lazy1);
        System.out.println(lazy2);
    }

输出结果:

证明在单线程情况下是可用的

我们再来用多线程测试一下

public static void test2(){
        for (int i = 0; i < 10 ; i++) {
            new Thread(()->{//此处使用lambda表达式
                Lazy lazy=Lazy.getLazy();
                System.out.println(lazy);
            }).start();
        }
    }

运行后测试结果 有时候会有不同的对象 说明在多线程的情况下该方法不行

3.多线程下的单例模式

第一种:首先可以使用synchronized关键字 将公共的获取对象方法枷锁

public static synchronized Lazy getLazy2(){
        if (lazy == null){
            lazy = new Lazy();
        }
        return lazy;
    }

使用和刚刚一样的方法测试

public static void test3(){
        for (int i = 0; i < 10 ; i++) {
            new Thread(()->{//此处使用lambda表达式
                Lazy lazy=Lazy.getLazy2();
                System.out.println(lazy);
            }).start();
        }
    }

经过多次测试都是一个对象说明该方法有效

但是大家都知道使用synchronized这个关键字效率不高

//测试synchronized锁下 1000个线程循环100000次获取对象的速度
    public static void test4() throws InterruptedException {

        long start = System.currentTimeMillis();
        //用于阻塞当前线程
       // 是通过一个计数器来实现的,计数器的初始值是线程的数量。
       // 每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
        CountDownLatch countDownLatch = new CountDownLatch(1000);
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{//此处使用lambda表达式
                for (int j = 0; j < 100000; j++) {
                    Lazy lazy=Lazy.getLazy2();
                }
                countDownLatch.countDown();//循环执行完毕 里面的线程数-1
            }).start();
        }
        countDownLatch.await();//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }

测试结果使用了1025毫秒!

我们用一种更加有效率的方式来加锁

双重检测锁模式的懒汉式单例 DCL模式

 //使用volatile修饰的原子性私有静态lazy对象
    private volatile static Lazy lazy1;
    
    //双重检测锁模式的懒汉式单例 DCL模式
    public static  Lazy getLazy3(){
        if (lazy1 == null){
            synchronized (Lazy.class){//将类对象锁定 其他线程无法获取类
                if (lazy1 == null){
                    lazy1 = new Lazy();
                }
            }
        }
        return lazy1;
    }
/*
    在对象创建时 会有以下几个步骤
    1.分配内存空间
    2.执行构造方法 初始化对象
    3.把这个对象指向这个空间
    但是这个步骤不是确定的
    123的情况下不会出错
    假如线程A的执行顺序是132 当A线程执行完3还没执行2的时候
    线程B也加入进来 此时判断lazy是否为空时 会认为是非空,但是lazy对象此时是空
    所以需要假如volatile特征符 禁止指令重排,这样必须用123的顺序执行
     */
   //测试DCL模式下多线程单例
 public static void test5(){
        for (int i = 0; i < 10 ; i++) {
            new Thread(()->{//此处使用lambda表达式
                Lazy lazy=Lazy.getLazy3();
                System.out.println(lazy);
            }).start();
        }
    }
//测试DCL模式下多线程单例
public static void test6() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1000);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{//此处使用lambda表达式
                for (int j = 0; j < 100000; j++) {
                    Lazy lazy=Lazy.getLazy3();
                }
                countDownLatch.countDown();
            }).start();
        }
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }

测试耗时为165

由此可见 DCL模式比使用synchronized关键字的效率高很多

4.静态内部类的单例模式

public class Holder {

    private Holder(){}

    public static  Holder getInstance(){
        return InnerClass.HOLDER;
    }
    //静态内部类
    static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

5.枚举类的单例模式

java中有个无敌的存在 那就是反射. 我们尝试用反射来破坏单例模式

Lazy lazy = Lazy.getLazy3();
        //获取空参构造器
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
        //获取私有的
        declaredConstructor.setAccessible(true);
        //使用无参构造器
        Lazy lazy2 = declaredConstructor.newInstance();
        System.out.println(lazy);
        System.out.println(lazy2);

用这段代码就可以创建出两个不同的对象

当然可以修改单例模式来防止 但是又会有新的反射方式来破坏

如何防止用反射的方式破坏呢?看newInstance这个方法的源码

红框那句话说明当前类如果是枚举类 就无法创建对象

使用枚举类来创建单例对象

//枚举类 枚举是什么 枚本身也是一个class类
public enum EnumSingle {

    INSTANCE ;

    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}

写一个主方法来测试一下

        EnumSingle enumSingle1 = EnumSingle.INSTANCE;
        EnumSingle enumSingle = EnumSingle.getInstance();
        System.out.println(enumSingle1);
        System.out.println(enumSingle);

这是输出结果

我们尝试用反射来破坏一下

EnumSingle enumSingle1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle2 = declaredConstructor.newInstance();
        System.out.println(enumSingle1);
        System.out.println(enumSingle2);

此时运行报错没有找到空参构造器 和我们预料的报错不一样

用官方的反编译工具查看不出问题

用jad反编译工具来看

构造器是有参数的 一个String类 一个int类

重新修改代码

Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle2 = declaredConstructor.newInstance();
        System.out.println(enumSingle1);
        System.out.println(enumSingle2);

执行结果如下

可以看到和上面预期的结果一样 枚举类的单例模式完美

二.工厂模式

工厂模式的核心本质:

实例化对象不使用new,用工厂方法代替,

将选择实现类 创建对象统一管理和控制 从而将调用者和我们的实现类解耦

1.简单工厂模式(静态工厂)

用来生同一等级结构中的任意产品 新增产品需要修改代码

创建一个汽车类的接口和两个汽车的实现类

/**
 * 汽车接口
 */
public interface Car {

    void name();
}

宝马实现类

public class BWM implements Car{
    @Override
    public void name() {
        System.out.println("别摸我");
    }
}

特斯拉实现类

public class Tesla implements Car{
    @Override
    public void name() {
        System.out.println("特斯拉");
    }
}

创建一个消费者类,模拟获取新车的步骤

public class Customer {
    public static void main(String[] args) {
        //使用原始的方法获取车
        Car car = new BWM();
        Car car2 = new Tesla();
        car.name();
        car2.name();
    }
}

这是最原始的获取方法 但这样需要需要了解接口 和 所有的实现类

我们使用一个Car工厂来解决这个问题,创建一个CarFactory类

public class CarFactory {
    public static  Car getCar(String car){
        if(car.equals("宝马")){
            return new BWM();
        }else if( car.equals("特斯拉")){
            return new Tesla();
        }else return null;
    }
}

此时我们获取车只需要调用静态方法就可以了 无需关注实现类

public class Customer {
    public static void main(String[] args) {
        //使用原始的方法获取车
//        Car car = new BWM();
//        Car car2 = new Tesla();
        //使用工厂模式获取车
        Car car = CarFactory.getCar("宝马");
        Car car2 = CarFactory.getCar("特斯拉");
        car.name();
        car2.name();
    }
}

这个工厂解决了获取车的问题,但是如果我们需要新的车 那么需要修改工厂方法

这个是模型图

比如新增 一个本田车 不修改代码 无法通过工厂获取

public class Honda implements Car {
    @Override
    public void name() {
        System.out.println("本田");
    }
}

2.工厂方法模式(动态工厂)

用来生产同一等级结构中的固定产品,可以随意增任意产品

上述的几个类不变car接口和实现类不变, 新建一个CarFactory 有一个获取车的方法

public interface CarFactory {
    Car getCar();
}

然后实现宝马和特斯拉的工厂

public class BMWFactory implements CarFactory {
    @Override
    public Car getCar() {
        return new BWM();
    }
}
public class TeslaFactory implements CarFactory {
    @Override
    public Car getCar() {
        return new Tesla();
    }
}

测试

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

        //使用方法工厂模式获取车
        Car car = new BMWFactory().getCar();
        Car car2 = new TeslaFactory().getCar();

        car.name();
        car2.name();
    }
}

如果此时新增一个本田车 我们在可以实现一个本田工厂来获取,而不需要修改之前代码的来获取

public class Honda implements Car {
    @Override
    public void name() {
        System.out.println("本田");
    }
}
public class HondaFactory implements CarFactory {
    @Override
    public Car getCar() {
        return new Honda();
    }
}

此时的结构模型图

虽然解决了问题 但是

代码复杂度 结构复杂度 编程复杂度 管理复杂度 都是简单工厂模式更好

根据设计原则 :工厂方法模式更优

但是实际业务中: 简单工厂模使用更多

三.抽象工厂模式

围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工工厂的工厂

抽象工程模式提供了一个创建一系列或者相互依赖对象的接口 无需制定他们的具体类型

抽象工厂模式的简易图

在分为产品种类和产品品牌来理解 产品种类有手机 电视 产品品牌 有 华为 小米

在IFactory中有获取手机和电视的抽象方法 手机和电视接口有各自的实现类

华为工厂可以实现获取华为手机和华为电视的方法 小米工厂同理

代码实现

//手机接口
public interface IPhone {
    void open();
    void close();
    void bilibli();
    void palyGame();
}
//电视接口
public interface ITV {
    void open();
    void close();
    void aiqiyi();
    void openDoor();
}

//抽象工厂接口
public interface IFactory {
    IPhone getPhone();
    ITV getTv();
}
//华为手机实现类
public class HuaweiPhone implements IPhone{
    @Override
    public void open() {
        System.out.println("华为手机开机了");
    }

    @Override
    public void close() {
        System.out.println("华为手机关机了");
    }

    @Override
    public void bilibli() {
        System.out.println("华为手机看哔哩哔哩");
    }

    @Override
    public void palyGame() {
        System.out.println("华为手机玩游戏");
    }
}
//华为电视实现类
public class HuaweiTV implements ITV{
    @Override
    public void open() {
        System.out.println("华为电视开机了");
    }

    @Override
    public void close() {
        System.out.println("华为电视关机了");
    }

    @Override
    public void aiqiyi() {
        System.out.println("华为电视看爱奇艺了");
    }

    @Override
    public void openDoor() {
        System.out.println("华为电视控制开门了");
    }
}
//华为工厂实现类
public class HuaweiFactory implements IFactory{
    @Override
    public IPhone getPhone() {
        return new HuaweiPhone();
    }

    @Override
    public ITV getTv() {
        return new HuaweiTV();
    }
}
//小米手机实现类
public class XiaomiPhone implements IPhone {
    @Override
    public void open() {
        System.out.println("小米手机开机了");
    }

    @Override
    public void close() {
        System.out.println("小米手机关机了");
    }

    @Override
    public void bilibli() {
        System.out.println("小米手机看哔哩哔哩");
    }

    @Override
    public void palyGame() {
        System.out.println("小米手机玩游戏");
    }
}
//小米电视实现类
public class XiaomiTV implements ITV {
    @Override
    public void open() {
        System.out.println("小米电视开机了");
    }

    @Override
    public void close() {
        System.out.println("小米电视关机了");
    }

    @Override
    public void aiqiyi() {
        System.out.println("小米电视看爱奇艺了");
    }

    @Override
    public void openDoor() {
        System.out.println("小米电视控制开门了");
    }
}
//小米工厂实现类
public class XiaomiFactory implements IFactory{
    @Override
    public IPhone getPhone() {
        return new XiaomiPhone();
    }

    @Override
    public ITV getTv() {
        return new XiaomiTV();
    }
}
//测试
public class Customer {
    public static void main(String[] args) {

        System.out.println("购买华为系列产品--------------");
        IFactory iFactory = new HuaweiFactory();
        IPhone phone = iFactory.getPhone();
        phone.bilibli();
        phone.open();
        ITV tv = iFactory.getTv();
        tv.aiqiyi();
        tv.close();

        System.out.println("购买小米系列产品---------------");
        IFactory iFactory1 = new XiaomiFactory();
        IPhone phone1 = iFactory1.getPhone();
        phone1.bilibli();
        phone1.open();
        ITV tv1 = iFactory1.getTv();
        tv1.aiqiyi();
        tv1.close();

    }
}

输出结果

四.建造者模式

建造者模式提供了一种创建对象的最佳方式

定义 :讲一个复杂对象的构建与发的表示粉类,是的同乡的构建过程可以创建不同的表示

主要作用: 在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象.

建造者模型

1.demo1 需要指挥者的模式

//抽象的构造器
public abstract class Builder {

    abstract void setA();
    abstract void setB();
    abstract void setC();
    abstract void setD();

    abstract Product getProduct();
}
//-----------------------------------------------------------------------
//产品
public class Product {

    private String shuxingA;
    private String shuxingB;
    private String shuxingC;
    private String shuxingD;

    public String getShuxingA() {
        return shuxingA;
    }

    @Override
    public String toString() {
        return "Product{" +
                "shuxingA='" + shuxingA + '\'' +
                ", shuxingB='" + shuxingB + '\'' +
                ", shuxingC='" + shuxingC + '\'' +
                ", shuxingD='" + shuxingD + '\'' +
                '}';
    }

    public void setShuxingA(String shuxingA) {
        this.shuxingA = shuxingA;
    }

    public String getShuxingB() {
        return shuxingB;
    }

    public void setShuxingB(String shuxingB) {
        this.shuxingB = shuxingB;
    }

    public String getShuxingC() {
        return shuxingC;
    }

    public void setShuxingC(String shuxingC) {
        this.shuxingC = shuxingC;
    }

    public String getShuxingD() {
        return shuxingD;
    }

    public void setShuxingD(String shuxingD) {
        this.shuxingD = shuxingD;
    }

}
//-----------------------------------------------------------------------
//真正干活的构造器
public class Worker extends Builder {
	
    private Product product ;

    public Worker() {
        this.product =new Product();
    }

    @Override
    void setA() {
        System.out.println("设置属性A");
        product.setShuxingA("属性A");
    }

    @Override
    void setB() {
        System.out.println("设置属性B");
        product.setShuxingB("属性B");
    }

    @Override
    void setC() {
        System.out.println("设置属性C");
        product.setShuxingC("属性C");
    }

    @Override
    void setD() {
        System.out.println("设置属性D");
        product.setShuxingD("属性D");
    }

    @Override
    Product getProduct() {
        return product;
    }
}
//-----------------------------------------------------------------------
//指挥核心
public class Director {

    public Product build(Builder bulider){
        bulider.setA();
        bulider.setB();
        bulider.setC();
        bulider.setD();
      return  bulider.getProduct();
    }
}
//-----------------------------------------------------------------------
//测试
public class Test {
    public static void main(String[] args) {

        Director director = new Director();
        Worker worker = new Worker();
        Product build = director.build(worker);
        System.out.println(build);
    }

}

2.demo2 去掉指挥核心 将建造权给使用者

public abstract class Builder {

    abstract Builder setA(String a);
    abstract Builder setB(String b);
    abstract Builder setC(String c);
    abstract Builder setD(String d);

    abstract Product getProduct();
}
//-------------------------------------------------------
public class Product {

    private String shuxingA = "原始属性A";
    private String shuxingB = "原始属性B";
    private String shuxingC = "原始属性C";
    private String shuxingD = "原始属性D";

    public String getShuxingA() {
        return shuxingA;
    }

    @Override
    public String toString() {
        return "Product{" +
                "shuxingA='" + shuxingA + '\'' +
                ", shuxingB='" + shuxingB + '\'' +
                ", shuxingC='" + shuxingC + '\'' +
                ", shuxingD='" + shuxingD + '\'' +
                '}';
    }

    public void setShuxingA(String shuxingA) {
        this.shuxingA = shuxingA;
    }

    public String getShuxingB() {
        return shuxingB;
    }

    public void setShuxingB(String shuxingB) {
        this.shuxingB = shuxingB;
    }

    public String getShuxingC() {
        return shuxingC;
    }

    public void setShuxingC(String shuxingC) {
        this.shuxingC = shuxingC;
    }

    public String getShuxingD() {
        return shuxingD;
    }

    public void setShuxingD(String shuxingD) {
        this.shuxingD = shuxingD;
    }


}
//-----------------------------------------------------------------------
public class Worker extends Builder {
    
    private Product product;

    public Worker(){
        product = new Product();
    }

    @Override
    Builder setA(String a) {
        product.setShuxingA(a);
        return this;
    }

    @Override
    Builder setB(String b) {
        product.setShuxingB(b);
        return this;
    }

    @Override
    Builder setC(String c) {
        product.setShuxingC(c);
        return this;
    }

    @Override
    Builder setD(String d) {
        product.setShuxingD(d);
        return this;
    }

    @Override
    Product getProduct() {
        return product;
    }
}
//-----------------------------------------------------------------------
public class Test {
    public static void main(String[] args) {
        Worker worker = new Worker();
        Product product = worker.setA("属性A").setB("属性B").getProduct();
        System.out.println(product.toString());
    }
}

五.原型模式

原型模式就是使用克隆方法来获取对象

实体类需要实现Cloneable接口 重写clone方法

1.demo1 浅克隆

package prototype.demo1;

import java.util.Date;
import java.util.Objects;

public class Dog implements Cloneable{
    private String name;
    private Date birthday;

    public Dog() {
    }

    public Dog(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}';
    }

}
//-------------------------------------------
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date = new Date();
        Dog d1 = new Dog("泰迪", date);
        Dog d2 = (Dog) d1.clone();
        System.out.println("d1:"+d1+"+hash:"+d1.hashCode());
        System.out.println("d2:"+d2+"+hash:"+d2.hashCode());
        System.out.println("修改d2---------------------");
        d2.setName("京巴");
        date.setTime(123654897);
        System.out.println("d1:"+d1+"+hash:"+d1.hashCode());
        System.out.println("d2:"+d2+"+hash:"+d2.hashCode());
    }
}
输出结果
/**
d1:Dog{name='泰迪', birthday=Tue May 05 16:43:29 CST 2020}+hash:1735600054
d2:Dog{name='泰迪', birthday=Tue May 05 16:43:29 CST 2020}+hash:21685669
修改d2---------------------
d1:Dog{name='泰迪', birthday=Fri Jan 02 18:20:54 CST 1970}+hash:1735600054
d2:Dog{name='京巴', birthday=Fri Jan 02 18:20:54 CST 1970}+hash:21685669
**/

发现两个dog的date指引是同一个 这样会有问题 当修改date时,d2的属性也会修改 我们尝试一下深克隆

2.demo2 深克隆

将克隆方法修改
@Override
    protected Object clone() throws CloneNotSupportedException {
        Object clone = super.clone();
        Dog dog = (Dog) clone;
        Date time = (Date) this.birthday.clone();
        dog.setBirthday(time);
        return clone;
    }
    测试结果
/**
d1:Dog{name='泰迪', birthday=Tue May 05 16:48:07 CST 2020}+hash:1735600054
d2:Dog{name='泰迪', birthday=Tue May 05 16:48:07 CST 2020}+hash:21685669
修改d2---------------------
d1:Dog{name='泰迪', birthday=Fri Jan 02 18:20:54 CST 1970}+hash:1735600054
d2:Dog{name='京巴', birthday=Tue May 05 16:48:07 CST 2020}+hash:21685669
**/

这样解决了d2的也会修改的问题