历史文章参见
今天讲讲面向接口编程的核心思想,它可以看到各种设计模式的一种杂糅。
面向接口编程的核心思想
以实际的代码举例子,我最近在写一个安卓的笔记程序,使用到了面向接口的编程方法,下面我以具体的类举例来说明面向接口编程的思想,以及后文解释,面向接口编程可以体现哪些设计模式。
一、依赖接口,而不是具体实现
1// ❌ 面向具体类(耦合) 2public class NoteListManager { 3 private MainActivity activity; // 直接依赖 MainActivity 4} 5 6// ✅ 面向接口(解耦) 7public class NoteListManager { 8 private INoteListCallback callback; // 依赖接口 9 // 优势:只要接口不变,实现类怎么变都行 10} 11
二、在这个场景中的体现
1. 定义接口(契约)
1// INoteListCallback.java - 定义"契约" 2public interface INoteListCallback { 3 // 定义需要什么方法,不关心怎么实现 4 String formatTimestamp(long timestamp); 5 int dpToPx(int dp); 6 NoteDbHelper getDbHelper(); 7} 8
2. 实现接口(具体实现)
1// MainActivity.java - 实现接口 2public class MainActivity implements INoteListCallback { 3 // 提供具体实现 4 @Override 5 public String formatTimestamp(long timestamp) { 6 // 具体怎么格式化,MainActivity 自己决定 7 } 8} 9
3. 使用接口(依赖抽象)
1// NoteListManager.java - 只依赖接口 2public class NoteListManager { 3 private INoteListCallback callback; // 只依赖接口,不依赖具体类 4 5 public void displayNote(Note note) { 6 // 通过接口调用,不知道具体是哪个类实现的 7 String time = callback.formatTimestamp(note.getTimestamp()); 8 } 9} 10
三、面向接口编程的优势
1. 解耦
1// NoteListManager 不知道 MainActivity 的存在 2// 它只知道有一个对象实现了 INoteListCallback 接口 3// 可以是 MainActivity,也可以是 TestActivity,也可以是 MockActivity 4
2. 可测试性
1// 测试时,可以用 Mock 实现 2class MockCallback implements INoteListCallback { 3 @Override 4 public String formatTimestamp(long timestamp) { 5 return "Mock时间"; // 测试用的简单实现 6 } 7} 8 9// 测试 NoteListManager 10NoteListManager manager = new NoteListManager(); 11manager.initialize(listView, new MockCallback()); // 用 Mock 测试 12
3. 可扩展性
1// 以后可以有不同的实现 2class AnotherActivity implements INoteListCallback { 3 @Override 4 public String formatTimestamp(long timestamp) { 5 return "另一种格式"; // 不同的实现方式 6 } 7} 8 9// NoteListManager 不需要改,只需要传入不同的实现 10manager.initialize(listView, new AnotherActivity()); 11
四、设计原则体现
1. 依赖倒置原则(DIP)
1高层模块(NoteListManager)不应该依赖低层模块(MainActivity) 2两者都应该依赖抽象(INoteListCallback) 3
2. 开闭原则(OCP)
1对扩展开放:可以添加新的实现类 2对修改关闭:NoteListManager 不需要修改 3
五、优点总结
1面向接口编程 = 定义接口(契约) 2 = 实现接口(具体实现) 3 = 使用接口(依赖抽象) 4 = 解耦、可测试、可扩展 5
Callback 就是面向接口编程思想的实际体现。下面我们看看哪些具体的设计模式都跟面向接口编程有关。
与面向接口编程有关的设计模式
常见的一种是策略模式。当然,面向接口编程不仅体现在策略模式中,还贯穿于多种行为型和结构型模式。
一、在行为型模式中的体现
1. 观察者模式(Observer) ⭐️ 高度体现
1// 定义接口 2interface Observer { 3 void update(String message); 4} 5 6interface Subject { 7 void registerObserver(Observer o); 8 void removeObserver(Observer o); 9 void notifyObservers(); 10} 11 12// 实现接口 13class User implements Observer { 14 @Override 15 public void update(String message) { 16 System.out.println("收到消息: " + message); 17 } 18} 19 20// 使用接口 21NewsPublisher publisher = new NewsPublisher(); 22publisher.registerObserver(new User()); // 依赖接口,不依赖具体类 23
接口思维:Observer是观察者的抽象,Subject是主题的抽象。双方通过接口通信。
2. 命令模式(Command) ⭐️ 核心就是接口
1// 定义命令接口 2interface Command { 3 void execute(); 4 void undo(); 5} 6 7// 不同实现 8class SaveCommand implements Command { 9 @Override 10 public void execute() { 11 // 保存逻辑 12 } 13} 14 15class DeleteCommand implements Command { 16 @Override 17 public void execute() { 18 // 删除逻辑 19 } 20} 21 22// 使用命令 23Button saveBtn = new Button(new SaveCommand()); // 传入接口 24
接口思维:将请求封装为对象,所有命令都实现Command接口,调用者只依赖接口。
3. 状态模式(State)
1interface State { 2 void handle(Context context); 3} 4 5class ConcreteStateA implements State { 6 @Override 7 public void handle(Context context) { 8 context.setState(new ConcreteStateB()); // 切换到另一个状态 9 } 10} 11
接口思维:状态抽象为接口,上下文对象依赖State接口而不是具体状态。
4. 模板方法模式(Template Method)
1abstract class DataProcessor { 2 // 模板方法 - 定义算法骨架 3 public final void process() { 4 readData(); // 固定步骤 5 transformData(); // 抽象方法,子类实现 6 saveData(); // 固定步骤 7 } 8 9 // 抽象方法 - 通过实现来变化 10 protected abstract void transformData(); 11} 12
接口思维:虽然用抽象类,但思想一致 - 定义算法框架,具体步骤由子类实现。
二、在结构型模式中的体现
1. 适配器模式(Adapter) - 核心是统一接口
1// 目标接口(期望的接口) 2interface MediaPlayer { 3 void play(String audioType, String fileName); 4} 5 6// 已有的类,接口不兼容 7class Mp4Player { 8 public void playMp4(String fileName) { /*...*/ } 9} 10 11// 适配器 - 实现目标接口,包装已有类 12class Mp4Adapter implements MediaPlayer { 13 private Mp4Player mp4Player; 14 15 @Override 16 public void play(String audioType, String fileName) { 17 if (audioType.equals("mp4")) { 18 mp4Player.playMp4(fileName); // 适配 19 } 20 } 21} 22
接口思维:通过适配器统一不同系统的接口,客户端只依赖MediaPlayer接口。
2. 桥接模式(Bridge) - 抽象与实现分离
1// 抽象部分 2abstract class Shape { 3 protected Color color; // 桥接 - 组合Color接口 4 5 public Shape(Color color) { 6 this.color = color; 7 } 8 9 abstract void draw(); 10} 11 12// 实现部分接口 13interface Color { 14 String fill(); 15} 16 17// 具体实现 18class Red implements Color { 19 @Override 20 public String fill() { 21 return "红色"; 22 } 23} 24 25// 使用 26Shape circle = new Circle(new Red()); // 通过接口组合 27
接口思维:将抽象(Shape)与实现(Color)解耦,通过接口组合。
3. 代理模式(Proxy)
1interface Image { 2 void display(); 3} 4 5class RealImage implements Image { 6 @Override 7 public void display() { 8 // 实际加载图片 9 } 10} 11 12class ProxyImage implements Image { // 代理类也实现相同接口 13 private RealImage realImage; 14 15 @Override 16 public void display() { 17 if (realImage == null) { 18 realImage = new RealImage(); // 延迟加载 19 } 20 realImage.display(); 21 } 22} 23
接口思维:代理类和真实类实现相同的接口,客户端无感知。
三、在创建型模式中的体现
1. 工厂方法模式(Factory Method)
1interface Product { 2 void use(); 3} 4 5interface Creator { 6 Product createProduct(); // 工厂方法 - 返回接口 7} 8 9class ConcreteCreator implements Creator { 10 @Override 11 public Product createProduct() { 12 return new ConcreteProduct(); // 返回具体产品,但声明为Product接口 13 } 14} 15
接口思维:工厂返回接口类型,客户端不依赖具体产品类。
2. 抽象工厂模式(Abstract Factory)
1interface GUIFactory { 2 Button createButton(); 3 Checkbox createCheckbox(); 4} 5 6interface Button { 7 void paint(); 8} 9 10// 客户端代码 11class Application { 12 private Button button; 13 14 public Application(GUIFactory factory) { // 依赖工厂接口 15 button = factory.createButton(); 16 } 17} 18
接口思维:整套产品族通过接口定义,具体工厂实现接口。
四、面向接口编程的层次
第一层:技术实现
1// 简单的接口定义与实现 2interface A { void doSomething(); } 3class B implements A { ... } 4
第二层:设计模式
1// 模式级别的接口应用 2// 1. 策略模式:定义算法族,使其可以互换 3// 2. 观察者模式:定义发布-订阅的通信机制 4// 3. 命令模式:将请求封装为对象 5// 4. 状态模式:将状态抽象为接口 6
第三层:架构思想
1// 系统架构层面的接口 2// - 依赖倒置:高层模块不依赖低层模块,都依赖抽象 3// - 接口隔离:多个专用接口优于一个通用接口 4// - 六边形架构:通过端口(接口)与外部世界通信 5
五、实际项目中的应用启示
何时选择哪种模式?
| 场景 | 适合的模式 | 接口的作用 |
|---|---|---|
| 算法可互换 | 策略模式 | 定义算法接口 |
| 对象状态变化 | 状态模式 | 定义状态接口 |
| 请求需要封装 | 命令模式 | 定义命令接口 |
| 解耦通知机制 | 观察者模式 | 定义观察者接口 |
| 统一不同接口 | 适配器模式 | 定义目标接口 |
| 延迟/控制访问 | 代理模式 | 定义主体接口 |
一个综合示例:电商订单系统
1// 策略模式:不同的折扣策略 2interface DiscountStrategy { 3 double calculateDiscount(Order order); 4} 5 6// 状态模式:订单状态 7interface OrderState { 8 void next(Order order); 9 void previous(Order order); 10 void process(Order order); 11} 12 13// 观察者模式:订单状态通知 14interface OrderObserver { 15 void update(Order order); 16} 17 18// 命令模式:订单操作 19interface OrderCommand { 20 void execute(); 21 void undo(); 22} 23
总结
面向接口编程是贯穿所有设计模式的灵魂思想:
- 策略模式:最直接的体现,但远不止于此
- 行为型模式中大部分都基于接口:
- 观察者、命令、状态、策略等核心都是接口抽象
- 结构型模式通过接口实现结构解耦:
- 适配器统一接口,桥接分离抽象与实现
- 创建型模式通过接口隐藏创建细节
核心思想:面向接口编程不仅仅是写一个interface,而是:
- 依赖抽象,而不是具体
- 定义契约,而不是实现
- 关注能做什么,而不是怎么做
这在GoF的23种设计模式中都有深刻体现,是面向对象设计的精髓所在。
《设计模式和设计原则-中高级架构思路-面向接口编程》 是转载文章,点击查看原文。