设计模式是软件设计经验的总结,面试中常考察单例、工厂、策略、观察者等模式的原理和应用场景。
1. 单例模式的实现方式?
- 饿汉式:类加载时初始化,线程安全。缺点:可能浪费资源。
- 懒汉式(双重检查锁):volatile + synchronized实现懒加载和线程安全。
- 静态内部类:利用类加载机制保证线程安全,推荐写法。
- 枚举:天然防反射和序列化攻击,《Effective Java》推荐。
双重检查锁示例:volatile防止指令重排序(new对象不是原子操作:分配内存→初始化→引用赋值)。
2. 工厂模式的三种形式?
- 简单工厂:一个工厂类根据参数创建不同产品。违反开闭原则(新增产品需改工厂)。
- 工厂方法:每个产品对应一个工厂类,符合开闭原则。类数量过多。
- 抽象工厂:创建一组相关产品(产品族)。如DatabaseFactory同时创建Connection和Statement。
Spring中的工厂模式:BeanFactory是简单工厂,FactoryBean是工厂方法。
3. 策略模式的应用场景?
策略模式将算法封装为独立类,使算法可互相替换,避免大量if-else。
- 支付方式:支付宝、微信、银行卡等不同支付策略。
- 排序算法:快速排序、归并排序等策略选择。
- 促销折扣:满减、打折、直减等促销策略。
JDK中的Collections.sort()传入Comparator就是策略模式。Spring的Resource接口也是策略模式的应用。
4. 观察者模式原理及应用?
观察者模式定义了一对多的依赖关系,当主题状态变化时所有观察者收到通知。
- 角色:Subject(主题)/Observable、Observer(观察者)。
- Java实现:java.util.Observable和Observer接口(已废弃)。
- Spring事件:ApplicationEvent + ApplicationListener + ApplicationEventPublisher。
- 消息队列:发布/订阅模型本质是观察者模式的分布式实现。
- MVC:Model是Subject,View是Observer。
5. 代理模式:静态代理 vs JDK动态代理 vs CGLIB?
- 静态代理:手动编写代理类,实现相同接口。一个接口一个代理类,麻烦。
- JDK动态代理:基于接口,通过Proxy.newProxyInstance()生成代理类。传入InvocationHandler处理逻辑。目标类必须实现接口。
- CGLIB:基于继承,通过字节码技术生成目标类的子类。不能代理final类/方法。比JDK代理快。
Spring AOP:目标类实现接口用JDK代理,否则用CGLIB。可通过@Aspect注解定义切面。
6. 模板方法模式?
模板方法在父类定义算法骨架,将某些步骤延迟到子类实现,不改变算法结构即可重定义步骤。
- AbstractList:get()、size()由子类实现,indexOf()在父类定义。
- HttpServlet:service()调用doGet()、doPost()等,由子类实现。
- Spring:JdbcTemplate、RestTemplate都使用模板方法模式。
- AbstractApplicationContext:refresh()定义IOC流程,子类实现customizeBeanFactory()等。
7. 责任链模式?
责任链模式将请求沿着处理者链传递,每个处理者决定处理或传递给下一个。
- Servlet Filter:FilterChain串联多个Filter,请求依次经过。
- Spring Interceptor:HandlerInterceptor的preHandle/postExecute链式调用。
- Netty Pipeline:ChannelHandler链式处理IO事件。
- Sentinel:ProcessorSlotChain处理限流、熔断等。
8. 装饰器模式 vs 代理模式?
两者结构相似但意图不同:
- 装饰器:增强对象功能,关注对象本身。如IO流:BufferedInputStream装饰FileInputStream。
- 代理:控制对象访问,关注访问控制。如远程代理、虚拟代理、保护代理。
- 区别:装饰器通常由客户端组装,代理通常由框架创建。
9. 适配器模式?
适配器模式将不兼容接口的类适配为期望接口。
- 类适配器:继承被适配类,实现目标接口。
- 对象适配器:持有被适配类的引用,实现目标接口。更灵活。
- JDK示例:InputStreamReader适配InputStream为Reader。
- Spring MVC:HandlerAdapter适配不同类型的Controller。
10. 设计模式六大原则?
- 单一职责原则(SRP):一个类只有一个引起变化的原因。
- 开闭原则(OCP):对扩展开放,对修改关闭。
- 里氏替换原则(LSP):子类必须能替换父类。
- 依赖倒置原则(DIP):依赖抽象,不依赖具体。
- 接口隔离原则(ISP):客户端不应依赖不需要的接口。
- 迪米特法则(LoD):最少知道原则,降低耦合。
