专业的JAVA编程教程与资源

网站首页 > java教程 正文

(十三) Java 设计模式: 常见23种设计模式和面试答疑

temp10 2025-03-30 21:00:20 java教程 2 ℃ 0 评论

一、引言

前面阳仔给大家介绍了很多Java 常见的面试题目,今天这篇阳仔再大家熟悉和认识一下常见的设计模式有哪些和已经常见的面试题;

二、什么是设计模式?

设计模式是在特定的软件设计情境下,对某一类问题的通用解决方案的总结与抽象。不是代码,而是一种解决问题的思路和方法,旨在提高软件的可复用性、可读性和可维护性,降低系统的耦合度,增强系统的灵活性和扩展性。Java 中共有 23 种经典的设计模式;

(十三) Java 设计模式: 常见23种设计模式和面试答疑

常见分类:

1.创建型模式

主要关注对象的创建过程,解决如何创建对象才能使系统更加灵活、解耦的问题;

包括:单例模式工厂模式抽象工厂模式建造者模式原型模式

  • 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。
  • 抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,无需指定它们具体的类。
  • 建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
  • 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

2.结构型模式

侧重于描述如何将类或对象组合成更大的结构,以实现新的功能,解决类与对象的组合和继承等问题;

包括: 适配器模式装饰器模式代理模式桥接模式组合模式外观模式享元模式过滤器模式

  • 适配器模式:将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  • 装饰器模式:动态地给对象添加一些额外的职责,就增加功能来说,装饰器模式比生成子类更为灵活。
  • 代理模式:为其他对象提供一种代理以控制对这个对象的访问。
  • 桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
  • 组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。使得客户对单个对象和组合对象的使用具有一致性。
  • 外观模式:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
  • 享元模式:运用共享技术有效地支持大量细粒度的对象。
  • 过滤器模式:通过过滤器来过滤一组对象,从而得到最终想要的子对象。

3.行为型模式

关注对象之间的协作和交互,解决如何让对象以合适的方式进行通信和协同工作的问题;

包括: 策略模式模板方法模式命令模式责任链模式观察者模式迭代器模式中介者模式备忘录模式访问者模式状态模式

  • 策略模式:定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。此模式让算法可独立于使用它的客户而变化。
  • 模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
  • 命令模式:将一个请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
  • 责任链模式:为解除请求的发送者和接收者之间的耦合,使多个对象都有机会处理请求。
  • 观察者模式:定义对象间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。
  • 迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露其内部的表示。
  • 中介者模式:用一个中介对象来封装一系列的对象交互。此模式能将各对象解耦,从而使各对象之间不再相互引用。
  • 备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后可以恢复到原先保存的状态。
  • 访问者模式:表示一个作用于某对象结构中的各元素的操作。它可以使你再不改变各元素的类的前提下定义作用于这些元素的新操作。
  • 状态模式:允许对象在内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

三、常见设计模式举例

1. 单例模式 : 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

模板代码:

单例模式通常有五种实现方式,分别是饿汉式懒汉式(线程不安全版)、懒汉式(线程安全版)双重校验锁版静态内部类版枚举版

  1. 饿汉式:在类加载时就创建实例,简单高效,但不能延迟加载。
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    private Singleton () {}
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
  1. 懒汉式(线程不安全版):在第一次使用时创建实例,可延迟加载,但在多线程环境下可能会创建多个实例。
public class Singleton {
    private static Singleton instance;
    private Singleton () {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  1. 懒汉式(线程安全版):通过加锁保证线程安全,但每次调用都需要进行同步,性能较低。
public class Singleton {
    private static Singleton instance;
    private Singleton () {}
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  1. 双重校验锁版:既保证了线程安全,又实现了延迟加载,性能较高。
public class Singleton {
    private static volatile Singleton instance;
    private Singleton () {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  1. 静态内部类版:利用 Java 的类加载机制实现线程安全的延迟加载,代码简洁。
public class Singleton {
    private Singleton () {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
  1. 枚举版:通过枚举实现单例,天然线程安全且防止反序列化和反射攻击。
public enum Singleton {
    INSTANCE;
    public void doSomething() {
        // 具体操作
    }
}

优缺点:

  • 优点:
  • 确保一个类只有一个实例,并提供全局访问点。
  • 节省内存,避免重复创建相同对象。
  • 缺点:
  • 单例模式违背了单一职责原则,多职责可能导致对象臃肿。
  • 增加了并发访问时的复杂性,需要考虑线程安全问题。

2. 工厂模式 :定义一个用于创建对象的接口,让子类决定实例化哪一个类。

模板代码:

工厂模式分为简单工厂模式、工厂方法模式和抽象工厂模式。

  1. 简单工厂模式:提供一个统一的工厂类来创建对象,但不符合开闭原则。
  2. 工厂方法模式:定义一个创建对象的接口,由子类决定实例化哪一个类。
  • 抽象产品类:
public abstract class Product {
    public abstract void use();
}
  • 具体产品类:
public class ConcreteProductA extends Product {
    @Override
    public void use() {
        System.out.println("使用产品 A");
    }
}
public class ConcreteProductB extends Product {
    @Override
    public void use() {
        System.out.println("使用产品 B");
    }
}
  • 抽象工厂类:
public abstract class Factory {
    public abstract Product createProduct();
}
  • 具体工厂类:
public class ConcreteFactoryA extends Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductA();
    }
}
public class ConcreteFactoryB extends Factory {
    @Override
    public Product createProduct() {
        return new ConcreteProductB();
    }
}
  • 客户端代码:
public class Client {
    public static void main(String[] args) {
        Factory factory = new ConcreteFactoryA();
        Product product = factory.createProduct();
        product.use();
    }
}
  1. 抽象工厂模式:提供一个接口,创建相关或依赖对象的家族,而无需指定具体类。

优缺点:

  • 优点:
  • 工厂方法模式将对象的创建和使用分离,依赖倒置原则。
  • 良好的可扩展性,新增产品只需增加相应工厂即可。
  • 缺点:
  • 每新增一个产品都需要新增一个具体工厂,增加了代码的复杂性。

3. 策略模式

模板代码:策略模式定义一系列算法,将每个算法封装起来,使它们可以互换使用。

  • 策略接口:
public interface Strategy {
    public int calculate(int num1, int num2);
}
  • 具体策略类:
public class AddStrategy implements Strategy {
    @Override
    public int calculate(int num1, int num2) {
        return num1 + num2;
    }
}
public class SubtractStrategy implements Strategy {
    @Override
    public int calculate(int num1, int num2) {
        return num1 - num2;
    }
}
  • 上下文类:
public class Context {
    private Strategy strategy;
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }
    public int executeStrategy(int num1, int num2) {
        return strategy.calculate(num1, num2);
    }
}
  • 客户端代码:
public class Client {
    public static void main(String[] args) {
        Context context = new Context(new AddStrategy());
        System.out.println(context.executeStrategy(10, 5)); // 输出 15
        context = new Context(new SubtractStrategy());
        System.out.println(context.executeStrategy(10, 5)); // 输出 5
    }
}

优缺点:

  • 优点:
  • 策略模式提供了开放封闭原则。
  • 屏蔽了策略的具体实现。
  • 缺点:
  • 策略模式中存在大量的策略类可能导致系统臃肿。

4. 观察者模式

模板代码:

观察者模式定义对象间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。

  • 观察者接口:
public interface Observer {
    void update(String message);
}
  • 被观察者接口:
public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers(String message);
}
  • 具体被观察者类:
import java.util.ArrayList;
import java.util.List;

public class ConcreteSubject implements Subject {
    private List observers = new ArrayList<>();
    private String message;
    
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }
    
    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
    
    @Override
    public void notifyObservers(String message) {
        this.message = message;
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}
  • 具体观察者类:
public class ConcreteObserver implements Observer {
    private String name;
    
    public ConcreteObserver(String name) {
        this.name = name;
    }
    
    @Override
    public void update(String message) {
        System.out.println(name + " 收到消息:" + message);
    }
}
  • 客户端代码:
public class Client {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer1 = new ConcreteObserver("观察者 1");
        Observer observer2 = new ConcreteObserver("观察者 2");
        subject.registerObserver(observer1);
        subject.registerObserver(observer2);
        subject.notifyObservers("你好,观察者们!");
    }
}

优缺点:

  • 优点:
  • 降低代码耦合度,符合依赖倒置原则。
  • 提高了系统的灵活性和可维护性。
  • 缺点:
  • 过多的通知消息可能导致性能问题。
  • 复杂的依赖关系可能导致调试困难。

四、常见设计模式面试题及答案

什么是设计模式?请列举常见的设计模式,并简要说明其用途。

设计模式是在软件开发过程中,针对特定问题场景总结出的一套通用的解决方案,它能够提高代码的可复用性、可维护性和可扩展性。常见的设计模式有 23 种,以下是几种常见设计模式及其用途:

  • 单例模式:确保一个类只有一个实例,常用于配置管理、日志记录等需要全局共享资源的场景。
  • 工厂模式:提供一个创建对象的接口,由子类决定实例化哪一个类,用于创建对象的过程较为复杂且需要根据条件创建不同类型的对象。
  • 策略模式:定义一系列算法,将每个算法封装起来,使它们可以互换使用,适用于需要根据不同的条件执行不同的操作算法的情况。
  • 观察者模式:定义对象间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新,常用于事件驱动的系统。

单例模式有几种实现方式?它们的区别是什么?

单例模式主要有以下几种实现方式,具体区别如下:

  • 饿汉式:在类加载时就创建实例,简单高效,但不能延迟加载。
  • 懒汉式(线程不安全版):在第一次使用时创建实例,可延迟加载,但在多线程环境下可能会创建多个实例。
  • 懒汉式(线程安全版):通过加锁保证线程安全,但每次调用都需要进行同步,性能较低。
  • 双重校验锁版:既保证了线程安全,又实现了延迟加载,性能较高。
  • 静态内部类版:利用 Java 的类加载机制实现线程安全的延迟加载,代码简洁。
  • 枚举版:通过枚举实现单例,天然线程安全且防止反序列化和反射攻击。

它们的区别主要在于线程安全性、延迟加载、性能等方面。

工厂模式和抽象工厂模式有什么区别?

  • 工厂模式:提供一个创建对象的接口,由子类决定实例化哪一个类,主要用于创建单个产品对象。例如,创建不同类型的数据库连接对象。
  • 抽象工厂模式:提供一个接口,创建相关或依赖对象的家族,而无需指定具体类,主要用于创建多个产品对象,这些产品对象之间存在关联或相互依赖关系。例如,创建不同品牌的电脑,包括 CPU、内存、硬盘等多个部件。

请简述策略模式和模板方法模式的区别。

  • 策略模式:定义一系列算法,将每个算法封装起来,使它们可以互换使用,策略模式使算法的变化独立于使用它的客户端。客户端可以根据需要选择不同的策略来执行操作。
  • 模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变算法结构即可重新定义算法的某些特定步骤。模板方法模式通常用于定义一个算法的框架,而具体步骤由子类实现。

什么是装饰器模式?请举例说明其应用场景。

装饰器模式动态地给对象添加一些额外的职责,就增加功能来说,装饰器模式比生成子类更为灵活。它通过将对象包装在一个装饰器对象中,来给原对象添加新的功能。

例如,在一个文本编辑器中,我们可能需要对文本进行加粗、斜体、下划线等格式化操作。可以使用装饰器模式来实现,将文本对象作为核心对象,然后通过不同的装饰器对象来添加不同的格式化功能。

说一下开发中需要遵守的设计原则?

1、单一职责原则 :一个类只负责完成一个职责或者功能

2、开放封闭原则 : 对扩展开放,对修改关闭

3、里氏替换原则 :期望行为一致的替换,在不了解派生类的情况下,仅通过接口或基类的方法,即可清楚的知道方法的行为

4、接口隔离原则 :要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

5、依赖倒置原则 : 指在设计代码架构时,高层模块不应该依赖于底层模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

6、迪米特法则 : 不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。

说一下单例模式,及其应用场景?

资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。控制资源的情况下,方便资源之间的互相通信。如线程池等。

介绍一下代理模式的种类和它们之间区别?

1.静态代理: 代理方式需要代理对象和目标对象实现一样的接口。代码冗余,不易维护

2.JDK动态代理 : 在内存中构建代理对象,从而实现对目标对象的代理功能.动态代理又被称

为JDK代理或接口代理.

静态代理与动态代理的区别:

1. 静态代理在编译时就已经实现了,编译完成后代理类是一个实际的class文件

2. 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节

码,并加载到JVM中.

3.CGLIB 动态代理一个第三方代码生成类库,cglib 为没有实现接口的类提供代理.

JDK动态代理和CGLIB 动态代理的区别?

  • JDK动态代理适用于目标对象实现了接口的情况,代码简单且性能较好。
  • CGLIB动态代理适用于目标对象没有实现接口的情况,可以代理任意类,但性能略低。

具体分析:

1.实现机制

  • JDK动态代理:基于接口实现,通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现,生成的代理对象必须实现一个或多个接口。
  • CGLIB动态代理:基于继承实现,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术来拦截所有父类方法的调用,生成的代理对象不需要实现接口,但不能代理final类和final方法。

2.使用场景

  • JDK动态代理:适用于目标对象实现了接口的情况,代码较为简单,易于理解和使用。
  • CGLIB动态代理:适用于目标对象没有实现接口的情况,可以代理没有接口的类,但性能略低于JDK动态代理。

3.性能

  • JDK动态代理:性能较好,因为它是基于接口的代理,反射调用相对简单。
  • CGLIB动态代理:性能稍差,因为需要生成字节码并进行方法拦截,但差距通常在可接受范围内。

代码示例

  • JDK动态代理示例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface MyInterface {
    void doSomething();
}

class MyInterfaceImpl implements MyInterface {
    @Override
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

class MyInvocationHandler implements InvocationHandler {
    private final MyInterface target;

    public MyInvocationHandler(MyInterface target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method call");
        Object result = method.invoke(target, args);
        System.out.println("After method call");
        return result;
    }
}

public class JdkDynamicProxyExample {
    public static void main(String[] args) {
        MyInterface target = new MyInterfaceImpl();
        MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
            MyInterface.class.getClassLoader(),
            new Class[]{MyInterface.class},
            new MyInvocationHandler(target)
        );
        proxy.doSomething();
    }
}
  • CGLIB动态代理示例
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

class MyClass {
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method call");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method call");
        return result;
    }
}

public class CglibDynamicProxyExample {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyClass.class);
        enhancer.setCallback(new MyMethodInterceptor());
        MyClass proxy = (MyClass) enhancer.create();
        proxy.doSomething();
    }
}

我是阳仔,喜欢的朋友欢迎点赞,收藏,转发,评论!!!!

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表