面试官让我手写单例,我写了7种问他要哪种
面试官让我手写单例我写了7种问他要哪种面试被问到设计模式大部分人背概念——单例保证全局唯一实例工厂解耦创建逻辑代理增强对象行为。面试官听完不会有任何感觉。因为这是标准答案任何人都能背出来。换个方式试试。场景一手写单例——写一个不算本事写清楚为什么选这个才算面试官写一个线程安全的单例。别上来就写 DCL双重检查锁定。先问一句您期望的是启动时初始化还是延迟初始化这比手写代码更有说服力因为你在考虑业务场景不是背模板。| 写法 | 初始化时机 | 线程安全 | 适用场景 | |------|-----------|---------|---------| | 饿汉式 | 类加载时 | JVM保证 | 创建成本低、一定会用到的对象 | | 静态内部类 | 首次访问时 | JVM保证 | 延迟加载但不想自己处理同步 | | DCL | 首次访问时 | volatilesynchronized | 延迟加载且构造中有复杂逻辑 | | 枚举 | 类加载时 | JVM保证 | 防反射、防序列化破坏 |DCL 写法java public class Singleton { private static volatile Singleton instance;private Singleton() { if (instance ! null) { throw new RuntimeException(反射攻击已被拦截); } } public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { if (instance null) { instance new Singleton(); } } } return instance; }} 这里三个关键点volatile防止指令重排——没有它instance指向的内存可能还没初始化完就被另一个线程拿到二次检查——多个线程同时过外层 if第一个拿到锁的创建完后面的进到内层 if 发现不为 null 直接退出构造里防反射——getDeclaredConstructor().setAccessible(true)能绕过去加个判断挡住但面试官真正想听的可能是如果你的系统里单例是用 Spring 管理的不需要自己写 DCLSpring 的DefaultSingletonBeanRegistry用ConcurrentHashMap保证线程安全并且在 Bean 销毁时处理生命周期。自己写的单例容易漏掉序列化破坏和反射破坏的问题。场景二模板方法——不是定义算法骨架而是别让我改核心流程面试官说说你在项目里怎么用模板方法的。别背书。说一个具体场景消息推送。你要对接短信、邮件、App 推送、微信模板消息。每种渠道的发送逻辑不一样但流程都一样校验内容→限流检查→发送→记录日志。java public abstract class MessageSender { public final SendResult send(Message msg) { validate(msg); // 子类覆写 if (!checkRateLimit(msg)) { // 基类统一实现 return SendResult.RATE_LIMITED; } SendResult result doSend(msg); // 子类实现 logResult(msg, result); // 基类统一日志 return result; }protected abstract void validate(Message msg); protected abstract SendResult doSend(Message msg); private boolean checkRateLimit(Message msg) { // 统一的限流逻辑 } private void logResult(Message msg, SendResult result) { // 统一的日志记录 }} 关键不是骨架和细节分离而是让新增渠道的成本降到最低。后来老板说加个钉钉消息推送你只需要java public class DingTalkSender extends MessageSender { Override protected void validate(Message msg) { if (msg.getContent().length() 5000) { throw new IllegalArgumentException(钉钉消息不能超过5000字符); } }Override protected SendResult doSend(Message msg) { return dingTalkClient.send(msg); }} 限流、日志这些公共逻辑完全不用管。这才是模板方法值钱的地方——不是代码少写几行是新增场景时不出 bug。场景三策略工厂——if-else 多到让你自己都烦的时候面试官怎么消除过多的 if-else用策略模式只是答对了一半。另一半是你怎么把策略注册到工厂里三种方式方式一手动注册java MapString, PaymentStrategy strategyMap new HashMap(); strategyMap.put(ALIPAY, new AlipayStrategy()); strategyMap.put(WECHAT, new WechatStrategy());最简单但每次加策略要改工厂代码违反开闭原则。方式二Spring 自动注入java Component public class PaymentFactory { private final Map strategyMap;public PaymentFactory(ListPaymentStrategy strategies) { this.strategyMap strategies.stream() .collect(Collectors.toMap( s - s.getClass().getAnnotation(PaymentType.class).value(), Function.identity() )); } public PaymentStrategy get(String type) { return strategyMap.get(type); }} 加策略只需要写新类加注解工厂不碰。这是大部分 Spring 项目的标准写法。方式三枚举策略java public enum PaymentMethod { ALIPAY { Override public PayResult pay(Order order) { return alipayClient.execute(order); } }, WECHAT { Override public PayResult pay(Order order) { return wechatClient.invoke(order); } };public abstract PayResult pay(Order order);} 适合策略数量固定且不频繁变动的场景。代码最紧凑但扩展性最差——加策略要改枚举本身。问到这一步你可以再加一句实际项目里我用的是方式二因为支付渠道是持续接入的。但如果只有两三种且永远不会再加枚举就够了。设计模式没有银弹看场景选。场景四观察者——别让下单接口扛住所有后续逻辑面试官下单完成后要发短信、减库存、送积分你怎么设计新手写法在createOrder()方法末尾串行调用发短信、减库存、送积分。结果是下单接口响应时间越来越长。Spring 事件机制java // 发布事件 Transactional public void createOrder(Order order) { orderDao.insert(order); eventPublisher.publishEvent(new OrderCreatedEvent(order)); }// 监听者各自独立 EventListener Async // 异步执行不阻塞下单 public void sendSms(OrderCreatedEvent event) { ... }EventListener Transactional(propagation Propagation.REQUIRES_NEW) public void deductInventory(OrderCreatedEvent event) { ... }EventListener Async public void addPoints(OrderCreatedEvent event) { ... } 但要补充一个踩坑点EventListener默认是同步执行的。不加Async的话三个监听器会串行阻塞下单线程。以及事务边界——减库存失败不应该回滚整个下单所以用REQUIRES_NEW独立事务。这些都是代码跑了才知道的事不是看图能看出来的。面试真正考验的不是你会多少是你踩过多少坑设计模式这东西过了入门阶段比的不是谁背得全是谁真的在项目里用过、遇到问题后重新理解了这些模式。面试的时候与其列一堆类图术语不如讲一个你用策略模式重构 if-else 的真实案例。面试官听到具体代码比听到概念要安心得多。最近在玩一个小项目用卡皮巴拉漫画讲设计模式叫「爪爪代码冒险记」。里面每个模式都配了答题关答对了才能进下一关。我写这些面试经验的时候也在想要是有人面试前刷一遍题至少聊起这些模式来不会只停留在定义算法骨架这种背诵层面。搜「爪爪代码冒险记」就能找到。