工厂模式

理论

工厂模式又称工厂方法模式,是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型

它的主要意图是定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类执行

简单来说就是为了提高代码结构的拓展性,屏蔽功能类的具体实现逻辑,外部只需要调用。这也是去掉众多ifelse的方式。缺点也很明显,需要实现的类非常多,如何去维护,怎样降低开发成本。

案例

public interface ICommodity {

void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;

}

public class CardCommodityService implements ICommodity {

private Logger logger = LoggerFactory.getLogger(CardCommodityService.class);

// 模拟注入
private IQiYiCardService iQiYiCardService = new IQiYiCardService();

public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
String mobile = queryUserMobile(uId);
iQiYiCardService.grantToken(mobile, bizId);
logger.info("请求参数[爱奇艺兑换卡] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
logger.info("测试结果[爱奇艺兑换卡]:success");
}

private String queryUserMobile(String uId) {
return "15200101232";
}

}

public class CouponCommodityService implements ICommodity {

private Logger logger = LoggerFactory.getLogger(CouponCommodityService.class);

private CouponService couponService = new CouponService();

public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
CouponResult couponResult = couponService.sendCoupon(uId, commodityId, bizId);
logger.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
logger.info("测试结果[优惠券]:{}", JSON.toJSON(couponResult));
if (!"0000".equals(couponResult.getCode())) throw new RuntimeException(couponResult.getInfo());
}

}

public class GoodsCommodityService implements ICommodity {

private Logger logger = LoggerFactory.getLogger(GoodsCommodityService.class);

private GoodsService goodsService = new GoodsService();

public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
DeliverReq deliverReq = new DeliverReq();
deliverReq.setUserName(queryUserName(uId));
deliverReq.setUserPhone(queryUserPhoneNumber(uId));
deliverReq.setSku(commodityId);
deliverReq.setOrderId(bizId);
deliverReq.setConsigneeUserName(extMap.get("consigneeUserName"));
deliverReq.setConsigneeUserPhone(extMap.get("consigneeUserPhone"));
deliverReq.setConsigneeUserAddress(extMap.get("consigneeUserAddress"));

Boolean isSuccess = goodsService.deliverGoods(deliverReq);

logger.info("请求参数[优惠券] => uId:{} commodityId:{} bizId:{} extMap:{}", uId, commodityId, bizId, JSON.toJSON(extMap));
logger.info("测试结果[优惠券]:{}", isSuccess);

if (!isSuccess) throw new RuntimeException("实物商品发放失败");
}

private String queryUserName(String uId) {
return "花花";
}

private String queryUserPhoneNumber(String uId) {
return "15200101232";
}

}

public class StoreFactory {

public ICommodity getCommodityService(Integer commodityType) {
if (null == commodityType) return null;
if (1 == commodityType) return new CouponCommodityService();
if (2 == commodityType) return new GoodsCommodityService();
if (3 == commodityType) return new CardCommodityService();
throw new RuntimeException("不存在的商品服务类型");
}

}

总结

工厂模式是创建者模式,创建者模式是为了创建出屏蔽内部实现细节的类,这些类可以简化代码的维护,还能不断复用,进而优化代码。

工厂方法模式旨在让子类自己决定需要实例化哪一个工厂类。

image-20250104111043977

image-20250104111207529

image-20250104113216062

抽象工厂模式

理论

抽象工厂模式和工厂方法模式都是为了解决接口选择问题。但在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。

工厂方法模式解决了一个系列的类的创建。而抽象工厂模式则解决了不同版本的一个系列的类的创建。

  • 这个设计模式满足了;单一职责原则、开闭原则、解耦等。但是随着业务的不断拓展,可能会造成类实现上的复杂度。可以通过引入其他设计模式和代理类以及自动生成加载的方式降低此项缺点。

案例

项目有多个Redis服务,每个Redis服务创建的时期不一样,各类操作的方法名不一样,并且以后可能还会拓展更多的Redis服务,使用抽象工厂模式来简化代码。

准备三个集群的代码

// 单体Redis服务
public class RedisUtils {

private Logger logger = LoggerFactory.getLogger(RedisUtils.class);
private Map<String, String> dataMap = new ConcurrentHashMap<>();

public String get(String key) {
logger.info("Redis获取数据 key:{}", key);
return dataMap.get(key);
}

public void set(String key, String value) {
logger.info("Redis写入数据 key:{} val:{}", key, value);
dataMap.put(key, value);
}

public void set(String key, String value, long timeout, TimeUnit timeUnit) {
logger.info("Redis写入数据 key:{} val:{} timeout:{} timeUnit:{}", key, value, timeout, timeUnit.toString());
dataMap.put(key, value);
}

public void del(String key) {
logger.info("Redis删除数据 key:{}", key);
dataMap.remove(key);
}
}
// 集群EGM
public class EGM {

private Logger logger = LoggerFactory.getLogger(EGM.class);

private Map<String, String> dataMap = new ConcurrentHashMap<String, String>();

public String gain(String key) {
logger.info("EGM获取数据 key:{}", key);
return dataMap.get(key);
}

public void set(String key, String value) {
logger.info("EGM写入数据 key:{} val:{}", key, value);
dataMap.put(key, value);
}

public void setEx(String key, String value, long timeout, TimeUnit timeUnit) {
logger.info("EGM写入数据 key:{} val:{} timeout:{} timeUnit:{}", key, value, timeout, timeUnit.toString());
dataMap.put(key, value);
}

public void delete(String key) {
logger.info("EGM删除数据 key:{}", key);
dataMap.remove(key);
}
}

// 集群 IIR
public class IIR {

private Logger logger = LoggerFactory.getLogger(IIR.class);

private Map<String, String> dataMap = new ConcurrentHashMap<String, String>();

public String get(String key) {
logger.info("IIR获取数据 key:{}", key);
return dataMap.get(key);
}

public void set(String key, String value) {
logger.info("IIR写入数据 key:{} val:{}", key, value);
dataMap.put(key, value);
}

public void setExpire(String key, String value, long timeout, TimeUnit timeUnit) {
logger.info("IIR写入数据 key:{} val:{} timeout:{} timeUnit:{}", key, value, timeout, timeUnit.toString());
dataMap.put(key, value);
}

public void del(String key) {
logger.info("IIR删除数据 key:{}", key);
dataMap.remove(key);
}
}

环境指出,我们的系统正在大量的使用Redis服务,但是因为系统不能满足业务的快速发展,因此需要迁移到集群服务中。而这时有两套集群服务需要兼容使用,又要满足所有的业务系统改造的同时不影响线上使用。

单体Redis使用代码

// 接口
public interface CacheService {

String get(final String key);

void set(String key, String value);

void set(String key, String value, long timeout, TimeUnit timeUnit);

void del(String key);

}
// 实现
public class CacheServiceImpl implements CacheService {

private RedisUtils redisUtils = new RedisUtils();

public String get(String key) {
return redisUtils.get(key);
}

public void set(String key, String value) {
redisUtils.set(key, value);
}

public void set(String key, String value, long timeout, TimeUnit timeUnit) {
redisUtils.set(key, value, timeout, timeUnit);
}

public void del(String key) {
redisUtils.del(key);
}

}

使用IFELSE改造为满足所有Redis服务的使用方式

// 接口
public interface CacheService {

String get(final String key, int redisType);

void set(String key, String value, int redisType);

void set(String key, String value, long timeout, TimeUnit timeUnit, int redisType);

void del(String key, int redisType);
}

// 实现
public class CacheServiceImpl implements CacheService {

private EGM egm = new EGM();

private IIR iir = new IIR();

private RedisUtils redisUtils = new RedisUtils();

@Override
public String get(String key, int redisType) {

if (1 == redisType) {
return egm.gain(key);
}

if (2 == redisType) {
return iir.get(key);
}

return redisUtils.get(key);
}

@Override
public void set(String key, String value, int redisType) {

if (1 == redisType) {
egm.set(key, value);
return;
}

if (2 == redisType) {
iir.set(key, value);
return;
}

redisUtils.set(key, value);
}

@Override
public void set(String key, String value, long timeout, TimeUnit timeUnit, int redisType) {

if (1 == redisType) {
egm.setEx(key, value, timeout, timeUnit);
return;
}

if (2 == redisType) {
iir.setExpire(key, value, timeout, timeUnit);
return;
}

redisUtils.set(key, value, timeout, timeUnit);
}

@Override
public void del(String key, int redisType) {

if (1 == redisType) {
egm.delete(key);
return;
}

if (2 == redisType) {
iir.del(key);
return;
}

redisUtils.del(key);
}
}
// 单元测试
@Test
public void test_CacheService() {

CacheService cacheService = new CacheServiceImpl();

cacheService.set("user_name_01", "Kin.", 2);
String val01 = cacheService.get("user_name_01", 2);
System.out.println("测试结果:" + val01);

}
// 结果
2025-01-05 10:54:20.592 INFO main (IIR.java:22) | IIR写入数据 key:user_name_01 val:Kin.
2025-01-05 10:54:20.593 INFO main (IIR.java:17) | IIR获取数据 key:user_name_01
测试结果:Kin.

使用抽象工厂模式重构代码

本次抽象工厂的创建和获取方式,会采用代理类的方式进行实现。所被代理的类就是目前的Redis操作方法类,让这个类在不需要任何修改的情况下就可以实现调用集群A和集群B的数据服务。

由于集群A和集群B在部分方法名上是不同的,因此需要做一个接口适配,而这个适配类就相当于工厂中的工厂,用于创建把不同的服务抽象为统一的接口做相同的业务。

  • 为了解决不同集群功能名的区别,需要适配器,有多少个集群就需要写多少个适配器,每个适配器也就是那个集群的标识,不同集群的适配器是同类型的产品,所以可以用工厂方法模式来创建。构建一个统一的适配器接口

    // 适配器接口
    public interface ICacheAdapter {

    String get(String key);

    void set(String key, String value);

    void set(String key, String value, long timeout, TimeUnit timeUnit);

    void del(String key);

    }
    // 实现具体的功能

    public class EGMCacheAdapter implements ICacheAdapter {

    private EGM egm = new EGM();

    @Override
    public String get(String key) {
    return egm.gain(key);
    }

    @Override
    public void set(String key, String value) {
    egm.set(key, value);
    }

    @Override
    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
    egm.setEx(key, value, timeout, timeUnit);
    }

    @Override
    public void del(String key) {
    egm.delete(key);
    }
    }

    public class IIRCacheAdapter implements ICacheAdapter {

    private IIR iir = new IIR();

    @Override
    public String get(String key) {
    return iir.get(key);
    }

    @Override
    public void set(String key, String value) {
    iir.set(key, value);
    }

    @Override
    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
    iir.setExpire(key, value, timeout, timeUnit);
    }

    @Override
    public void del(String key) {
    iir.del(key);
    }
    }

  • 抽象工厂需要解决的问题是针对不同的集群,实现统一的调用,所以需要定义统一调用的接口,抽象工厂产生的是各个工厂,也就是适配器,这里使用代理模式,实现根据适配器类型来自动生成对应类型的工厂

    public interface CacheService {

    String get(final String key);

    void set(String key, String value);

    void set(String key, String value, long timeout, TimeUnit timeUnit);

    void del(String key);
    }
    // 代理类执行逻辑
    public class JDKInvocationHandler implements InvocationHandler {

    private ICacheAdapter cacheAdapter;

    public JDKInvocationHandler(ICacheAdapter cacheAdapter) {
    this.cacheAdapter = cacheAdapter;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
    }
    }
    // 代理类创建逻辑
    /**
    *
    * @param interfaceClass 需要的产品类型
    * @param cacheAdapter 委托生产的工厂
    */
    public static <T> T getProxy(Class<T> interfaceClass, ICacheAdapter cacheAdapter) throws Exception {
    InvocationHandler handler = new JDKInvocationHandler(cacheAdapter);
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    Class<?>[] classes = interfaceClass.getInterfaces();
    return (T) Proxy.newProxyInstance(classLoader, new Class[]{classes[0]}, handler);
    }
  • 测试

    public class ApiTest {

    @Test
    public void test_CacheService() throws Exception {

    CacheService proxy_EGM = JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
    proxy_EGM.set("user_name_01", "kin.");
    String val01 = proxy_EGM.get("user_name_01");
    System.out.println("测试结果:" + val01);

    CacheService proxy_IIR = JDKProxy.getProxy(CacheServiceImpl.class, new IIRCacheAdapter());
    proxy_IIR.set("user_name_01", "kin.");
    String val02 = proxy_IIR.get("user_name_01");
    System.out.println("测试结果:" + val02);

    }
    }
    2025-01-05 11:32:59.404 INFO main (EGM.java:22) | EGM写入数据 key:user_name_01 val:kin.
    2025-01-05 11:32:59.407 INFO main (EGM.java:17) | EGM获取数据 key:user_name_01
    测试结果:kin.
    2025-01-05 11:32:59.407 INFO main (IIR.java:22) | IIR写入数据 key:user_name_01 val:kin.
    2025-01-05 11:32:59.407 INFO main (IIR.java:17) | IIR获取数据 key:user_name_01
    测试结果:kin.

总结

抽象工厂模式旨在解决在一个产品族,存在多个不同类型的产品(Redis集群、操作系统)情况下,接口的选择问题。

image-20250105113504836

建造者模式

理论

建造者模式完成的内容是将多个简单对象通过一步步的组装构建出一个复杂对象的过程。

建造者模式主要解决的问题是在软件系统中,有时候面临“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的过程构成;由于需求的变化,这个复杂对象的各个部分经常面临着重大的变化,但是将他们组合在一起的过程却相对稳定

案例

public interface IMenu {

/**
* 吊顶
*/
IMenu appendCeiling(Matter matter);

/**
* 涂料
*/
IMenu appendCoat(Matter matter);

/**
* 地板
*/
IMenu appendFloor(Matter matter);

/**
* 地砖
*/
IMenu appendTile(Matter matter);

/**
* 明细
*/
String getDetail();

}

/**
* 装修包
*/
public class DecorationPackageMenu implements IMenu {

private List<Matter> list = new ArrayList<Matter>(); // 装修清单
private BigDecimal price = BigDecimal.ZERO; // 装修价格

private BigDecimal area; // 面积
private String grade; // 装修等级;豪华欧式、轻奢田园、现代简约

private DecorationPackageMenu() {
}

public DecorationPackageMenu(Double area, String grade) {
this.area = new BigDecimal(area);
this.grade = grade;
}

public IMenu appendCeiling(Matter matter) {
list.add(matter);
price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price()));
return this;
}

public IMenu appendCoat(Matter matter) {
list.add(matter);
price = price.add(area.multiply(new BigDecimal("1.4")).multiply(matter.price()));
return this;
}

public IMenu appendFloor(Matter matter) {
list.add(matter);
price = price.add(area.multiply(matter.price()));
return this;
}

public IMenu appendTile(Matter matter) {
list.add(matter);
price = price.add(area.multiply(matter.price()));
return this;
}

public String getDetail() {

StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
"装修清单" + "\r\n" +
"套餐等级:" + grade + "\r\n" +
"套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
"房屋面积:" + area.doubleValue() + " 平米\r\n" +
"材料清单:\r\n");

for (Matter matter: list) {
detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
}

return detail.toString();
}

}

public class Builder {

public IMenu levelOne(Double area) {
return new DecorationPackageMenu(area, "豪华欧式")
.appendCeiling(new LevelTwoCeiling()) // 吊顶,二级顶
.appendCoat(new DuluxCoat()) // 涂料,多乐士
.appendFloor(new ShengXiangFloor()); // 地板,圣象
}

public IMenu levelTwo(Double area){
return new DecorationPackageMenu(area, "轻奢田园")
.appendCeiling(new LevelTwoCeiling()) // 吊顶,二级顶
.appendCoat(new LiBangCoat()) // 涂料,立邦
.appendTile(new MarcoPoloTile()); // 地砖,马可波罗
}

public IMenu levelThree(Double area){
return new DecorationPackageMenu(area, "现代简约")
.appendCeiling(new LevelOneCeiling()) // 吊顶,二级顶
.appendCoat(new LiBangCoat()) // 涂料,立邦
.appendTile(new DongPengTile()); // 地砖,东鹏
}

}

总结

当一个类的基本组成部分不会发生变化,但是其组成部分的类型很多,组成部分的组合经常发生变化,就可以选择建造者模式

原型模式

理论

原型模式主要解决的问题是创建重复对象,且这部分对象的内容本身较为复杂,生成过程可能从库或者RPC接口中获取数据的耗时较长,因此采取克隆的方式节省时间。

案例

原型模式主要解决的问题就是创建大量重复的类,而我们模拟的场景就需要给不同的用户都创建相同的试卷,但这些试卷的题目不便于每次都从库中获取,甚至有时候需要从远程的RPC中获取。这些是非常耗时的。而且随着创建对象的增多将严重影响效率

public class Topic {

private Map<String, String> option; // 选项;A、B、C、D
private String key; // 答案;B

public Topic() {
}

public Topic(Map<String, String> option, String key) {
this.option = option;
this.key = key;
}

public Map<String, String> getOption() {
return option;
}

public void setOption(Map<String, String> option) {
this.option = option;
}

public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}
}

public class TopicRandomUtil {

/**
* 乱序Map元素,记录对应答案key
* @param option 题目
* @param key 答案
* @return Topic 乱序后 {A=c., B=d., C=a., D=b.}
*/
static public Topic random(Map<String, String> option, String key) {
Set<String> keySet = option.keySet();
ArrayList<String> keyList = new ArrayList<String>(keySet);
Collections.shuffle(keyList);
HashMap<String, String> optionNew = new HashMap<String, String>();
int idx = 0;
String keyNew = "";
for (String next : keySet) {
String randomKey = keyList.get(idx++);
if (key.equals(next)) {
keyNew = randomKey;
}
optionNew.put(randomKey, option.get(next));
}
return new Topic(optionNew, keyNew);
}

}

public class QuestionBank implements Cloneable {

private String candidate; // 考生
private String number; // 考号

private ArrayList<ChoiceQuestion> choiceQuestionList = new ArrayList<ChoiceQuestion>();
private ArrayList<AnswerQuestion> answerQuestionList = new ArrayList<AnswerQuestion>();

public QuestionBank append(ChoiceQuestion choiceQuestion) {
choiceQuestionList.add(choiceQuestion);
return this;
}

public QuestionBank append(AnswerQuestion answerQuestion) {
answerQuestionList.add(answerQuestion);
return this;
}

@Override
public Object clone() throws CloneNotSupportedException {
QuestionBank questionBank = (QuestionBank) super.clone();
questionBank.choiceQuestionList = (ArrayList<ChoiceQuestion>) choiceQuestionList.clone();
questionBank.answerQuestionList = (ArrayList<AnswerQuestion>) answerQuestionList.clone();

// 题目乱序
Collections.shuffle(questionBank.choiceQuestionList);
Collections.shuffle(questionBank.answerQuestionList);
// 答案乱序
ArrayList<ChoiceQuestion> choiceQuestionList = questionBank.choiceQuestionList;
for (ChoiceQuestion question : choiceQuestionList) {
Topic random = TopicRandomUtil.random(question.getOption(), question.getKey());
question.setOption(random.getOption());
question.setKey(random.getKey());
}
return questionBank;
}

public void setCandidate(String candidate) {
this.candidate = candidate;
}

public void setNumber(String number) {
this.number = number;
}

@Override
public String toString() {

StringBuilder detail = new StringBuilder("考生:" + candidate + "\r\n" +
"考号:" + number + "\r\n" +
"--------------------------------------------\r\n" +
"一、选择题" + "\r\n\n");

for (int idx = 0; idx < choiceQuestionList.size(); idx++) {
detail.append("第").append(idx + 1).append("题:").append(choiceQuestionList.get(idx).getName()).append("\r\n");
Map<String, String> option = choiceQuestionList.get(idx).getOption();
for (String key : option.keySet()) {
detail.append(key).append(":").append(option.get(key)).append("\r\n");;
}
detail.append("答案:").append(choiceQuestionList.get(idx).getKey()).append("\r\n\n");
}

detail.append("二、问答题" + "\r\n\n");

for (int idx = 0; idx < answerQuestionList.size(); idx++) {
detail.append("第").append(idx + 1).append("题:").append(answerQuestionList.get(idx).getName()).append("\r\n");
detail.append("答案:").append(answerQuestionList.get(idx).getKey()).append("\r\n\n");
}

return detail.toString();
}

}

public class QuestionBankController {

private QuestionBank questionBank = new QuestionBank();

public QuestionBankController() {

Map<String, String> map01 = new HashMap<String, String>();
map01.put("A", "JAVA2 EE");
map01.put("B", "JAVA2 Card");
map01.put("C", "JAVA2 ME");
map01.put("D", "JAVA2 HE");
map01.put("E", "JAVA2 SE");

Map<String, String> map02 = new HashMap<String, String>();
map02.put("A", "JAVA程序的main方法必须写在类里面");
map02.put("B", "JAVA程序中可以有多个main方法");
map02.put("C", "JAVA程序中类名必须与文件名一样");
map02.put("D", "JAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来");

Map<String, String> map03 = new HashMap<String, String>();
map03.put("A", "变量由字母、下划线、数字、$符号随意组成;");
map03.put("B", "变量不能以数字作为开头;");
map03.put("C", "A和a在java中是同一个变量;");
map03.put("D", "不同类型的变量,可以起相同的名字;");

Map<String, String> map04 = new HashMap<String, String>();
map04.put("A", "STRING");
map04.put("B", "x3x;");
map04.put("C", "void");
map04.put("D", "de$f");

Map<String, String> map05 = new HashMap<String, String>();
map05.put("A", "31");
map05.put("B", "0");
map05.put("C", "1");
map05.put("D", "2");

questionBank.append(new ChoiceQuestion("JAVA所定义的版本中不包括", map01, "D"))
.append(new ChoiceQuestion("下列说法正确的是", map02, "A"))
.append(new ChoiceQuestion("变量命名规范说法正确的是", map03, "B"))
.append(new ChoiceQuestion("以下()不是合法的标识符",map04, "C"))
.append(new ChoiceQuestion("表达式(11+3*8)/4%3的值是", map05, "D"))
.append(new AnswerQuestion("小红马和小黑马生的小马几条腿", "4条腿"))
.append(new AnswerQuestion("铁棒打头疼还是木棒打头疼", "头最疼"))
.append(new AnswerQuestion("什么床不能睡觉", "牙床"))
.append(new AnswerQuestion("为什么好马不吃回头草", "后面的草没了"));
}

public String createPaper(String candidate, String number) throws CloneNotSupportedException {
QuestionBank questionBankClone = (QuestionBank) questionBank.clone();
questionBankClone.setCandidate(candidate);
questionBankClone.setNumber(number);
return questionBankClone.toString();
}

}

总结

原型模式旨在减少创建重量级对象的创建消耗,也方便在创建过程中进行一些拓展。通常的实现方式是使用clone(),这样可以避免重复做初始化操作、不需要与类中所属的其他类耦合等。

缺点:如果对象中包含了循环引用的克隆,以及类中深度使用对象的克隆,都会让该模式变的十分麻烦。

单例模式

理论

单例模式需要保证一个类在任意上下文中只有一个实例,并提供一个全局访问该实例的点。

案例

  • 数据库的连接池不会反复创建
  • spring中一个单例模式bean的生成和使用
  • 代码中需要设置的一些全局属性的保存

7种单例模式的实现

静态类使用

/**
* 静态类
*/
public class Singleton_00 {

public static Map<String,String> cache = new ConcurrentHashMap<String, String>();

}

  • 在程序第一次运行的时候直接初始化,并且不需要延迟加载
  • 不需要维持任何状态,仅仅用于全局访问

懒汉模式(线程不安全)

/**
* 懒汉模式(线程不安全) 空时加载
*/
public class Singleton_01 {

private static Singleton_01 instance;

private Singleton_01() {
}

public static Singleton_01 getInstance(){
if (null != instance) return instance;
return new Singleton_01();
}

}

  • 懒汉模式满足了懒加载,也就是在需要使用类的时候再去初始化类
  • 单例模式不允许外部创建类,所以加上了 private 限制
  • 如果有多个线程同时获取实例,会导致对象重复创建

懒汉模式(线程安全)

/**
* 懒汉模式(线程安全) + synchronized
*/
public class Singleton_02 {

private static Singleton_02 instance;

private Singleton_02() {
}

public static synchronized Singleton_02 getInstance(){
if (null != instance) return instance;
return new Singleton_02();
}

}
  • 使用锁来保证多个线程同时获取实例的时候,实例只会被创建一次
  • 锁保证了实例只有一个,但是后续所有线程获取实例的时候因为锁,都会被阻塞,这样导致运行效率大大降低。

饿汉模式(线程安全)

/**
* 饿汉模式(线程安全) 先加载
*/
public class Singleton_03 {

private static Singleton_03 instance = new Singleton_03();

private Singleton_03() {
}

public static Singleton_03 getInstance() {
return instance;
}

}
  • 类似第一种静态类,在类被初始化的时候直接加载,JVM会保证类的初始化是线程安全的
  • 浪费内存

使用类的内部类(线程安全)

/**
* 内部类懒加载(线程安全)
*/
public class Singleton_04 {

private static class SingletonHolder {
private static Singleton_04 instance = new Singleton_04();
}

private Singleton_04() {
}

public static Singleton_04 getInstance() {
return SingletonHolder.instance;
}

}
  • 推荐使用,既保证了类的懒加载,也保证了类的多线程安全,同时不会因为加锁而损耗性能

双重校验锁(线程安全)

/**
* 双检锁(线程安全) 空时 synchronized 再判空
*/
public class Singleton_05 {

private static volatile Singleton_05 instance;

private Singleton_05() {
}

public static Singleton_05 getInstance(){
if(null != instance) return instance;
synchronized (Singleton_05.class){
if (null == instance){
instance = new Singleton_05();
}
}
return instance;
}

}

  • 保证了懒加载
  • 把锁优化到了方法里面,保证了后续获取实例的时候不需要获取锁

CAS [AtomicReference] (线程安全)

/**
* CAS(线程安全) AtomicReference
*/
public class Singleton_06 {

private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<Singleton_06>();

private static Singleton_06 instance;

private Singleton_06() {
}

public static final Singleton_06 getInstance() {
for (; ; ) {
Singleton_06 instance = INSTANCE.get();
if (null != instance) return instance;
INSTANCE.compareAndSet(null, new Singleton_06());
return INSTANCE.get();
}
}

public static void main(String[] args) {
System.out.println(Singleton_06.getInstance()); // org.itstack.demo.design.Singleton_06@2b193f2d
System.out.println(Singleton_06.getInstance()); // org.itstack.demo.design.Singleton_06@2b193f2d
}
}
  • Java并发类提供了很多原子类来支持并发访问的数据安全
  • CAS不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相较于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发性。
  • CAS的缺点是可能忙等,如果一直没有获取到会处于死循环中

枚举单例(线程安全)

/**
* 枚举单例(线程安全) Effective Java 作者推荐
*/
public enum Singleton_07 {

INSTANCE;
public void test(){
System.out.println("hi~");
}

}

public void test() {
Singleton_07.INSTANCE.test();
}
  • 这种方式解决了最主要的:线程安全、自由串行化、单一实例
  • 该方式在存在继承场景下是不可用的

这种写法在功能上与共有域方法相近,但是它更简洁,无偿地提供了患行化机制,绝对防止对此实例化,即使是在面对复杂的串行化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的校举类型已经成为实现singleton的最佳方法。