用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/microwind/ai-skills --skill value-object命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | 值对象 (Value Object) |
| description | 没有身份标识,通过属性值判断相等的对象。不可变,通常代表领域中的度量或描述。 |
Eric Evans 将值对象定义为:描述领域中某个事物特征的对象,没有概念上的身份("describes some characteristic of a thing")。两个值对象只要属性值相同,就被视为等价。
核心特征:
大多数情况下实体具有很多属性,这些属性一般都是平铺。但有的属性归类和聚合后能表达一个业务含义,就把这些属性封装成值对象,从而:
Address)替代多个原始字段String、BigDecimal一句话:值对象就是用来描述实体的特征。实体的单一属性也可以是值对象。
只封装一个值,但赋予它领域含义。通常基于字符串、整型、枚举等原始类型。
// ❌ 原始类型偏执
public class User {
private String email; // 只是个字符串,随便赋值
private String phone;
}
// ✅ 单一属性值对象
public final class Email {
private final String value;
public Email(String value) {
if (value == null || !value.matches("^[\\w.-]+@[\\w.-]+$")) {
throw new DomainException("邮箱格式不合法: " + value);
}
this.value = value.toLowerCase();
}
public String domain() {
return value.substring(value.indexOf('@') + 1);
}
@Override
public boolean equals(Object o) {
return (o instanceof Email) && value.equals(((Email) o).value);
}
@Override
public int hashCode() { return value.hashCode(); }
}
public enum SubscriptionStatus { // 枚举也是值对象
ACTIVE, CANCELLED, EXPIRED
}
public class User {
private final Email email; // 有自我验证
private final SubscriptionStatus status; // 业务语义清晰
}
把多个相关属性组合成一个概念整体。设计成 class(或 record / dataclass),包含多个属性,但没有 ID。值对象中可以嵌套值对象。
// ✅ 多属性值对象:Money(amount + currency)
public final class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) { ... }
public Money add(Money other) {
if (!currency.equals(other.currency)) {
throw new DomainException("币种不一致");
}
return new Money(amount.add(other.amount), currency); // 返回新对象
}
}
// ✅ 多属性值对象:Address(嵌套 PostCode 值对象)
public final class Address {
private final String province;
private final String city;
private final String street;
private final PostCode postCode; // 嵌套值对象
// ...
}
// ✅ 多属性值对象:DateRange
public final class DateRange {
private final LocalDate start;
private final LocalDate end;
public {
(end.isBefore(start)) {
();
}
.start = start;
.end = end;
}
{
!date.isBefore(start) && !date.isAfter(end);
}
Duration { Duration.between(start.atStartOfDay(), end.atStartOfDay()); }
}
from dataclasses import dataclass
from decimal import Decimal
# 单一属性值对象
@dataclass(frozen=True)
class Email:
value: str
def __post_init__(self):
if '@' not in self.value:
raise ValueError(f"Invalid email: {self.value}")
object.__setattr__(self, 'value', self.value.lower()) # frozen 下用 object.__setattr__
def domain(self) -> str:
return self.value.split('@')[1]
# 多属性值对象
@dataclass(frozen=True)
class Money:
amount: Decimal
currency: str
def __post_init__(self):
if self.amount < 0:
raise ValueError("Amount cannot be negative")
def add() -> :
.currency != other.currency:
ValueError()
Money(.amount + other.amount, .currency)
// ✅ 值对象:Money
public class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("Cannot add different currencies");
}
return new Money(amount.add(other.amount), currency);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money money = (Money) o;
return amount.equals(money.amount) && currency.equals(money.currency);
}
{
Objects.hash(amount, currency);
}
}
( (), Currency.USD);
( (), Currency.USD);
assertEquals(m1, m2);
from dataclasses import dataclass
from decimal import Decimal
@dataclass(frozen=True) # 不可变
class Money:
amount: Decimal
currency: str
def __post_init__(self):
if self.amount < 0:
raise ValueError("Amount cannot be negative")
def add(self, other: 'Money') -> 'Money':
if self.currency != other.currency:
raise ValueError("Cannot add different currencies")
return Money(self.amount + other.amount, self.currency)
# 使用
m1 = Money(Decimal("100"), "USD")
m2 = Money(Decimal("100"), "USD")
assert m1 == m2 # 值相等
| 特性 | Entity | Value Object |
|---|---|---|
| 身份 | 有 ID | 无 ID |
| 相等性 | 基于 ID | 基于值 |
| 可变性 | 可变 | 不可变 |
| 示例 | User, Order | Money, Email, Address |
money.add(other) 返回新 Money)equals / hashCode 基于全部属性值对象不是越多越"DDD"。下列场景直接用原始类型反而更清晰:
description、remark、title),强行包成 Description 只增加噪音判据:能写出一句业务约束("邮箱必须含 @"、"金额非负"、"两个货币必须同种才能相加")的,值得包成 VO;写不出业务约束的,就是个字段。
❌ 给值对象加 ID——Money 加 id 字段以便存数据库
→ 值对象按值相等,没有概念身份;持久化时打平到所属实体的列即可
❌ 修改字段而非返回新对象——money.setAmount(x)
→ 操作必须返回新实例(money.add(other) 返回新 Money),保证不可变
❌ 构造时不校验——new Email("not-an-email") 也能通过
→ 构造即合法:所有领域约束在构造函数 / 工厂方法里强制校验
值对象简化了代码,提高了表达力与安全性。