十种常用设计模式

什么是设计模式

设计模式是前人对代码开发经验的总结, 是解决特定问题的一系列套路; 它不是语法规定, 而是一套用来提高代码可复用性, 可维护性, 稳定性以及安全性的解决方案; 它是在1995年由四人帮合作出版的;
重点是面向对象,面向过程用不到, 是对类的封装,继承和多态以及类的关联关系和组合关系的充分理解;

正确的使用设计模式具有以下优点:

  1. 可以提高程序员的思维能力(抽象能力), 编程能力和设计能力,
  2. 使程序设计更加标准化, 代码绘制更加工程化, 使软件开发效率大大提高, 从而缩短软开发周期,
  3. 使设计的代码可重性高, 可读性强, 可靠性高, 可维护性强;

设计模式的基本要素

  1. 模式名称: 根据问题,特点,解决方案,功能来命名,方便记住这个模式是干嘛的;
  2. 解决什么问题: 要想正确理解设计模式,首先必须明确它是为了解决什么问题而提出来的;
  3. 解决的方案是什么: 不提供具体的解决方案, 它提供的是设计问题的抽象描述;
  4. 解决问题后的效果是什么: 应用这个模式的优缺点是什么, 系统的可扩展性, 复杂度,灵活性, 可重性高, 可读性强, 可靠性高, 可维护性==>如果这些问题都能够得到显著的解决,那么这个模式就是一个好的设计模式;

23种设计模式

创建型模式: 怎么样去创建对象, 让创建和使用分离, 帮我们更好的创建对象;
结构型模式: 描述类和对象按照某种模式或者布局组成一种更大的结构;
行为型模式: 描述类和对象之间怎么去相互协作, 去共同完成单个对象无法完成的任务, 主是是分配一些职责;
在这里插入图片描述

面向对象的七大原则

  1. 开闭原则: 最重要的原则; 对扩展开放, 对修改关闭==>就是说当我新增一个类(功能)的时候不会影响现有的功能;

  2. 里氏替换原则: 继承必须确保超类所拥有的性质在子类中仍然成立==>意思就是尽量不要重写父类的方法, 尽量在子类去新增功能, 重写父类的方法实现起来是比较简单,但是代码的复用性会越来越差;

  3. 依赖倒置原则: 要面向接口编程, 降低程序的耦合性,不要面向实现编程==>高层的模块不应该依赖低层的模块,而应该要依赖他们的抽象(面向接口编程), 抽象不依赖细节 , 细节应该依赖抽象;

  4. 单一职责原则:也被称之为单一功能原则, 控制类的粒度大小/将对象解耦/提高其内聚性==>一个对象不应该承担太多的原则, 尽量保证一个方法只做一件事情(原子性), 主要注重实现和细节上的问题

  5. 接口隔离原则: 它跟单一职责原则是差不多的, 他是要为各个类建立它们需要的专用接口,也是为了提高类的内聚性, 降低不同接口功能的耦合性, 他主要注重的是接口依赖的隔离, 主要用来约束接口, 根据面向对象的思想系统的定义一些了规则;

  6. 迪米特法则: 只与你的直接朋友交谈, 不跟陌生人说话==>比如有A,B,C三个类, A和B通信,B和C通信, A和C之间可以访问方法, 但是不是去建立直接的通信, 所有的操作尽量通过B去执行, 降低类的耦合性提高模块的独立性;

  7. 合成复用原则: 尽量先使用组合或者聚合等关联关系来实现 , 其次才考虑使用继承关系来实现==>组合是has a的关系, 继承是is a的关系, 比如A类的对象是B类的成员变量,这就是has a;

一. 单例模式

定义

单例模式是指确保一个类在任何情况下都绝对只有一个实例

要实现单例模式, 首先要做的就是构造器私有化, 如果用默认的构造函数,那么new一个依然可以获取到另一份单例实体, 这对于单例来说是不允许的,我们需要加上了private的修饰来保证外界无法直接使用new 对象的方式来访问,要用这个对象只能通过暴露给外界的getInstance方法获取, 通过将构造方法限定为private避免了类在外部被实例化;

1. 饿汉式单例

就是不管你用不用, 一上来就把对象给你加载好了;
存在的问题: 浪费内存,浪费空间

/*
 * @author: zhuojh
 * 描述:饿汉式单例模式
 * 优点:没有任何锁,执行效率高,用户体验比懒汉式单例模式更好
 * 缺点:类加载的时候就初始化,不管用不用都占内存空间
 * 建议:适用于单例模式较少的场景
 *      如果我们在程序启动后,一定会加载到类,那么用饿汉模式实现的单例简单又实用;
 *      如果我们是写一些工具类,则优先考虑使用懒汉模式,可以避免提前被加载到内存中,占用系统资源。
 * */
public class HungryMan {

  /*
   *初始化参数 开销问题,浪费内存
   List<String> list1 =  new ArrayList(256*256);
   List<String> list2 =  new ArrayList(256*256);
   List<String> list3 =  new ArrayList(256*256);
   List<String> list4 =  new ArrayList(256*256);
   */

    /*
    * 单例一个重要的思想, 构造器私有,
    * 这样别人无法去 new 对象,
    * */
    private HungryMan(){

    }
    //保证唯一
   private static final HungryMan hungryMan = new HungryMan();

    private static HungryMan newInstance(){
        return hungryMan;
    }

2. 懒汉式单例

当我们用到的时候再去加载;
也就是判断对象不为空的时候才会去创建,
这种方式在单线程的情况下是OK的;

/*
* @author: zhuojh
* 只支持单线程懒汉式单例, 多线程的情况下这种方式存在安全问题,
* */
public class LaxyMan {

    private LaxyMan() {
        System.out.println("线程" + Thread.currentThread().getName());//当前执行线程
    }

    private static LaxyMan laxyMan;

    //只支持单线程的懒汉式单例
    public static LaxyMan getInstance(){
        if (laxyMan == null) {
            laxyMan = new LaxyMan();
        }
        return laxyMan;
    }
}

多线程的情况下存在的问题: 前一个线程对象还没有创建, 后面的若干个线程已经经过了不为空的判断, 导致创建了多个对象;
解决方法:
双重检测锁模式的懒汉式单例—>简称DCL懒汉式;

/**
 * @author: zhuojh
 * 描述:懒汉式单例模式---双重检查锁
 * 用到了synchronized关键字上锁,对程序性能还是存在一定的性能影响,在JDK低版本的时候他是一个重量级锁,高版本做了优化, 会有一个
 * 从偏向锁-->自旋锁-->轻量级锁-->重量级锁的过程, 关于synchronized加锁的过程大家可以找资料学习一下,这里不再讲述
 *
 * 建议:如果我们在程序启动后,一定会加载到类,那么用饿汉模式实现的单例简单又实用;
 *      如果我们是写一些工具类,则优先考虑使用懒汉模式,可以避免提前被加载到内存中,占用系统资源。
 */
public class LaxyMan {

    private LaxyMan() {
        System.out.println("线程" + Thread.currentThread().getName());//当前执行线程
    }
    //为了延迟加载, 不能用final修饰
    private static LaxyMan laxyMan;

    //双重检测锁模式的懒汉式单例
    public static LaxyMan getInstance02(){
        if (laxyMan == null){
            synchronized (LaxyMan.class){
                if (laxyMan == null) {
                    laxyMan = new LaxyMan();
                }
            }
        }
        return laxyMan;
    }
}

DCL懒汉式存在的问题: new LazyMan() //他不是原子性操作, 在极端情况下是有可能会出现问题的,
new LazyMan()在创建这个对象的时候他会经过三个步骤:

  1. 分配内存空间;
  2. 执行构造方法, 也就是初始化对象;
  3. 把这个对象指向这个内存空间;

那么在创建这个对象的过程中, 可能会出现一个cpu指令重排序的现象, 比如我们要他执行的是123, 而他执行的顺序可能是132;
比如现在有两个线程, 线程A和线程B, 当线程A执行了13, 而线程B进来做判断的时候线程A的第2步还没执行, 但是这时候线程A已经分配了内存空间了, 而线程B就会判断这个对象是不为空的, 他就不去创建这个对象了, 而是直接跳到return laxyMan, 把这个对象直接给返回去了,

解决方法:
volatile===>避免cpu指命重排序;

 private static volatile LaxyMan laxyMan;

Volatile

Java内存模型

在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致

Volatile作用

在这里插入图片描述

1. 多线程下保证线程的可见性,

解释:
一个线程把一个共享变量从缓存3中读取到自己的cpu内核1的缓存2中, 当如果有另一个线程也去读取了这个共享变量然后拿到自己的内核2的缓存2中并对这个值做了修改的话, 内核1上的线程他是不知道你把这个值给改了的,
如果想要内核1上的线程知道这个值被修改了的话就需要在缓存3中对共享变量用Volatile进行修饰, 当有一个线程对这个值做了修改马上把这个值写回共享变量中, 当另一个线程要用到这个值了, 下次调用的时候需要重新去获取缓存3中的共享变量, 这样才能实现数据一致性

2. volatile的第二个作用, 禁止cpu指令重排序,

以下这个案例中, 当线程1进到getInstance方法, 判断这个实例为空, 他就会去创建new出来这个对象, 当他只new到一半的时候对象是已经创建了, 但是值还没赋上, 而cpu底层进行了指令重排序,
那么在这个时候第二个线程进来了 ,他也去判断最外层的这个实列为不为空, 当你初始化到一半时候实例是已经不为空了, 只是还没有赋值, 所以他不会去拿锁, 而是直接跳过, 去拿下面直接拿这个retrun的实例, 当如果我们执行的是一个订单的数据, 比如m在线程一本来是10000个订单, 而线程二拿到的是订单数m为0, 这里是有问题,
所以我们需要用volatilef去修饰我们这个实例, 禁止创建这个对象的时候cpu指令重排序,

volatile是怎么解决指令重排序的?

他是使用jvm的内存屏障,
屏障两端的指令不可以重排, 保障他的的有序性

volatile的实现细节

1, 源码层上的实现还是用的 volatile i
2, 字节码层上的实现还是用的 ACC_VOLATILE
2, cpu的层级上的实现还是用的lock指令, 总线锁,
3,jvm层:
写的操作: 必须前面的全部写完了, 才能执行读操作, 也就是在一个线程进行写操作的时候, 其他线程只能在我写操作的屏障外面, 必须等我写完了, 他才能进来做读操作,
读的操作: 必须前面的全部读完了, 才能执行写操作, 也就是在一个线程进行读操作的时候, 其他线程只能在我读操作的屏障外面, 必须等我读完了, 他才能进来做写操作,

反射对单例有什么影响?

我们可以通过反射获得单例类的构造函数,由于该构造函数是private的,通过setAccessible(true)指示反射的对象在使用时应该取消 Java 语言访问检查, 也就是获取到了构造器的访问权限, 使得私有的构造函数能够被访问,这样使得单例模式失效。

public class client {
    //LaxyMan laxyMan = new LaxyMan();

    public static void main(String[] args) throws Exception{

        LaxyMan instance02 = LaxyMan.getInstance02();
        System.out.println(instance02+" 我是懒汉式单例");

        //反射迫使单例失效
        Constructor<LaxyMan> constructor = LaxyMan.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        LaxyMan laxyMan = constructor.newInstance();
        System.out.println(laxyMan+" 我是反射创建的");
    }

}
结果:两个不同的对象
singleton.lazyMan.LaxyMan@4ac68d3e 我是懒汉式单例
singleton.lazyMan.LaxyMan@277c0f21 我是反射创建的

如果要抵御这种攻击,要防止构造函数被成功调用两次。我们可以在构造函数中对实例化次数进行统计,大于一次就抛出异常。

private static int count = 0 ;
private Singleton(){
       if (count > 0 ){//判断是否多次创建实例
            throw new RuntimeException( "创建了两个实例" );
        }
     count++;
     }

三 静态内部类实现单例模式

/**
 * @author: zhuojh
 * 描述: 静态内部类实现单例模式,
 * 1,屏蔽饿汉式单例模式的内存浪费问题,
 * 2,解决DCL懒汉式双重检查锁中synchronized的性能问题
 * 3,避免因为反射破坏单例
 **/
public class LaxyManInner {

        /**
         * 使用LaxyManInner的时候会先默认初始化换内部类
         * 如果没有使用,则内部类是不加载的
         */
        private LaxyManInner() {
            //为了避免反射破坏单例,需要在构造方法中增加限制,一旦出现多次重复创建,直接抛出异常
            if (null != Laxyholder.LAZY_INNER_CLASS_SINGLETON) {
                throw new RuntimeException("创建LaxyManInner异常,不允许创建多个实例!");
            }
        }

        /**
         * static是为了使单例的空间共享,保证这个方法不会被重写、重载
         */
        public static final LaxyManInner getInstance() {
            //在返回结果前,一定会先加载内部类
            return Laxyholder.LAZY_INNER_CLASS_SINGLETON;
        }

        /**
         * 默认不加载
         */
        private static class Laxyholder {
            private static final LaxyManInner LAZY_INNER_CLASS_SINGLETON = new LaxyManInner();
        }

    public static void main(String[] args) {
        //多线程
        for (int i = 0; i < 8; i++) {
            new Thread(()->{
                System.out.println(LaxyManInner.getInstance());
            }).start();
        }
  }

}

枚举类单例模式(Java推荐单例模式)

关于枚举, 你可以把他当做一个比较特殊的java类

/**
 * @author: zhuojh
 * 描述:枚举类单例模式,将每个实例都登记到一个地方,使用唯一的标识获取单例。
 *      在底层的实现上如果发现有反射去创建对象, 他会抛出一个异常(不允许用反射创建枚举实例)
 */
public enum EnumSingle {
    /*枚举类单例模式*/
    INSTANCE;

    private String name;

    public String getName() {
        return name;
    }

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

    public static EnumSingle getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        EnumSingle instance = EnumSingle.getInstance();
        instance.setName("_Mac");
        System.out.println("INSTANCE对象属性name:"+instance.getName());
    }
}

可以说是最优的实现方式,但是枚举这个东西可读性不高, 大家有兴趣的可以深入学习一下枚举

二. 工厂模式

定义

工厂模式是指一个包含专门用来创建其他对象的的方法的类,
说白了就是创建对象用的, 以前是你用new去创建对象, 而用到工厂的时候, 你只需要把你想要创建的对象的部分参数给到工厂, 让他来帮你创建;

1. 简单工厂模式

定义一个手机接口


public interface Phone {
    void create();//提供一个方法, 由具体手机类去实现
}


定义具体手机类

package factory.easyFactory.phone;

public class Miphone implements Phone {
    //创建对象的同时执行创建的方法
    public Miphone() {
        this.create();
    }

    @Override
    public void create() {
        System.out.println("创建了小米手机对象");
    }
}
package factory.easyFactory.phone;

public class Iphone implements Phone {
    //创建对象的同时执行创建的方法
    public Iphone() {
        this.create();
    }
    @Override
    public void create() {
        System.out.println("创建了苹果手机对象!");
    }
}

定义简单工厂类

/**
 * @author: zhuojh
 * 描述:  我们无需提供具体的子类类名,只需要提供一个字符串即可得到相应的实例对象。这样的话,
 *       当子类的类名更换或者增加子类时我们都无需修改客户端代码,只需要在简单工厂类上增加一个分支判断代码即可。
 *       简单工厂模式, 不符合开闭原则;
 * 优点:  使用这种模式,我们在生成工厂的时候可以加一些业务代码,如日志、判断业务等,这时候可以直接在else中加上去就行了,
 *       我们可以对创建的对象进行一些 “加工” ,而且客户端并不知道,因为工厂隐藏了这些细节。如果没有工厂的话,
 *       那我们是不是就得自己在客户端上写这些代码,这就好比本来可以在工厂里生产的东西,拿来自己手工制作,不仅麻烦以后还不好维护;
 * 缺点:  如果需要在方法里写很多与对象创建有关的业务代码,而且需要的创建的对象还不少的话,我们要在这个简单工厂类里编写很多个方法,
 *       每个方法里都得写很多相应的业务代码,而每次增加子类或者删除子类对象的创建都需要打开这简单工厂类来进行修改。这会导致这个简单工
 *       厂类很庞大臃肿、耦合性高,而且增加、删除某个子类对象的创建都需要打开简单工厂类来进行修改代码也违反了开-闭原则;
 *
 * */
public class EasyFactory {

    public Phone makePhone(String phoneType) {
        if(phoneType.equalsIgnoreCase("MiPhone")){
            return new Miphone();
        }else if(phoneType.equalsIgnoreCase("iPhone")) {
            return new Iphone();
        }
      return null;
    }

}

调用

public class Client {

    public static void main(String[] arg) {
        EasyFactory factory = new EasyFactory();
        Phone miPhone = factory.makePhone("MiPhone");
        Phone iPhone = factory.makePhone("iPhone");
    }
}

结果:
创建了小米手机对象!
创建了苹果手机对象!

如果想要满足开闭原则我们会付出非常多的代价===>工厂方法模式

2. 工厂方法模式

工厂方法模式是在简单工厂模式的基础上再进行拆分, 把原来的一个工厂分成多个不同的工厂(每个产品都有自己的工厂, 然后去实现一个共同的超级工厂接口); 这样就实现了不修改原来工厂类代码的目的;
当消费者要去获取到一个车/产品的时候, 他只需要去new一个对应的工厂, 然后去获取到工厂对应的车就行了, 如果有部分业务是可以在创建对象时就去完成的, 那这部分代码完全可以交给对应的工厂去完成;

创建超级工厂

public interface Factory {
    public Phone createPhone();
}

创建苹果手机具体工厂

/**
 * @author: zhuojh
 * 描述: 工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个子类对应一个工厂类,
 *      而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,
 *     这样代码就不会都耦合在同一个类里了====>在这同时,你的类将会横向的扩展, 当业务种类很多的时候, 会使你的类变的非常多;
 * 优点: 各个不同功能的实例对象的创建代码,也没有耦合在同一个工厂类里,这也是工厂方法模式对简单工厂模式解耦的一个体现,
 *      也符合开闭原则, 对添加开放, 对修改关闭;
 * 缺点: 你的类将会横向的扩展, 当业务种类很多的时候, 会使你的类变的非常多;
 *
 * */
public class IphoneFactory implements Factory {
    @Override
    public Phone createPhone() {
        return new Iphone();
    }

}

创建小米手机具体工厂

public class MiPhoneFactory implements Factory {
    @Override
    public Phone createPhone() {
        return new Miphone();
    }
}

他的缺点是每多一个产品就要多创建一个对应的工厂类, 那样子的话可能类会变的非常的多, 非常不好管理;

两者作对比:

从结构复杂度考虑, 简单工厂模式更优;
从代码复杂度考虑, 简单工厂模式更优;
从编程复杂度考虑, 简单工厂模式更优;
从管理的复杂度考虑, 简单工厂模式更优;
如果根据设计原则考虑: 选用工厂方法模式;
如果根据实际业务/实际开发运用, 更多的是选用简单工厂模式;

工厂方法模式是面向单接口的, 所有产品去实现了同一个接口, 当出现一种情况, 比如: 一辆车他有很多很多的零件…发动机, 轮胎, 大灯, 显示屏等等…那么可能会涉及很多的接口, 这时候这种普通的工厂方法模式就解决不了了===>引申出了抽象工厂模式

3. 抽象工厂模式

适用场景:

一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是很重要的,用户无须关心对象的创建过程,将对象的创建和使用解耦。
系统中有多于一个的产品族,而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。

实例结构分析:
在这里插入图片描述

  1. Creator–创建者: 他是一个超级工厂(工厂的工厂), 在这个工厂里会创建很多不同的产品, 比如这里有手机产品, 有路由器产品…
  2. ConcreteCreator1(华为的工厂)/ConcreteCreator2(小米的工厂)会去实现Creator这个超级工厂, 并实现两个方法, 华为的工厂就有了华为的手机/华为的路由器, 小米的工厂就有了小米的手机/小米的路由器;
  3. ProductA和ProductB是产品接口, 接口中各自抽象了一些自己的功能, 比如IPhoneProduct手机产品接口(有开机/关机/打电话/发短信的功能), IRouteProduct路由器产品接口(有开机/关机/开wifi的功能),
  4. 手机产品接口下有实现类小米手机/华为手机, 路由器产品接口下有小米路由器/华为路由器

创建超级工厂

public interface SuperFactory {
    IphoneProduct phoneProduct();//手机产品
    RouteProduct routeProduct();//路由产品
    
}

创建具体工厂/华为工厂

/**
 * 华为具体工厂: 实现具体的华为手机产品和华为路由产品的对象创建
 * */
public class HuaweiFactory implements SuperFactory{

    @Override
    public IphoneProduct phoneProduct() {
      return new iPhoneHuawei();
    }

    @Override
    public RouteProduct routeProduct() {
       return new RouteHuawei();
    }
}

创建具体工厂/小米工厂

/**
 * 小米具体工厂: 实现具体的小米手机产品和小米路由产品的对象创建
 * */
public class XaioMiFactory implements SuperFactory{


    @Override
    public IphoneProduct phoneProduct() {
        return new iPhoneXiaoMi();
    }

    @Override
    public RouteProduct routeProduct() {
        return new RouteXiaoMi();
    }
}

产品接口

/**
* 手机产品接口
* */
public interface IphoneProduct {

    void sendSMS();
    void callNumber();
    void game();

}



/**
 * 路由器产品接口
 * */
public interface RouteProduct {

    void opendWifi();
    void settingPassword();
    void netInterface();
}

具体手机产品实现类/华为手机

/**
 * 具体手机产品(华为手机)的实现,
 * 
 * */
public class iPhoneHuawei implements IphoneProduct {
    @Override
    public void sendSMS() {
        System.out.println("华为手机能发信息");
    }

    @Override
    public void callNumber() {
        System.out.println("华为手机能打电话");
    }

    @Override
    public void game() {
        System.out.println("华为手机能玩游戏");
    }
}

具体手机产品实现类/小米手机

/**
 * 具体手机产品(小米手机)的实现
 * */
public class iPhoneXiaoMi implements IphoneProduct {
    @Override
    public void sendSMS() {
        System.out.println("小米手机能发信息");
    }

    @Override
    public void callNumber() {
        System.out.println("小米手机能打电话");
    }

    @Override
    public void game() {
        System.out.println("小米手机能玩游戏");
    }
}

具体路由品产品实现类/华为路由器

/**
 * 具体路由器产品(华为路由器)的实现
 * */
public class RouteHuawei implements RouteProduct{

    @Override
    public void opendWifi() {
        System.out.println("华为的路由器有wifi功能");
    }

    @Override
    public void settingPassword() {
        System.out.println("华为的路由器有修改密码功能");
    }

    @Override
    public void netInterface() {
        System.out.println("华为的路由器有网络接入功能");
    }
}

具体路由品产品实现类/小米路由器

/**
 * 具体路由器产品(小米路由器)的实现
 * */
public class RouteXiaoMi implements RouteProduct{

    @Override
    public void opendWifi() {
        System.out.println("小米的路由器有wifi功能");
    }

    @Override
    public void settingPassword() {
        System.out.println("小米的路由器有修改密码功能");
    }

    @Override
    public void netInterface() {
        System.out.println("小米的路由器有网络接入功能");
    }
}

调用者

/**
 * @author: zhuojh
 * 抽象工厂与工厂方法模式的区别在于:抽象工厂是可以生产多个产品的,
 * 例如 HuaweiFactory 里可以生产 RouteHuawei 以及 IphoneHuawei 两个产品,
 * 而这两个产品又是属于一个系列的,因为它们都是属于华为的。而工厂方法模式则只能生产一个产品,
 * 例如之前的 IphoneFactory 里就只可以生产一个 Iphone 产品。
 *
 * */
public class Invoke {

    public static void main(String[] args) {
        HuaweiFactory huaweiFactory = new HuaweiFactory();
        IphoneProduct iphoneProduct = huaweiFactory.phoneProduct();
        iphoneProduct.callNumber();//根据业务需求也可以放在构造器中
        iphoneProduct.game();
        iphoneProduct.sendSMS();
        System.out.println();
        RouteProduct routeProduct = huaweiFactory.routeProduct();
        routeProduct.opendWifi();
        routeProduct.settingPassword();
        routeProduct.netInterface();
        System.out.println("---------------------------分割线---------------------------------");
        XaioMiFactory xaioMiFactory = new XaioMiFactory();
        IphoneProduct iphoneProduct1 = xaioMiFactory.phoneProduct();
        iphoneProduct1.callNumber();
        iphoneProduct1.game();
        iphoneProduct1.sendSMS();
        System.out.println();
        RouteProduct routeProduct2 = xaioMiFactory.routeProduct();
        routeProduct2.opendWifi();
        routeProduct2.settingPassword();
        routeProduct2.netInterface();
    }
}

抽象工厂可以理解为他是一个超级工厂, 围绕一个超级工厂创建其他工厂, 该超级工厂又称为其他工厂的工厂;
**抽象工厂模式在产品(内容)及其明确/稳定的情况下他是非常强大的, 不明确的情况下是比较麻烦的, 那样每次修改都要改很多地方; **

抽象工厂模式总的来说产品功能实现和产品对象的创建是分离的; 产品功能的实现是由产品接口抽象成方法由实现类去实现, 而产品对象的创建则是由超级工厂去抽象再由具体工厂去实现创建对象, 然后再由调用者去调用具体工厂从而创建对象后再去操作由产品实现的功能;
抽象工厂模式的产品和工厂之间的关联发生在具体工厂的创建对象中;

抽象工厂模式的优缺点:

优点:

  1. 抽象工厂模式最大的好处是易于交换产品系列,由于具体工厂类,例如 SuperFactory huaweiFactory = new HuaweiFactory(); 在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。不管是任何人的设计都无法去完全防止需求的更改,或者项目的维护,那么我们的理想便是让改动变得最小、最容易,例如我现在要更改具体的产品时,只需要更改具体的工厂即可。
  2. 抽象工厂模式的另一个好处就是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例;

缺点:

  1. 修改产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,
    抽象工厂模式实际中用的不多, 了解一下即可, 有兴趣的可以深入学习;

三. 建造者模式

定义

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

描述

  1. 建造者模式也属于创建型模式, 它提供了一种创建对象的最佳方式;
  2. **主要作用: 在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象, 也就是一般用于创建复杂对象; **
  3. 同样的构建过程可以创建不同的表示;
  4. 用户只需要给出指定复杂对象的类型和内容, 建造者模式负责按顺序创建复杂对象 (把内部的建造过程和细节隐藏起来);

应用场景:

  1. 需要生成的产品对象有复杂的内部结构, 这些产品对象具备共性;
  2. 隔离复杂对象的创建和使用, 并使得相同的创建过程可以创建不同的产品对象;
  3. 适合一个具有较多的零件(属性)的产品对象的创建过程;

例子:
建造者: 负责制造汽车 (组装过程和细节)
汽车购买者 (用户): 只需要说出你要购买的汽车型号(对象类型), 然后直接购买就可以使用了 (不需要知道汽车是怎么组装/创建的);

场景:
比如现在有一个对象, 你构建他的时候,你需要给他传很多的参数, 几十个或上百个, 然后你整个对象才能构建完成,
那么要创建这种特别复杂的对象的时候, 我们可以用构建者模式来构建这个对象, 我们用不同的具体构建者我们可以构建出不同的对象, 复杂的/简单的

实例:

构建一个比较复杂的Person对象;

/**
 * @author: zhuojh
 * 1、建立一个复杂人物对象Person
 * */
public class Person {

    private Head head;//头
    private Hand hand;//手
    private Leg leg;//腿
    //......其他
}

//头部, 手部, 腿部简单的表示长度大小...
class Head{
  int headWidth;
  int headLength;

    public Head(int headWidth, int headLength) {
        this.headWidth = headWidth;
        this.headLength = headLength;
    }

}


class Hand{
    int handWidth;
    int handLength;

    public Hand(int handWidth, int handLength) {
        this.handWidth = handWidth;
        this.handLength = handLength;
    }

}


class Leg{
    int legWidth;
    int legLength;

    public Leg(int legWidth, int legLength) {
        this.legWidth = legWidth;
        this.legLength = legLength;
    }

}

2、建立Builder接口(构建器接口)

/**
 * @author: zhuojh
 * 2、建立Builder接口(构建器接口)
 *   定义了构建对象的步骤方法: 构建头/手/腿, 最后构建完了返回一个完整的
 *   对象Person;
 *
 给出一个抽象接口,以规范产品对象的各个组成成分的建造。
 这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建
 * */
public interface PersonBuilder {

    //返回PersonBuilder使编码更方便/链式,如果这里返回的各自己的头/手/腿/那调用的时候需要分别去返回,要写好几行
    PersonBuilder builderHead();

    PersonBuilder builderHand();

    PersonBuilder builderLeg();

    Person build(); //组装对象
}

3、创建实现类:ComplexPersonBuilder, 复杂的人物构建器;

/**
 * @author: zhuojh
 * 3、创建实现类:ComplexPersonBuilder, 复杂的人物构建器;
 *     实现Builder接口,具体化复杂对象的各部分的创建。 在建造过程完成后,提供复杂人物的实例对象;
 * */
public class ComplexPersonBuilder implements PersonBuilder {
       Person person = new Person();//创建一个Person实例,用于调用set方法


    @Override
    public PersonBuilder builderHead() {
        person.setHead(new Head(15,20));
        return this;
    }

    @Override
    public PersonBuilder builderHand() {
        person.setHand(new Hand(15,80));
        return this;
    }

    @Override
    public PersonBuilder builderLeg() {
        person.setLeg(new Leg(30,150));
        return this;
    }

    /**
     * build完以上三部分再把整个对象返回回去
     * */
    @Override
    public Person build() {
        return person;
    }
}

客户端

/**
 * @author: zhuojh
 * 创建Director指导者/其他就是使用的人(客户端)
 * 调用具体建造者来创建复杂对象的各个部分,
 * 负责保证对象各部分完整创建或按某种顺序创建;
 * */
public class PersonDirector {

    //所有builder的动作返回的都是同一个对象,所以可以一直.builder====>链式编程,最后得到你想要的复杂对象
    public static void main(String[] args) {
        ComplexPersonBuilder builder = new ComplexPersonBuilder();
        Person p = builder
                .builderHead()
                .builderHand()
                .builderLeg()
                .build();
        System.out.println(p);

       /**
        *当如果是对象很复杂的时候可读性很差, 对不同属性的操作很麻烦, 需要去看哪些要传,哪些不用传,要用null.....
        Person person = new Person(new Head(10,10), new Hand(10,10),new Leg(10,20));
        System.out.println(person);*/



       /**
        * 建造者模式在java中的典型用法;
        * */
        Person02 p2 = new Person02.PersonBuilder()
                .basicInfo(1199811221,18,"Mac_")
                .height(178)
                //.weight(135)
                .phoneNum(16675131)
                .build();
        System.out.println(p2);

    }
}

说白了, 建造者模式就是你想要什么样的对象,告诉他具体信息, 他给你定制;

行为型模式

行为型模式: 描述类和对象之间怎么去相互协作, 去共同完成单个对象无法完成的任务, 主是是分配一些职责;

1.策略模式

日常遇到的应用场景

登录类型, 支付类型, 多种交易类型, 不同等级会员享受的优惠券价格不一样,等等业务判断,大量if else导致拓展(侧重新增)极其麻烦,维护(侧重修改)自然是改起来越改越头痛; (其实一个类型的增加[拓展一个类型] 往往对应这个类型的增删改查CRUD[维护]), 比如业务一开始一个简单的登录,往往做一个电话号码和验证码登录的方式或者账号密码登录方式,后来随着业务的增加或者提高用户体验,那么需要拓展(新增一种)三方登录,比如新增微信登录,支付宝登录,甚至抖音登录,一大堆,你要根据登录方式来处理,那就有点恼火吧,支付方式也是同样的问题; 策略模式(往往搭配工厂模式使用更配);

没有用策略模式:

我们一般是下面的写法, 直接写一个类, 在类里面直接写策略实现;

/**
 * @author: zhuojh
 * 没有策略的做法
 * 实现起来比较容易,符合一般开发人员的思路
 * 假如,类型特别多,算法比较复杂时,整个条件语句的代码就变得很长,难于维护。
 * 如果有新增类型,就需要频繁的修改此处的代码!
 * 不符合开闭原则!---对这个类的修改要关闭,就是这个类要是写好了就不要去改他了,对类的功能的拓展要开放,显然只有面向接口编程才满足,
 * 所以策略模式Strategy这个接口(文中涉及到的)就应运而生了
 */
public class NoStrategy {
	/**
	 * 传入客人vip等级类型获取相应的价格
	 * @param type   会员类型(等级)
	 * @param price  响应的价格
	 */
	public double getPrice(String type, double price) {
 
		if ("vip0".equals(type)) {
			System.out.println("不打折,原价");
			return price;
		} else if ("vip1".equals(type)) {
			System.out.println("打九折");
			return price * 0.9;
		} else if ("vip2".equals(type)) {
			System.out.println("打八折");
			return price * 0.8;
		} else if ("vip3".equals(type)) {
			System.out.println("打五折");
			return price * 0.5;
 
 
		//拓展一种策略 不符合开闭原则!
		}else if("vip6".equals(type)){
			System.out.println(" 打骨折");
			return price*0.3;
		}
 
		//无符合类型 不打折
		return price;
	}
 
}
扩展成策略模式:

1:面向接口编程, 首先创建策略接口,通过策略获取价格;

/**
 * 策略接口
 * 这个是对类NoStrategy改成面向接口的方式实现策略,不要像NoStrategy一样,
 * 直接写死策略的实现,而是使用这个接口先定义策略,功能实现后面再说
 */

public interface IDiscount {

    /*
    * 商品打折接口,通过策略获取价格
    * price: 商品原价
    * */
    double getPrice(double price);
}

2.策略模式上下文—策略接收器,专门接收策略实现的算法, 负责和具体的策略类交互, 这样的话,具体的算法和直接的客户端调用分离了;

/*
 * @author: zhuojh
 *策略接口IDiscount实现类
 **/
public class DiscountManage implements IDiscount{

    
    private IDiscount discount;

    /**
     * 有参构造器(不写无参构造器,那么new 策略实现保证必须传一种策略,这里set方法也不用设置,
     * 设置了也没用(要设置set方法那么还是把无参构造也写出来才会有用)
     * 这样同时也知道了为什么有参构造器设置了为什么无参构造器就失效了)
     * ---总之set注入也行,而且也推荐,只是这个例子采用构造器而已
     * public void setDiscount(IDiscount discount){
           this.discount = discount;
      }
     **/

    public DiscountManage(IDiscount discount){
        this.discount = discount;
    }



    @Override
    public double getPrice(double price) {
        return discount.getPrice(price);
    }
}

3:既然是策略模式接口IDiscount都明确了要做的事情是根据会员情况,返回价格,但是没有真正的实现,那么总有类来实现;
策略实现类1

/*
* vip1会员的策略
**/
public class Vip1DiscountImpl implements IDiscount{
    /*
     * 实现商品打折接口 0.8折
     **/
    @Override
    public double getPrice(double price) {
        return price * 0.8;
    }
}

策略实现类2

/*
* vip2会员的策略
**/
public class Vip2DiscountImpl implements IDiscount{
    /*
     * 实现商品打折接口 0.5折
     * */
    @Override
    public double getPrice(double price) {
        return  price * 0.5;
    }
}

3.客户端, 可以把他想象成我们的controller, 接收用户请求, 实现不同会员等级折扣计算逻辑;

public class Client {

    public static void main(String[] args) {
        //vip1
        Vip1DiscountImpl vip1Discount = new Vip1DiscountImpl();   //折扣对象;
        DiscountManage discountManage = new DiscountManage(vip1Discount);//把折扣对象给到折扣管理类
        double price01 = discountManage.getPrice(100); //对原价进行打折

        //vip2
        Vip2DiscountImpl vip2Discount = new Vip2DiscountImpl();
        DiscountManage discountManage02 = new DiscountManage(vip2Discount);
        double price02 = discountManage02.getPrice(100);


        System.out.println("折后价:"+price01);  //vip1
        System.out.println("折后价:"+price02);  //vip2
    }
}

输出结果:
折后价:80.0
折后价:50.0

以上的例子中, 如果有新增策略, 只需新增一个实现, 无需对现有代码做改动, 不会修改策略接口,只是添加一个实现,不会对已有代码造成影响,低耦合, 符合开闭原则;
缺点: 会有大量的横向扩展的类, 实际的应用中, 一般一个功能的新增都会包括CRUD, 当一个功能的实现存在大量策略的时候, 是建议使用策略模式的;

2.观察者模式

观察者模式的定义:

定义了对象之间的一对多的依赖, 让多个观察者对象同时监听某一个主题对象, 这个主题对象在状态变化时,会通知所有的观察者对象,使他们都能够收到通知并自动更新。

实例来解析观察者模式的定义,以微信公众号为背景,
如图:
在这里插入图片描述

如上图所示,服务号就是我们的主题(被观察者),使用者就是观察者。现在我们明确下功能:
1、服务号就是主题,业务就是推送消息;
2、观察者只需要订阅主题,只要有新的消息就会送来;
3、当观察者不想要接收此主题消息时,取消订阅;

在观察者模式中有如下角色:

Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
Observer:抽象观察者,是观察者的接口, 它定义了一个更新方法,使得在得到主题更改通知时更新自己。
ConcrereObserver:具体观察者,实现抽象观察者定义的更新方法,以便在得到主题更改通知时更新自身的状态。

应用场景:

假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号,当这个公众号更新时就会通知这些订阅的微信用户;

代码实现:

抽象观察者(Observer)
里面定义了一个更新的方法:

public interface Observer {
    public void update(String message);
}

具体观察者(ConcrereObserver)
微信用户是观察者,里面实现了更新的方法:

/**
 * @author: zhuojh
 * */
public class WeixinUser implements Observer {
    // 微信用户名
    private String name;
    public WeixinUser(String name) {
        this.name = name;
    }
    @Override
    public void update(String message) {
        System.out.println(name + "-" + message);
    }
}

抽象被观察者(Subject)
抽象主题,提供了attach、detach、notify三个方法:

/**
 * @author: zhuojh
 * */
public interface Subject {
    /**
     * 增加订阅者
     * @param observer
     */
    public void attach(Observer observer);
    /**
     * 删除订阅者
     * @param observer
     */
    public void detach(Observer observer);
    /**
     * 通知订阅者更新消息
     */
    public void notify(String message);
}

具体被观察者(ConcreteSubject)
微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法:

/**
 * @author: zhuojh
 * */
public class SubscriptionSubject implements Subject {
    //储存订阅公众号的微信用户
    private List<Observer> weixinUserlist = new ArrayList<Observer>();
    @Override
    public void attach(Observer observer) {
        weixinUserlist.add(observer);
    }
    @Override
    public void detach(Observer observer) {
        weixinUserlist.remove(observer);
    }
    @Override
    public void notify(String message) {
        for (Observer observer : weixinUserlist) {
            observer.update(message);
        }
    }
}

客户端调用

/**
 * @author: zhuojh
 * */
public class Client {
     public static void main(String[] args) {
        SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
        //创建微信用户
        WeixinUser user1=new WeixinUser("sasa");
        WeixinUser user2=new WeixinUser("coco");
        WeixinUser user3=new WeixinUser("V_Shen");
        //订阅公众号
        mSubscriptionSubject.attach(user1);
        mSubscriptionSubject.attach(user2);
        mSubscriptionSubject.attach(user3);
        //公众号更新发出消息给订阅的微信用户
        mSubscriptionSubject.notify("Mac_的专栏更新了");
    }
}

结果:

sasa-Mac_的专栏更新了
coco-Mac_的专栏更新了
V_Shen-Mac_的专栏更新了

使用观察者模式的场景和优缺点
使用场景
跨系统的消息交换场景,如消息队列、事件处理机制

优点
解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
缺点
在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。

结构型模式

结构型模式: 描述类和对象按照某种模式或者布局组成一种更大的结构;

1.门面模式

定义

门面模式是对象的结构模式,外部与一个子系统的通信必须通过一个统一的门面对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。

解决的问题

为子系统提供一个高层次的接口,使子系统易于使用,

小例子01

开学了, Mac要去上学, 可是新学期上学有好多事情, 去到学校,首先要招生办报名, 财务科交钱, 教务处领书本, 后勤处拿校服…Mac想要上学就需要把以上的事情全部去做一遍, 很麻烦, 那有没有什么办法更方便一点的?
解决的方式就是让校方在校门口安排一个老师, 让这个老师去帮我们做这些事情,他比较熟嘛, 而我们只管给资料就行了;
在我们的门面模式中, 这个师姐就相当于门面, 使用者只需要与这个门面接触, 让这个门面去跟子系统对接, 让他去帮我们调用子系统的方法等, 而使用者不需要跟子系统接触, 也不管他子系统内部的实现多复杂;

小例子02

Mac自己买了个房子, 二手的, 我们需要签合同, 过户, 立契, 交税, 办理产权转移手续, 银行贷款… 完了他对这些事情很陌生, 合同签好后, 他要去到房地产管理部门过户, 完了相关人员告诉他,你没有带公证书, 你先去某某地办个公证书再来吧, 后续各个部门一大堆事情…Mac觉得很烦, 然后有个叫Mab的小伙告诉他, 你买房要找中介, 买不了吃亏,买不了上当, 所有事情他帮你搞定;
这里的中介也就是相当于对外的门面;

子系统的具体业务
/**
 * 报名
 */
public class Signup {
    public void signup() {
        System.out.println("报名!");
    }
}


/**
 * 交费
 */
public class DoPay {
    public void doPay() {
        System.out.println("交费!");
    }
}


/**
 * 取书本
 */
public class Book {
    public void getBook() {
        System.out.println("取书本!");
    }
}


/**
 * 领校服
 */
public class SchoolUniform {
    public void getSchoolUniform() {
        System.out.println("领校服!");
    }
}




门面类(帮我们对接子系统的三好师姐)
/**
 * @author: zhuojh
 * 门面类,相当于蹲校门口等着帮我们报名的那个师姐
 */
public class Facade {
    private Signup signup;
    private DoPay doPay;
    private Book book;
    private SchoolUniform schoolUniform;

    public Facade() {
        signup = new Signup();
        doPay = new DoPay();
        book = new Book();
        schoolUniform = new SchoolUniform();
    }

    //报名
    public void signup() {
        signup.signup();
    }

    //交学费
    public void Pay() {
        doPay.doPay();
    }

    //拿课本
    public void getBook() {
        book.getBook();
    }

    //拿校服
    public void getSchoolUniform() {
      schoolUniform.getSchoolUniform();
    }
}


/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        Facade facade = new Facade();
        facade.signup();
        facade.Pay();
        facade.getBook();
        facade.getSchoolUniform();
    }
}

优点

松散耦合
使客户端与子系统解耦,让子系统内部的模块能更容易扩展和维护。
简单易用
客户端只需要跟门面类交互就可以了。
更好划分访问层次
有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到 门面中,这样既方便客户端使用,也很好地隐藏了内部的细节。

缺点

不符合开闭原则, 就是说, 如果你子系统的业务有变动的话, 那么必须所有关联的都要进行修改,

对外====>门面模式
在这里插入图片描述

对内===>他叫调停者模式,
在这里插入图片描述

2.调停者模式

定义

调停者模式(Mediator):用一个中介对象来封装一系列关于对象交互行为。

用于模块间解耦,通过避免对象互相显式的指向对方从而降低耦合。

使用场景

上面我们说对外使用的是Facade,那么对内就是使用Mediator了。各个对象之间的交互操作非常多时,每个对象的行为操作都依赖彼此对方,修改一个对象的行为,同时会涉及到修改很多其他对象的行为,这种情况下就需要一个对内的大管家来管理各个对象之间的关系,使多对多变为一对多。
顶顶大名的应用====>消息中间件, 生产者/消费者/负责调度的队列

门面模式和调停者模式的区别:

门面模式针对的是别人使用你的模块;
调停者模式更多的是在软件内部不同模块之间,当软件内部对象或模块之间相互通信时如何调用[解耦]
两个模式的代码都是差不多的, 这里就不再写了

有部分资料来自网友们的分享, 我自己做了一个整理;

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顾轻鸿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值