26、完整项目实战(二):架构设计——使用单例管理配置、工厂创建通知器、观察者监听状态
好,咱们接着往下走。上一章我们把项目背景和需求理清楚了,这一章,我打算直接动手做架构设计。
说白了,架构设计就是给代码“搭骨架”。骨架搭得稳,后面往里填肉(写业务逻辑)才不慌。我个人习惯,在动手写任何一行代码之前,先想清楚三个问题:
- 配置怎么管?—— 全局只有一份,不能乱,不能丢。
- 通知器怎么创建?—— 以后可能会加新的通知方式,不能每次都改核心代码。
- 状态变化怎么通知?—— 一个变了,所有关心它的地方都得知道。
你想想看,这三个问题,正好对应了我们这节课要讲的三个模式:单例模式、工厂模式、观察者模式。不是硬套模式,而是问题本身就在那里,模式只是恰好能解决它。
一、单例模式:全局配置管理中心
先聊配置。我在项目中遇到过好几次这种坑:配置散落在各个类里,有的用静态变量,有的用配置文件,有的干脆写死在代码里。结果改一个配置,得翻遍整个项目。嗯,这滋味不好受。
所以这次,我决定用单例模式来管理所有配置。为什么用单例?因为配置信息在整个应用生命周期里,只需要一份就够了。你new出来十个配置对象,内容都一样,浪费内存不说,还容易搞出数据不一致的问题。
来看代码:
public class ConfigManager {
// 私有静态实例,volatile保证多线程可见性
private static volatile ConfigManager instance;
// 配置存储
private Properties properties;
// 私有构造器,防止外部new
private ConfigManager() {
properties = new Properties();
loadConfig();
}
// 双重检查锁,保证线程安全
public static ConfigManager getInstance() {
if (instance == null) {
synchronized (ConfigManager.class) {
if (instance == null) {
instance = new ConfigManager();
}
}
}
return instance;
}
private void loadConfig() {
// 实际项目中,这里会从文件或数据库加载
properties.setProperty("app.name", "NotificationSystem");
properties.setProperty("app.version", "1.0.0");
properties.setProperty("notifier.email.enabled", "true");
properties.setProperty("notifier.sms.enabled", "false");
}
public String getConfig(String key) {
return properties.getProperty(key);
}
}
你可能会问:为什么不直接用枚举实现单例?嗯,枚举确实更简洁,而且天然防反射攻击。但有些场景下,比如需要继承父类或者延迟加载复杂配置,我还是倾向于用双重检查锁。说白了,没有银弹,看场景选。
二、工厂模式:灵活创建通知器
接下来是通知器的创建。我们的系统支持邮件、短信、站内信等多种通知方式。如果直接在业务代码里写if-else来判断创建哪种通知器,那以后每加一种新方式,就得改一堆代码。这违反了开闭原则——对扩展开放,对修改关闭。
工厂模式就是干这个的。它把“创建对象”这件事封装起来,调用方只需要告诉工厂“我要什么类型的”,工厂负责把对象造出来。
// 通知器接口
public interface Notifier {
void send(String message);
}
// 邮件通知器
public class EmailNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("发送邮件: " + message);
}
}
// 短信通知器
public class SmsNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("发送短信: " + message);
}
}
// 通知器工厂
public class NotifierFactory {
public static Notifier createNotifier(String type) {
// 从配置中心读取是否启用
ConfigManager config = ConfigManager.getInstance();
switch (type.toLowerCase()) {
case "email":
if ("true".equals(config.getConfig("notifier.email.enabled"))) {
return new EmailNotifier();
}
break;
case "sms":
if ("true".equals(config.getConfig("notifier.sms.enabled"))) {
return new SmsNotifier();
}
break;
default:
throw new IllegalArgumentException("未知的通知类型: " + type);
}
return null;
}
}
这里有个细节:工厂方法里调用了ConfigManager.getInstance()来检查配置。这意味着,如果配置里把短信通知关了,工厂根本不会创建SmsNotifier对象。配置和创建,在这里优雅地结合了。
我曾经在一个老项目里见过,通知器创建逻辑散落在十几个Service里,每个地方都写一遍if-else。后来要加一个微信通知,改了七八个文件,还漏了两个。从那以后,我坚持用工厂模式来管理对象的创建。你想想看,把变化收敛到一个地方,多省心。
三、观察者模式:状态变化实时通知
最后是观察者模式。这个模式解决的是“一对多”的依赖关系——当一个对象状态发生变化时,所有依赖它的对象都能收到通知。
在我们的项目里,场景是这样的:用户提交了一个订单,订单状态从“待支付”变成“已支付”。这个变化需要触发一系列动作:发送邮件通知、更新积分、记录日志等等。如果把这些逻辑都写在订单状态更新的代码里,那代码会变得又臭又长。
观察者模式的做法是:把“订单”作为被观察者(Subject),把“邮件通知器”、“积分服务”、“日志服务”作为观察者(Observer)。订单状态一变,自动通知所有观察者。
import java.util.ArrayList;
import java.util.List;
// 观察者接口
interface Observer {
void update(String eventType, String data);
}
// 被观察者(主题)
class OrderSubject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(String eventType, String data) {
for (Observer observer : observers) {
observer.update(eventType, data);
}
}
// 订单状态变更
public void changeStatus(String orderId, String newStatus) {
System.out.println("订单 " + orderId + " 状态变更为: " + newStatus);
notifyObservers("ORDER_STATUS_CHANGE", "订单:" + orderId + ", 新状态:" + newStatus);
}
}
// 具体观察者:邮件通知
class EmailObserver implements Observer {
@Override
public void update(String eventType, String data) {
if ("ORDER_STATUS_CHANGE".equals(eventType)) {
Notifier notifier = NotifierFactory.createNotifier("email");
if (notifier != null) {
notifier.send("【邮件通知】" + data);
}
}
}
}
// 具体观察者:积分服务
class PointsObserver implements Observer {
@Override
public void update(String eventType, String data) {
if ("ORDER_STATUS_CHANGE".equals(eventType)) {
System.out.println("【积分服务】更新用户积分,事件数据: " + data);
}
}
}
四、架构全景图:三个模式如何协作
好了,三个模式都讲完了。但它们是各自独立的吗?当然不是。在实际项目中,它们会紧密协作。我画了一张图,帮你理清它们之间的关系:
从这张图可以看得很清楚:
- ConfigManager(单例)在最左边,它负责提供全局配置。比如邮件通知是否开启、短信通知的API地址等。
- NotifierFactory(工厂)在中间,它从ConfigManager读取配置,然后按需创建具体的通知器。
- OrderSubject(观察者主题)在最右边,它维护了一个观察者列表。当订单状态变化时,遍历列表通知所有观察者。
- 观察者(如EmailObserver)在收到通知后,会调用NotifierFactory来创建通知器,然后执行具体的发送逻辑。
核心思想:三个模式各司其职,又互相配合。单例管配置,工厂管创建,观察者管通知。每个模式只解决一个问题,组合起来就形成了一个完整的架构闭环。
五、避坑指南与个人经验
讲到这里,我想分享几个我在实际项目中踩过的坑:
- 单例的线程安全问题:我曾经在一个高并发场景下,忘了给单例加volatile,结果两个线程同时拿到了不同的实例。排查了好久才发现是指令重排搞的鬼。所以,双重检查锁一定要加volatile,或者直接用静态内部类的方式实现。
- 工厂的扩展性:工厂模式虽然好,但如果通知器类型太多,switch-case会变得很长。我后来改用注册表的方式——每种通知器在启动时自己注册到工厂里,工厂根据类型从Map中查找。这样加新类型时,连工厂类都不用改。
- 观察者的内存泄漏:观察者模式里,如果观察者注册了但没有及时注销,会导致内存泄漏。尤其是在Android或Web前端场景下,Activity或页面销毁时一定要调用detach方法。我见过一个项目,因为忘了注销观察者,导致页面关闭后还在接收通知,引发了空指针异常。
嗯,这些坑说起来都是泪。但反过来想,踩过坑之后,对模式的理解反而更深了。你想想看,纸上得来终觉浅,绝知此事要躬行。模式本身不难,难的是在合适的场景用合适的模式,并且把细节处理好。
好了,这一章的内容就到这里。架构设计搭好了,下一章我们就可以开始写具体的业务代码了。到时候,你会看到这些模式是如何在真实代码中流畅运转的。