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

Java常用设计模式详解

2023-02-176.2k 阅读

单例模式

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。在Java中,单例模式有多种实现方式。

饿汉式单例

饿汉式单例在类加载时就创建实例,代码如下:

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() {
        return instance;
    }
}

这种方式简单直接,类加载时就创建实例,保证了线程安全。缺点是如果实例创建比较耗时,且可能一直不会用到,就会造成资源浪费。

懒汉式单例(线程不安全)

懒汉式单例在第一次调用getInstance方法时才创建实例,以下是线程不安全的版本:

public class LazySingletonUnsafe {
    private static LazySingletonUnsafe instance;
    private LazySingletonUnsafe() {}
    public static LazySingletonUnsafe getInstance() {
        if (instance == null) {
            instance = new LazySingletonUnsafe();
        }
        return instance;
    }
}

这种方式在多线程环境下可能会创建多个实例,因为多个线程可能同时判断instancenull,然后各自创建实例。

懒汉式单例(线程安全 - 同步方法)

为了使懒汉式单例在多线程环境下安全,可以使用synchronized关键字修饰getInstance方法:

public class LazySingletonSafe {
    private static LazySingletonSafe instance;
    private LazySingletonSafe() {}
    public static synchronized LazySingletonSafe getInstance() {
        if (instance == null) {
            instance = new LazySingletonSafe();
        }
        return instance;
    }
}

虽然这种方式保证了线程安全,但每次调用getInstance方法都要进行同步,性能较低。

双重检查锁(DCL)实现单例

双重检查锁是一种优化的线程安全懒汉式单例实现:

public class DoubleCheckedLockingSingleton {
    private volatile static DoubleCheckedLockingSingleton instance;
    private DoubleCheckedLockingSingleton() {}
    public static DoubleCheckedLockingSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLockingSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
}

这里使用volatile关键字确保instance变量的可见性和禁止指令重排序。第一次if (instance == null)检查是为了避免不必要的同步,只有当instancenull时才进入同步块。在同步块内再次检查instance是否为null,防止多个线程同时通过第一次检查。

静态内部类实现单例

public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton() {}
    private static class SingletonHolder {
        private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

这种方式利用了类加载机制,在外部类被加载时,静态内部类不会被加载。只有当调用getInstance方法时,静态内部类才会被加载并创建实例,保证了懒加载和线程安全。

枚举实现单例

public enum EnumSingleton {
    INSTANCE;
    // 可以添加其他方法和属性
    public void doSomething() {
        System.out.println("Doing something in EnumSingleton");
    }
}

枚举实现单例简单高效,并且在序列化和反序列化时能够保证单例的唯一性。

工厂模式

工厂模式是一种创建型设计模式,它将对象的创建和使用分离,通过一个工厂类来负责创建对象。工厂模式分为简单工厂、工厂方法和抽象工厂三种类型。

简单工厂模式

简单工厂模式定义了一个工厂类,用于创建产品对象。假设我们有一个Shape接口和它的具体实现类CircleRectangle,简单工厂类如下:

interface Shape {
    void draw();
}
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");
    }
}
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 SimpleFactoryClient {
    public static void main(String[] args) {
        ShapeFactory factory = new ShapeFactory();
        Shape circle = factory.createShape("circle");
        circle.draw();
        Shape rectangle = factory.createShape("rectangle");
        rectangle.draw();
    }
}

简单工厂模式的优点是实现简单,将对象创建逻辑封装在工厂类中。缺点是不符合开闭原则,如果要添加新的产品类型,需要修改工厂类的代码。

工厂方法模式

工厂方法模式将对象创建的方法抽象成抽象方法,由具体的工厂子类实现。继续以Shape为例:

interface Shape {
    void draw();
}
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");
    }
}
abstract class ShapeFactory {
    public abstract Shape createShape();
}
class CircleFactory extends ShapeFactory {
    @Override
    public Shape createShape() {
        return new Circle();
    }
}
class RectangleFactory extends ShapeFactory {
    @Override
    public Shape createShape() {
        return new Rectangle();
    }
}

客户端代码:

public class FactoryMethodClient {
    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();
    }
}

工厂方法模式符合开闭原则,添加新的产品类型只需要创建新的工厂子类,不需要修改现有代码。但缺点是工厂子类过多时,代码会变得复杂。

抽象工厂模式

抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。假设我们有ShapeColor两个系列的产品,并且有创建这两个系列产品的抽象工厂:

interface Shape {
    void draw();
}
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");
    }
}
interface Color {
    void fill();
}
class Red implements Color {
    @Override
    public void fill() {
        System.out.println("Filling with red");
    }
}
class Blue implements Color {
    @Override
    public void fill() {
        System.out.println("Filling with blue");
    }
}
abstract class AbstractFactory {
    public abstract Shape createShape();
    public abstract Color createColor();
}
class CircleRedFactory extends AbstractFactory {
    @Override
    public Shape createShape() {
        return new Circle();
    }
    @Override
    public Color createColor() {
        return new Red();
    }
}
class RectangleBlueFactory extends AbstractFactory {
    @Override
    public Shape createShape() {
        return new Rectangle();
    }
    @Override
    public Color createColor() {
        return new Blue();
    }
}

客户端代码:

public class AbstractFactoryClient {
    public static void main(String[] args) {
        AbstractFactory circleRedFactory = new CircleRedFactory();
        Shape circle = circleRedFactory.createShape();
        Color red = circleRedFactory.createColor();
        circle.draw();
        red.fill();
        AbstractFactory rectangleBlueFactory = new RectangleBlueFactory();
        Shape rectangle = rectangleBlueFactory.createShape();
        Color blue = rectangleBlueFactory.createColor();
        rectangle.draw();
        blue.fill();
    }
}

抽象工厂模式的优点是隔离了具体产品类的创建,客户端只需要和抽象工厂交互。缺点是如果产品系列增加,会导致抽象工厂及其子类的代码复杂度过高。

代理模式

代理模式为其他对象提供一种代理以控制对这个对象的访问。代理对象可以在客户端和目标对象之间起到中介作用,对客户端的请求进行预处理或后处理。

静态代理

假设我们有一个Subject接口和它的实现类RealSubject,以及代理类ProxySubject

interface Subject {
    void request();
}
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject is handling request");
    }
}
class ProxySubject implements Subject {
    private RealSubject realSubject;
    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }
    @Override
    public void request() {
        System.out.println("ProxySubject pre - processing");
        realSubject.request();
        System.out.println("ProxySubject post - processing");
    }
}

客户端代码:

public class StaticProxyClient {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxySubject proxySubject = new ProxySubject(realSubject);
        proxySubject.request();
    }
}

静态代理的优点是实现简单,容易理解。缺点是如果有多个不同的接口需要代理,就需要为每个接口创建对应的代理类,代码量较大。

动态代理(JDK动态代理)

JDK动态代理是基于接口的动态代理,使用InvocationHandlerProxy类来创建代理对象。

interface Subject {
    void request();
}
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject is handling request");
    }
}
class DynamicProxyHandler implements InvocationHandler {
    private Object target;
    public DynamicProxyHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("DynamicProxy pre - processing");
        Object result = method.invoke(target, args);
        System.out.println("DynamicProxy post - processing");
        return result;
    }
}

客户端代码:

import java.lang.reflect.Proxy;
public class JDKDynamicProxyClient {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        DynamicProxyHandler handler = new DynamicProxyHandler(realSubject);
        Subject proxy = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                handler);
        proxy.request();
    }
}

JDK动态代理的优点是不需要为每个接口创建代理类,灵活性高。缺点是只能代理实现了接口的类。

CGLIB代理

CGLIB代理是基于子类的动态代理,它可以代理没有实现接口的类。首先需要引入CGLIB相关依赖,例如在Maven项目中添加:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

代码如下:

class RealSubject {
    public void request() {
        System.out.println("RealSubject is handling request");
    }
}
class CglibProxy implements MethodInterceptor {
    private Object target;
    public CglibProxy(Object target) {
        this.target = target;
    }
    public Object getProxyInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CglibProxy pre - processing");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("CglibProxy post - processing");
        return result;
    }
}

客户端代码:

public class CglibProxyClient {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        CglibProxy cglibProxy = new CglibProxy(realSubject);
        RealSubject proxy = (RealSubject) cglibProxy.getProxyInstance();
        proxy.request();
    }
}

CGLIB代理的优点是可以代理没有实现接口的类,性能较高。缺点是生成的代理类是目标类的子类,可能会受到一些继承限制。

策略模式

策略模式定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。 假设我们有一个计算运费的场景,不同的运输方式有不同的计费策略。

interface ShippingStrategy {
    double calculateShippingCost(double weight, double distance);
}
class ExpressShipping implements ShippingStrategy {
    @Override
    public double calculateShippingCost(double weight, double distance) {
        return weight * distance * 0.15;
    }
}
class StandardShipping implements ShippingStrategy {
    @Override
    public double calculateShippingCost(double weight, double distance) {
        return weight * distance * 0.1;
    }
}
class ShippingContext {
    private ShippingStrategy shippingStrategy;
    public ShippingContext(ShippingStrategy shippingStrategy) {
        this.shippingStrategy = shippingStrategy;
    }
    public double calculateShippingCost(double weight, double distance) {
        return shippingStrategy.calculateShippingCost(weight, distance);
    }
}

客户端代码:

public class StrategyPatternClient {
    public static void main(String[] args) {
        ShippingContext expressContext = new ShippingContext(new ExpressShipping());
        double expressCost = expressContext.calculateShippingCost(10, 50);
        System.out.println("Express shipping cost: " + expressCost);
        ShippingContext standardContext = new ShippingContext(new StandardShipping());
        double standardCost = standardContext.calculateShippingCost(10, 50);
        System.out.println("Standard shipping cost: " + standardCost);
    }
}

策略模式的优点是符合开闭原则,添加新的策略只需要创建新的策略类,不需要修改现有代码。同时,策略类之间相互独立,易于维护和扩展。缺点是客户端需要了解不同的策略,选择合适的策略类进行使用。

观察者模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者对象,使它们能够自动更新。 假设我们有一个WeatherData主题类和CurrentConditionsDisplayStatisticsDisplay两个观察者类。

import java.util.ArrayList;
import java.util.List;
interface Observer {
    void update(float temperature, float humidity, float pressure);
}
interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}
class WeatherData implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;
    public WeatherData() {
        observers = new ArrayList<>();
    }
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }
    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }
    public void measurementsChanged() {
        notifyObservers();
    }
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}
class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;
    private Subject weatherData;
    public CurrentConditionsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
    public void display() {
        System.out.println("Current conditions: " + temperature + "°C, " + humidity + "% humidity");
    }
}
class StatisticsDisplay implements Observer {
    private float maxTemp = 0;
    private float minTemp = 200;
    private float tempSum = 0;
    private int numReadings = 0;
    private Subject weatherData;
    public StatisticsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    @Override
    public void update(float temperature, float humidity, float pressure) {
        tempSum += temperature;
        numReadings++;
        if (temperature > maxTemp) {
            maxTemp = temperature;
        }
        if (temperature < minTemp) {
            minTemp = temperature;
        }
        display();
    }
    public void display() {
        System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp);
    }
}

客户端代码:

public class ObserverPatternClient {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        weatherData.setMeasurements(25, 60, 1013);
    }
}

观察者模式的优点是实现了主题和观察者之间的松耦合,主题只需要关注状态变化并通知观察者,而不需要关心具体的观察者实现。缺点是如果观察者过多,通知的效率可能会受到影响。

装饰器模式

装饰器模式动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。 假设我们有一个Component接口和它的实现类ConcreteComponent,以及装饰器类Decorator和具体的装饰器实现类ConcreteDecoratorAConcreteDecoratorB

interface Component {
    void operation();
}
class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("ConcreteComponent operation");
    }
}
abstract class Decorator implements Component {
    protected Component component;
    public Decorator(Component component) {
        this.component = component;
    }
    @Override
    public void operation() {
        component.operation();
    }
}
class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }
    @Override
    public void operation() {
        super.operation();
        System.out.println("ConcreteDecoratorA added behavior");
    }
}
class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
        super(component);
    }
    @Override
    public void operation() {
        super.operation();
        System.out.println("ConcreteDecoratorB added behavior");
    }
}

客户端代码:

public class DecoratorPatternClient {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        Component decoratedComponentA = new ConcreteDecoratorA(component);
        Component decoratedComponentAB = new ConcreteDecoratorB(decoratedComponentA);
        decoratedComponentAB.operation();
    }
}

装饰器模式的优点是可以在运行时动态地给对象添加功能,比继承更加灵活。缺点是多层装饰可能会导致代码复杂度过高,难以维护。

适配器模式

适配器模式将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

类适配器

假设我们有一个Adaptee类,它有一个specificRequest方法,我们想将它适配成Target接口的request方法。

class Adaptee {
    public void specificRequest() {
        System.out.println("Adaptee's specific request");
    }
}
interface Target {
    void request();
}
class ClassAdapter extends Adaptee implements Target {
    @Override
    public void request() {
        specificRequest();
    }
}

客户端代码:

public class ClassAdapterClient {
    public static void main(String[] args) {
        Target target = new ClassAdapter();
        target.request();
    }
}

类适配器的优点是实现简单,通过继承Adaptee类就可以复用其方法。缺点是由于Java单继承的限制,不能再继承其他类。

对象适配器

class Adaptee {
    public void specificRequest() {
        System.out.println("Adaptee's specific request");
    }
}
interface Target {
    void request();
}
class ObjectAdapter implements Target {
    private Adaptee adaptee;
    public ObjectAdapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    @Override
    public void request() {
        adaptee.specificRequest();
    }
}

客户端代码:

public class ObjectAdapterClient {
    public static void main(String[] args) {
        Adaptee adaptee = new Adaptee();
        Target target = new ObjectAdapter(adaptee);
        target.request();
    }
}

对象适配器的优点是通过组合方式实现,避免了单继承的限制,可以灵活地适配不同的Adaptee对象。缺点是代码相对复杂一些,需要创建组合关系。

桥接模式

桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。 假设我们有一个Shape抽象类和它的具体子类CircleRectangle,以及一个Color接口和它的具体实现类RedBlue

interface Color {
    void applyColor();
}
class Red implements Color {
    @Override
    public void applyColor() {
        System.out.println("Applying red color");
    }
}
class Blue implements Color {
    @Override
    public void applyColor() {
        System.out.println("Applying blue color");
    }
}
abstract class Shape {
    protected Color color;
    public Shape(Color color) {
        this.color = color;
    }
    public abstract void draw();
}
class Circle extends Shape {
    public Circle(Color color) {
        super(color);
    }
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
        color.applyColor();
    }
}
class Rectangle extends Shape {
    public Rectangle(Color color) {
        super(color);
    }
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle");
        color.applyColor();
    }
}

客户端代码:

public class BridgePatternClient {
    public static void main(String[] args) {
        Color red = new Red();
        Shape redCircle = new Circle(red);
        redCircle.draw();
        Color blue = new Blue();
        Shape blueRectangle = new Rectangle(blue);
        blueRectangle.draw();
    }
}

桥接模式的优点是实现了抽象和实现的解耦,使得它们可以独立变化。缺点是增加了系统的理解和设计难度。

组合模式

组合模式将对象组合成树形结构以表示“部分 - 整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。 假设我们有一个Component抽象类,它有Leaf叶子节点和Composite组合节点。

import java.util.ArrayList;
import java.util.List;
abstract class Component {
    protected String name;
    public Component(String name) {
        this.name = name;
    }
    public abstract void add(Component component);
    public abstract void remove(Component component);
    public abstract void display(int depth);
}
class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }
    @Override
    public void add(Component component) {
        System.out.println("Cannot add to a leaf");
    }
    @Override
    public void remove(Component component) {
        System.out.println("Cannot remove from a leaf");
    }
    @Override
    public void display(int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print("-");
        }
        System.out.println(name);
    }
}
class Composite extends Component {
    private List<Component> children = new ArrayList<>();
    public Composite(String name) {
        super(name);
    }
    @Override
    public void add(Component component) {
        children.add(component);
    }
    @Override
    public void remove(Component component) {
        children.remove(component);
    }
    @Override
    public void display(int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print("-");
        }
        System.out.println(name);
        for (Component child : children) {
            child.display(depth + 2);
        }
    }
}

客户端代码:

public class CompositePatternClient {
    public static void main(String[] args) {
        Composite root = new Composite("root");
        root.add(new Leaf("Leaf A"));
        root.add(new Leaf("Leaf B"));
        Composite composite = new Composite("Composite X");
        composite.add(new Leaf("Leaf XA"));
        composite.add(new Leaf("Leaf XB"));
        root.add(composite);
        root.display(1);
    }
}

组合模式的优点是使得客户端可以一致地处理单个对象和组合对象,简化了客户端代码。缺点是在使用组合模式时,叶子对象和组合对象的声明类型必须相同,可能会给客户端带来一些理解上的困难。

享元模式

享元模式运用共享技术有效地支持大量细粒度的对象。通过共享已经存在的对象来减少对象创建开销。 假设我们有一个Flyweight接口和它的具体实现类ConcreteFlyweight,以及FlyweightFactory工厂类。

interface Flyweight {
    void operation(String extrinsicState);
}
class ConcreteFlyweight implements Flyweight {
    private String intrinsicState;
    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }
    @Override
    public void operation(String extrinsicState) {
        System.out.println("Intrinsic State: " + intrinsicState + ", Extrinsic State: " + extrinsicState);
    }
}
class FlyweightFactory {
    private static final java.util.Map<String, Flyweight> flyweights = new java.util.HashMap<>();
    public Flyweight getFlyweight(String key) {
        Flyweight flyweight = flyweights.get(key);
        if (flyweight == null) {
            flyweight = new ConcreteFlyweight(key);
            flyweights.put(key, flyweight);
        }
        return flyweight;
    }
}

客户端代码:

public class FlyweightPatternClient {
    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();
        Flyweight flyweight1 = factory.getFlyweight("A");
        Flyweight flyweight2 = factory.getFlyweight("A");
        flyweight1.operation("Extrinsic State 1");
        flyweight2.operation("Extrinsic State 2");
    }
}

享元模式的优点是减少了对象的创建,降低了内存消耗。缺点是需要分离出内部状态和外部状态,增加了系统的复杂性。同时,如果共享的对象过多,查找共享对象的时间开销可能会增大。