设计模式与用户系统:代理模式控制访问、观察者通知用户事件、策略模式选择认证方式
用户系统,说白了就是每个应用的「门面」和「管家」。门面管你进不进得来,管家管你进来后能看到什么、收到什么通知。我这些年做过不少用户系统,从简单的单点登录到复杂的多租户权限体系,踩过的坑真不少。今天咱们就聊聊怎么用三个经典模式——代理、观察者、策略——把用户系统做得既灵活又健壮。
一、代理模式:别让用户直接碰核心对象
先说说代理模式。你想想看,用户系统里最敏感的是什么?当然是用户数据和权限判断。如果每次请求都直接操作核心对象,那风险太大了。代理模式就是在这中间加一层「守门员」。
核心思想:代理对象控制对真实对象的访问,可以在调用前后附加额外逻辑。
我在项目中遇到过这样一个场景:一个文档管理系统,普通用户只能看文档摘要,VIP用户才能看全文。如果让前端来判断权限,那太容易绕过了。后端每个接口都写一遍权限判断?代码会变得又臭又长。
我的做法是:用代理模式包装文档访问对象。代理先检查当前用户的角色,再决定是否调用真实对象。
// Java 示例:文档访问代理
public interface Document {
String getContent();
}
public class RealDocument implements Document {
private String content;
public RealDocument(String content) {
this.content = content;
}
@Override
public String getContent() {
return content;
}
}
public class DocumentProxy implements Document {
private RealDocument realDocument;
private String userRole;
public DocumentProxy(RealDocument realDocument, String userRole) {
this.realDocument = realDocument;
this.userRole = userRole;
}
@Override
public String getContent() {
if ("VIP".equals(userRole)) {
return realDocument.getContent();
} else {
return "您无权查看完整内容";
}
}
}
我的习惯:代理模式特别适合做「懒加载」和「访问控制」。比如用户头像,不用每次登录都加载,代理可以在真正需要显示时才去拉取。
二、观察者模式:用户事件别靠轮询
用户系统里事件太多了:用户注册、密码修改、账号冻结、积分变动……如果每个模块都去轮询数据库,那系统性能就完蛋了。观察者模式就是解决这个问题的。
说白了就是:一个对象状态变了,自动通知所有关注它的对象。用户系统里,我经常用这个模式来做「事件通知总线」。
// C++ 示例:用户事件通知
#include <vector>
#include <functional>
#include <string>
class UserEventBus {
private:
std::vector<std::function<void(const std::string&)>> listeners;
public:
void addListener(std::function<void(const std::string&)> listener) {
listeners.push_back(listener);
}
void notify(const std::string& eventType) {
for (auto& listener : listeners) {
listener(eventType);
}
}
};
// 使用示例
int main() {
UserEventBus bus;
// 邮件服务订阅事件
bus.addListener([](const std::string& event) {
if (event == "register") {
std::cout << "发送欢迎邮件" << std::endl;
}
});
// 日志服务订阅事件
bus.addListener([](const std::string& event) {
std::cout << "记录日志: " << event << std::endl;
});
// 用户注册时触发
bus.notify("register");
return 0;
}
我曾经踩过的坑:观察者模式如果管理不当,容易造成内存泄漏。比如监听器注册了但忘记注销,对象就永远无法被回收。我的建议是:用弱引用或者显式的生命周期管理。
三、策略模式:认证方式别写死
现在的用户系统,认证方式五花八门:密码登录、短信验证码、微信扫码、指纹识别……如果每个认证方式都写一个if-else,那代码维护起来就是噩梦。
策略模式就是干这个的:把每个认证方式封装成独立的策略,运行时动态选择。
// Java 示例:认证策略
public interface AuthStrategy {
boolean authenticate(String credentials);
}
public class PasswordAuth implements AuthStrategy {
@Override
public boolean authenticate(String credentials) {
// 密码校验逻辑
System.out.println("使用密码认证");
return true;
}
}
public class SmsAuth implements AuthStrategy {
@Override
public boolean authenticate(String credentials) {
// 短信验证码校验
System.out.println("使用短信验证码认证");
return true;
}
}
public class AuthContext {
private AuthStrategy strategy;
public void setStrategy(AuthStrategy strategy) {
this.strategy = strategy;
}
public boolean executeAuth(String credentials) {
return strategy.authenticate(credentials);
}
}
我的经验:策略模式配合工厂模式使用效果更佳。工厂根据用户选择的认证方式,自动创建对应的策略对象。这样新增一种认证方式,只需要加一个策略类和一行工厂配置。
四、三个模式如何协同工作
光说单个模式还不够,咱们看看它们怎么在用户系统里配合使用。我画了一张图,帮你理清关系:
流程是这样的:用户请求先到代理层,代理检查你有没有权限。有权限了,进入策略层,根据你选择的认证方式进行验证。验证通过后,核心业务开始处理。处理完了,观察者模式自动通知各个模块——发邮件、记日志、更新缓存,一气呵成。
五、实际项目中的避坑指南
说了这么多,我再分享几个实际项目中容易踩的坑:
- 代理模式别滥用:不是所有对象都需要代理。我见过有人给每个DAO都加一层代理,结果性能下降30%。代理只用在真正需要控制访问的地方。
- 观察者注意异常处理:一个监听器抛异常,会不会影响其他监听器?我的做法是:每个监听器用try-catch包起来,保证互不影响。
- 策略模式别太细:策略粒度太细会导致类爆炸。比如每种认证方式一个策略没问题,但每种认证方式再分「测试环境」和「生产环境」两个策略,那就过度设计了。
总结一下:代理模式管「能不能进」,策略模式管「怎么进」,观察者模式管「进来后通知谁」。三个模式各司其职,用户系统就能做到高内聚低耦合。你想想看,如果不用这些模式,代码会变成什么样?一个类里塞满if-else和循环,改一个地方牵一发动全身。用了设计模式,每个模块各管各的事,改起来轻松多了。
嗯,今天就聊到这儿。这些模式说起来简单,真正用好还是需要多练。我建议你拿自己的项目试试,从最简单的代理模式开始,慢慢把观察者和策略也加进去。实践出真知嘛。