【Java常用设计模式】通俗易懂的玩转单例、建造者、工厂、策略模式(保姆篇)

在这里插入图片描述


更多相关内容可查看

在一个狂风骤雨的下午,有人突然问了我一句,单例模式是什么,我愣了,相信看完这篇就不会愣了,本文以通俗易懂的方式写的,可能有不严谨的地方🙏🙏

单例模式

一个类只有一个实例对象(例外:Spring中的Bean的单例是指在一个容器中是单例)

那是怎么实现的呢?我不能new很多吗?看看如下代码

public class Singleton {
    // 创建 Singleton 类的一个对象
    private static Singleton instance = new Singleton();

    // 让构造函数为 private,这样该类就不会被实例化
    private Singleton(){}

    // 获取唯一可用的对象
    public static Singleton getInstance(){
        return instance;
    }

    public void showMessage(){
        System.out.println("Hello World!");
    }
}

看了如上代码,因为构造函数私有,想用这个对象只能Singleton.getInstance()来获取这个对象,是不是就很一目了然了,所以引出单例模式的核心:无论在哪里,无论何时,只要你调用 Singleton.getInstance(),你都将得到同一个 Singleton 对象

那我什么时候用他比较优雅呢?
如果你有一个配置类,它读取配置文件并提供配置设置,你可能只想在程序中创建一个这样的对象。使用单例模式,你可以确保所有需要访问配置设置的代码都使用同一个配置对象,这样可以避免在内存中存储多个相同的配置对象,从而节省内存

以上这种方式为饿汉式(静态常量方式):在类加载的时候就完成了实例化,我不用他他就已经存在了

有饿汉式就有懒汉式(双重检查机制),看看如下代码

public class Singleton {

    private volatile static Singleton INSTANCE;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

这里初始化的是一个INSTANCE变量,在调用getInstance(),这两个if会确保INSTANCE只生成一个对象

第二个if是干嘛的?为什么还要判断一遍?

假设AB同时走到同步代码块,A先抢到锁,进入代码,创建了对象,释放锁,此时B进入代码块,如果没有判断null,那么就会直接再次创建对象,那么就不是单例的了

那为什么变量上用一个volatile?

volatile:可以保证变量的可见性禁止指令重排序

那什么是重排序?
在多线程环境下,为了保证性能,编译器和处理器可能会对代码进行重排序

例如原来的执行顺序是:

  • 第一步:分配内存空间给Singleton这个对象
  • 第二步:初始化对象
  • 第三步:将INSTANCE变量指向Singleton这个对象内存地址

假设没有使用volatile关键字发生了重排序,第二步和第三步执行过程被调换了,也就是先将INSTANCE变量指向Singleton这个对象内存地址,再初始化对象。这样在发生并发的情况下,另一个线程经过第一个if非空判断时,发现已经为不为空,就直接返回了这个对象,但是此时这个对象还未初始化,内部的属性可能都是空值,一旦被使用的话,就很有可能出现空指针这些问题。


建造者模式

优雅的构建一个对象

public class BuilderPatternDemo {
    public static void main(String[] args) {
        Car car = new Car.CarBuilder()
                .buildEngine("Ferrari V8 Engine")
                .buildWheels("Ferrari Sports Wheels")
                .buildColor("Red")
                .build();
        System.out.println("Car built: " + car);
    }
}

class Car {
    private String engine;
    private String wheels;
    private String color;

    // ... getter and setter methods ...

    static class CarBuilder {
        private Car car;

        public CarBuilder() {
            car = new Car();
        }

        public CarBuilder buildEngine(String engine) {
            car.setEngine(engine);
            return this;
        }

        public CarBuilder buildWheels(String wheels) {
            car.setWheels(wheels);
            return this;
        }

        public CarBuilder buildColor(String color) {
            car.setColor(color);
            return this;
        }

        public Car build() {
            return car;
        }
    }
}

注意每一个build方法(例如:buildEngine)返回的this都是CarBuilder对象,所以他可以继续调用CarBuilder的其他build方法,最后返回一个Car对象,这样是不是以后我们在创建实体类的时候也可以如此呢,是不是比new一个对象,然后不断set显得更优雅一点了


工厂模式

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

简单工厂模式

调用者不需要关心对象是如何具体创建的

public class SimpleAnimalFactory {

    public Animal createAnimal(String animalType) {
        if ("cat".equals(animalType)) {
            Cat cat = new Cat();
            //一系列复杂操作
            return cat;
        } else if ("dog".equals(animalType)) {
            Dog dog = new Dog();
            //一系列复杂操作
            return dog;
        } else {
            throw new RuntimeException("animalType=" + animalType + "无法创建对应对象");
        }
    }

}
//调用
SimpleAnimalFactory animalFactory = new SimpleAnimalFactory();
Animal cat = animalFactory.createAnimal("cat");

看了以上代码,我为什么不能直接new 一个Cat的对象,需要这样折腾一下?

对,是不需要,这种情况直接new就可以,但如果你想想,这个if语句是进行类型判断,是不是字符串,是不是数值类型,根据类型去创建对象的业务场景时,你的工厂类可能穿过来个json串,进行解析,解析完进行createAnimal创建对象就可以使用这种方式

工厂方法模式

上面说的简单工厂模式看起来没啥问题,但是还是违反了七大设计原则的OCP原则,也就是开闭原则。也就是在加一个动物,是不是就要去修改代码的问题

所以解决简单工厂模式违反开闭原则的问题,就可以使用工厂方法模式来解决。

/**
 * 工厂接口
 */
public interface AnimalFactory {
    Animal createAnimal();
}

/**
 * 小猫实现
 */
public class CatFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        Cat cat = new Cat();
        //一系列复杂操作
        return cat;
    }
}

/**
 * 小狗实现
 */
public class DogFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        Dog dog = new Dog();
        //一系列复杂操作
        return dog;
    }
}

AnimalFactory animalFactory = new CatFactory();
Animal cat = animalFactory.createAnimal();

此时假设需要新增一个动物兔子,那么只需要实现AnimalFactory接口就行,对于原来的猫和狗的实现,其实代码是不需要修改的,遵守了对修改关闭的原则,同时由于是对扩展开放,实现接口就是扩展的意思,那么也就符合扩展开放的原则。

应用(应用一说,offer到手):
1.在Mybatis中,当需要调用Mapper接口执行sql的时候,需要先获取到SqlSession,通过SqlSession再获取到Mapper接口的动态代理对象,而SqlSession的构造过程比较复杂,所以就提供了SqlSessionFactory工厂类来封装SqlSession的创建过程。

在这里插入图片描述

2.BeanFactory就是Bean生成的工厂。一个Spring Bean在生成过程中会经历复杂的一个生命周期,而这些生命周期对于使用者来说是无需关心的,所以就可以将Bean创建过程的逻辑给封装起来,提取出一个Bean的工厂。

在这里插入图片描述

抽象工厂模式
工厂方法模式其实是创建一个产品的工厂,比如上面的例子中,AnimalFactory其实只创建动物这一个产品。而抽象工厂模式特点就是创建一系列产品,比如说,不同的动物吃的东西是不一样的,那么就可以加入食物这个产品,通过抽象工厂模式来实现。

public interface AnimalFactory {

    Animal createAnimal();

    Food createFood();
        
}

策略模式

假设现在有一个需求,需要将消息推送到不同的平台。

最简单的做法其实就是使用if else来做判断就行了。

public void notifyMessage(User user, String content, int notifyType) {
    if (notifyType == 0) {
        //调用短信通知的api发送短信
    } else if (notifyType == 1) {
        //调用app通知的api发送消息
    }
}

根据不同的平台类型进行判断,调用对应的api发送消息。

虽然这样能实现功能,但是跟上面的提到的简单工厂的问题是一样的,同样违反了开闭原则。当需要增加一种平台类型,比如邮件通知,那么就得修改notifyMessage的方法,再次进行else if的判断,然后调用发送邮件的邮件发送消息。

此时就可以使用策略模式来优化了。

首先设计一个策略接口:

public interface MessageNotifier {

    /**
     * 是否支持改类型的通知的方式
     *
     * @param notifyType 0:短信 1:app
     * @return
     */
    boolean support(int notifyType);

    /**
     * 通知
     *
     * @param user
     * @param content
     */
    void notify(User user, String content);

}

短信通知实现:

@Component
public class SMSMessageNotifier implements MessageNotifier {
    @Override
    public boolean support(int notifyType) {
        return notifyType == 0;
    }

    @Override
    public void notify(User user, String content) {
        //调用短信通知的api发送短信
    }
}

app通知实现:

public class AppMessageNotifier implements MessageNotifier {
    @Override
    public boolean support(int notifyType) {
        return notifyType == 1;
    }

    @Override
    public void notify(User user, String content) {
       //调用通知app通知的api
    }
}

最后notifyMessage的实现只需要要循环调用所有的MessageNotifier的support方法,一旦support方法返回true,说明当前MessageNotifier支持该类的消息发送,最后再调用notify发送消息就可以了。

@Resource
private List<MessageNotifier> messageNotifiers;

public void notifyMessage(User user, String content, int notifyType) {
    for (MessageNotifier messageNotifier : messageNotifiers) {
        if (messageNotifier.support(notifyType)) {
            messageNotifier.notify(user, content);
        }
    }
}

那么如果现在需要支持通过邮件通知,只需要实现MessageNotifier接口,注入到Spring容器就行,其余的代码根本不需要有任何变动。

到这其实可以更好的理解策略模式了。就拿上面举的例子来说,短信通知,app通知等其实都是发送消息一种策略,而策略模式就是需要将这些策略进行封装,抽取共性,使这些策略之间相互替换。

应用:

1、策略模式在SpringMVC中提供了一个策略接口HandlerMethodArgumentResolver对接口方法参数的处理

比如说,我们经常在写接口的时候,会使用到了@PathVariable、@RequestParam、@RequestBody等注解,一旦我们使用了注解,SpringMVC会处理注解,从请求中获取到参数,然后再调用接口传递过来,而这个过程,就使用到了策略模式。

在这里插入图片描述

2、SpringBoot对于配置文件的加载PropertySourceLoader也是策略模式的运用

在这里插入图片描述

本篇小结

本文通过对设计模式的讲解加源码举例的方式介绍了4种在代码设计中常用的设计模式:

  • 单例模式
  • 建造者模式
  • 工厂模式
  • 策略模式

写到这就开始搬砖去了,看完这篇相信再问你单例模式是什么你已经能侃侃而谈了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

来一杯龙舌兰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值