PHP 7.4 的协变与逆变使工厂接口实现更自然、类型更安全:协变允许子工厂方法返回更具体的子类类型,逆变允许子工厂方法参数更宽泛;二者仅适用于类/接口层级,需严格类型声明与自动加载支持。

PHP 7.4 的协变与逆变本身不直接“增强”工厂模式,但它们让工厂接口的实现更自然、类型更安全——前提是工厂方法的返回类型能随子类具体化而收窄(协变),参数类型能随子类抽象化而放宽(逆变)。
协变让 create() 返回值可以更具体
工厂接口通常定义一个泛型创建方法,比如返回基类;但实际子工厂应返回子类实例。PHP 7.4 之前,这种写法会报错:
interface Factory {
public function create(): Animal;
}
class CatFactory implements Factory {
public function create(): Cat { // ⚠️ Fatal error in PHP <p>PHP 7.4 起允许该写法,只要 <code>Cat</code> 是 <code>Animal</code> 的子类。这就是返回类型协变:父接口声明宽泛,子实现可返回更具体的类型。</p>
- 必须启用自动加载(如 Composer autoloader),否则单文件中类顺序依赖易触发“未定义类”错误
- 仅适用于对象类型,
int、string等标量类型不参与协变 - 若基类方法有返回类型声明(如
: Animal),子类方法必须声明兼容类型,不能是mixed或void
逆变让 buildFrom() 参数可以更宽泛
当工厂需要接收配置或上下文时,逆变允许子工厂接受比父接口更宽泛的参数类型。例如:
interface ConfigurableFactory {
public function buildFrom(Config $config): Animal;
}
class DogFactory implements ConfigurableFactory {
public function buildFrom(AdvancedConfig $config): Dog {
// $config 是 Config 的子类,但这里反而能接收更具体的类型?
// ❌ 不对 —— 逆变是“反着来”的:子类方法参数应比父类更宽(即父类能传的,子类也得能接)
// 正确示例:
}
}
真正体现逆变的是:父接口要求 Config,子类可声明接受 array|Config 或 stdClass(只要它能被安全地当作 Config 使用)。但注意:
- PHP 7.4 的参数逆变支持有限,仅在继承链中且类型存在明确父子关系时生效
- 常见误用:试图用
array替代Config—— 这不是逆变,是类型不兼容;逆变要求array必须能“安全替代”Config的所有使用场景,实际几乎不可行 - 更现实的逆变场景是:父类方法参数为
Traversable,子类可接受ArrayIterator或Generator(它们都实现Traversable)
工厂模式中协变/逆变的实际约束
别指望靠协变/逆变让工厂“自动适配任意子类型”。它们只是让类型系统不拦你,但不解决逻辑问题:
- 协变只作用于返回类型,不影响运行时行为;
CatFactory::create()返回Cat,调用方仍需按接口契约处理Animal,除非显式转型 - 逆变在 PHP 7.4 中无法用于标量或联合类型参数(如
string|int),仅支持类/接口层级的父子关系 - 若项目未启用
declare(strict_types=1),协变带来的类型安全会被弱类型转换削弱(例如return "cat"可能静默转成对象) - IDE 和静态分析工具(如 PHPStan)对协变/逆变的支持程度不一;部分工具可能仍报“返回类型不匹配”,需升级到较新版本
真正关键的不是“用了协变”,而是你是否把工厂的类型契约想清楚了:接口定义的是能力边界,子类实现的是具体路径。协变和逆变只是让这个边界在类型层面更贴合真实意图——但边界本身,还得你来划。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











