design patterns (23种设计模式)

687 阅读7分钟

什么是软件设计模式?

软件设计模式的概念与意义

    1. 软件设计模式的概念 软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
    1. 学习设计模式的意义 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点。 可以提高程序员的思维能力、编程能力和设计能力。 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

设计模式的基本要素

  • 模式名称
  • 问题
  • 解决方案
  • 效果

GoF 23

OOP七大原则

单例模式(single)

1、 饿汉式

//饿汉式
public class Hungry{

    private Hungry() {
    }

    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
        return hungry;
    }

}

2、懒汉式

2.1 普通懒汉式(非线程安全)

public class LazyMan {
    private LazyMan(){
    }

    private static LazyMan lazyMan;

    public static LazyMan getInstance(){
                if (lazyMan == null){
                    lazyMan = new LazyMan();
            }
        return lazyMan;
    }

}

2.2 双重检测锁模式的懒汉式(简称DCL懒汉式)

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private volatile static LazyMan lazyMan;

    //双重检测锁模式的 懒汉式单例 DCL懒汉式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan == null){
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                    /**
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把对象指向内存空间
                     *
                     * 正常123
                     * 实际可能132 A
                     *           B //此时lazyman还没有完成构造
                     *
                     * */
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }

}

正常执行过程中:

1.分配内存空间

2.执行方法初始化对象

3.将对象指向内存空间。

但实际中可能出现132的顺序,当线程A在执行到13时,线程B进来

此时if判断 lazyMan!= null 则到return语句返回lazyMan 而此时的对象lazyMan并未完成构造

此时加上volatile关键字可解决

volatile如何解决DCL存在的问题

volatile有两个特性:可见性和有序性

什么是可见性,即线程A修改变量x,那么对于线程B应该立即可以获取到信息x被修改,并重新读取

什么是有序性,即禁止许指令重排序

禁止重排序是怎么做到的?

答案:一是语义层次,二是内存屏障。内存屏障分为读屏障(loadload),写屏障(storestore),读写屏障(loadstore或storeload);如load,loadload,load,在进行新的读操时,加入读屏障,保证上一次的读操作完成再执行新的读操作而不会越过屏障去执行新操作。

3、静态内部类

public class Holder {
    private Holder(){

    }

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

    public static Holder getInstance(){
        return InnerClass.holder;
    }
}

4、反射机制与DCL单例模式的博弈

4.1 通过反射机制获取无参构造器来创建对象

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

4.2 在构造器中设置锁并设置异常处理机制

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

但此时出现一个问题 当不使用getInstance方法来创建时 lazyMan恒为空 而通过无参构造器来创建多个对象 仍然破坏了单例

4.3 此时可以通过设置一个变量(红绿灯方式)

private static boolean axj = false;//这个值可以设置复杂加密

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

4.4 这时仍然可以通过反射来获取这个变量 去改变他的值

public static void main(String[] args) throws Exception {
        Field axj = LazyMan.class.getDeclaredField("axj");
        axj.setAccessible(true);

        //LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance = declaredConstructor.newInstance();
        
        axj.setBoolean(instance,false);
        LazyMan instance1 = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(instance1);

    }

setBoolean的用法:

import java.lang.reflect.Field;

public class FieldDemo {

   public static void main(String[] args) throws NoSuchFieldException, 
      SecurityException, IllegalArgumentException, IllegalAccessException {

      SampleClass sampleObject = new SampleClass();

      Field field = SampleClass.class.getField("sampleField");

      field.setBoolean(sampleObject, false);

      System.out.println(field.getBoolean(sampleObject));
   }
}

class SampleClass {
   public static boolean sampleField = true;
}

运行结果为 false

5、枚举实现

public enum EnumSingle {
    INSTANCE;

    public static EnumSingle getInstance(){
        return INSTANCE;
    }

    static class Test{
        public static void main(String[] args) {
            EnumSingle instance = EnumSingle.getInstance();
            EnumSingle instance2 = EnumSingle.getInstance();
            System.out.println(instance);
            System.out.println(instance2);
        }
    }
}

工厂模式(factory)

作用: 实现创建者和调用者的分离

核心本质:

  • 实例化对象不需要new,用工厂方法代替
  • 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
public interface Car {
    void name();
}
public class Bmw implements Car{
    @Override
    public void name() {
        System.out.println("宝马");
    }
}
public class HONDA implements Car{
    @Override
    public void name() {
        System.out.println("本田");
    }
}
public class Customer {
    public static void main(String[] args) {
        /* 顾客类购买车时,创建车对象 如果对象内有很多参数要传 作为顾客调用者
        需要了解类的内部参数,等同于了解车是如果造出来的 这个非常不合理*/
        Bmw car = new Bmw();
        HONDA car2 = new HONDA();
        car.name();
        car2.name();
        
    }
}

简单工厂模式

  • 用来生产同一等级结构中的任意产品(对于增加新的产品,需要修改已有代码)
public class CarFactory {
    public static Car getCar(String car){
        if (car.equals("宝马")){
            return new Bmw();
        } else if (car.equals("本田")){
            return new HONDA();
        } else
            return null;
    }
}
/* 创建一个工厂类 顾客只需调用这个工厂去实现买车 而不需要了解车的细节*/
public class Customer {
    public static void main(String[] args) {
        Car car = CarFactory.getCar("宝马");
        car.name();
    }
}

工厂方法模式

  • 用来生产同一等级中的固定产品(支持增加任意产品)
//工厂接口
public interface CarFactory {
    Car getCar();
}
public class HondaFactory implements CarFactory{
    @Override
    public Car getCar() {
        return new HONDA();
    }
}
public class BmwFactory implements CarFactory{
    @Override
    public Car getCar() {
        return new Bmw();
    }
}
public class Customer {
    public static void main(String[] args) {
        Car car = new BmwFactory().getCar();
        car.name();
        Car car1 = new HondaFactory().getCar();
        car1.name();
    }
}

新增业务时,可以不需要修改原有代码

抽象工厂模式

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

  • 适用场景:
    • 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
    • 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
    • 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖具体的实现
  • 优点:
    • 具体产品在应用层的代码隔离,无需关心创建的细节
    • 将一个系列的产品统一到一起创建
  • 缺点:
    • 规定了所有可能被创建的产品集合,产品族中拓展性的产品困难;
    • 增加了系统的抽象性和理解难度
//手机产品接口
public interface Phone {
    void start();

    void shutdown();

    void call();

    void send();
}
//路由器产品接口
public interface Router {
    void start();

    void shutdown();

    void openWifi();

    void setting();
}

具体手机产品:

public class HuaweiPhone implements Phone{
    @Override
    public void start() {
        System.out.println("华为-手机开机");
    }

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

    @Override
    public void call() {
        System.out.println("华为-打电话");
    }

    @Override
    public void send() {
        System.out.println("华为-发短信");
    }
}
public class XiaomiPhone implements Phone{
    @Override
    public void start() {
        System.out.println("小米-手机开机");
    }

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

    @Override
    public void call() {
        System.out.println("小米-打电话");
    }

    @Override
    public void send() {
        System.out.println("小米-发短信");
    }
}

具体路由器产品:

public class HuaweiRouter implements Router{
    @Override
    public void start() {
        System.out.println("华为-路由器启动");
    }

    @Override
    public void shutdown() {
        System.out.println("华为-路由器关闭");
    }

    @Override
    public void openWifi() {
        System.out.println("华为-开wifi");
    }

    @Override
    public void setting() {
        System.out.println("华为-路由器设置");
    }
}
public class XiaomiRouter implements Router{
    @Override
    public void start() {
        System.out.println("小米-路由器启动");
    }

    @Override
    public void shutdown() {
        System.out.println("小米-路由器关闭");
    }

    @Override
    public void openWifi() {
        System.out.println("小米-开wifi");
    }

    @Override
    public void setting() {
        System.out.println("小米-路由器设置");
    }
}

抽象工厂:

//工厂
public interface Factory {
    //生产手机
    Phone phoneFactory();

    //生产路由器
    Router routerFactory();
}

华为工厂

//华为
public class HuaweiFactory implements Factory{

    @Override
    public Phone phoneFactory() { return new HuaweiPhone(); }

    @Override
    public Router routerFactory() {
        return new HuaweiRouter();
    }
}

小米工厂

public class XiaomiFactory implements Factory {
    @Override
    public Phone phoneFactory() {
        return new XiaomiPhone();
    }

    @Override
    public Router routerFactory() {
        return new XiaomiRouter();
    }
}

不同的产品族调用不同的工厂

public class Client {
    public static void main(String[] args) {
        //小米工厂
        XiaomiFactory xiaomiFactory = new XiaomiFactory();
        Phone xiaomiphone = xiaomiFactory.phoneFactory();
        xiaomiphone.call();
        Router xiaomirouter = xiaomiFactory.routerFactory();
        xiaomirouter.openWifi();

        //华为工厂
        HuaweiFactory huaweiFactory = new HuaweiFactory();
        Phone huaweiphone = huaweiFactory.phoneFactory();
        huaweiphone.send();
        Router huaweirouter = huaweiFactory.routerFactory();
        huaweirouter.setting();
    }
}