组合模式中add方法应使用consumer语义,声明为public void add(component child),因添加是写入操作;若需泛型支持可定义 void add(t child),而? extends component不适用于add参数。

在组合模式中,当 Composite 节点需要添加子节点时,若希望接口既安全又灵活,PECS 原则能直接指导泛型参数的设计——关键不在于“能传什么类型”,而在于“这个添加操作的语义是写入(消费者)还是读取(生产者)”。
add 方法本质是“写入操作”,应使用 ? super Component
Composite.add() 的职责是接收一个子组件并存入内部列表,属于典型的**消费者行为**。此时应声明为:
-
public void add(Component child)—— 最基础、最常见,适用于明确知道子类型是Component及其子类的场景 - 若想支持更宽泛的输入(比如允许传入
Leaf或Composite的任意具体子类实例),可升级为泛型方法:<t extends component> void add(T child)</t>—— 利用类型推导保留具体子类型信息,但调用方仍需传入Component子类实例 - 若设计的是通用工具方法(如批量添加),且目标容器本身是泛型集合,例如:
public static <t extends component> void addAll(Composite target, Collection extends T> children)</t>,则源集合用? extends T(生产者),目标用具体Composite(已知可接受T)
为什么不用 ? extends Component 做参数
? extends Component 表示“某个未知的 Component 子类型”,例如可能是 ? extends Leaf 或 ? extends Composite。这种通配符只适合读取——你可以安全地把它当作 Component 来用,但不能往里 add 任何具体对象(除了 null)。因为编译器无法确认你加进去的 Leaf 是否与实际的未知子类型兼容。所以把它用在 add 参数上,会导致编译失败:
-
void add(? extends Component c)❌ 语法非法,Java 不允许通配符出现在形参位置(除非是泛型方法约束) - 即使绕过语法限制,逻辑上也不成立:添加动作需要确定“能接收什么”,而非“产出什么”
Composite 内部 children 列表的泛型应为 List
虽然 Composite 的 children 字段常声明为 List<component></component>,但这不是 PECS 的应用点,而是类型建模的自然选择:
- 它要存储所有合法子节点:叶子(
Leaf)、其他组合(Composite),它们都继承自Component - 这个字段本身不对外暴露读写接口,只是内部容器;对外暴露的是
add(Component)和getChild(int)等方法 -
getChild(int)返回Component,符合“只读”语义,此时若对外提供getChildren()方法返回集合,才需考虑 PECS:
→ 若只读,应返回Collection extends Component>;
→ 若允许外部修改(不推荐),才返回Collection<component></component>
实际工程中的典型写法
多数成熟框架和业务代码采用清晰、直接的设计:
-
public void add(Component child)—— 明确表达“我接受任意组件” -
public void remove(Component child)—— 同理,移除也基于统一抽象 -
public Component getChild(int index)—— 返回抽象类型,调用方可按需向下转型(或通过访问者模式避免转型) - 若需类型安全的批量操作,单独提供泛型工具方法,而非污染主接口
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











