各位道友,贫道吕洞宾又来给大家讲Java设计模式啦!今天咱们不炼丹,改讲"接口变形术"、“代码翻译官”、“消息小灵通"和"多重分身术”!准备好你的Java飞剑,咱们开始吧!
🔄 改变接口:给代码"整容"的艺术
有时候啊,咱们的代码就像相亲一样——你看上的对象接口不对,怎么办?总不能重新投胎吧?这时候就需要"改变接口"的设计模式来帮忙了!
1. 适配器模式:代码界的"转接头"
适配器模式就像给你的旧手机配个Type-C转接头,让新充电器也能用!
特征:
- 将一个类的接口转换成客户端期望的另一个接口
- 让原本不兼容的类可以一起工作
- 就像翻译官,让说不同语言的对象能沟通
代码示例:
// 旧接口 - 就像老式圆孔耳机插口 interface OldPlayer { void playMp3(String fileName); } // 新接口 - 新式Type-C接口 interface NewPlayer { void playAudio(String file); } // 旧实现类 class OldMp3Player implements OldPlayer { public void playMp3(String fileName) { System.out.println("播放MP3: " + fileName); } } // 适配器 - 转接头来了! class PlayerAdapter implements NewPlayer { private OldPlayer oldPlayer; PlayerAdapter(OldPlayer oldPlayer) { this.oldPlayer = oldPlayer; } public void playAudio(String file) { // 把新接口调用转成旧接口调用 oldPlayer.playMp3(file); } } // 使用 NewPlayer player = new PlayerAdapter(new OldMp3Player()); player.playAudio("song.mp3"); // 成功播放!反例分析:如果不用适配器,代码会变成这样:
// 糟糕的写法 - 直接修改客户端代码 class Client { void playMusic(OldPlayer player) { // 需要知道旧接口的细节 if (player instanceof OldMp3Player) { ((OldMp3Player)player).playMp3("song.mp3"); } // 每新增一种播放器就要修改这里! } }问题:
- 客户端需要知道具体实现类的细节
- 违反开闭原则,每次新增类型都要修改客户端
- 代码耦合度高,难以维护
优点:
让不兼容的接口能够协同工作
提高代码复用性
符合开闭原则
缺点:
- 增加系统复杂度
- 过多使用会让系统变得混乱
2. 外观模式:代码界的"前台小姐姐"
外观模式就像公司前台,你不需要知道公司内部有多少部门,找前台小姐姐就行!
特征:
- 为复杂的子系统提供一个统一的接口
- 隐藏系统的复杂性
- 客户端只需要和外观类交互
代码示例:
// 复杂的子系统 - 就像公司各个部门 class CPU { void start() { System.out.println("CPU启动"); } void execute() { System.out.println("CPU执行指令"); } } class Memory { void load() { System.out.println("内存加载"); } } class HardDisk { void read() { System.out.println("硬盘读取"); } } // 外观类 - 公司前台 class ComputerFacade { private CPU cpu; private Memory memory; private HardDisk hardDisk; ComputerFacade() { this.cpu = new CPU(); this.memory = new Memory(); this.hardDisk = new HardDisk(); } // 统一接口 - 开机按钮 void startComputer() { System.out.println("=== 电脑开机 ==="); hardDisk.read(); memory.load(); cpu.start(); cpu.execute(); System.out.println("=== 开机完成 ==="); } } // 使用 ComputerFacade computer = new ComputerFacade(); computer.startComputer(); // 一键开机,多简单!反例分析:如果不用外观模式:
// 糟糕的写法 - 客户端需要了解所有细节 class Client { void startComputer() { CPU cpu = new CPU(); Memory memory = new Memory(); HardDisk hardDisk = new HardDisk(); // 需要知道正确的启动顺序 hardDisk.read(); memory.load(); cpu.start(); cpu.execute(); // 如果顺序错了,电脑就开不了机! } }问题:
- 客户端需要了解子系统所有细节
- 客户端与子系统高度耦合
- 修改子系统会影响所有客户端
优点:
- 简化客户端使用
- 降低系统耦合度
- 提高子系统独立性
缺点:
- 不符合开闭原则(新增功能可能需要修改外观)
- 可能变成"上帝类"(什么都管)
🧠 解释器模式:代码界的"翻译官"
解释器模式就像给你配了个随身翻译,你说"我要喝咖啡",翻译官帮你转换成机器能懂的指令!
特征:
- 定义语言的文法规则
- 构建解释器来解释语言中的句子
- 适用于需要解释执行的语言
代码示例:
// 抽象表达式 interface Expression { int interpret(Context context); } // 终结符表达式 - 数字 class NumberExpression implements Expression { private int number; NumberExpression(int number) { this.number = number; } public int interpret(Context context) { return number; } } // 非终结符表达式 - 加法 class AddExpression implements Expression { private Expression left; private Expression right; AddExpression(Expression left, Expression right) { this.left = left; this.right = right; } public int interpret(Context context) { return left.interpret(context) + right.interpret(context); } } // 上下文 class Context { // 可以存储变量等信息 } // 使用 Expression expression = new AddExpression( new NumberExpression(10), new AddExpression( new NumberExpression(5), new NumberExpression(3) ) ); int result = expression.interpret(new Context()); System.out.println("10 + (5 + 3) = " + result); // 输出: 18反例分析:如果不用解释器模式:
// 糟糕的写法 - 硬编码解析逻辑 class Calculator { int calculate(String expression) { // 需要自己解析字符串 if (expression.contains("+")) { String[] parts = expression.split("\\+"); // 还要处理嵌套括号、优先级等 // 代码会非常复杂! } return 0; } }问题:
- 解析逻辑与业务逻辑混在一起
- 难以扩展新的语法规则
- 代码复杂,难以维护
优点:
- 容易扩展新的语法规则
- 实现简单语法很容易
- 符合单一职责原则
缺点:
- 复杂文法难以维护
- 执行效率较低
- 类数量会很多
📞 回调模式:代码界的"消息小灵通"
回调模式就像你给朋友留个电话:"有事打我电话!"朋友有事就call你。
观察者模式:订阅-发布系统
观察者模式就像微信公众号,你关注了,公众号一更新你就收到推送!
特征:
- 定义对象间的一对多依赖关系
- 当一个对象状态改变时,所有依赖它的对象都得到通知
- 也叫发布-订阅模式
代码示例:
// 观察者接口 interface Observer { void update(String message); } // 主题接口 interface Subject { void attach(Observer observer); void detach(Observer observer); void notifyObservers(); } // 具体主题 - 微信公众号 class WeChatPublicAccount implements Subject { private List<Observer> observers = new ArrayList<>(); private String latestArticle; public void attach(Observer observer) { observers.add(observer); } public void detach(Observer observer) { observers.remove(observer); } public void notifyObservers() { for (Observer observer : observers) { observer.update(latestArticle); } } // 发布新文章 void publishArticle(String article) { this.latestArticle = article; System.out.println("公众号发布了新文章: " + article); notifyObservers(); } } // 具体观察者 - 用户 class User implements Observer { private String name; User(String name) { this.name = name; } public void update(String message) { System.out.println(name + " 收到推送: " + message); } } // 使用 WeChatPublicAccount account = new WeChatPublicAccount(); User user1 = new User("张三"); User user2 = new User("李四"); account.attach(user1); account.attach(user2); account.publishArticle("Java设计模式详解"); // 输出: // 公众号发布了新文章: Java设计模式详解 // 张三 收到推送: Java设计模式详解 // 李四 收到推送: Java设计模式详解反例分析:如果不用观察者模式:
// 糟糕的写法 - 主题直接调用所有观察者 class BadPublicAccount { private List<User> users = new ArrayList<>(); void addUser(User user) { users.add(user); } void publishArticle(String article) { System.out.println("发布文章: " + article); // 需要知道User的具体实现 for (User user : users) { user.receiveArticle(article); // 方法名硬编码! } } } class User { void receiveArticle(String article) { // 只能接收文章,不能接收其他类型消息 } }问题:
- 主题与观察者紧耦合
- 难以扩展新的观察者类型
- 违反开闭原则
优点:
- 降低耦合度
- 支持广播通信
- 符合开闭原则
缺点:
- 观察者过多时通知开销大
- 可能引起循环引用
🎭 多路分发:代码界的"多重分身术"
多路分发就像你同时跟好几个人聊天,根据对方是谁和聊什么内容,给出不同的回复。
特征:
根据多个对象的实际类型决定调用哪个方法
Java本身只支持单分发(根据接收者类型)
通过设计模式实现多分发
代码示例:
// 游戏角色接口 interface Character { String fight(Character opponent); String fightAgainst(Knight knight); String fightAgainst(Wizard wizard); String fightAgainst(Dragon dragon); } // 具体角色 class Knight implements Character { public String fight(Character opponent) { return opponent.fightAgainst(this); } public String fightAgainst(Knight knight) { return "骑士 vs 骑士: 决斗开始!"; } public String fightAgainst(Wizard wizard) { return "骑士 vs 法师: 冲锋!"; } public String fightAgainst(Dragon dragon) { return "骑士 vs 龙: 屠龙勇士!"; } } class Wizard implements Character { public String fight(Character opponent) { return opponent.fightAgainst(this); } public String fightAgainst(Knight knight) { return "法师 vs 骑士: 火球术!"; } public String fightAgainst(Wizard wizard) { return "法师 vs 法师: 魔法对决!"; } public String fightAgainst(Dragon dragon) { return "法师 vs 龙: 召唤元素!"; } } class Dragon implements Character { public String fight(Character opponent) { return opponent.fightAgainst(this); } public String fightAgainst(Knight knight) { return "龙 vs 骑士: 喷火!"; } public String fightAgainst(Wizard wizard) { return "龙 vs 法师: 魔法抗性!"; } public String fightAgainst(Dragon dragon) { return "龙 vs 龙: 同类不相残"; } } // 使用 Character knight = new Knight(); Character wizard = new Wizard(); Character dragon = new Dragon(); System.out.println(knight.fight(wizard)); // 骑士 vs 法师 System.out.println(wizard.fight(dragon)); // 法师 vs 龙 System.out.println(dragon.fight(knight)); // 龙 vs 骑士反例分析:如果不用多路分发:
// 糟糕的写法 - 使用大量if-else class BadCharacter { String fight(BadCharacter opponent) { if (this instanceof Knight) { if (opponent instanceof Knight) { return "骑士 vs 骑士"; } else if (opponent instanceof Wizard) { return "骑士 vs 法师"; } // 每新增一个角色类型,这里就要加一个if! } else if (this instanceof Wizard) { // 同样需要很多if判断 } return "未知战斗"; } }问题:
- 代码臃肿,大量if-else语句
- 违反开闭原则
- 难以维护和扩展
优点:
- 消除条件判断
- 易于扩展新类型
- 代码更优雅
缺点:
- 类数量增多
- 理解成本较高
📊 模式对比总结
| 模式 | 适用场景 | 核心思想 | 优点 | 缺点 |
|---|---|---|---|---|
| 适配器模式 | 接口不兼容需要协作 | 转换接口 | 复用旧代码,解耦 | 增加复杂度 |
| 外观模式 | 简化复杂子系统 | 统一接口 | 简化使用,降低耦合 | 可能违反开闭原则 |
| 解释器模式 | 需要解释特定语言 | 解释执行 | 易于扩展语法 | 效率低,复杂文法难维护 |
| 观察者模式 | 一对多依赖关系 | 发布-订阅 | 解耦,支持广播 | 可能引起循环引用 |
| 多路分发 | 多类型交互 | 双重虚拟调用 | 消除条件判断 | 类数量多 |
💡 实战应用建议
- 何时用适配器:
- 想使用现有类但接口不匹配时
- 系统需要复用一些类,但这些类接口不符合系统需求
- 想创建一个可以复用的类,与不相关类协同工作
- 何时用外观:
- 为复杂子系统提供简单接口
- 客户端与多个子系统耦合严重时
- 需要分层构建系统时
- 何时用解释器:
- 需要解释简单语言时
- 文法规则相对固定时
- 效率要求不高时
- 何时用观察者:
- 一个对象状态改变需要通知其他对象
- 需要建立触发链时
- 广播通知场景
- 何时用多路分发:
- 多个对象类型需要交互
- 想避免大量if-else判断
- 类型组合爆炸时
🎯 组合使用案例
// 组合使用多个模式 // 外观 + 观察者 + 适配器 // 旧系统接口 interface OldNotificationSystem { void sendSMS(String message); } // 新系统接口 interface NewNotificationSystem { void notify(String message); } // 适配器 class NotificationAdapter implements NewNotificationSystem { private OldNotificationSystem oldSystem; NotificationAdapter(OldNotificationSystem oldSystem) { this.oldSystem = oldSystem; } public void notify(String message) { oldSystem.sendSMS(message); } } // 观察者 interface Subscriber { void receive(String message); } // 外观 - 统一的通知中心 class NotificationCenter { private List<Subscriber> subscribers = new ArrayList<>(); private NewNotificationSystem notificationSystem; NotificationCenter(NewNotificationSystem system) { this.notificationSystem = system; } void subscribe(Subscriber subscriber) { subscribers.add(subscriber); } void broadcast(String message) { // 使用适配后的系统 notificationSystem.notify(message); // 通知所有观察者 for (Subscriber subscriber : subscribers) { subscriber.receive(message); } } }记住,设计模式就像武功招式,用对了威力无穷,用错了就是花拳绣腿。关键是要理解每个模式的适用场景,不要为了用模式而用模式!
好了,今天的Java设计模式课就到这里。贫道要去修炼(写代码)了,下次再见!记得多练习,代码就像内功,越练越深厚!🚀