Oop 服务访问实体属性

Oop 服务访问实体属性,oop,domain-driven-design,encapsulation,ddd-service,Oop,Domain Driven Design,Encapsulation,Ddd Service,在oop中,我们寻求封装。我们尽量不通过getter或公共字段公开内部状态,只公开方法 到目前为止还不错 当我们希望在多个实体上操作时我们引入服务。 但这项服务如何在这些实体上自由运行呢 如果所有(服务和实体)都在同一个包中,则实体可以公开包私有方法或字段,服务可以使用它们,从而保留封装。但当实体和服务来自不同的包时会发生什么呢?实体似乎应该要么公开公共getter(第一步是贫血模型和从实体中提取逻辑),要么公开执行特定于服务需求的逻辑的公共方法(可能仅由该服务的需求引入),这看起来也很糟糕。如

在oop中,我们寻求封装。我们尽量不通过getter或公共字段公开内部状态,只公开方法

到目前为止还不错

当我们希望在多个实体上操作时我们引入服务。 但这项服务如何在这些实体上自由运行呢


如果所有(服务和实体)都在同一个包中,则实体可以公开包私有方法或字段,服务可以使用它们,从而保留封装。但当实体和服务来自不同的包时会发生什么呢?实体似乎应该要么公开公共getter(第一步是贫血模型和从实体中提取逻辑),要么公开执行特定于服务需求的逻辑的公共方法(可能仅由该服务的需求引入),这看起来也很糟糕。如何解决这个问题?

在面向对象的上下文中,您需要了解的最重要的事情是对象响应消息,特别是在面向对象p中,方法是如何实现这些响应的

例如,假设您有一个
对象,您(作为程序员)已将响应“grow”消息的责任分配给该对象。通常,您可以将其实现为
Person.grow()
方法,如下所示

class Person {
    int age;

    public void grow() { this.age++; }
}
这似乎相当明显,但您必须注意,从消息发送者的角度来看,
Person
对象如何反应是毫无意义的。不管怎么说,方法
Person.grow()
可能会触发导弹发射,这并不重要,因为其他一些对象(或多个对象)可能会以正确的方式响应(例如,一个UI组件在屏幕上自我更新)。但是,您决定当
Person
对象处理“grow”消息时,它必须增加其age属性的值。这是封装

因此,为了解决您的担忧,“执行特定于服务需求的逻辑的公共方法(可能仅由该服务的需求引入)似乎也不好”,这一点也不坏,因为您正在设计实体,以特定方式响应来自服务的消息,以匹配应用程序的需求。需要记住的重要一点是,服务并不决定实体的行为,而是实体以自己的方式响应来自服务的请求

最后,您可能会问自己:实体如何知道它们需要响应某些消息?这很容易回答:您决定如何将消息链接到响应。换句话说,您考虑应用程序的需求(各种对象将发送什么“消息”)以及如何满足它们(如何以及哪些对象将响应消息)

当我们想在多个实体上操作时,我们引入了服务

不,我们没有。嗯,我想有些人会,但关键是他们不应该

在面向对象中,我们对一个特定的问题域进行建模。我们不会(同样,也不应该)根据单个对象操作的其他对象的数量进行区分。如果我必须模拟一个
约会
和一组
约会
,我不会引入
约会服务
,我会引入一个
时间表
时间表
,或任何适合该领域的东西

实体
服务
的区别不符合领域。这纯粹是技术性的,通常是回归到程序性思维,其中
实体
是数据,
服务
是对其采取行动的程序

今天实践的DDD不是基于OOP的,它只是使用对象语法。一个明显的迹象是,在大多数项目中,实体是直接持久化的,甚至包含数据库ID或与数据库相关的注释


所以,要么做OOP,要么做DDD,你不能同时做这两件事。这是我关于OO和DDD的一篇演讲(演讲是德语,幻灯片是英语)。

我不认为使用getter是迈向贫血模型的一步。或者至少,就像编程中的每件事一样,这要视情况而定。
贫血模型的缺点是,访问对象的每个组件都可以在不强制执行其不变量的情况下对其进行变异(可能导致数据不一致),使用setter方法可以很容易地完成

(我将使用terms命令和查询来指示修改对象状态的方法和只返回数据而不更改任何内容的方法)

拥有聚合/实体的目的是强制执行对象不变量,因此它公开了“命令”方法,这些方法不反映对象的内部结构,而是“面向域”(使用“通用语言”进行命名),公开其“域行为”(建议避免使用get/set命名,因为它们是表示对象内部结构的标准命名)

这是关于集合方法的问题,关于get呢?
由于set方法可以被视为聚合的“命令”,因此可以将getter视为用于向聚合请求数据的“查询”方法。向聚合请求数据完全可以,如果这不会破坏聚合强制执行不变量的责任。这意味着您应该注意查询方法返回的内容。
如果查询方法结果是一个对象,那么,不可变的,拥有它是完全可以的。这样,谁查询聚合,谁就得到了只能读取的东西。
因此,您可以让查询方法使用对象内部状态进行计算(例如,一个方法
int missingstustudents()
,该方法计算
课程
实体的失踪学生人数,该实体具有
学生总数
,且
列表处于其内部状态),或者使用简单方法