MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java创建型设计模式解析

2022-04-281.1k 阅读

单例模式

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 方法,并且此时 instancenull,那么两个线程都会通过 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();
            }
        }
    }
    

    这种方式通过将对象序列化到字节流,然后再反序列化来创建一个全新的对象,确保了对象及其内部引用对象都被复制,实现了深拷贝。但这种方式的性能开销相对较大,因为涉及到序列化和反序列化的操作。