设计模式(上)创建型篇
- Singleton Pattern - 单例模式
- Factory Method Pattern - 工厂方法模式
- Abstract Factory Pattern - 抽象工厂模式
- Prototype Pattern - 原型模式
- Builder Pattern - 建造者模式
- Adapter Pattern - 适配器模式
- Bridge Pattern - 桥接模式
- Composite Pattern - 组合模式
- Decorator Pattern - 装饰器模式
- Facade Pattern - 外观模式
- Flyweight Pattern - 享元模式
- Proxy Pattern - 代理模式
- Chain of Responsibility Pattern - 责任链模式
- Command Pattern - 命令模式
- Interpreter Pattern - 解释器模式
- Iterator Pattern - 迭代器模式
- Mediator Pattern - 中介者模式
- Memento Pattern - 备忘录模式
- Observer Pattern - 观察者模式
- State Pattern - 状态模式
- Strategy Pattern - 策略模式
- Template Method Pattern - 模板方法模式
- Visitor Pattern - 访问者模式
1、创建型设计模式
用于描述“怎么创建对象”,它的主要特点是“将对象的创建与使用分离”。GOF(四人组)书中提供了单例、原型、工厂方法、抽象工厂、建造者等5种创建型模式。
- 单例模式 (Singleton Pattern): 保证一个类只有一个实例,并提供全局访问点。例如,地球上只有一个太阳。
- 工厂方法模式 (Factory Method Pattern): 使用工厂方法创建对象,将具体的创建逻辑委托给子类。就像一家汽车制造公司可以生产不同型号的汽车。
- 抽象工厂模式 (Abstract Factory Pattern): 提供创建一系列相关对象的接口,允许不同子类提供不同实现。类似于一个电子设备公司可以生产不同类型的产品系列,如手机、平板电脑、电视等。
- 建造者模式 (Builder Pattern): 逐步构建复杂对象,将构建过程分步进行。就像建造一座房子,需要先建立地基、墙壁,然后加屋顶。
- 原型模式 (Prototype Pattern): 通过复制现有对象来创建新对象,避免从头开始构建。类似于制作相似的工艺品副本。
1.1、单例模式
只存在一个太阳
1. 懒汉式(Lazy Initialization)
懒汉式在首次访问实例时才创建对象。
public class LazySingleton {
// 私有静态变量,用于保存唯一实例
private static LazySingleton instance;
// 私有构造方法,防止外部创建实例
private LazySingleton() {
}
// 公有静态方法,提供全局访问点
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
优点:
- 实例在首次使用时创建,节约内存。
- 线程安全(通过 synchronized 关键字)。
缺点:
- 每次获取实例都需要进行同步,可能影响性能。
2. 饿汉式(Eager Initialization)
饿汉式在类加载时就创建了实例。
public class EagerSingleton {
// 私有静态变量,类加载时初始化
private static final EagerSingleton instance = new EagerSingleton();
// 私有构造方法,防止外部创建实例
private EagerSingleton() {
}
// 公有静态方法,提供全局访问点
public static EagerSingleton getInstance() {
return instance;
}
}
优点:
- 实例在类加载时就创建,避免了多线程问题。
- 无需进行同步操作,性能较好。
缺点:
- 可能会造成内存浪费,因为实例在类加载时就被创建,不管是否使用。
3. 双重检验锁式(Double-Checked Locking)
双重检验锁式在首次访问实例时进行双重检验,提高了性能。
public class DoubleCheckedSingleton {
// 使用 volatile 关键字确保多线程环境下的可见性和禁止指令重排序
private static volatile DoubleCheckedSingleton instance;
// 私有构造方法,防止外部创建实例
private DoubleCheckedSingleton() {
}
public static DoubleCheckedSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
优点:
- 首次获取实例时才进行同步,提高了性能。
- 线程安全。
缺点:
- 可能会引发指令重排序问题,需要使用 volatile 关键字来解决。
4. 静态内部类(Static Inner Class)
静态内部类方式利用了类加载机制,保证了线程安全和延迟加载。
public class StaticInnerSingleton {
// 私有静态内部类,用于持有实例
private static class SingletonHolder {
private static final StaticInnerSingleton instance = new StaticInnerSingleton();
}
// 私有构造方法,防止外部创建实例
private StaticInnerSingleton() {
}
public static StaticInnerSingleton getInstance() {
return SingletonHolder.instance;
}
}
优点:
- 利用静态内部类的特性,实现了延迟加载和线程安全。
- 无需使用 synchronized 关键字。
缺点:
- 实例在类加载时就被创建,可能造成内存浪费。
5. 枚举单例(Enum Singleton)
使用枚举实现单例是一种简单、线程安全且不容易被破坏的方式。
public enum EnumSingleton {
INSTANCE;
// 可以添加其他方法和属性
public void doSomething() {
// 执行操作
}
}
优点:
- 简单、线程安全、不容易被破坏。
- 支持更多的方法和属性。
缺点:
- 不支持延迟加载。
6. 登记式/容器式单例(Registry/Container Singleton)
这种方式通过一个专门的容器来管理单例的实例。
public class ContainerSingleton {
private static Map<String, Object> singletonMap = new HashMap<>();
private ContainerSingleton() {
}
public static void registerInstance(String key, Object instance) {
if (!singletonMap.containsKey(key)) {
singletonMap.put(key, instance);
}
}
public static Object getInstance(String key) {
return singletonMap.get(key);
}
}
优点:
- 可以集中管理多个单例实例。
- 动态注册和获取实例。
缺点:
- 不是线程安全的,需要自行考虑线程安全控制。
7. ThreadLocal 单例
使用 ThreadLocal 实现单例,在每个线程中都拥有一个独立的实例。
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
ThreadLocal.withInitial(ThreadLocalSingleton::new);
private ThreadLocalSingleton() {
}
public static ThreadLocalSingleton getInstance() {
return threadLocalInstance.get();
}
}
优点:
- 每个线程都有独立的实例,线程安全。
- 无锁,性能较好。
缺点:
- 无法实现全局共享的单例。
1.2、工厂方法模式
工厂方法模式是一种创建型设计模式,它通过定义一个用于创建对象的接口,但将实际创建对象的过程延迟到子类中。这样,客户端代码就不需要直接依赖于具体的类,而是依赖于抽象的接口或类,从而使代码更加灵活和可维护。
假设我们要创建一个简单的图形绘制应用,它可以绘制不同类型的图形,如圆形和矩形。
步骤1:定义产品接口
首先,我们定义一个表示图形的接口:
interface Shape {
void draw();
}
步骤2:创建具体产品类
然后,我们创建两个具体的产品类,分别实现上述接口:
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Rectangle");
}
}
步骤3:定义工厂接口
接着,我们定义一个工厂接口,用于创建不同类型的图形对象:
interface ShapeFactory {
Shape createShape();
}
步骤4:创建具体工厂类
然后,我们创建两个具体的工厂类,分别实现上述工厂接口,用于创建相应的图形对象:
class CircleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Circle();
}
}
class RectangleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Rectangle();
}
}
步骤5:使用工厂方法模式
现在,我们可以使用工厂方法模式来创建图形对象,而无需直接实例化具体的图形类:
public class Main {
public static void main(String[] args) {
ShapeFactory circleFactory = new CircleFactory();
Shape circle = circleFactory.createShape();
circle.draw();
ShapeFactory rectangleFactory = new RectangleFactory();
Shape rectangle = rectangleFactory.createShape();
rectangle.draw();
}
}
在这个示例中,我们详细说明了工厂方法模式的步骤,并通过两个具体的图形类和工厂类来展示了它的实现方式。
1.3、抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供一个接口用于创建一系列相关或依赖对象的家族,而无需指定具体的类。抽象工厂模式的主要目标是使客户端代码与具体的类相分离,从而使系统更加灵活、可扩展和可维护。
在抽象工厂模式中,有两个主要角色:
- 抽象工厂(Abstract Factory):定义了创建一系列相关对象的接口,其中包含一组创建不同产品的抽象方法。
- 具体工厂(Concrete Factory):实现抽象工厂接口,负责创建具体产品的实例。
- 抽象产品(Abstract Product):定义了产品的接口,包含一些通用的方法。
- 具体产品(Concrete Product):实现抽象产品接口,是具体工厂所创建的对象。
具体实现
以下是用 Java 实现抽象工厂模式的示例,以创建不同操作系统的按钮和窗口为例:
// 抽象产品 - 按钮
interface Button {
void click();
}
// 具体产品 - Windows 按钮
class WindowsButton implements Button {
public void click() {
System.out.println("Windows 按钮被点击!");
}
}
// 具体产品 - MacOS 按钮
class MacOSButton implements Button {
public void click() {
System.out.println("MacOS 按钮被点击!");
}
}
// 抽象产品 - 窗口
interface Window {
void minimize();
}
// 具体产品 - Windows 窗口
class WindowsWindow implements Window {
public void minimize() {
System.out.println("Windows 窗口最小化!");
}
}
// 具体产品 - MacOS 窗口
class MacOSWindow implements Window {
public void minimize() {
System.out.println("MacOS 窗口最小化!");
}
}
// 抽象工厂
interface GUIFactory {
Button createButton();
Window createWindow();
}
// 具体工厂 - Windows 工厂
class WindowsFactory implements GUIFactory {
public Button createButton() {
return new WindowsButton();
}
public Window createWindow() {
return new WindowsWindow();
}
}
// 具体工厂 - MacOS 工厂
class MacOSFactory implements GUIFactory {
public Button createButton() {
return new MacOSButton();
}
public Window createWindow() {
return new MacOSWindow();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
// 创建 Windows 工厂
GUIFactory windowsFactory = new WindowsFactory();
Button windowsButton = windowsFactory.createButton();
Window windowsWindow = windowsFactory.createWindow();
windowsButton.click();
windowsWindow.minimize();
// 创建 MacOS 工厂
GUIFactory macOsFactory = new MacOSFactory();
Button macOsButton = macOsFactory.createButton();
Window macOsWindow = macOsFactory.createWindow();
macOsButton.click();
macOsWindow.minimize();
}
}
在上述代码中,我们定义了抽象产品接口 Button
和 Window
,以及它们的具体实现。然后,我们定义了抽象工厂接口 GUIFactory
,具体工厂类 WindowsFactory
和 MacOSFactory
分别实现了该接口,用于创建不同操作系统的按钮和窗口。客户端代码通过选择具体工厂来创建特定操作系统的界面元素。
抽象工厂模式的优点在于它将一组相关的产品组合在一起,使得系统更加灵活和可扩展。当需要增加新的产品族时,只需要扩展抽象工厂和具体工厂即可,而不需要修改现有客户端代码。这种模式有助于保持代码的一致性,同时也能够更轻松地切换不同的产品组合。
总之,抽象工厂模式适用于需要创建一系列相关对象的场景,特别是当希望客户端代码与具体产品的实现细节解耦时,可以使用这种模式来提高系统的灵活性和可维护性。
与工厂方法模式的区别
抽象工厂模式和工厂方法模式都属于创建型设计模式,它们都旨在解决对象的创建问题,但在一些方面有不同的应用场景和特点。
- 关注点不同:
- 抽象工厂模式(Abstract Factory Pattern)关注于创建一系列相关的产品对象,这些产品对象构成了一个
产品族
。它的目标是为客户端提供一个接口,使得客户端可以创建一组相关的产品,而不关心具体的产品是如何创建的。 - 工厂方法模式(Factory Method Pattern)关注于创建单一的产品对象,它通过定义一个工厂方法来创建产品。每个具体工厂类负责创建
特定的产品
,客户端通过与工厂接口交互来创建产品。
- 抽象工厂模式(Abstract Factory Pattern)关注于创建一系列相关的产品对象,这些产品对象构成了一个
- 产品组合的角度:
- 抽象工厂模式强调创建一系列相关的产品,这些产品通常在不同的维度上组合起来形成一个完整的产品族。比如在抽象工厂示例中,按钮和窗口就是一个产品族。
- 工厂方法模式关注于创建单一的产品对象,通常没有明显的产品组合关系。
- 扩展性:
- 抽象工厂模式在需要增加新的产品族时相对容易,只需要新增抽象产品和对应的具体产品及工厂。
- 工厂方法模式在需要新增新的产品时较为容易,只需新增具体产品类和对应的具体工厂类。
- 依赖关系:
- 抽象工厂模式中,客户端通常依赖于抽象工厂和抽象产品接口,从而使客户端代码与具体产品的实现细节解耦。
- 工厂方法模式中,客户端通常依赖于工厂接口和具体产品,同样也实现了一定程度的解耦。
- 应用场景:
- 抽象工厂模式适用于需要创建一系列相关产品族的场景,例如创建不同操作系统下的界面元素。
- 工厂方法模式适用于需要创建单一类型的产品对象,但可能有多种不同的实现方式的场景,例如创建不同类型的日志记录器。
总之,抽象工厂模式适用于需要创建一系列相关产品的情况,特别是在需要维护不同产品族的一致性时。工厂方法模式适用于需要创建单一类型产品对象的场景,并且通过工厂方法将产品的创建与客户端解耦。选择使用哪种模式取决于具体的需求和设计目标。
1.4、建造者模式
建造者模式是一种创建型设计模式,它的主要目的是将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。它允许您通过一步一步的方式创建一个复杂对象,而不需要将所有构建逻辑直接放在对象的构造函数中。这种分离可以使得构建过程更加灵活,也使得代码更加可维护。
在建造者模式中,有四个主要角色:
- Director(指挥者):负责按照一定的顺序调用建造者的方法来构建对象。
- Builder(建造者):定义构建对象各个部分的接口,具体的建造者实现这个接口以构建不同的部分。
- ConcreteBuilder(具体建造者):实现Builder接口,负责构建具体部件并实现装配方法。
- Product(产品):最终构建出的复杂对象。
具体实现
下面是一个简单的例子,实现建造者模式来构建一个电脑对象,其中电脑有处理器、内存和存储等部分。
// 产品类:电脑
class Computer {
private String processor;
private String memory;
private String storage;
public void setProcessor(String processor) {
this.processor = processor;
}
public void setMemory(String memory) {
this.memory = memory;
}
public void setStorage(String storage) {
this.storage = storage;
}
@Override
public String toString() {
return "Computer{" +
"processor='" + processor + '\'' +
", memory='" + memory + '\'' +
", storage='" + storage + '\'' +
'}';
}
}
// 抽象建造者接口
interface ComputerBuilder {
void buildProcessor();
void buildMemory();
void buildStorage();
Computer getResult();
}
// 具体建造者实现类
class HighEndComputerBuilder implements ComputerBuilder {
private Computer computer = new Computer();
@Override
public void buildProcessor() {
computer.setProcessor("高性能处理器");
}
@Override
public void buildMemory() {
computer.setMemory("16GB 内存");
}
@Override
public void buildStorage() {
computer.setStorage("1TB SSD 存储");
}
@Override
public Computer getResult() {
return computer;
}
}
// 指挥者类
class Director {
private ComputerBuilder computerBuilder;
public Director(ComputerBuilder computerBuilder) {
this.computerBuilder = computerBuilder;
}
public void constructComputer() {
computerBuilder.buildProcessor();
computerBuilder.buildMemory();
computerBuilder.buildStorage();
}
}
public class BuilderPatternExample {
public static void main(String[] args) {
ComputerBuilder builder = new HighEndComputerBuilder();
Director director = new Director(builder);
director.constructComputer();
Computer computer = builder.getResult();
System.out.println(computer);
}
}
在上面的代码中,我们创建了一个简单的电脑建造者模式的例子。我们有一个产品类 Computer
,一个抽象建造者接口 ComputerBuilder
,一个具体建造者类 HighEndComputerBuilder
,以及一个指挥者类 Director
来协调建造过程。
通过这个例子,我们可以看到建造者模式的核心思想:将一个复杂对象的构建过程分解为多个步骤,并在指挥者的控制下,逐步构建出最终的产品对象。这种方式使得产品的构建过程与表示分离,能够更好地适应不同的构建需求。
总结
建造者模式就是把一个对象的具体属性通过具体实现的建造者来赋值,并且通过指挥者来调用这个赋值的方法。关注点在属性的赋值过程
比如@Builder
就是一个建造者模式的实现。
在类的内部生成一个final的静态内部类(具体建造者),主类的构造器(指挥者)
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
// 指挥者
private Phone(Builder builder) {
cpu = builder.cpu;
screen = builder.screen;
memory = builder.memory;
mainboard = builder.mainboard;
}
// 具体建造者,无抽象建造者
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Builder() {}
public Builder cpu(String val) {
cpu = val;
return this;
}
public Builder screen(String val) {
screen = val;
return this;
}
public Builder memory(String val) {
memory = val;
return this;
}
public Builder mainboard(String val) {
mainboard = val;
return this;
}
public Phone build() {
return new Phone(this);}
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone.Builder()
.cpu("intel")
.mainboard("华硕")
.memory("金士顿")
.screen("三星")
.build();
System.out.println(phone);
}
}
1.5、原型模式
原型模式(Prototype Pattern)是一种创建型设计模式,其主要目的是通过复制现有对象来创建新对象,而无需从头开始重新构建。这种模式在需要创建多个相似对象时非常有用,它可以减少重复的初始化步骤,提高性能并简化代码。
在原型模式中,我们首先创建一个原型对象,然后通过克隆这个原型来创建新对象。这个克隆可以是浅克隆(只复制基本数据类型的值,但引用类型的对象仍然共享)或者深克隆(完全复制,包括引用类型的对象)。
具体实现
以下是使用Java代码实现原型模式的示例,我们将创建一个简单的图形对象作为原型,然后通过克隆来创建新的图形对象。
import java.util.HashMap;
import java.util.Map;
// 原型接口
interface Shape extends Cloneable {
void draw();
Shape clone();
}
// 具体原型 - 圆形
class Circle implements Shape {
private String type;
public Circle() {
type = "Circle";
}
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
@Override
public Shape clone() {
return new Circle();
}
}
// 具体原型 - 正方形
class Square implements Shape {
private String type;
public Square() {
type = "Square";
}
@Override
public void draw() {
System.out.println("Drawing a Square");
}
@Override
public Shape clone() {
return new Square();
}
}
// 原型管理器
class ShapePrototypeManager {
private static Map<String, Shape> shapeMap = new HashMap<>();
static {
shapeMap.put("Circle", new Circle());
shapeMap.put("Square", new Square());
}
public static Shape getShape(String type) {
return shapeMap.get(type).clone();
}
}
public class PrototypePatternExample {
public static void main(String[] args) {
Shape circle1 = ShapePrototypeManager.getShape("Circle");
circle1.draw(); // 输出: Drawing a Circle
Shape square1 = ShapePrototypeManager.getShape("Square");
square1.draw(); // 输出: Drawing a Square
Shape circle2 = ShapePrototypeManager.getShape("Circle");
circle2.draw(); // 输出: Drawing a Circle
}
}
在上面的代码中,我们定义了一个Shape
接口作为原型,然后实现了Circle
和Square
两个具体原型类。ShapePrototypeManager
是原型管理器,它维护了一个原型对象的映射,通过克隆方法从中获取原型对象。
在main
方法中,我们演示了如何使用原型模式创建图形对象。我们首先从原型管理器获取圆形和正方形的克隆,并分别调用它们的draw
方法来展示不同类型的图形被绘制。注意,当我们获取同一类型的图形对象时,它们实际上是不同的实例,但它们的类型和行为是一样的。
原型模式的优点包括减少对象创建的开销,提高性能,以及简化了新对象的创建过程。然而,需要注意正确处理深浅克隆的问题,以防止意外的数据共享或复制问题。
总结
关键点在
clone
主要就是clone对象,有深拷贝浅拷贝,深拷贝需要让对象序列化,然后通过反序列化得到的对象才是深拷贝
浅拷贝
//奖状类
public class Citation implements Cloneable {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return (this.name);
}
public void show() {
System.out.println(name + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
@Override
public Citation clone() throws CloneNotSupportedException {
// 浅拷贝
return (Citation) super.clone();
}
}
//测试访问类
public class CitationTest {
public static void main(String[] args) throws CloneNotSupportedException {
Citation c1 = new Citation();
c1.setName("张三");
//复制奖状
Citation c2 = c1.clone();
//将奖状的名字修改李四
c2.setName("李四");
c1.show();
c2.show();
}
}
深拷贝
public class CitationTest1 {
public static void main(String[] args) throws Exception {
Citation c1 = new Citation();
Student stu = new Student("张三", "西安");
c1.setStu(stu);
//创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\b.txt"));
//将c1对象写出到文件中
oos.writeObject(c1);
oos.close();
//创建对象出入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\b.txt"));
//读取对象
Citation c2 = (Citation) ois.readObject();
//获取c2奖状所属学生对象
Student stu1 = c2.getStu();
stu1.setName("李四");
//判断stu对象和stu1对象是否是同一个对象
System.out.println("stu和stu1是同一个对象?" + (stu == stu1));
c1.show();
c2.show();
}
}