工厂模式
我要实现一个发送邮件的功能,一开始我想着是准备用QQ邮箱去发送,于是我在项目消息通知模块定义了一个类
package com.hwq.message;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class QQMailService {
public boolean sendMail(String content){
System.out.println("通过QQ邮箱发送邮件");
System.out.println("内容:"+content);
return true;
}
}
package com.hwq.order;
import com.hwq.message.QQMailService;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class OrderService {
public void createOrder(){
System.out.println("开始创建订单");
System.out.println("订单创建成功");
// 发邮件通知用户
QQMailService mailService = new QQMailService();
mailService.sendMail("您的订单XXX,已在仓库下单成功");
}
}
package com.hwq.user;
import com.hwq.message.QQMailService;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class UserService {
public void registerUser(){
System.out.println("开始注册用户");
System.out.println("用户注册成功");
QQMailService mailService = new QQMailService();
mailService.sendMail("您好,XXX 恭喜您成功尊贵的VIP");
}
}
当然还可能有其他模块需要发邮件,这里只列举了两个,看起来这样用好像没啥问题,但是有一天QQ邮箱不让用了,要求换成网易邮箱。OK那这个时候,我们去新增一个WYMailService,然后把所有new QQMailService的地方,改成new WYMaillService。好我们作为一个受气包,那我去改。然后过了几天领导有说要改成雅虎邮箱。那这个时候不得改废了,这个时候我们就想到了先辈们的经验工厂模式,那我们就可以改成这样
package com.hwq.message;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public interface MailFactory {
boolean sendMail(String content);
static MailFactory getFactory(){
return mailService;
}
static MailFactory mailService = new WYMailService();
}
package com.hwq.order;
import com.hwq.message.MailFactory;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class OrderService {
public void createOrder(){
System.out.println("开始创建订单");
System.out.println("订单创建成功");
// 发邮件通知用户
//QQMailService mailService = new QQMailService();
MailFactory mailService = MailFactory.getFactory();
mailService.sendMail("您的订单XXX,已在仓库下单成功");
}
}
package com.hwq.user;
import com.hwq.message.MailFactory;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class UserService {
public void registerUser(){
System.out.println("开始注册用户");
System.out.println("用户注册成功");
//QQMailService mailService = new QQMailService();
MailFactory mailService = MailFactory.getFactory();
mailService.sendMail("您好,XXX 恭喜您成功尊贵的VIP");
}
}
终于好了一点,后面再变动邮箱我就只用改动MailFactory里面的getFactory方法,不用去改其他模块的代码,是不是要舒服点。
那这么做除了能在一定程度上符合闭合原则(尽量不修改已有代码,而是新增扩展代码满足需求),其实还有一个功能,那就是new XXMailService是我可以控制的,我可以在一个地方给初始化一些参数进去,而其他用到的地方是不用关心的,这个地方可以参考Integer.valueOf的方法实现
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
它可以帮你从缓存中取,缓存中取不到再new 一个,而这些使用的人是不需要关心的。
抽象工厂模式
先说一下抽象工厂模式和工厂模式有啥区别吧,工厂模式是提供了一个创建一个对象的接口,具体这个产品是由什么实现的这个是子类决定的,抽象工厂是为了创建一系列相关的对象的,抽象工厂没有具体的实现,他的产品都是抽象,挺绕的。简单点理解就是抽象工厂里面的方法也是抽象的,我需要有实际的工厂去实现、重写我们抽象工厂里面的抽象方法。
上代码吧,我现在要实现一个云存储,市面上有两家供应商一家上传下载快,但是数据安全性稍低,另一家上传下载一般,但是数据安全性很高,我可能要根据用户设置的偏好来决定,他是需要把数据上传到第一家还是第二家
package com.hwq.afactory;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public interface AbstractFactory {
ImageService createImageService();
VideoService createVideoService();
static AbstractFactory getFactory(String name){
if("fast".equals(name)){
return new FastFactory();
} else if("safety".equals(name)){
return new SafetyFactory();
} else {
throw new IllegalArgumentException("未知的类型");
}
}
}
---------------------------------------------------------
package com.hwq.afactory;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public interface ImageService {
void createImage();
}
---------------------------------------------------------
package com.hwq.afactory;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public interface VideoService {
void createVideo();
}
---------------------------------------------------------
package com.hwq.afactory;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class FastImagetServiceImpl implements ImageService {
@Override
public void createImage() {
System.out.println("速度快的上传图片方法");
}
}
---------------------------------------------------------
package com.hwq.afactory;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class FastVideoServiceImpl implements VideoService {
@Override
public void createVideo() {
System.out.println("速度快的上传视频的方法");
}
}
---------------------------------------------------------
package com.hwq.afactory;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class FastFactory implements AbstractFactory {
@Override
public ImageService createImageService() {
return new FastImagetServiceImpl();
}
@Override
public VideoService createVideoService() {
return new FastVideoServiceImpl();
}
}
---------------------------------------------------------
package com.hwq.afactory;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class SafetyImageService implements ImageService {
@Override
public void createImage() {
System.out.println("安全性高的上传图片的方法");
}
}
---------------------------------------------------------
package com.hwq.afactory;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class SafetyVideoService implements VideoService {
@Override
public void createVideo() {
System.out.println("安全性高的上传视频方法");
}
}
---------------------------------------------------------
package com.hwq.afactory;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class SafetyFactory implements AbstractFactory {
@Override
public ImageService createImageService() {
return new SafetyImageService();
}
@Override
public VideoService createVideoService() {
return new SafetyVideoService();
}
}
抽象工厂是为了让创建工厂和一组产品与使用分离,并可以随时切换到另一个实际工厂和另一组实际产品,抽象工厂只定义我什么样的一个工厂,我工厂需要什么样的产品,具体如何实现工厂你可以用FastFactory也可以用SafetyFactory去实现,那产品具体怎么实现你可以用FastImageServiceImpl也可以用SafetyImageServiceImpl,其他模块在用的时候我只用使用抽象工厂和使用抽象工厂里面的抽象产品即可。
生成器模式
生成器模式是为了解决在创建一个对象的,要装上很多别的零部件以实现一些扩展的功能或者多带一些参数,下面实现一个简单加密的方法。
package com.hwq.builder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class Signature {
public static class Builder{
private String method;
private String queryString;
private String body;
public Builder addMethod(String m){
method = m;
return this;
}
public Builder addQueryString(String q){
queryString = q;
return this;
}
public Builder addBody(String b){
body = b;
return this;
}
public String build(){
String baseStr = new String(Base64.getEncoder().encode((this.method + this.queryString + this.body).getBytes(StandardCharsets.UTF_8)));
return baseStr;
}
}
}
package com.hwq.builder;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class Main {
public static void main(String[] args) {
String str = new Signature.Builder()
.addMethod("GET")
.addBody("")
.addQueryString("state=1")
.build();
}
}
这种链式调用大家肯定很熟悉StringBuilder.append不就是这样的吗,其实那些给对象set属性的方式也是生成器的一种,只不过如果set的方法里面返回this,就可以链式调用。
原型模式
个人感觉用得比较少,从字面意思来理解就是以某个对象为原型,创建一个新的对象,其实就是实现一个clone的方法,然后做一次深拷贝。
package com.hwq.prototype;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class User {
private String name;
private int age;
public User(String name,int age){
this.name = name;
this.age = age;
}
public User clone(){
User user = new User(this.name,this.age);
return user;
}
}
顺便看一下Arrays.copyOf的源码
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
单例
单例模式还是用的比较多,使用单例模式我们要处理单例的类里面不要记录一些状态数据,比如我想记录类里面某个方法的调用次数,我在类中定义了int counter = 0,然后方法末尾执行counter++。单线程下这个是没问题的,多线程的时候这个counter的值可能就不准了,因为每个线程调用这个方法的时候都会复制一次这个变量的值,两个线程同时调用的时候他们可能都复制到了counter=2,然后线程1做了counter++,在把值3设置回去,线程2也用读到的counter=2做了一次counter++,也把值3设置回去了,我们会认为这个方法只调用了3次,实际确调用了4次。
实现单例的方式有几种,我就列举四种吧,饿汉式、懒汉式、静态代码块、枚举
package com.hwq.singleton;
/**
* @author huangwq
* @date 2024/4/12
* @description 描述
*/
public class SingleService {
// 饿汉式
private SingleService(){}
private static SingleService singleService = new SingleService();
public static SingleService getInstance(){
return singleService;
}
}
饿汉式直接在类中定义并初始化了一个静态变量,那么这个变量会随类加载而初始化,并且JVM为我们保证了类加载的过程是单线程的。但是这个还会有一个问题,我们还是可以通过反射去调用这个private的构造函数,像序列化反序列化的时候代码如下:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<SingleService> declaredConstructor = SingleService.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingleService ss1 = declaredConstructor.newInstance();
SingleService ss2 = SingleService.getInstance();
SingleService ss3 = SingleService.getInstance();
System.out.println(System.identityHashCode(ss1)); // 1975012498
System.out.println(System.identityHashCode(ss2)); // 1808253012
System.out.println(System.identityHashCode(ss3)); // 1808253012
}
ss1 就是我们没有通过单例模式创建出来的,ss2\ss3是通过单例模式创建出来的。
public class SingleService {
// 懒汉式初级版
private SingleService(){}
private static SingleService singleService = null;
public static SingleService getInstance(){
if(singleService == null){
singleService = new SingleService();
}
return singleService;
}
}
public class SingleService {
// 懒汉式加锁版
private SingleService(){}
private static SingleService singleService = null;
public static synchronized SingleService getInstance(){
if(singleService == null){
singleService = new SingleService();
}
return singleService;
}
}
public class SingleService {
// 懒汉式加锁小粒度版(也叫双重判断)
private SingleService(){}
private static SingleService singleService = null;
public static SingleService getInstance(){
if(singleService == null){
synchronized (SingleService.class) {
if(singleService == null) {
singleService = new SingleService();
}
}
}
return singleService;
}
}
懒汉式这三中方式的好处是只有在这个类被用到的时候才去创建对象,但是会有线程安全问题,所有后面两种方式加上了锁,但是加了又会造成性能问题,所以我们一般不用这种方式,当然这种方式依然存在反射调用私有构造函数的问题。
public class SingleService {
// 静态代码块
private SingleService(){}
private static SingleService singleService = null;
static {
singleService = new SingleService();
}
public static SingleService getInstance(){
return singleService;
}
}
静态代码块其实就和饿汉式差不多,都是用类加载的机制保证线程安全,静态代码块依然存在反射调用私有构造函数的问题。
public enum ESingleService {
INSTANCE;
private String name = "test";
public String getName(){
return name;
}
}
public static void main(String[] args) {
ESingleService.INSTANCE.getName();
}
枚举是唯一可以避免私有构造反射调用的方式,所以一般推荐大家用枚举。
总结一下其实我们日常开发过程中已经很少去自己实现单例了,更多的是用Spring框架创建一个Singleton的bean,而且Spring默认bean为Singleton模式。