Java创建型设计模式解析
单例模式
1. 单例模式的概念
单例模式是一种创建型设计模式,它确保一个类仅有一个实例,并提供一个全局访问点。在许多应用场景中,我们需要确保某些类只有一个实例存在,例如数据库连接池、线程池等。如果创建多个实例,可能会导致资源浪费、数据不一致等问题。
2. 实现方式
-
饿汉式
public class EagerSingleton { // 类加载时就初始化实例 private static final EagerSingleton instance = new EagerSingleton(); // 私有构造函数,防止外部实例化 private EagerSingleton() {} // 提供全局访问点 public static EagerSingleton getInstance() { return instance; } }
优点:这种方式在类加载时就创建实例,实现简单,并且天生线程安全。因为在类加载过程中,JVM 会保证类的初始化操作是线程安全的。 缺点:如果这个单例实例在整个应用生命周期中很少被使用,那么类加载时就创建实例会造成资源浪费。
-
懒汉式(线程不安全)
public class LazySingletonUnsafe { private static LazySingletonUnsafe instance; private LazySingletonUnsafe() {} // 延迟实例化,线程不安全 public static LazySingletonUnsafe getInstance() { if (instance == null) { instance = new LazySingletonUnsafe(); } return instance; } }
优点:延迟实例化,只有在调用
getInstance
方法时才创建实例,避免了资源浪费。 缺点:在多线程环境下,可能会创建多个实例。假设有两个线程同时调用getInstance
方法,并且此时instance
为null
,那么两个线程都会通过if (instance == null)
的判断,从而创建两个不同的实例。 -
懒汉式(线程安全,同步方法)
public class LazySingletonSafe { private static LazySingletonSafe instance; private LazySingletonSafe() {} // 同步方法保证线程安全 public static synchronized LazySingletonSafe getInstance() { if (instance == null) { instance = new LazySingletonSafe(); } return instance; } }
优点:通过
synchronized
关键字修饰getInstance
方法,保证了在多线程环境下只有一个线程能够进入方法并创建实例,从而实现了线程安全。 缺点:由于整个getInstance
方法都被同步,在高并发场景下,性能会受到影响。每次调用getInstance
方法都需要获取锁,即使实例已经创建,这会增加额外的开销。 -
双重检查锁定(DCL)
public class DoubleCheckedLockingSingleton { private static volatile DoubleCheckedLockingSingleton instance; private DoubleCheckedLockingSingleton() {} public static DoubleCheckedLockingSingleton getInstance() { if (instance == null) { synchronized (DoubleCheckedLockingSingleton.class) { if (instance == null) { instance = new DoubleCheckedLockingSingleton(); } } } return instance; } }
优点:这种方式结合了延迟实例化和高性能。首先通过外层的
if (instance == null)
检查,避免了每次都进入同步块,只有在实例未创建时才进入同步块。在内层的if (instance == null)
检查确保在同步块内再次确认实例是否已经创建,防止多个线程同时通过外层检查而创建多个实例。volatile
关键字保证了instance
的可见性和禁止指令重排序,确保在多线程环境下实例创建的正确性。 缺点:实现相对复杂,需要正确使用volatile
关键字和双重检查逻辑,对开发人员要求较高。 -
静态内部类
public class StaticInnerClassSingleton { private StaticInnerClassSingleton() {} private static class SingletonHolder { private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton(); } public static StaticInnerClassSingleton getInstance() { return SingletonHolder.INSTANCE; } }
优点:延迟实例化,并且利用了类加载机制保证线程安全。只有在调用
getInstance
方法时,才会加载SingletonHolder
类,从而创建实例。同时,类加载过程是线程安全的,不需要额外的同步操作。 缺点:虽然实现相对简洁,但对于不熟悉类加载机制的开发人员来说,理解起来可能有一定难度。
工厂模式
1. 简单工厂模式
- 概念:简单工厂模式不属于 GOF 23 种设计模式,但它是工厂模式的基础。它定义了一个工厂类,用于创建产品对象。工厂类有一个创建产品的方法,根据传入的参数决定创建哪种具体的产品。
- 代码示例:
- 定义产品接口
public interface Shape { void draw(); }
- 具体产品类
public class Circle implements Shape { @Override public void draw() { System.out.println("Drawing a circle."); } }
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Drawing a rectangle."); } }
- 工厂类
public class ShapeFactory { public Shape createShape(String shapeType) { if ("circle".equalsIgnoreCase(shapeType)) { return new Circle(); } else if ("rectangle".equalsIgnoreCase(shapeType)) { return new Rectangle(); } return null; } }
- 客户端代码
优点:将对象的创建和使用分离,提高了代码的可维护性和可扩展性。如果需要添加新的产品,只需要在工厂类中添加相应的创建逻辑即可。 缺点:工厂类的职责过重,如果产品种类过多,工厂类的public class Client { public static void main(String[] args) { ShapeFactory factory = new ShapeFactory(); Shape circle = factory.createShape("circle"); circle.draw(); Shape rectangle = factory.createShape("rectangle"); rectangle.draw(); } }
createShape
方法会变得非常复杂,违背了单一职责原则。同时,不符合开闭原则,添加新的产品需要修改工厂类的代码。
2. 工厂方法模式
- 概念:工厂方法模式定义了一个创建产品对象的工厂接口,将具体产品的创建延迟到具体的工厂子类中。每个具体工厂子类负责创建一种具体的产品。
- 代码示例:
- 产品接口和具体产品类同简单工厂模式
- 工厂接口
public interface ShapeFactory { Shape createShape(); }
- 具体工厂类
public class CircleFactory implements ShapeFactory { @Override public Shape createShape() { return new Circle(); } }
public class RectangleFactory implements ShapeFactory { @Override public Shape createShape() { return new Rectangle(); } }
- 客户端代码
优点:符合开闭原则,当需要添加新的产品时,只需要创建一个新的具体工厂子类,而不需要修改现有代码。同时,每个具体工厂类只负责创建一种产品,符合单一职责原则。 缺点:如果产品种类过多,会导致具体工厂类的数量过多,增加了系统的复杂度。public class Client { 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(); } }
3. 抽象工厂模式
- 概念:抽象工厂模式提供了一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它允许客户端使用抽象的接口来创建一组相关产品,而不需要了解具体的实现。
- 代码示例:
- 定义产品族接口
public interface Color { void fill(); }
public interface Shape { void draw(); }
- 具体产品类
public class Red implements Color { @Override public void fill() { System.out.println("Filling with red color."); } }
public class Green implements Color { @Override public void fill() { System.out.println("Filling with green color."); } }
public class Circle implements Shape { @Override public void draw() { System.out.println("Drawing a circle."); } }
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Drawing a rectangle."); } }
- 抽象工厂接口
public interface AbstractFactory { Color createColor(); Shape createShape(); }
- 具体工厂类
public class RedCircleFactory implements AbstractFactory { @Override public Color createColor() { return new Red(); } @Override public Shape createShape() { return new Circle(); } }
public class GreenRectangleFactory implements AbstractFactory { @Override public Color createColor() { return new Green(); } @Override public Shape createShape() { return new Rectangle(); } }
- 客户端代码
优点:隔离了具体产品的创建,客户端只需要关心抽象工厂接口,提高了代码的可维护性和可扩展性。同时,对于产品族的创建,保证了产品之间的一致性。 缺点:实现复杂,当产品族中的产品种类增加时,抽象工厂接口及其具体实现类都需要进行修改,违背了开闭原则。public class Client { private Color color; private Shape shape; public Client(AbstractFactory factory) { color = factory.createColor(); shape = factory.createShape(); } public void draw() { color.fill(); shape.draw(); } public static void main(String[] args) { AbstractFactory redCircleFactory = new RedCircleFactory(); Client redCircleClient = new Client(redCircleFactory); redCircleClient.draw(); AbstractFactory greenRectangleFactory = new GreenRectangleFactory(); Client greenRectangleClient = new Client(greenRectangleFactory); greenRectangleClient.draw(); } }
建造者模式
1. 建造者模式的概念
建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。它适用于创建对象的过程比较复杂,且对象的创建步骤固定,但创建的对象类型可能不同的场景。
2. 代码示例
- 定义产品类
public class Computer { private String cpu; private String ram; private String hardDisk; public void setCpu(String cpu) { this.cpu = cpu; } public void setRam(String ram) { this.ram = ram; } public void setHardDisk(String hardDisk) { this.hardDisk = hardDisk; } @Override public String toString() { return "Computer{" + "cpu='" + cpu + '\'' + ", ram='" + ram + '\'' + ", hardDisk='" + hardDisk + '\'' + '}'; } }
- 定义建造者接口
public interface ComputerBuilder { void buildCpu(); void buildRam(); void buildHardDisk(); Computer getComputer(); }
- 具体建造者类
public class HighEndComputerBuilder implements ComputerBuilder { private Computer computer = new Computer(); @Override public void buildCpu() { computer.setCpu("Intel Core i9"); } @Override public void buildRam() { computer.setRam("32GB DDR4"); } @Override public void buildHardDisk() { computer.setHardDisk("1TB SSD"); } @Override public Computer getComputer() { return computer; } }
public class LowEndComputerBuilder implements ComputerBuilder { private Computer computer = new Computer(); @Override public void buildCpu() { computer.setCpu("Intel Core i3"); } @Override public void buildRam() { computer.setRam("8GB DDR4"); } @Override public void buildHardDisk() { computer.setHardDisk("500GB HDD"); } @Override public Computer getComputer() { return computer; } }
- 导演类
public class ComputerDirector { private ComputerBuilder computerBuilder; public ComputerDirector(ComputerBuilder computerBuilder) { this.computerBuilder = computerBuilder; } public Computer constructComputer() { computerBuilder.buildCpu(); computerBuilder.buildRam(); computerBuilder.buildHardDisk(); return computerBuilder.getComputer(); } }
- 客户端代码
优点:将复杂对象的构建过程封装在导演类和具体建造者类中,使得代码结构清晰,易于维护和扩展。可以方便地创建不同类型的复杂对象,而不需要在客户端代码中编写复杂的创建逻辑。 缺点:如果产品的内部结构发生变化,可能需要修改建造者接口和具体建造者类的代码,增加了维护成本。同时,对于简单对象的创建,使用建造者模式会显得过于复杂。public class Client { public static void main(String[] args) { ComputerBuilder highEndBuilder = new HighEndComputerBuilder(); ComputerDirector highEndDirector = new ComputerDirector(highEndBuilder); Computer highEndComputer = highEndDirector.constructComputer(); System.out.println(highEndComputer); ComputerBuilder lowEndBuilder = new LowEndComputerBuilder(); ComputerDirector lowEndDirector = new ComputerDirector(lowEndBuilder); Computer lowEndComputer = lowEndDirector.constructComputer(); System.out.println(lowEndComputer); } }
原型模式
1. 原型模式的概念
原型模式通过复制现有对象来创建新对象,而不是通过实例化类来创建。它适用于创建对象的成本较高,或者对象的创建过程比较复杂的场景。通过克隆现有对象,可以提高对象创建的效率。
2. 实现方式
-
实现
Cloneable
接口public class Prototype implements Cloneable { private int value; public Prototype(int value) { this.value = value; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } @Override protected Prototype clone() throws CloneNotSupportedException { return (Prototype) super.clone(); } }
- 客户端代码
public class Client { public static void main(String[] args) { Prototype prototype1 = new Prototype(10); try { Prototype prototype2 = prototype1.clone(); System.out.println("Prototype1 value: " + prototype1.getValue()); System.out.println("Prototype2 value: " + prototype2.getValue()); prototype2.setValue(20); System.out.println("Prototype1 value after change in prototype2: " + prototype1.getValue()); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } }
优点:通过克隆对象,可以避免复杂的对象创建过程,提高对象创建的效率。同时,对于一些需要根据现有对象进行少量修改创建新对象的场景,使用原型模式更加方便。 缺点:如果对象的结构比较复杂,实现
Cloneable
接口并正确实现clone
方法可能会比较困难。而且,Cloneable
接口是一个标记接口,没有定义任何方法,clone
方法的实现依赖于Object
类的clone
方法,这可能会导致一些问题,例如浅拷贝的问题。 -
深拷贝 当对象中包含其他对象引用时,
Object
类的clone
方法默认执行浅拷贝,即只复制对象的引用,而不会复制引用指向的对象。如果需要进行深拷贝,可以通过序列化和反序列化的方式实现。import java.io.*; public class DeepCopyPrototype implements Serializable { private int value; private InnerObject innerObject; public DeepCopyPrototype(int value, InnerObject innerObject) { this.value = value; this.innerObject = innerObject; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } public InnerObject getInnerObject() { return innerObject; } public void setInnerObject(InnerObject innerObject) { this.innerObject = innerObject; } public DeepCopyPrototype deepClone() throws IOException, ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (DeepCopyPrototype) ois.readObject(); } } class InnerObject implements Serializable { private String data; public InnerObject(String data) { this.data = data; } public String getData() { return data; } public void setData(String data) { this.data = data; } }
- 客户端代码
public class DeepCopyClient { public static void main(String[] args) { InnerObject innerObject = new InnerObject("Initial data"); DeepCopyPrototype prototype1 = new DeepCopyPrototype(10, innerObject); try { DeepCopyPrototype prototype2 = prototype1.deepClone(); System.out.println("Prototype1 value: " + prototype1.getValue()); System.out.println("Prototype1 inner object data: " + prototype1.getInnerObject().getData()); System.out.println("Prototype2 value: " + prototype2.getValue()); System.out.println("Prototype2 inner object data: " + prototype2.getInnerObject().getData()); prototype2.getInnerObject().setData("Modified data"); System.out.println("Prototype1 inner object data after change in prototype2: " + prototype1.getInnerObject().getData()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
这种方式通过将对象序列化到字节流,然后再反序列化来创建一个全新的对象,确保了对象及其内部引用对象都被复制,实现了深拷贝。但这种方式的性能开销相对较大,因为涉及到序列化和反序列化的操作。