设计模式详解——创建型
文章目录
- 设计模式详解——创建型
- 一、单例设计模式
- 为什么要有单例设计模式?
- 单例模式基本结构
- 单例模式基本要求
- 单例模式几种实现
- 1. 饿汉式
- 2. 懒汉式
- 二、工厂方法模式
- 为什么要有工厂设计模式?
- 工厂模式应用场景
- 工厂方法模式基本结构
- 工厂模式代码实现
- 三、抽象工厂模式
- 为什么要使用抽象工厂模式?
- 抽象工厂模式应用场景
- 抽象工厂基本结构
- 抽象工厂模式代码实现
- 四、建造者模式
- 为什么要用建造者模式?
- 建造者模式应用场景
- 建造者模式基本结构
- 建造者模式代码实现
- 五、原型模式
- 为什么要用原型模式?
- 原型模式应用场景
- 原型模式基本结构
- 原型模式代码实现
设计模式详解——创建型
创建型设计模式主要有5种,分别是:单例模式、(抽象)工厂模式、建造者模式、原型模式。
一、单例设计模式
单例模式(Singleton Pattern)是一种创建型设计模式,它就是说:一个类在整个应用运行期间,只能有一个实例,而且这个实例对外提供一个全局访问点。换句话说,无论是在哪儿调用这个类,拿到的永远都是同一个对象。
例如像数据库连接池、线程池、缓存管理器这些东西,如果每次用都重新new,一个项目跑不了几天内存就炸了。所以我们通常只让它们有一个唯一实例,整个系统共享,这样不仅节省资源,还方便统一管理。
单例模式解决的正是这个问题:全局只能有一个对象,而且这个对象谁都能随时找到。
为什么要有单例设计模式?
它有下列优点:
- 节省资源:控制实例数量,避免在高频访问场景中反复创建销毁。
- 统一管理:全局只有一个入口,方便统一初始化和清理,比如统一配置、日志级别控制。
- 线程安全:好的单例实现可以天然保证多线程环境下只有一次创建,避免竞态条件。
- 与其他模式结合:单例常常作为工厂、抽象工厂等模式的基础组件,为更复杂的结构型、行为型模式提供支持。
单例模式基本结构
单例模式基本要求
单例方法模式的基本特点:
- 私有化构造器:禁止外部直接new,这样才能确保外部拿不到新对象。
- 持有唯一实例的静态变量:通常写成private static Singleton instance;,程序启动或首次访问时再创建
- 全局访问点:提供一个public static getInstance()方法,外部就通过这个方法拿到唯一实例。
- 线程安全:在多线程场景下,还得保证并发时也只有一份,常见做法有加锁、双检锁、静态内部类、或者用枚举。
单例模式几种实现
1. 饿汉式
饿汉式是在类加载阶段就完成实例化,保证从第一次访问该类到程序结束,全局只有这一个实例。它依赖M的类加载机制来确保线程安全。
public class Singleton {private static final Singleton INSTANCE = new Singleton();private Singleton() { }public static Singleton getInstance() {return INSTANCE;}
}
2. 懒汉式
懒汉式在第一次调用getInstance()时才创建实例,通过对该方法加锁来保证线程安全,适合对启动性能有要求且实例,不一定马上需要的场景。
public class Singleton {private static Singleton instance;private Singleton() { }public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
二、工厂方法模式
工厂方法模式(Factory Method Pattern)是一种创建型设计模式,旨在定义一个用于创建对象的接口,但将具体的实例化工作放到子类中去完成。换句话说,工厂方法模式通过让子类决定实例化哪个具体类,从而实现了对象创建的灵活性和可广展性。
为什么要有工厂设计模式?
它有下列优点:
- 解耦代码:客户端无需知道具体的产品类,只需要依赖抽象的工厂接口,降低了代码之间的耦合度。
- 提高可扩展性:如果以后需要增加新的产品类型,只需新增相应的工厂类,而无需修改现有代码,符合开闭原则。
- 集中管理:产品的创建逻辑集中在工厂中,便于维护和管理。
工厂模式应用场景
举一些开发中典型的应用场景:
- 消息通知系统:系统支持短信、邮件、站内信等不同通知方式时,可通过工厂方法屏蔽创建细节,让调用方只负责发消息。
- 支付系统:用户可能选择支付宝、微信、银行卡等支付方式,工厂方法可以根据用户选择生成对应的支付通道对象,方便扩展新方式。
- 文件解析模块:上传的文件可能是Excel、,CSV、JSON、ML等不同格式,工厂方法可以根据文件类型创建对应的解析器实例,统一解析入口。
- 业务规则引擎:当一个系统支持多个业务规则版本(如不同行业、不同客户),可以通过工厂方法创建对应的规则计算器,灵活支持定制化逻辑。
工厂方法模式基本结构
工厂方法模式具有的角色和职责:
- 抽象产品(Product):定义产品的公共接口,是所有具体产品的父类。
- 具体产品(ConcreteProduct):实现了抽象产品接口,表示某种具体的产品。
- 抽象工厂(Factory).:定义了一个返回产品对象的方法(一般是一个抽象方法)。
- 具体工厂(Concrete Factory):实现了抽象工厂中的创建产品的方法,生成具体的产品实例。
工厂模式代码实现
// 产品接口
interface Product {void use();
}// 具体产品A
class ConcreteProductA implements Product {@Overridepublic void use() {System.out.println("使用具体产品A");}
}// 具体产品B
class ConcreteProductB implements Product {@Overridepublic void use() {System.out.println("使用具体产品B");}
}// 工厂接口
interface Factory {Product createProduct();
}// 具体工厂A,用于创建产品A
class ConcreteFactoryA implements Factory {@Overridepublic Product createProduct() {return new ConcreteProductA();}
}// 具体工厂B,用于创建产品B
class ConcreteFactoryB implements Factory {@Overridepublic Product createProduct() {return new ConcreteProductB();}
}
三、抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,说白了,它就是提供一个"超级工厂"的接口,专门用来创建一整组互相关联的产品对象,而且不用我们关心这些产品具体是怎么实现的。
为什么要使用抽象工厂模式?
抽象工厂模式的主要目的是为了解决对象创建的复杂性和耦合性问题。当我们有多个系列的产品,每个系列包含多个相关联的对象时,如果直接在客户端代码中创建这些对象,会导致代码变得难以维护和扩展。抽象工厂模式通过将对象的建过程抽象化,让客户端无需关心具体的产品实现,只需要通过工厂接口来获取相应的产品对象。这样,客户端代码和品的具体实现解耦,便于产品的扩展和修改,同时也方便在不改变客户端代码的前提下,轻松切换不同系列的产品,从而提高了系统的灵活性和可维护性。
抽象工厂模式应用场景
举一些开发中典型的应用场景:
- 多平台UI渲染引擎:在开发一个支持Web、Android、小程序等多端渲染的内容管理系统时,可以用抽象工厂为不同平台提供一整套组件(按钮、表单、图片组件等),工厂屏蔽平台差异,保持调用方式一致。
- 多数据库适配场景(逻辑与物理结构差异大):当一个系统需要支持MySQL、Oracle、SQL Server等数据库,且它们不仅语法不同,连分页、存储过程、数据结构等都不统一时,可用抽象工厂提供一整套数据访问组件(分页查询器、SQL拼接器、字段映射器等)。
- 多种导出格式支持系统:比如报表系统或试卷系统中,一套业务数据可能要导出为PDF、Word、Ecel、HTML等格式,每种格式对应一组导出组件(布局器、样式渲染器、文件写入器),通过抽象工厂生成完整的导出工具链。
- 多语言/多地区内容适配:跨境系统中,不同地区展示的内容、日期格式、币种符号等都不相同,可以通过抽象工厂为每个地区创建一整套”本地化服务组件”,如文本翻译器、格式转换器、金额展示器等。
抽象工厂基本结构
抽象工厂模式具有的角色和职责:
- 抽象工厂(AbstractFactory):声明一组创建产品的方法。
- 具体工厂(ConcreteFactory):实现创建具体产品对象的方法。
- 抽象产品(AbstractProduct):定义每个产品的公共接口。
- 具体产品(ConcreteProduct):实现具体的产品对象。
- 客户端(Client):只依赖抽象工厂和抽象产品,负责调用工厂去生产对象。
抽象工厂模式代码实现
public interface Button {void render();
}public interface TextBox {void render();
}public interface Image {void render();
}
public interface UIFactory {Button createButton();TextBox createTextBox();Image createImage();
}
public class WebButton implements Button {@Overridepublic void render() {System.out.println("渲染 Web 风格按钮");}
}public class WebTextBox implements TextBox {@Overridepublic void render() {System.out.println("渲染 Web 风格文本框");}
}public class WebImage implements Image {@Overridepublic void render() {System.out.println("渲染 Web 风格图片组件");}
}
public class WebUIFactory implements UIFactory {@Overridepublic Button createButton() {return new WebButton();}@Overridepublic TextBox createTextBox() {return new WebTextBox();}@Overridepublic Image createImage() {return new WebImage();}
}
public class AndroidButton implements Button {@Overridepublic void render() {System.out.println("渲染 Android 原生按钮");}
}public class AndroidTextBox implements TextBox {@Overridepublic void render() {System.out.println("渲染 Android 原生文本框");}
}public class AndroidImage implements Image {@Overridepublic void render() {System.out.println("渲染 Android 原生图片组件");}
}public class AndroidUIFactory implements UIFactory {@Overridepublic Button createButton() {return new AndroidButton();}@Overridepublic TextBox createTextBox() {return new AndroidTextBox();}@Overridepublic Image createImage() {return new AndroidImage();}
}
public class Client {public static void main(String[] args) {// 假设当前运行环境为 WebUIFactory factory = new WebUIFactory();Button button = factory.createButton();TextBox textBox = factory.createTextBox();Image image = factory.createImage();button.render();textBox.render();image.render();}
}
四、建造者模式
建造者模式(Builder Pattern)是一种创建型设计模式,它的核心思想是:把一个复杂对象的创建过程拆解成多个小步骤,然后一步步构建出来。这个过程中,客户端只需要告诉系统“我想要什么”,而不用关心“怎么一步步造出来的”。
为什么要用建造者模式?
它有下列优点:
- 对象构建过程复杂:如果需要创建的对象有很多步骤或者很多参数,直接在构造函数或者其他方法中传递这些信息会变得复杂难懂。而建造者模式能把这些步骤分开处理,使得对象的构建过程更加清晰。
- 需要灵活的对象构建方式:当我们需要根据不同的需求构建相似的对象时,建造者模式允许我们通过改变建造过程中的某些步骤来创建不同的对象,而不需要重新编写整个对象的构造代码。
- 构建过程和表示分离:建造者模式可以把构建过程和对象的表示(即结果)分离开来,使得同一个构建过程能够创建出不同表示的对象。
建造者模式应用场景
举一些开发中典型的应用场景:
- 复杂表单或请求对象的构建:如创建一个包含多个可选字段的用户注册请求、订单提交请求、报表筛选条件等,可以用建造者模式灵活拼接参数,避免构造方法过长或参数顺序混乱。
- 导出文件内容构建:例如生成复杂的PDF报告、Word文件或Excel报表,往往需要一步步构建文档结构(页眉、表格、图片、段落等),使用建造者模式可以分步骤构造并保证内容完整性。
- 业务流水记录对象创建:例如在支付系统或交易系统中,流水日志往往由多个字段拼成(请求来源、时间戳、交易码、响应内容、错误栈等),用建造者模式可以按需构造,避免创建臃肿的构造器或st方法杂乱调用。
- 消息推送内容构建器:推送系统中,不同渠道(短信、微信、邮件)消息格式各异,有标题、正文、按钮、跳转链接等字段,通过建造者统一构造内容体,保持代码清晰灵活。
建造者模式基本结构
建造者模式具有的角色和职责:
- 产品类((Product):要构建的复杂对象,包含多个组成部分。
- 抽象建造者类(Builder):定义构建产品各个部分的抽象方法,以及返回最终产品的方法。
- 具体建造者类(ConcreteBuilder):实现Builder接口,具体负责各个部分的构建细节,并最终组装出完整产品。
- 指挥者类(Director),:统一指挥建造者按照一定步骤来构建产品,屏蔽了构建过程的细节。
- 客户端类(Client),:发起建造请求,选择具体的建造者并使用指挥者来完成产品的创建。
建造者模式代码实现
public class Computer {private String cpu;private String memory;private String hardDisk;private String gpu;public void setCpu(String cpu) {this.cpu = cpu;}public void setMemory(String memory) {this.memory = memory;}public void setHardDisk(String hardDisk) {this.hardDisk = hardDisk;}public void setGpu(String gpu) {this.gpu = gpu;}@Overridepublic String toString() {return "Computer{" +"cpu='" + cpu + '\'' +", memory='" + memory + '\'' +", hardDisk='" + hardDisk + '\'' +", gpu='" + gpu + '\'' +'}';}
}
public abstract class ComputerBuilder {protected Computer computer = new Computer();public abstract void buildCpu();public abstract void buildMemory();public abstract void buildHardDisk();public abstract void buildGpu();public Computer getResult() {return computer;}
}
public class GamingComputerBuilder extends ComputerBuilder {@Overridepublic void buildCpu() {computer.setCpu("Intel i9");}@Overridepublic void buildMemory() {computer.setMemory("32GB DDR5");}@Overridepublic void buildHardDisk() {computer.setHardDisk("1TB NVMe SSD");}@Overridepublic void buildGpu() {computer.setGpu("NVIDIA RTX 4090");}
}
public class Director {private ComputerBuilder builder;public Director(ComputerBuilder builder) {this.builder = builder;}public Computer construct() {builder.buildCpu();builder.buildMemory();builder.buildHardDisk();builder.buildGpu();return builder.getResult();}
}
public class Main {public static void main(String[] args) {ComputerBuilder builder = new GamingComputerBuilder();Director director = new Director(builder);Computer gamingPc = director.construct();System.out.println(gamingPc);}
}
五、原型模式
原型模式(Prototype Pattern)是一种创建型设计模式,主要用于通过复制现有的对象来创建新对象,而不是通过"new"关键字来直接实例化。简而言之,原型模式让我们能够在已有对象的基础上创建新对象。它依赖于“克隆”已有对象的状态,从而减少了重复构建相同对象的成本。
为什么要用原型模式?
它有下列优点:
- 对象创建过程开销较大:当对象创建的过程比较复杂或者需要消耗较多资源时,直接复制现有的对象可能会比重新构建对象更高效。
- 需要大量相似的对象:如果我们要创建大量结构相似、但又不完全相同的对象,那么通过克隆一个模板对象来创建新对象,会比每次都手动设置每个属性要简单许多。
- 对象的状态是变化的:当对象的状态不止一次构建就能完成,并且在多次使用后可能会有不同的变化时,使用原型模式可以帮助我们根据现有对象状态来快速创建新对象。
原型模式应用场景
举一些开发中典型的应用场景:
- 图形编辑软件中的形状复制:在图形编辑系统(如绘图软件、C八D系统)中,用户常常需要复制图形(圆形、矩形、多边形等)。每种图形可能有许多相以的属性,使用原型模式可以通过克隆一个现有的对象来快速创建一个新的对象,而不是重新初始化对象。
- 配置对象复制:例如系统的初始化配置、模板文件、配置信息等,这些通常是对象的集合(如数据库连接池配置、缓存配置等),如果某个配置在程序中有多个实例,原型模式可以通过克隆来快速生成新的配置对象,避免重复创建和设置。
- 缓存对象克隆:在缓存系统中,尤其是当缓存对象需要被更新并生成新对象时,使用原型模式可以避免重复创建对象,直接从缓存中克隆出新对象并进行修改。
- 文档复制功能:在内容管理系统、企业知识库、在线协作文档平台中,很多时候我们需要基于一份已有文档快速创建一个新文档。这个过程需要复制文档的标题、正文、作者信息、附件、目录结构、版本记录等内容。
原型模式基本结构
原型模式具有的角色和职责:
- 原型接口(Prototype):声明一个克隆自身的接口,所有具体原型类都需要实现这个接口。
- 具体原型类(ConcretePrototype):实现原型接口,定义如何复制自身的对象。
- 客户端(client):通过调用原型对象的克隆方法,来获取新的对象实例。
原型模式代码实现
// 抽象原型接口
interface Prototype extends Cloneable {Prototype clone();void setName(String name);String getName();
}// 具体原型类 - 实现了克隆方法
class ConcretePrototype implements Prototype {private String name;private int id;// 构造方法public ConcretePrototype(String name, int id) {this.name = name;this.id = id;// 模拟复杂对象的创建过程,可能包含资源加载等耗时操作System.out.println("创建了一个新的ConcretePrototype实例: " + name + " (ID: " + id + ")");}@Overridepublic Prototype clone() {try {System.out.println("克隆了一个ConcretePrototype实例");return (ConcretePrototype) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();return null;}}@Overridepublic void setName(String name) {this.name = name;}@Overridepublic String getName() {return name;}public int getId() {return id;}@Overridepublic String toString() {return "ConcretePrototype{name='" + name + "', id=" + id + "}";}
}// 客户端代码
public class PrototypePatternDemo {public static void main(String[] args) {// 创建原型对象Prototype prototype = new ConcretePrototype("原型对象", 1);System.out.println("原型对象: " + prototype);// 通过克隆创建新对象Prototype clone1 = prototype.clone();clone1.setName("克隆对象1");System.out.println("克隆对象1: " + clone1);// 再次克隆Prototype clone2 = prototype.clone();clone2.setName("克隆对象2");System.out.println("克隆对象2: " + clone2);// 验证克隆对象的独立性System.out.println("\n验证克隆对象的独立性:");System.out.println("原型对象ID: " + ((ConcretePrototype)prototype).getId());System.out.println("克隆对象1ID: " + ((ConcretePrototype)clone1).getId());System.out.println("克隆对象2ID: " + ((ConcretePrototype)clone2).getId());}
}: " + prototype);// 通过克隆创建新对象Prototype clone1 = prototype.clone();clone1.setName("克隆对象1");System.out.println("克隆对象1: " + clone1);// 再次克隆Prototype clone2 = prototype.clone();clone2.setName("克隆对象2");System.out.println("克隆对象2: " + clone2);// 验证克隆对象的独立性System.out.println("\n验证克隆对象的独立性:");System.out.println("原型对象ID: " + ((ConcretePrototype)prototype).getId());System.out.println("克隆对象1ID: " + ((ConcretePrototype)clone1).getId());System.out.println("克隆对象2ID: " + ((ConcretePrototype)clone2).getId());}
}