领域模型模式示例

8

我只是想找一些Martin Fowler的领域模型模式的例子,但我找不到。

从我在互联网上找到的资料来看,领域模型只是将一些“逻辑”方法添加到类中。例如

public class Income {
    private String title;
    private String comment;
    private String date;
    private Double amount;
    private Integer category;

    public ExIn(String title, String comment, Double amount, Integer category, String date) {
        this.title = title;
        this.comment = comment;
        this.date = date;
        this.amount = amount;
        this.category = category;
    }

    public Integer getCategory() {
        return category;
    }

    public void setCategory(Integer category) {
        this.category = category;
    }

    // more getters and setters
    // Domain Model part starts
    public boolean isPositive()
    {
        return amount > 0 ? true : false;
    }
    // more methods like that 
}

我是否正确理解了?如果没有,我会感激一些领域模型模式使用的小例子。


你能引用一下这个模式的参考资料吗? - Fuhrmanator
@Fuhrmanator 我添加了链接和名称。希望这已经足够了。 - Aleksander Monk
3个回答

28

我是否理解正确?如果不是,能否给个小例子让我更好地理解呢?

总体上,是的。

根据Martin Fowler的定义,领域模型是一个结合了行为和数据的领域对象模型。领域模型通常与只包含特定类来代表数据以及其他具有特定行为/处理功能的类的模型相对立。
如果以您的Income类为例,它看起来更像是一个保存属性/数据而非具有真正行为的领域模型。

public boolean isPositive(){
   return amount > 0 ? true : false;
}

这是一种与模型无关的实用函数。
您可以将其放在一个 Math 类中。

我将尝试为您提供一个领域模型示例,然后展示模型将数据和处理分开的版本。

假设在您正在建模的应用程序的领域需求中,我们需要为收入添加奖金。例如,这个奖金可能会在冬季为圣诞节而发放(但为什么不为其他事件呢)

与其使用服务类来执行此处理,我们让领域模型对象执行任务。

Incomes,作为高级别对象,可以迭代 Income 实例并应用奖金,我们可以有一个奖金规则类来根据某些输入值定义奖金。
我引入多个类,因为想要让每个对象根据其职责进行协作。

Incomes:

public class Incomes {
  List<Income> incomes = ...
  ....
  public void applyBonus(BonusRule bonusRule){
     for (Income income : incomes){
       income.applyBonus(bonusRule);
     }      
}

收入:

public class Income {

  private float amount;
...
  public void applyBonus(BonusRule bonusRule){
       float bonus = bonusRule.compute(this);
       amount += bonus;
  }      
...
}

圣诞节规则:

public class ChristmasBonusRule implements BonusRule {
...
  @Override
  public float compute(Income income){
       float bonus = ...
       return bonus;  
  }      
...
}

最后,我们可以这样应用处理:

void foo(){   
  // create a domain object that has both behavior and data
  Incomes incomes = ...; 
  // invoke a functional method on the object by passing another domain object
  incomes.applyBonus(new ChristmasBonusRule()); 
}
在将数据和逻辑分离到不同的类中的设计中,它可能看起来更像这样:

In a design where you separate data and logic in distinct classes, it could look like more like that :

public class IncomeBonusService {
  // stateless : no incomes data inside it
  ....
  public void applyChristmasBonus(List<Income> incomes){
     for (Income income : incomes){
       // Christmas bonus computation here
       float bonus = ...
       income.setAmount(bonus + income.getAmount());
     }
  }
}

我们可以这样应用处理:

// inject the service
@Autowired
IncomeBonusService incomeBonusService;

void foo(){       
   // create a domain object that has only data
   List<Income> incomes = ...; 
   // invoke a service method by passing data as parameter
   incomeBonusService.applyChristmasBonus(incomes); 
}
一个模型设计中,对象没有行为(仅具有 getter/setter)的称为 贫血领域模型
这个例子展示了两种方法之间的差异:
领域模型:
- 对象具有实际意义。 - 行为职责在类之间被精细定义。因此,具有良好的隔离性、可测试性和可维护性。例如,添加/删除/单元测试一个 BonusRule 很容易。 - 对象负责其状态。确实,不需要提供 setter,因为对象可以与其他对象协作后自行更新其状态。我们可以在 Amount.applyBonus() 中看到这一点:
```java float bonus = bonusRule.compute(this); amount += bonus; ```
贫血领域模型:
- 所有逻辑都在服务类中。因此,在一个地方获取代码。对于少量行数,这很好。但请注意,这种优势有一定的限制,因为逻辑变得庞大或复杂时,最好经常将逻辑拆分成多个服务类来处理。 - 无论需要多少个服务类,整个逻辑都位于服务类中而非其他地方。如果与领域模型相比,逻辑可能在某些不同的类别中爆炸,那么这可能会简化开发规范。 - 必须为域类提供 getter/setter。域不负责自己的状态和其不变规则。因此,依赖于域类的任何类都可能“破坏”其状态。
另外,一些框架(用于持久性、映射、序列化等)默认依赖 getter/setter。这就是为什么尽管具有缺点,但在某些项目中该模型仍然颇具优势的原因。

10
Fowler提到的书籍是指Larman的Larman's book,作为介绍性理解和示例。有趣的是,Larman对于领域建模的方法从未向领域类添加行为。
在领域模型中,有一个概念是类是概念性的,并不是软件类,而软件类是基于领域(概念性)类构建的。Larman的实现行为遵循responsibility driven design和GoF设计模式。领域模型仍然是软件开发过程中的独立元素。这种建模方式的好处是将问题与解决方案分离。领域模型应该符合问题本身(捕捉问题域的要求,而不涉及实现细节)。
Larman提出“操作契约”作为确保域模型内部行为一致的一种方式。契约应该独立于解决方案(实现)。契约具有后置条件,描述了在执行操作后域模型中的限制。例如,当顾客在商店购买商品时,一个后置条件是销售对象与顾客购买的每件商品相关联。
业务逻辑的实现应遵守为域模型定义的契约(后置条件)。Larman采用Controller GRASP pattern以及其他GRASP模式的方法最终将这些逻辑放入各种类中(通常是域层,即受域模型中概念类启发的软件类)或处理系统操作的Facade(Controller)类中。
Larman的方法比本说明更为复杂,但关键点是行为永远不仅仅在域模型中作为方法单独定义。Larman多次表示,域(概念)类没有方法,因为它们不是软件类。
Fowler的书还提到了他写的另一本关于分析模式的书,其中包含来自不同领域(包括医疗保健、金融交易和会计)的模式。每个模式都有文字描述和简单的预UML符号表示(该书是在UML稳定成可用形式之前编写的)。那本书中没有展示软件类的例子,也就是说,我无法找到定义在编程语言中的方法的示例。
我知道至少有一本书在领域模型中使用了UML,例如分子生物学领域。以下是一个示例(请注意,为节省空间,UML已修改--子类型框显示在超类型框中):

enter image description here

上述书籍没有对行为进行建模,可能是因为这些行为实际上取决于软件应用的要求。这些模型捕获了一些业务规则,例如:
  • 每个化学配方必须由两个或多个化学元素化合物组成,或者两者兼有。
但该书中的模型大多是数据模型。
通过谷歌搜索,您可以找到BRIDG生物医学研究集成域组织的庞大模型。例如,深入到分子生物学子域和类Gene,您会发现它没有行为,其他(概念)类也是如此。

enter image description here

领域模型是在编程语言中定义的吗?

Larman的理念清楚地将它们显示为与编程语言无关(概念而非软件类),作为与问题域(需求)明确相关的单独工件,与代码分离。

另一方面,你会发现Fowler说,"我更喜欢POJO领域模型。", 这基本上意味着领域模型是在代码中定义的。

Eric Evans的DDD假设软件开发中的许多复杂性来自于领域,因此这种复杂领域的模型对于管理复杂性至关重要。因此,在领域复杂时需要进行领域建模。DDD建议使用一种普遍存在的领域建模语言;即,领域专家和开发人员都通用的语言。这意味着在至少某些情况下,领域模型不会在编程语言中定义。

有一个相关问题可能会提供一些线索(尽管它引发了很多争议)。一些人批评这个问题的例子过于琐碎(不够复杂),不能构建一个合理的领域模型。


2
一个“领域模型”就是代表你业务领域中某个可辨识概念的对象。比如“客户”,“订单”等。无论业务逻辑是什么,构成该逻辑的有形实体都是领域中的模型。
有些模型会有很多业务逻辑(也许值得拆分为其他类),有些则几乎没有(甚至完全没有)。
“领域模型”与任何其他类之间的区别并不是Java语言本身的构造,而主要是由你定义的业务逻辑的语义构造。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接