接口是系统间通信的“语言协议”,只声明方法签名以实现解耦;抽象类因含状态和默认行为易导致紧耦合;接口支持多实现、便于di装配与单元测试;命名应聚焦契约而非实现;结构型模式依赖统一接口实现动态替换。

Interface 不是模板,是系统间通话的“语言协议”
接口不是为了写代码时少敲几行,而是为了让两个模块能“听懂彼此在说什么”。比如 PaymentGateway 接口里只有一句 charge(float $amount): bool,它不关心你是走支付宝、银联还是模拟测试环境——只要返回 true 或 false,上层业务逻辑就继续跑。这种“只约定义、不约实现”的约束,才是解耦的起点。
为什么不能用 abstract class 替代 interface 做契约?
抽象类容易悄悄塞进状态或默认行为,比如一个 BaseLogger 里带了 protected $logLevel 字段和 formatMessage() 默认实现,后续所有子类就被这个字段和格式逻辑绑死了。而接口强制你只声明方法签名:Log(string $message),连参数名都不能含糊。这带来三个实际好处:
- 多个类可以同时实现同一接口(Java/C# 支持多实现,C++/Go 用组合或 embed 模拟)
- Spring 等容器能安全地自动装配
ILogger实现,不会因父类字段冲突失败 - 单元测试时可直接 new 一个
MockLogger,不用绕过抽象类的构造逻辑
接口命名暴露的是“谁要什么”,不是“谁怎么干”
看一个反例:FileBasedUserRepository 这种名字已经泄露了实现细节,一旦要切到 Redis,你就得改一堆引用;而换成 UserRepository 接口,调用方只依赖这个契约,save(User $user) 和 findById(int $id): ?User 的语义不变,背后换数据库、换序列化方式都不影响上层。
真正难的不是定义接口,而是守住它的边界:一旦发现某个实现类需要加新方法才能工作,别急着往接口里塞——先问一句:“这是所有实现都必须提供的能力吗?” 如果只是某一个实现的特需逻辑,说明它不该属于这个契约,该拆新接口或用策略模式隔离。
结构型模式依赖接口,是因为它们靠“换人不换岗”运转
装饰器、适配器、代理这些模式,核心动作都是“把一个对象包一层,再交给原来的地方用”。它们能成立的前提,是被包装对象和包装对象实现了同一个接口。比如 CompressionDecorator 必须实现 TextProcessor,否则调用方的 $processor->process($text) 就会报错。
这里没有魔法:客户端代码只认接口类型,不认具体类名。所以当你要动态加日志、加重试、加缓存时,不是去改原始类,而是提供一个新的实现类,让它也实现那个接口——然后通过 DI 容器或工厂注入进去。变化被锁死在“实现替换”这一步,而不是散落在各处的 if-else 或 instanceof 判断里。
接口最难守的,是它一旦发布,就成了一条双向约束:既约束实现者不能偷懒少写方法,也约束调用者不能越界索要实现细节。很多耦合问题,其实始于第一次为“方便”而在接口里加了一个本不该共有的字段或默认方法。










