设计模式之创建型模式

83 阅读9分钟

oop7大原则

  • 开闭原则:对扩展开放,对修改关闭
  • 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立
  • 依赖倒置原则:面向接口编程,不要面向实现编程
  • 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性
  • 接口隔离原则:要为各个类建立他们需要的专用接口
  • 迪米特法则:只与你的直接朋友交谈,不跟”陌生人“说话
  • 合成复用原则: 尽量先使用组合或者聚合等关联关系来实现,其次才考虑继承关系来实现

一、创建型模式

1、单例模式

  1. 核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
  2. 应用场景:Windows中的任务管理器;数据库连接池;项目中的配置文件类等等。
  3. 优点:减少了系统性能开销。
  • 常见场景:
    • Windows的任务管理器
    • Windows的回收站
    • 项目中,读取配置文件的类,一般也只有一个对象,没必要每次都去new对象读取
    • 网站的计数器一般也会采用单例模式,可以保证同步
    • 数据库连接池的设计一般也是单例模式
    • 在Servlet编程中,每个Servlet也是单例的
    • 在Spring中,每一个Bean默认就是单例的
    • ......

重要的原则:构造器私有

1、饿汉式 (线程安全,调用效率高。但是,不能延时加载)

package com.znd.single;

//饿汉式单例
public class Hungry {

    //饿汉式浪费内存
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];

    private Hungry(){

    }

    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return  HUNGRY;
    }
}

类初始化时立即加载,类初始化时,处于天然的线程安全模式,因此线程安全,方法没有同步,效率高。而且可能存在浪费内存的问题

2、DCL懒汉式(单例对象延迟加载)

真正用的时候才加载,资源利用率高了,但每次调用getInstance()方法都要同步,并发效率低。

package com.znd.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

//懒汉式单例
//道高一尺魔高一丈
public class LazyMan {
    //红绿灯
    private static boolean znd = false;

    private LazyMan(){
        if (znd==false){
            znd=true;
        }else {
            throw new RuntimeException("不要试图用反射破坏异常");
        }
//        synchronized (LazyMan.class){
//            if (lazyMan!=null){
//                throw new RuntimeException("不要试图用反射破坏异常");
//            }
//        }
    }

    //volatile必免指令重排
    private volatile static LazyMan lazyMan;

    //双重检测锁模式的懒汉式单例 DCL 懒汉式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan = new LazyMan(); //不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }
    //多线程并发
//    public static void main(String[] args) {
//        for (int i = 0; i < 10; i++) {
//            new Thread(()->{
//                lazyMan.getInstance();
//            }).start();
//        }
//    }
    /**
     * 1、分配内存空间
     * 2、执行构造方法,初始化对象
     * 3、把这个对象指向这个空间
     *
     * 指令重排
     * 123
     * 132 A
     *     B //次时lazyman还没有完成构造
     */

    // 反射!
    public static void main(String[] args) throws Exception {
       // LazyMan instance1 = LazyMan.getInstance();
        Field znd = LazyMan.class.getDeclaredField("znd");
        znd.setAccessible(true);

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance2 = declaredConstructor.newInstance();

        znd.set(instance2,false);

        LazyMan instance3 = declaredConstructor.newInstance();
      //  System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance3);
    }

}

3、静态内部类

外部类没有static属性,则不会像饿汉式那样立即加载对象,只有真正调用getInstance(),才会加载静态内部类。加载 类时是线程安全的,兼并了并发高效调用和延迟加载的优势。

package com.znd.single;

//静态内部类
public class Holder {

    private Holder(){

    }

    private static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

单例不安全,因为有反射

4、枚举

实现较简单,枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞。

缺点是无延迟加载。

package com.znd.single;

import java.lang.reflect.Constructor;

//enum 是什么? 本身也是一个类
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }

}
class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        //Exception in thread "main" java.lang.NoSuchMethodException: com.znd.single.EnumSingle.<init>()
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

枚举的最终反编译源码

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingle.java

package com.znd.single;


public final class EnumSingle extends Enum
{

    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }

    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(com/znd/single/EnumSingle, name);
    }

    private EnumSingle(String s, int i)
    {
        super(s, i);
    }

    public EnumSingle getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];

    static 
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }
}

2、工厂模式

OOP七大原则

  • 开闭原则:一个软件的实体应当对扩展开放,对修改关闭
  • 依赖倒转原则:要针对接口编程,不要正对实现编程
  • 迪米特法则:只与你直接的朋友通信,而避免和陌生人通信

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

package com.znd.factory.simple;

public class CarFactory {

    public static Car getCar(String car) {

        //方法一
        if ("五菱".equals(car)){
            return new WuLing();
        }else if ("特斯拉".equals(car)){
            return new Tesla();
        }else {
            return null;
        }
    }

    //方法二
    public static Car getWuLing(){
        return new WuLing();
    }

    public static Car getTesla(){
        return new Tesla();
    }
}

package com.znd.factory.simple;

public class Consumer {
    public static void main(String[] args) {
        //传统new
//        Car car1 = new WuLing();
//        Car car2 = new Tesla();
        //方法一 if逻辑
//        Car car1 = CarFactory.getCar("五菱");
//        Car car2 = CarFactory.getCar("特斯拉");
        //方法二 单独静态方法
        Car car1 = CarFactory.getWuLing();
        Car car2 = CarFactory.getTesla();
        car1.name();
        car2.name();
    }
}

!!不满足开闭原则,添加类还是需要修改CarFactory的代码

工厂方法模式

package com.znd.factory.method;

public interface CarFactory {
    Car getCar();
}
package com.znd.factory.method;

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

        Car car1 = new WuLingFactory().getCar();
        Car car2 = new TeslaFactory().getCar();

        car1.name();
        car2.name();
        Car car3 = new MoBaiFactory().getCar();
        car3.name();
    }
}

新增一种车就新增一个实现类工厂。满足开闭原则。

!! 但是代码复杂度变高

  • 结构复杂度 simple胜
  • 代码复杂度 simple胜
  • 编程复杂度 simple胜
  • 管理复杂度 simple胜
  • 根据设计原则 method胜出
    • 根据实际业务 选择简单工厂模式!

3、抽象工厂模式

定义:抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类

package com.znd.factory.abstract1;

public class Client {
    public static void main(String[] args) {
        System.out.println("===========小米系列产品============");
        XiaoMiFactory xiaoMiFactory = new XiaoMiFactory();
        IPhoneProduct iPhoneProduct = xiaoMiFactory.iphoneProduct();
        iPhoneProduct.callUp();
        iPhoneProduct.sendSMS();
        iPhoneProduct.shutdown();
        iPhoneProduct.start();
        IRouterProduct iRouterProduct = xiaoMiFactory.irouterProduct();
        iRouterProduct.openWifi();
        iRouterProduct.setting();
        iRouterProduct.start();
        iRouterProduct.shutdown();
        System.out.println("===========华为系列产品============");
        HuaweiFactory huaweiFactory = new HuaweiFactory();
        IPhoneProduct iPhoneProduct1 = huaweiFactory.iphoneProduct();
        iPhoneProduct1.start();
        iPhoneProduct1.shutdown();
        iPhoneProduct1.sendSMS();
        iPhoneProduct1.callUp();
        IRouterProduct iRouterProduct1 = huaweiFactory.irouterProduct();
        iRouterProduct1.shutdown();
        iRouterProduct1.start();
        iRouterProduct1.setting();
        iRouterProduct1.openWifi();
    }
}

虽然不满足开闭原则,但是若长期稳定,修改还是很有必要的。

4、创造者模式

  • 它提供了一种创建对象的最佳方式

  • 定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不用的表示

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

  • 用户只需要给出指定复杂的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)

  • 例子:

    • 工厂(建造者模式):负责制造汽车(组装过程和细节在工厂内)
    • 汽车购买者(用户):你只需要说出你需要的型号(对象的类型和内容),然后直接购买就可以使用了(不需要知道汽车时怎么组装的(车轮、车门、发动机、方向盘等等))

建房子过程:

  1. 地基
  2. 钢筋工程
  3. 铺电线
  4. 粉刷

如果要盖房子,首先要找一个承包商(指挥者)。承包商指挥工人(具体建造者)过来造房子(产品),最后验收。

常规用法

  • 抽象的Builder
package com.znd.factory.builder;

//抽象的建造者:方法
public abstract class Builder {
    abstract void buildA(); //地基
    abstract void buildB(); //钢筋工程
    abstract void buildC(); //铺电线
    abstract void buildD(); //粉刷
    abstract Product getProduct();
}

  • 具体的Builder(工人)
package com.znd.factory.builder;

public class Worker extends Builder {

    private Product product;

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

    @Override
    void buildA() {
        product.setBuildA("地基");
        System.out.println("地基");
    }

    @Override
    void buildB() {
        product.setBuildB("钢筋工程");
        System.out.println("钢筋工程");
    }

    @Override
    void buildC() {
        product.setBuildC("铺地基");
        System.out.println("铺地基");
    }

    @Override
    void buildD() {
        product.setBuildD("粉刷");
        System.out.println("粉刷");
    }

    @Override
    Product getProduct() {
        return product;
    }
}

  • 产品product
package com.znd.factory.builder;

//产品:房子
public class Product {
    private String buildA;
    private String buildB;
    private String buildC;
    private String buildD;

    @Override
    public String toString() {
        return "Product{" +
                "buildA='" + buildA + '\'' +
                ", buildB='" + buildB + '\'' +
                ", buildC='" + buildC + '\'' +
                ", buildD='" + buildD + '\'' +
                '}';
    }

    public String getBuildA() {
        return buildA;
    }

    public void setBuildA(String buildA) {
        this.buildA = buildA;
    }

    public String getBuildB() {
        return buildB;
    }

    public void setBuildB(String buildB) {
        this.buildB = buildB;
    }

    public String getBuildC() {
        return buildC;
    }

    public void setBuildC(String buildC) {
        this.buildC = buildC;
    }

    public String getBuildD() {
        return buildD;
    }

    public void setBuildD(String buildD) {
        this.buildD = buildD;
    }
}

  • 指挥Director(控制产品的先后顺序)
package com.znd.factory.builder;

//指挥:核心,负责指挥构建一个工程,工程如何构建,由他决定
public class Director {
    public Product build(Builder builder){
        builder.buildA();
        builder.buildB();
        builder.buildD();
        builder.buildC();
        return builder.getProduct();
    }
}

  • test
package com.znd.factory.builder;

public class Test {
    public static void main(String[] args) {
        //指挥
        Director director = new Director();
        //指挥 具体的工人完成 产品
        Product build = director.build(new Worker());
        System.out.println(build.toString());
    }
}

静态内部类

客户来充当指挥

服务员(具体的建造者)可以随意搭配任意几种产品(零件)组成一款套餐(产品),然后出售给客户。比第一种方式少了指挥者,主要时给了客户指挥的权力。时产品的创建更加灵活。

  • 产品类Product
package com.znd.factory.builder.demo02;
//产品:套餐
public class Product {
    private String buildA = "汉堡";
    private String buildB = "可乐";
    private String buildC = "薯条";
    private String buildD = "鸡翅";

    public String getBuildA() {
        return buildA;
    }

    public void setBuildA(String buildA) {
        this.buildA = buildA;
    }

    public String getBuildB() {
        return buildB;
    }

    public void setBuildB(String buildB) {
        this.buildB = buildB;
    }

    public String getBuildC() {
        return buildC;
    }

    public void setBuildC(String buildC) {
        this.buildC = buildC;
    }

    public String getBuildD() {
        return buildD;
    }

    public void setBuildD(String buildD) {
        this.buildD = buildD;
    }

    @Override
    public String toString() {
        return "Product{" +
                "buildA='" + buildA + '\'' +
                ", buildB='" + buildB + '\'' +
                ", buildC='" + buildC + '\'' +
                ", buildD='" + buildD + '\'' +
                '}';
    }
}

  • 抽象类建造者Builder
package com.znd.factory.builder.demo02;
//抽象建造者
public abstract class Builder {
    abstract Builder buildA(String msg); //汉堡
    abstract Builder buildB(String msg); //可乐
    abstract Builder buildC(String msg); //薯条
    abstract Builder buildD(String msg); //鸡翅
    abstract Product getProduct();
}

  • 具体实现类Worker
package com.znd.factory.builder.demo02;

//具体的建造者
public class Worker extends Builder {

    private Product product;

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

    @Override
    Builder buildA(String msg) {
        product.setBuildA(msg);
        return this;
    }

    @Override
    Builder buildB(String msg) {
        product.setBuildB(msg);
        return this;
    }

    @Override
    Builder buildC(String msg) {
        product.setBuildC(msg);
        return this;
    }

    @Override
    Builder buildD(String msg) {
        product.setBuildD(msg);
        return this;
    }

    @Override
    Product getProduct() {
        return product;
    }
}

  • test
package com.znd.factory.builder.demo02;

public class Test {
    public static void main(String[] args) {
        //服务员
        Worker worker = new Worker();
        //链式编程 在原来的基础上自由组合,不组合也有默认套餐
        Product product = worker.buildA("披萨").buildB("咖啡").getProduct();
        System.out.println(product.toString());
    }
}

产品和建造使用分离,实现了解耦,符合开闭原则

5、原型模式

浅克隆

package com.znd.factory.prototype.demo01;

import java.util.Date;

/**
 * 1、实现一个接口 Cloneable
 * 2、重写一个方法 clone
 */
public class Video implements Cloneable {

    private String name;
    private Date createTime;

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

    public Video() {
    }

    public Video(String name, Date createTime) {
        this.name = name;
        this.createTime = createTime;
    }

    public String getName() {
        return name;
    }

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

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

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

package com.znd.factory.prototype.demo01;

import java.util.Date;

//客户端克隆
public class BiliBili {
    public static void main(String[] args) throws CloneNotSupportedException {
        //原型对象v1
        Date date = new Date();
        Video v1 = new Video("徐大SAO", date);
        //v1克隆v2
        //Video v2 = new Video("徐大SAO", date);
        Video v2 = (Video) v1.clone();
        System.out.println("v1=>"+v1);
        System.out.println("v2=>"+v2);
        System.out.println("=================");
        date.setTime(234341231);
        v2.setName("双人鱼");
        System.out.println("v1=>"+v1);
        System.out.println("v2=>"+v2);
//        System.out.println("v1=>hash:"+v1.hashCode());
//        System.out.println("v2=>hash:"+v2.hashCode());
    }
}

深克隆

package com.znd.factory.prototype.demo01;

import java.util.Date;

//客户端克隆
public class BiliBili {
    public static void main(String[] args) throws CloneNotSupportedException {
        //原型对象v1
        Date date = new Date();
        Video v1 = new Video("徐大SAO", date);
        //v1克隆v2
        //Video v2 = new Video("徐大SAO", date);
        Video v2 = (Video) v1.clone();
        System.out.println("v1=>"+v1);
        System.out.println("v2=>"+v2);
        System.out.println("=================");
        date.setTime(234341231);
        v2.setName("双人鱼");
        System.out.println("v1=>"+v1);
        System.out.println("v2=>"+v2);
//        System.out.println("v1=>hash:"+v1.hashCode());
//        System.out.println("v2=>hash:"+v2.hashCode());
    }
}

package com.znd.factory.prototype.demo02;

import java.util.Date;
//Spring Bean:单例模式,原型模式
//原型模式 + 工厂模式 ==> new <==>原型模式
public class BiliBili {
    public static void main(String[] args) throws CloneNotSupportedException {
        //原型对象v1
        Date date = new Date();
        Video v1 = new Video("徐大SAO", date);
        //v1克隆v2
        //Video v2 = new Video("徐大SAO", date);
        Video v2 = (Video) v1.clone();
        System.out.println("v1=>"+v1);
        System.out.println("v2=>"+v2);
        System.out.println("=================");
        date.setTime(234341231);
        v2.setName("双人鱼");
        System.out.println("v1=>"+v1);
        System.out.println("v2=>"+v2);
    }
}

通过克隆可以提高效率,如果对象很复杂,使用原型模式很有必要。