- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
导读
本文以 java-design-patterns 仓库中 localization/es/property/README.md(Property 模式的西班牙语文档)为核心骨架,结合仓库内 property 模块的完整源码与单元测试,系统讲解 Property 模式的意图、原型继承的实现原理、可运行的代码示例、适用场景与权衡取舍。读完本文,你将掌握如何在不改动类结构的前提下,通过原型链动态地为对象添加、移除和覆盖属性,并能在游戏角色、配置系统、用户画像等场景中直接落地该模式。
一、模式意图(Propósito)
Property 模式的核心目标是:利用已有对象作为父对象(原型),创建新的对象并构成对象层级。它允许为一个对象定义和管理一组动态属性,在运行时灵活地增加、删除或修改属性,而无需重新定义核心类的结构。
用一句话概括(与文档 "En palabras simples" 对应):
定义并管理对象的一组动态属性,在不改变其结构的前提下实现个性化定制。
二、现实世界类比
文档给出了两个贴近生活的例子:
- 奇幻大陆 "Elandria":冒险者通过收集古代遗物(Reliquias)来定制自己的技能。每件遗物代表一项独一无二的属性或能力,随着冒险者探索、发现并融合新遗物,他们的能力会基于所拥有的遗物动态增强。
- 智能手机定制软件:设计师可以从处理器型号、摄像头规格、电池容量等组件中自由选择,每个组件都是智能手机的一项属性。当技术演进、新组件出现时,设计师可以无缝地添加或替换属性,拼出独一无二的配置,而无需重写核心设计结构。
这两个例子共同揭示出 Property 模式的本质:把"对象拥有什么"从类的静态定义中解放出来,交给运行时动态决定。
三、可运行的程序化示例
文档提供了基于游戏角色的完整示例。在仓库中,该示例的完整实现位于 property/src/main/java/com/iluwatar/property,共四个核心文件:
| 文件 | 作用 |
|---|---|
| Stats.java | 定义角色可拥有的全部属性枚举 |
| Character.java | 核心角色类,实现 Prototype 接口 |
| Prototype.java | 原型继承的统一操作接口 |
| App.java | 演示入口 main 方法 |
3.1 属性与角色类型枚举
Stats.java 定义了角色可能拥有的全部属性;Character.java 内部则嵌套定义了角色类型枚举:
// 角色可拥有的属性:敏捷、力量、攻击力、护甲、智力、精神、能量、怒气 public enum Stats { AGILITY, STRENGTH, ATTACK_POWER, ARMOR, INTELLECT, SPIRIT, ENERGY, RAGE } // Character 类内部嵌套的角色类型 public enum Type { WARRIOR, MAGE, ROGUE }3.2 Prototype 接口:属性操作的统一契约
Prototype.java 声明了四个核心方法,构成原型继承的操作契约:
public interface Prototype { Integer get(Stats stat); // 获取指定属性值(可能从原型链上继承而来) boolean has(Stats stat); // 判断是否拥有该属性 void set(Stats stat, Integer val); // 设置/覆盖属性 void remove(Stats stat); // 移除属性 }3.3 Character 实现:委托式原型链
Character.java 是模式的落地实现,其关键设计在于用委托(delegation)构成原型链:
- 每个
Character持有一个Prototype prototype引用(父原型)和一个本地的Map<Stats, Integer> properties存储; get方法采用"本地优先、向上委托"策略:本地 Map 命中则直接返回,否则递归委托给父原型prototype.get(stat):
@Override public Integer get(Stats stat) { var containsValue = properties.containsKey(stat); if (containsValue) { return properties.get(stat); } else { return prototype.get(stat); // 沿原型链向上查找 } } @Override public boolean has(Stats stat) { return get(stat) != null; // 只要链上某处有值即视为拥有 } @Override public void set(Stats stat, Integer val) { properties.put(stat, val); // 本地覆盖,不影响父原型 } @Override public void remove(Stats stat) { properties.put(stat, null); // 置空以屏蔽原型链上的继承值 }三个构造方法分别对应三种创建场景:
Character():创建"根原型",其父原型是一个空值对象(Null-value object)——匿名实现Prototype,get返回null、has返回false、set/remove空操作,作为原型链的终止端点(见 Character.java);Character(Type type, Prototype prototype):基于已有原型创建新原型,只设置类型并持有父原型引用;Character(String name, Character prototype):基于原型创建具名角色,同时继承原型的type并挂接原型链。
3.4 App 演示:三层原型链构建
App.java 完整展示了"基础原型 → 职业原型 → 具体角色"的三层结构:
// 第一层:通用基础原型,设置全角色共有的默认属性 var charProto = new Character(); charProto.set(Stats.STRENGTH, 10); charProto.set(Stats.AGILITY, 10); charProto.set(Stats.ARMOR, 10); charProto.set(Stats.ATTACK_POWER, 10); // 第二层:基于基础原型派生职业原型,并添加职业专属属性 var mageProto = new Character(Type.MAGE, charProto); mageProto.set(Stats.INTELLECT, 15); mageProto.set(Stats.SPIRIT, 10); var warProto = new Character(Type.WARRIOR, charProto); warProto.set(Stats.RAGE, 15); warProto.set(Stats.ARMOR, 15); // 覆盖默认护甲 var rogueProto = new Character(Type.ROGUE, charProto); rogueProto.set(Stats.ENERGY, 15); rogueProto.set(Stats.AGILITY, 15); // 覆盖默认敏捷 // 第三层:基于职业原型创建具体玩家角色,可再局部微调 var mag = new Character("Player_1", mageProto); mag.set(Stats.ARMOR, 8); // 玩家级覆盖 var warrior = new Character("Player_2", warProto); var rogue = new Character("Player_3", rogueProto); var rogueDouble = new Character("Player_4", rogue); rogueDouble.set(Stats.ATTACK_POWER, 12); // 继承自 rogue 再微调运行App.main后(日志输出由 Lombok 的@Slf4j提供),控制台输出如下(文档记录的真实运行结果):
Player: Player_1 Character type: MAGE Stats: - AGILITY:10 - STRENGTH:10 - ATTACK_POWER:10 - ARMOR:8 - INTELLECT:15 - SPIRIT:10 Player: Player_2 Character type: WARRIOR Stats: - AGILITY:10 - STRENGTH:10 - ATTACK_POWER:10 - ARMOR:15 - RAGE:15 Player: Player_3 Character type: ROGUE Stats: - AGILITY:15 - STRENGTH:10 - ATTACK_POWER:10 - ARMOR:10 - ENERGY:15 Player: Player_4 Character type: ROGUE Stats: - AGILITY:15 - STRENGTH:10 - ATTACK_POWER:12 - ARMOR:10 - ENERGY:15注意Player_4的示范意义:它直接以rogue(Player_3 的原型)为父原型创建,仅覆盖ATTACK_POWER,其余属性(含AGILITY:15、ENERGY:15)全部沿原型链继承而来——这正是"对象继承对象"的典型体现。
格式化输出由 Character.java 的toString()完成:遍历全部Stats,对链上解析为null的属性跳过,从而只展示有效属性。
四、类图与运行时序
4.1 类图
文档给出了 Property 模式的 UML 类图,完整刻画了Prototype接口、Character实现类以及Stats/Type枚举之间的关联关系:
4.2 时序图
结合仓库英文版 property/README.md 中的时序图,可以直观看到原型继承的运行流程:客户端基于原型创建新角色 → 原型把基础属性复制给新角色 → 客户端为新角色设置专属属性 → 新角色将自定义属性写入本地存储并覆盖继承值:
五、测试用例佐证
仓库为 Property 模式提供了两组单元测试,验证了模式的行为契约:
- CharacterTest.java:验证原型链的完整行为。
testPrototypeStats逐一验证每个属性在set后has为真、get返回期望值,remove后恢复为false/null;testCharacterStats验证基于原型创建的子角色能正确继承全部属性;testToString与testName/testType则验证了格式化输出、具名角色与类型继承的边界行为。 - AppTest.java:直接调用
App.main,断言整个演示流程不抛异常。
这些测试从侧面印证了文档中get/has/set/remove四方法的语义约定,可作为实现正确性的判据。
六、适用场景(Aplicabilidad)
根据文档与源码结构,当出现以下需求时适合采用 Property 模式:
- 需要对象拥有动态的字段集合:对象具备哪些属性在运行时才确定,无法用固定类字段穷举;
- 需要原型式继承:希望以"对象继承对象"的方式构造层级(而非类继承),新对象是已有对象的轻量变体;
- 需要运行时动态增删属性:属性集随运行状态、用户选择或配置变化而改变;
- 同类实例需要不同的属性组合:例如不同职业、不同玩家的角色属性差异巨大,但共用同一角色类。
典型落地领域包括:游戏开发中的实体属性管理(角色、道具)、需要不同参数集的应用配置系统、允许动态扩展字段的用户画像/资料管理。
七、现实世界的参照实现
文档特别提到 JavaScript 的原型继承是 Property 模式在现实语言中的天然体现:JavaScript 对象通过原型链(prototype chain)实现属性查找与继承,任何对象都可以作为另一个对象的原型,并可在运行时动态增删属性。Property 模式本质上就是把这种"基于对象而非基于类"的继承范式引入到 Java 这类静态类型语言中。
八、优点与权衡
优点
- 灵活性(Flexibility):属性可在运行时动态添加、移除、修改,适配多变需求;
- 解耦(Decoupling):对象的属性管理与其类定义解耦,降低类与属性之间的依赖;
- 易用性(Ease of Use):在大系统中以统一接口集中管理属性,简化了维护成本;
- 原型级复用:父原型属性的增删会沿原型链即时传导给所有子对象,一处修改、全局生效。
权衡(Trade-offs)
- 性能开销(Performance Overhead):动态属性管理依赖 Map 查找与原型链委托,相比直接字段访问有额外运行时开销;
- 复杂度(Complexity):原型链的委托逻辑会增加代码理解与维护成本,层级过深时排查困难;
- 类型安全(Type Safety):属性以
Map<Stats, Integer>形式的键值对存储,丧失编译期类型检查,取值可能为null,需要调用方自行处理。
九、与其他设计模式的关系
- Composite(组合模式):Composite 构建对象的树形层级,Property 可视为其"扁平化"版本——不维护层级关系,仅管理属性集合;
- Decorator(装饰器模式):两者都增强对象能力,但 Decorator 以包装方式动态添加职责,Property 则直接动态添加属性;
- Strategy(策略模式):Strategy 同样支持运行时行为变化,但关注点是替换算法,Property 关注点是增减属性数据。
十、快速体验方式
仓库采用 Maven 多模块结构,Property 是独立子模块(property/pom.xml)。在仓库根目录执行以下命令即可运行演示程序与测试:
./mvnw -pl property compile exec:java # 运行 App 演示(需按模块实际配置调整) ./mvnw -pl property test # 运行 CharacterTest 与 AppTest如果希望进一步研读,可重点对照 App.java 的调用链、Character.java 的委托实现与 CharacterTest.java 的行为断言,三者结合即可完整掌握该模式。
- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
相关推荐
Java 装饰器模式实战:基于 java-design-patterns 的巨魔示例解读动态扩展与组合优于继承
Java 装饰器模式实战:基于 java design patterns 的巨魔示例解读动态扩展与组合优于继承 本文以 GitHub 精选项目 java des
示例工程教程Java 动态代理模式实战:基于 java-design-patterns 的 TinyRestClient 注解式 REST 客户端实现
Java 动态代理模式实战:基于 java design patterns 的 TinyRestClient 注解式 REST 客户端实现 动态代理(Dynam
示例工程教程Java 设计模式之 Subclass Sandbox(子类沙箱):基于 java-design-patterns 的源码级实战解析
Java 设计模式之 Subclass Sandbox(子类沙箱):基于 java design patterns 的源码级实战解析 本文以 java desi
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考