一,创建者模式介绍
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为:
- 单例模式
- 工厂方法模式
- 抽象工程模式
- 原型模式
- 建造者模式
二,单例设计模式
单例模式 (singleton pattern) 是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式可以直接访问,不需要实例化该类的对象。
2.1 单例设计模式的结构
单例模式的主要有以下角色:
- 单例类:只能创建一个实例的类
- 访问类:使用单例类
2.2 单例模式的实现
单例模式有两种实现方法:
- 饿汉式:类加载就会导致该单实例对象被创建
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象才会创建
2.2.1 饿汉式-方式1(静态变量方式)
public class Singleton {
//1. 在本类中创建本类对象
private static Singleton instance = new Singleton();
//2. 私有化构造方法
private Singleton() {
}
//3. 提供一个公共的访问方式
public static Singleton getInstance() {
return instance;
}
}
该方式在成员位置声明 singleton 类型的静态变量,并创建 singleton 类的对象 instance。instance 对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
2.2.2 饿汉式-方式2(静态代码块方式)
public class Singleton {
//私有构造方法
private Singleton(){
}
//声明变量
private static Singleton instance = null;
//静态代码块
static{
instance = new Singleton();
}
//获取实例
public static Singleton getInstance(){
return instance;
}
}
该方式在成员位置声明 singleton 类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式 1 基本上一样,当然该方式也存在内存浪费问题。
2.2.3 懒汉式-方式1(线程不安全)
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
//只有在获取实例的时候才去创建对象
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
从上面代码我们可以看出该方式在成员位置声明 singleton 类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢? 当调用getInstance()方法获取 singleton 类的对象的时候才创建 singleton 类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。
2.2.4 懒汉式-方式2(双重检查锁)
再来讨论一下懒汉模式中加锁的问题,对于 getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以
我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
//第一次判断,如果instance的值不为null,不需要抢占,直接返回对象
synchronized (Singleton.class){
//第二次判断
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题 **,在多线程的情况下,可能会出现空指针问题 **,出现问题的原因是在实例化对象的时候会进行优化和指令重排序操作。要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile 关键字, volatile关键字可以保证可见性和有序性。
public class Singleton {
private static volatile Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
//第一次判断,如果instance的值不为null,不需要抢占,直接返回对象
synchronized (Singleton.class){
//第二次判断
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
2.2.5 懒汉式-方式3(静态内部类方式)
静态内部类单例模式中实例由内部类创建,由于在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性 / 方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。
/**
* 懒汉式-静态内部类方式
*/
public class Singleton {
private static Singleton instance;
private Singleton5(){}
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
第一次加载singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载singletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 singleton 类的唯一性。
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
2.2.6 枚举方式
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举柔型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯 - 一种不会被破坏的单例实现模式。
/**
* 枚举单例
* @author normaling
*
*/
public enum Singleton {
INSTANCE;
}
注:枚举方式属于饿汉式方式。
2.3 单例模式存在的问题
2.3.1 问题演示
破坏单例模式:使上面定义的单例 (Singleton) 可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射
-
序列化反序列化破坏单例模式
Singleton 类
import java.io.Serializable; public class Singleton implements Serializable { private static final long serialVersionUID = 1L; private Singleton(){} private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } }测试类
package com.normaling.designmodeldemo.principles.demo7; import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; /** * 测试使用序列化反序列化破坏单例设计模式 */ public class Client { public static void main(String[] args) throws IOException, ClassNotFoundException { // writeObject(); readObject(); readObject(); } //从文件中读取数据(对象) public static Singleton readObject() throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("d:/a.txt"))); Singleton singleton = (Singleton) ois.readObject(); System.out.println(singleton); return singleton; } //向文件写数据(对象) public static void writeObject() throws IOException { Singleton instance = Singleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:/a.txt")); oos.writeObject(instance); oos.close(); } } -
反射破坏单例模式
Singleton 类
/** * 饿汉式-静态代码块方式 */ public class Singleton { //私有构造方法 private Singleton(){ } //声明变量 private static Singleton instance = null; //静态代码块 static{ instance = new Singleton(); } //获取实例 public static Singleton getInstance(){ return instance; } }测试类
/** * 测试使用反射破坏单例模式 */ public class Client { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class<Singleton> clazz = Singleton.class; //获取无参构造方法 Constructor constructor = clazz.getDeclaredConstructor(); //取消访问检查 constructor.setAccessible(true); //创建对象 Singleton instance1 = (Singleton) constructor.newInstance(); Singleton instance2 = (Singleton) constructor.newInstance(); System.out.println(instance1 == instance2); } }
2.3.2 问题解决
-
序列化、反序列方式破坏单例模式的解决方法
在 singleton 类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新 new 出来的对象。Singleton 类
public class Singleton implements Serializable { private static final long serialVersionUID = 1L; private Singleton(){} private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } //解决序列化反序列化造成的破坏单例模式 //当进行反序列化时,会调用readResolve方法,返回单例对象 public Object readResolve(){ return getInstance(); } } -
反射方式破解单例的解决方法
public class Singleton { //私有构造方法 private Singleton(){ //反射破解单例模式解决方法 if(instance != null){ throw new RuntimeException("单例构造器禁止反射调用"); } } //声明变量 private static Singleton instance = null; //静态代码块 static{ instance = new Singleton(); } //获取实例 public static Singleton getInstance(){ return instance; } }
2.4 单例应用场景
2.4.1 核心思想:为什么要用单例模式?
想象一下一个公司的总经理办公室:
- 唯一性:整个公司只有一个总经理办公室。
- 全局访问点:任何部门的员工,如果需要审批或决策,都知道要去哪里找(访问这个实例)。
- 资源共享:办公室里那台昂贵的打印机、那个客户数据库,是所有访问者共享的,不需要每人配一套。
单例模式的作用就是这个:确保一个类只有一个实例,并提供一个全局访问点,同时解决资源重复创建和访问一致性的问题。
2.4.2 应用场景
-
配置信息管理器(最经典场景)
一个应用程序的配置信息(如数据库连接字符串、系统参数)只需要一份,在内存中保存,全局共享。
-
数据库连接池
连接池管理着一组数据库连接,整个应用应该只有一个连接池实例来管理这些宝贵资源。
-
日志记录器
应用程序的日志系统应该只有一个,所有模块都向同一个日志器写入日志。
-
缓存系统
应用级缓存应该只有一个实例,所有模块共享缓存数据。
-
任务调度器
一个应用通常只需要一个任务调度器来管理所有定时任务。
2.4.3 在Spring在的运用
-
Spring 默认就是单例
@Service public class UserService { private int accessCount = 0; public User findUserById(Long id) { accessCount++; System.out.println("Service被访问次数: " + accessCount); // 查询用户逻辑 } } @RestController public class UserController { @Autowired private UserService userService; // 注入的是单例 @GetMapping("/user/{id}") public User getUser(@PathVariable Long id) { return userService.findUserById(id); } }测试结果:无论多少次调用
/user/1,控制台输出的accessCount会持续累加,证明是同一个 UserService 实例。 -
为什么 Spring 选择单例作为默认作用域?
// 单例模式 - Spring默认 @Service public class OrderService { // 整个应用共享一个实例 // 启动时创建,运行时直接使用 } // 如果每次都是新实例 - 原型作用域 @Service @Scope("prototype") public class OrderService { // 每次注入都创建新实例 // 频繁创建/GC,性能差 } -
缓存服务(全局共享)
@Service public class ProductCacheService { // 使用ConcurrentHashMap保证线程安全 private final Map<Long, Product> cache = new ConcurrentHashMap<>(); // 所有用户共享同一份缓存 public Product getFromCache(Long productId) { return cache.get(productId); } public void putToCache(Long productId, Product product) { cache.put(productId, product); } public void clearCache() { cache.clear(); } } -
配置管理中心
@Service public class SystemConfigService { private Map<String, String> configMap = new HashMap<>(); @PostConstruct public void init() { // 应用启动时加载配置到内存 configMap.put("max_login_attempts", "5"); configMap.put("session_timeout", "1800"); configMap.put("upload_max_size", "10MB"); } public String getConfig(String key) { return configMap.get(key); } // 所有业务模块共享同一份配置 public boolean allowUserLogin(String username) { int maxAttempts = Integer.parseInt(getConfig("max_login_attempts")); // 检查登录次数逻辑 return true; } } -
Spring 中单例的线程安全问题
重要提醒:Spring 单例不是线程安全的,需要自己处理并发!
@Service public class StatelessService { // 安全:无状态,只有方法局部变量 public String process(String input) { String localVar = input.toUpperCase(); // 局部变量,线程安全 return localVar + "_processed"; } } @Service public class StatefulService { private int counter = 0; // 危险:成员变量,非线程安全 public synchronized void increment() { // 使用同步保证安全 counter++; } private AtomicInteger safeCounter = new AtomicInteger(0); // 使用原子类 public void safeIncrement() { safeCounter.incrementAndGet(); } }
三,工厂方法模式
3.1 咖啡例子
需求:设计一个咖啡店点餐系统。
设计一个咖啡类 (coffee),并定义其两个子类 (美式咖啡【americancoffee】和拿铁咖啡【Iattecoffee】); 再设计一个咖啡
店类 (coffeestore),咖啡店具有点咖啡的功能。
具体类的设计如下:

在 java 中,万物皆对象,这些对象都需要创建,如果创建的时候直接 new 该对象,就会对该对象耦合严重,假如我们要更换对象,所有 new 对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。
如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。
3.2 简单工厂模式
简单工厂不是一种设计模式,反而比较像一种编程习惯。
3.2.1 结构
简单工厂包含如下角色:
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品:实现或者继承抽象产品的子类
- 具体工厂:提供了创建产品的方法,调用者通过该方法来创建产品
3.2.2 实现

-
CoffeeStorepublic class CoffeeStore { public Coffee orderCoffee(String type) { Coffee coffee = SimpleCoffeeFactory.createCoffee(type); coffee.addSugar(); coffee.addMilk(); System.out.println(coffee.getName()); return coffee; } } -
Coffeepublic abstract class Coffee { public abstract String getName(); public void addSugar(){ System.out.println("加糖"); } public void addMilk(){ System.out.println("加奶"); } } -
AmericanCoffeepublic class AmericanCoffee extends Coffee{ @Override public String getName() { return "美式咖啡"; } } -
LatteCoffeepublic class LatterCoffee extends Coffee{ @Override public String getName() { return "拿铁咖啡"; } } -
SimpleCoffeeFactorypublic class SimpleCoffeeFactory { public static Coffee createCoffee(String type) { Coffee coffee = null; if("american".equals(type)) { coffee = new AmericanCoffee(); } else if("latter".equals(type)) { coffee = new LatterCoffee(); } else { throw new RuntimeException("对不起,您所点的咖啡没有"); } return coffee; } } -
Clietnpublic class Client { public static void main(String[] args) { CoffeeStore store = new CoffeeStore(); Coffee coffee = store.orderCoffee("american"); } }
3.2.3 简单工厂的优缺点
优点:
-
封装了创建对象的过程,可以通过参数直接获取对象 **。把对象的创建和业务逻辑层分开 **,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点: -
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
3.2.4 扩展-静态工厂
在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它不是 23 种设计模式中的
public class SimpleCoffeeFactory{
public static Coffe createCoffee(String type){
Coffee coffee = null;
if("americano".equals(type)){
coffee = new AmericanCoffee();
}else if ("latter".equals(type)){
coffee = new LatteCoffee();
}
return coffee;
}
}
3.3 工厂方法模式
针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。
3.3.1 概念
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
3.3.2 结构
工厂方法模式的主要角色:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(concreteEactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(concreteproduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间-一对应。
3.3.3 实现
使用工厂方法模式对上例进行改进,类图如下

-
CoffeeFactorypublic interface CoffeeFactory { public Coffee createCoffee(); } -
AmericanCoffeeFactorypublic class AmericanCoffeeFactory implements CoffeeFactory{ @Override public Coffee createCoffee() { return new AmericanCoffee(); } } -
LatteCoffeeFactorypublic class LatteCoffeeFactory implements CoffeeFactory{ @Override public Coffee createCoffee() { return new LatteCoffee(); } } -
CoffeeStorepublic class CoffeeStore { private CoffeeFactory factory; public CoffeeStore(CoffeeFactory factory) { this.factory = factory; } public Coffee orderCoffee() { Coffee coffee = factory.createCoffee(); coffee.addSugar(); coffee.addMilk(); System.out.println(coffee.getName()); return coffee; } } -
Coffeepublic abstract class Coffee { public abstract String getName(); public void addSugar() { System.out.println("加糖"); } public void addMilk() { System.out.println("加奶"); } } -
AmericanCoffeepublic class AmericanCoffee extends Coffee{ @Override public String getName() { return "美式"; } } -
LatteCoffeepublic class LatteCoffee extends Coffee{ @Override public String getName() { return "拿铁"; } } -
Clientpublic class Client { public static void main(String[] args) { CoffeeFactory factory = new AmericanCoffeeFactory(); //要用哪个coffee就创建对应的工厂 CoffeeStore coffeeStore = new CoffeeStore(factory); coffeeStore.orderCoffee(); } }
3.3.4 优缺点
优点:
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程,。在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
缺点:
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度.。
3.4 简单工厂-注册表工厂
思想:用一个 Map<String, Supplier<InvoiceService>> 注册所有实现类,新增厂商时,只需注册一次,不改 if-else。
实现
public class InvoiceFactory {
private static final Map<String, Supplier<InvoiceService>> registry = new HashMap<>();
static {
registry.put("A", TaxAInvoiceService::new);
registry.put("B", TaxBInvoiceService::new);
}
public static void register(String key, Supplier<InvoiceService> creator) {
registry.put(key, creator);
}
public static InvoiceService create(String key) {
Supplier<InvoiceService> supplier = registry.get(key);
if (supplier == null) {
throw new IllegalArgumentException("No service found for vendor: " + key);
}
return supplier.get();
}
}
使用
InvoiceService service = InvoiceFactory.create("A");
service.createInvoice(data);
新增一个厂商,只需:
InvoiceFactory.register("C", TaxCInvoiceService::new);
不动老代码。
3.5 简单工厂-反射式工厂
** 思路:** 通过配置文件(如 YAML、JSON、properties)注册映射关系, 运行时用反射创建对象,避免硬编码。
配置文件(factory-config.properties)
A=com.company.invoice.TaxAInvoiceService
B=com.company.invoice.TaxBInvoiceService
工厂类
public class ReflectInvoiceFactory {
private static final Properties config = new Properties();
static {
try (InputStream input = ReflectInvoiceFactory.class
.getClassLoader().getResourceAsStream("factory-config.properties")) {
config.load(input);
} catch (IOException e) {
throw new RuntimeException("Failed to load config", e);
}
}
public static InvoiceService create(String vendor) {
String className = config.getProperty(vendor);
if (className == null) {
throw new IllegalArgumentException("Unknown vendor: " + vendor);
}
try {
Class<?> clazz = Class.forName(className);
// 反射构造实例
return (InvoiceService) clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to create instance for: " + vendor, e);
}
}
}
使用
InvoiceService service = ReflectInvoiceFactory.create("A");
service.createInvoice(data);
优点:新增厂商,只需在配置文件里加一行!
缺点:性能稍低一点,不支持复杂构造函数。
3.6 简单工厂-Spring Bean 工厂(最常用)
思想:结合依赖注入,实现自动注册 + 动态选择 Bean。
定义接口
public interface InvoiceService {
void createInvoice(InvoiceData data);
String getVendorCode();
}
各实现类
@Service
public class TaxAInvoiceService implements InvoiceService {
@Override
public void createInvoice(InvoiceData data) { ... }
@Override
public String getVendorCode() { return "A"; }
}
@Service
public class TaxBInvoiceService implements InvoiceService {
@Override
public void createInvoice(InvoiceData data) { ... }
@Override
public String getVendorCode() { return "B"; }
}
工厂类
@Component
public class SpringInvoiceFactory {
private final Map<String, InvoiceService> serviceMap = new HashMap<>();
@Autowired
public SpringInvoiceFactory(List<InvoiceService> services) {
for (InvoiceService service : services) {
serviceMap.put(service.getVendorCode(), service);
}
}
public InvoiceService getService(String vendorCode) {
InvoiceService service = serviceMap.get(vendorCode);
if (service == null) {
throw new IllegalArgumentException("No service found for vendor: " + vendorCode);
}
return service;
}
}
使用
@Service
public class InvoiceFacade {
private final SpringInvoiceFactory factory;
public InvoiceFacade(SpringInvoiceFactory factory) {
this.factory = factory;
}
public void createInvoice(String vendorCode, InvoiceData data) {
factory.getService(vendorCode).createInvoice(data);
}
}
优点:
- 无需配置文件
- 无需 if-else
- 新增厂商只需加一个
@Service实现类 - 支持依赖注入(构造参数、配置、组件都能注入)
四,抽象工厂模式
4.1 概述
抽象工厂模式:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机等。
这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级 (种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。
抽象工厂模式将考虑多等级产品的生产 **,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族 **,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。

举例:想象一下,你要去买一套 **「电竞装备」**。这套装备里包含三样东西:键盘,鼠标,耳机
现在有两个知名品牌供你选择:
- 品牌A: 罗技 (Logitech)
- 品牌B: 雷蛇 (Razer)
你的需求是:这一套装备必须是同一个品牌的! 你不能要一个罗技的键盘,却配一个雷蛇的鼠标,那样风格不统一,可能驱动软件都不一样,用起来别扭。
现在,我们把这件事对应到抽象工厂模式:
- 「抽象工厂」 (Abstract Factory)
- 它就是那个**「电竞装备套装」的抽象概念**。
- 它不关心具体是罗技还是雷蛇,它只定义一套装备里必须包含哪些产品(比如:能创建键盘、能创建鼠标、能创建耳机)。它是一个标准,一个蓝图。
- 「具体工厂」 (Concrete Factory)
- 这就是具体的品牌了。
- 「罗技工厂」:专门生产一整套罗技风格的装备(罗技键盘 + 罗技鼠标 + 罗技耳机)。
- 「雷蛇工厂」:专门生产一整套雷蛇风格的装备(雷蛇键盘 + 雷蛇鼠标 + 雷蛇耳机,全是炫酷的绿光)。
- 「抽象产品」 (Abstract Product)
- 这是每一类产品的抽象概念。
- 比如「抽象键盘」,它只定义键盘都应该有
打字()和设置宏()的功能,但不关心是哪个牌子的。
- 「具体产品」 (Concrete Product)
- 这就是某个品牌生产的具体产品。
- 比如「罗技G913键盘」、「雷蛇黑寡妇键盘」、「罗技G502鼠标」、「雷蛇北海巨妖耳机」等等。
- **「产品族」 (Product Family) **
- 大白话:就是同一个品牌(同一个具体工厂)创建出来的那一整套产品。
- 「罗技产品族」:包含了罗技键盘、罗技鼠标、罗技耳机。它们都属于罗技这个品牌,设计语言、软件驱动、品牌风格都是一致的。
- 「雷蛇产品族」:包含了雷蛇键盘、雷蛇鼠标、雷蛇耳机。它们都属于雷蛇这个品牌,都有那种酷炫的RGB灯效。
为什么要这么设计?
-
好处就是:保证兼容性和风格统一。
比如,你的电脑如果主要是和罗技驱动配套的,那你选择「罗技工厂」后,它给你生产的所有产品(键盘、鼠标、耳机)都能完美地被同一个罗技驱动软件管理,绝对不会出现雷蛇耳机不兼容的问题。
-
切换整个产品族变得极其容易。
假如你今天喜欢罗技的低调,就用「罗技工厂」。明天你想换一套酷炫的灯效,只需要把工厂切换到「雷蛇工厂」,你得到的所有产品就立刻变成了雷蛇风格,你不需要一个个产品去单独设置。
总结一下:
- 「抽象工厂」 是创建一套有关联的产品的老板。
- 「产品族」 就是老板手下的一个个团队(比如罗技团队、雷蛇团队),每个团队都打包生产一整套风格统一、能互相协作的产品。
所以核心思想就是:
- 制定标准(抽象工厂定义接口)
- 分工实现(具体工厂按标准生产自己品牌的一套产品)
- 保证兼容(客户端拿到的一定是完整、兼容的某一套产品族,而不会出现不匹配的杂牌军)
4.2 结构
抽象工厂模式的主要角色如下:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(concreteproduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
4.3 实现
现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。
其中
-
拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;
-
提拉米苏、抹茶慕斯也是一个产品等级;
-
拿铁咖啡和提拉米苏是同一产品族 ( 也就是都属于意大利风味),
-
美式咖啡和抹茶慕斯是同一产品族 ( 也就是都属于美式风味)。
所以这个案例可以使用抽象工厂模式实现。类图如下:

代码如下:
-
抽象工厂
public interface DessertFactory { //创建甜点 public Dessert createDessert(); //创建咖啡 public Coffee createCoffee(); } -
美式甜点工厂
/** * 美式工厂 */ public class AmericanDessertFactory implements DessertFactory{ @Override public Dessert createDessert() { return new Tiramsu(); } @Override public Coffee createCoffee() { return new AmericanCoffee(); } -
意式甜点工厂
public class ItalyDessertFactory implements DessertFactory{ @Override public Dessert createDessert() { return new MatchMouse(); } @Override public Coffee createCoffee() { return new LatteCoffee(); } } -
咖啡抽象类
/** * 咖啡类 */ public abstract class Coffee { public abstract String getName(); public void addSugar() { System.out.println("加糖"); } public void addMilk() { System.out.println("加奶"); } } -
美式咖啡
/** * 美式咖啡 */ public class AmericanCoffee extends Coffee{ @Override public String getName() { return "美式咖啡"; } } -
拿铁咖啡
public class LatteCoffee extends Coffee{ @Override public String getName() { return "拿铁咖啡"; } } -
甜点抽象类
/** * 甜点类 */ public abstract class Dessert { abstract String getName(); void eat(){ System.out.println("吃" + getName()); } } -
提拉米苏
public class Tiramsu extends Dessert{ @Override String getName() { return "提拉米苏"; } } -
慕斯
/** * 慕斯 */ public class MatchMouse extends Dessert{ @Override String getName() { return "慕斯"; } } -
测试类
public class Client { public static void main(String[] args) { // 创建意大利风味工厂 DessertFactory factory = new ItalyDessertFactory(); Coffee coffee = factory.createCoffee(); Dessert dessert = factory.createDessert(); System.out.println(coffee.getName()); dessert.eat(); } }
总结:抽象工厂模式可以看作是 工厂方法模式的升级和扩展。
- 它把一堆工厂方法(
createKeyboard,createMouse,createHeadset)打包,组合成了一个更高级的“工厂的工厂”。 - 它不再是管理一个产品的创建,而是管理一个产品家族的创建。
- 说白了就是添加了一个产品族的概念,将产品族的类的创建统一放到了一个工厂里面而已。
4.4 优缺点与使用场景
** 优点:** 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:
- 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
- 如果需要创建的类过多,会导致类爆炸
使用场景:
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。
4.5 源码解析-Collection.iterator方法
public class Demo{
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("l1");
list.add("l2");
list.add("l3");
//获取迭代器对象
Iterator<String> it = list.iterator();
while(it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
}
}

Collection 接口是抽象工厂类,ArrayList是具体的工厂类;Iterator 接口是抽象商品,ArrayList 类中的 Iter 内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。
另外:
DateForamt类中的getInstance()方法使用的是工厂模式Calendar类中的getInstance()方法使用的是工厂模式;
4.6 抽象工厂的缺点和解决方法
4.6.1 缺点1
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
解决方法:使用 jdk8 的默认方法
public interface VehicleFactory {
Car createCar();
Truck createTruck();
// 新增产品:使用默认方法提供默认实现
default Motorcycle createMotorcycle() {
throw new UnsupportedOperationException("摩托车功能暂不支持");
}
// 可选:提供能力检测方法
default boolean supportsMotorcycle() {
return false;
}
}
// 具体工厂 - 只有需要该产品的工厂才重写
public class AFactory implements VehicleFactory {
public Car createCar() { return new ACar(); }
public Truck createTruck() { return new ATruck(); }
// A工厂支持摩托车,重写方法
@Override
public Motorcycle createMotorcycle() {
return new AMotorcycle();
}
@Override
public boolean supportsMotorcycle() {
return true;
}
}
public class BFactory implements VehicleFactory {
public Car createCar() { return new BCar(); }
public Truck createTruck() { return new BTruck(); }
// B工厂不支持摩托车,使用默认实现
}
// 客户端使用
public class Client {
public void useFactory(VehicleFactory factory) {
Car car = factory.createCar();
// 安全地使用新功能
if (factory.supportsMotorcycle()) {
Motorcycle motorcycle = factory.createMotorcycle();
// 使用摩托车...
}
}
}
4.6.2 缺点2
抽象工厂如果需要创建的类过多,类的数量会爆炸的问题
解决方法:
-
方案 1:使用 Lambda 和匿名类
// 传统方式:每个产品都要一个具体类 public class WindowsButton implements Button { } public class MacButton implements Button { } public class LinuxButton implements Button { } // 新方式:使用Lambda避免类爆炸 public interface GUIFactory { Button createButton(); Checkbox createCheckbox(); Menu createMenu(); // 预定义工厂实例 static GUIFactory windows() { return new GUIFactory() { public Button createButton() { return () -> System.out.println("Windows风格按钮"); } public Checkbox createCheckbox() { return () -> System.out.println("Windows风格复选框"); } public Menu createMenu() { return () -> System.out.println("Windows风格菜单"); } }; } static GUIFactory mac() { return new GUIFactory() { public Button createButton() { return () -> System.out.println("Mac风格按钮"); } public Checkbox createCheckbox() { return () -> System.out.println("Mac风格复选框"); } public Menu createMenu() { return () -> System.out.println("Mac风格菜单"); } }; } } // 产品接口使用函数式接口 public interface Button { void render(); } // 使用:零额外类! GUIFactory factory = GUIFactory.windows(); Button button = factory.createButton(); button.render(); -
方案 2:枚举工厂
public enum ThemeFactory { WINDOWS("Windows") { public Button createButton() { return new SimpleButton("Windows按钮", "blue"); } public Checkbox createCheckbox() { return new SimpleCheckbox("Windows复选框", true); } public Menu createMenu() { return new SimpleMenu("Windows菜单", Arrays.asList("文件", "编辑")); } }, MAC("Mac") { public Button createButton() { return new SimpleButton("Mac按钮", "gray"); } public Checkbox createCheckbox() { return new SimpleCheckbox("Mac复选框", false); } public Menu createMenu() { return new SimpleMenu("Mac菜单", Arrays.asList("Finder", "视图")); } }, DARK("暗黑") { public Button createButton() { return new SimpleButton("暗黑按钮", "black"); } public Checkbox createCheckbox() { return new SimpleCheckbox("暗黑复选框", true); } public Menu createMenu() { return new SimpleMenu("暗黑菜单", Arrays.asList("设置", "帮助")); } }; private final String name; ThemeFactory(String name) { this.name = name; } public abstract Button createButton(); public abstract Checkbox createCheckbox(); public abstract Menu createMenu(); // 通用产品实现类 private static class SimpleButton implements Button { private final String text; private final String color; // 实现... } private static class SimpleCheckbox implements Checkbox { private final String text; private final boolean checked; // 实现... } private static class SimpleMenu implements Menu { private final String title; private final List<String> items; // 实现... } } // 使用:一个枚举搞定所有! Button button = ThemeFactory.WINDOWS.createButton();
这俩个方案的核心就是将类的内容全部聚合到了抽象工厂里面了,如果类内部创建细节很多,不建议这么玩,这样会导致一个类里面大几千行代码,难以维护,所以:要么类数量多,要么一个类存大量代码,这个具体情况具体分析取舍吧。
4.7 总结
在现代 Java 开发中,尤其是在强调“组合优于继承”的设计理念下:
- 抽象工厂(Abstract Factory) -> 优先定义为 接口。
- 抽象产品(Abstract Product) -> 优先定义为 接口。
- 具体工厂(Concrete Factory) -> 实现抽象工厂接口的类。
- 具体产品(Concrete Product) -> 实现抽象产品接口的类。
记住一个简单的原则:凡是需要定义“蓝图”或“规范”的地方,首先考虑使用接口(Interface)
五,原型模式
5.1 概述
核心思想:不重新创建对象,而是克隆现有对象
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
5.2 结构
原型模式包含如下角色:
- 抽象原型类:规定了具体原型对象必须实现的的
clone()方法。 - 具体原型类:实现抽象原型类的
clone()方法,它是可被复制的对象。 - 访问类:使用具体原型类中的
clone()方法来复制新的对象。

5.3 实现
原型模式的克隆分为浅克隆和深克隆。
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址,
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
Java 中的obiect类中提供了 clone()方法来实现浅克隆。Cloneable 接口是上面的类图中的抽象原型类,而实现了cloneable接口的子实现类就是具体的原型类。
代码如下:
-
Realizetype具体原型类public class Realizetype implements Cloneable{ public Realizetype() { System.out.println("具体原型创建成功!"); } @Override protected Realizetype clone() throws CloneNotSupportedException { System.out.println("具体原型复制成功!"); return (Realizetype)super.clone(); } } -
测试类
public class Client { public static void main(String[] args) throws CloneNotSupportedException { Realizetype r = new Realizetype(); Realizetype r1 = r.clone(); System.out.println(r == r1);//false } }
使用场景:
- 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
- 性能和安全要求比较高。
5.4 深克隆
5.4.1 手动实现深克隆(推荐)
public class Product implements Cloneable {
private String name;
private Date createDate;
private List<String> tags;
// 构造方法、getter、setter等
@Override
public Product clone() {
try {
Product cloned = (Product) super.clone();
// 手动深克隆引用类型字段
if (this.createDate != null) {
cloned.createDate = (Date) this.createDate.clone();
}
if (this.tags != null) {
cloned.tags = new ArrayList<>(this.tags);
}
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // 不会发生
}
}
}
5.4.2 使用序列化实现深克隆
import java.io.*;
public class Product implements Serializable {
private String name;
private Date createDate;
private List<String> tags;
public Product deepClone() {
try {
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (Product) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("深克隆失败", e);
}
}
}
5.4.3 拷贝构造器
public class User {
private String name;
private int age;
private Address address;
private List<String> hobbies;
// 原始构造器
public User(String name, int age, Address address, List<String> hobbies) {
this.name = name;
this.age = age;
this.address = address;
this.hobbies = hobbies;
}
// 拷贝构造器 - 更直观,避免Cloneable接口的缺陷
public User(User other) {
this.name = other.name;
this.age = other.age;
this.address = other.address != null ? new Address(other.address) : null;
this.hobbies = other.hobbies != null ? new ArrayList<>(other.hobbies) : null;
}
// 基于拷贝构造器的clone方法
public User clone() {
return new User(this);
}
}
public class Address {
private String city;
private String street;
private Coordinate coordinate;
public Address(String city, String street, Coordinate coordinate) {
this.city = city;
this.street = street;
this.coordinate = coordinate;
}
// 拷贝构造器
public Address(Address other) {
this.city = other.city;
this.street = other.street;
this.coordinate = other.coordinate != null ? new Coordinate(other.coordinate) : null;
}
}
方案选择
- 优先选择手动深克隆:性能更好,控制更精确
- 对于复杂对象图:考虑使用序列化方式
- 注意循环引用:手动克隆时需要处理循环引用问题
- 使用不可变对象:尽可能使用不可变对象,可以减少深克隆的需求
- 文档化克隆行为:在类文档中明确说明克隆是深克隆还是浅克隆
5.5 使用场景
应该使用原型模式的情况:
- 对象创建成本高(数据库查询、网络请求、文件I/O、复杂计算)
- 需要创建多个相似对象(大部分属性相同,只有少量差异)
- 构造过程复杂(多个步骤、依赖外部服务)
- 性能敏感(游戏、实时系统、高并发场景)
- 想要避免重复的初始化代码
不应该使用原型模式的情况:
- 对象很简单(只是几个字段的POJO)
- 每次都需要完全不同的对象
- 对象不可变(直接new就可以了)
- 构造过程很快(没有昂贵的操作)
记住这个简单原则:当你发现自己在写这样的代码时:
// 重复执行昂贵的构造过程
for (int i = 0; i < n; i++) {
MyObject obj = new MyObject(/* 相同的参数 */); // 每次都要执行昂贵操作
// 只修改少量属性
obj.setSpecificProperty(getVariant(i));
}
就应该考虑使用原型模式:
// 使用原型模式
MyObject prototype = new MyObject(/* 相同的参数 */); // 只执行一次昂贵操作
for (int i = 0; i < n; i++) {
MyObject obj = prototype.clone(); // 快速复制
obj.setSpecificProperty(getVariant(i)); // 只修改差异部分
}
核心价值:将一次性的昂贵初始化成本分摊到多次快速复制中。
六,建造者模式
6.1 概述
建造者模式是用来创建复杂对象的设计模式,特别适用于有很多可选参数或构造过程复杂的对象。
核心思想:分离构造与表示
传统构造的问题:
// 问题:构造器参数太多,难以阅读和维护
public class User {
private String name; // 必填
private int age; // 必填
private String email; // 可选
private String phone; // 可选
private String address; // 可选
private String nickname; // 可选
private String avatar; // 可选
private String bio; // 可选
// 构造器灾难!
public User(String name, int age, String email, String phone,
String address, String nickname, String avatar, String bio) {
this.name = name;
this.age = age;
this.email = email;
this.phone = phone;
this.address = address;
this.nickname = nickname;
this.avatar = avatar;
this.bio = bio;
}
// 或者多个构造器,也很混乱
public User(String name, int age) { /* ... */ }
public User(String name, int age, String email) { /* ... */ }
public User(String name, int age, String email, String phone) { /* ... */ }
}
// 使用:完全不知道每个参数是什么
User user = new User("张三", 25, "zhang@example.com", null, "北京", null, null, "热爱编程");
// 哪个是email?哪个是phone?完全看不懂!
6.2 实现方法
public class User {
private final String name; // 必填,final确保不可变
private final int age; // 必填
private final String email; // 可选
private final String phone; // 可选
private final String address; // 可选
private final String nickname; // 可选
private final String avatar; // 可选
private final String bio; // 可选
// 私有构造器,只能通过Builder创建
private User(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.email = builder.email;
this.phone = builder.phone;
this.address = builder.address;
this.nickname = builder.nickname;
this.avatar = builder.avatar;
this.bio = builder.bio;
// 可以在构造器中做验证
validate();
}
private void validate() {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("姓名不能为空");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄不合法");
}
}
// 静态方法获取Builder
public static Builder builder(String name, int age) {
return new Builder(name, age);
}
// Builder内部类
public static class Builder {
// 必填参数
private final String name;
private final int age;
// 可选参数(有默认值)
private String email = "";
private String phone = "";
private String address = "";
private String nickname = "";
private String avatar = "";
private String bio = "";
// Builder构造器只包含必填参数
public Builder(String name, int age) {
this.name = name;
this.age = age;
}
// 每个可选参数一个方法,返回this支持链式调用
public Builder email(String email) {
this.email = email;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public Builder nickname(String nickname) {
this.nickname = nickname;
return this;
}
public Builder avatar(String avatar) {
this.avatar = avatar;
return this;
}
public Builder bio(String bio) {
this.bio = bio;
return this;
}
// 构建最终对象
public User build() {
return new User(this);
}
}
// getters...
}
使用方法
// 清晰、可读、灵活
User user = User.builder("张三", 25)
.email("zhang@example.com")
.phone("13800138000")
.address("北京市朝阳区")
.nickname("三哥")
.bio("热爱编程的开发者")
.build();
// 也可以只设置部分参数
User simpleUser = User.builder("李四", 30)
.email("li@example.com")
.build();
// 链式调用,非常流畅
6.3 lombok实现建造者模式
6.3.1 基础使用
-
最简单的建造者
import lombok.Builder; import lombok.Getter; @Getter @Builder public class User { private String name; private int age; private String email; private String phone; } // 使用:自动生成的建造者 User user = User.builder() .name("张三") .age(25) .email("zhang@example.com") .phone("13800138000") .build(); -
带必填参数的建造者
@Getter @Builder public class Order { // 必填参数用final修饰 private final String orderId; private final Customer customer; // 可选参数 private String status; private BigDecimal amount; private Date createTime; } // 使用:Lombok会自动生成接受必填参数的builder()方法 Order order = Order.builder() .orderId("ORD123") .customer(customer) .status("PENDING") .amount(new BigDecimal("100.00")) .build();
高级玩法:
-
设置默认值
@Getter @Builder public class HttpConfig { @Builder.Default private int timeout = 5000; @Builder.Default private boolean followRedirects = true; @Builder.Default private int maxRetries = 3; private String baseUrl; } // 使用:默认值自动生效 HttpConfig config = HttpConfig.builder() .baseUrl("https://api.example.com") // timeout, followRedirects, maxRetries 使用默认值 .build(); -
自定义建造者方法
@Getter @Builder public class Product { private String sku; private String name; private BigDecimal price; private List<String> tags; // 自定义建造者方法 public static class Builder { // 添加自定义方法 public Builder price(String price) { this.price = new BigDecimal(price); return this; } public ProductBuilder tag(String tag) { if (this.tags == null) { this.tags = new ArrayList<>(); } this.tags.add(tag); return this; } public ProductBuilder tags(String... tags) { if (this.tags == null) { this.tags = new ArrayList<>(); } Collections.addAll(this.tags, tags); return this; } } } // 使用自定义方法 Product product = Product.builder() .sku("PROD001") .name("笔记本电脑") .price("5999.99") // 自定义方法,接受字符串 .tag("电子") .tag("数码") .tags("便携", "高性能") // 自定义方法,接受多个标签 .build(); -
使用
@Builder(toBuilder = true)@Getter @Builder(toBuilder = true) public class Customer { private String id; private String name; private String email; private String phone; @Builder.Default private boolean vip = false; } // 使用:基于现有对象创建建造者 Customer original = Customer.builder() .id("CUST001") .name("张三") .email("zhang@example.com") .build(); // 修改部分属性创建新对象 Customer updated = original.toBuilder() .phone("13800138000") .vip(true) .build();
6.4 什么时候使用建造者模式?
使用场景:
- 参数很多(超过4个参数)
- 很多可选参数
- 参数之间有依赖或约束
- 需要创建不可变对象
- 构造过程复杂,需要清晰表达
不使用场景:
- 参数很少(1-3个参数)
- 所有参数都是必填的
- 对象很简单
6.5 优缺点
优点:
- 可读性好:链式调用很清晰
- 灵活性高:可以灵活组合参数
- 易于维护:添加新参数不影响现有代码
- 可以做验证:在build()方法中统一验证
缺点:
- 代码量多:需要写Builder类
- 学习成本:新手可能不熟悉这种模式