存储库模式如何符合 OOP 封装原则?
How can repository pattern fit with OOP encapsulation principle?
假设我们有一个 class Event
:
class Event {
private final Long id;
private final Date date;
//constructor and other stuff
public boolean hasExpired() {
return date > today();
}
}
如您所见,它的 public 接口只是一个方法 hasExpired
,它使用 Event
内部状态 (date
) 来执行逻辑。
现在,我们要使用存储库将此 Event
保存到数据库:
class EventRepository {
public void saveEvent(Event event) {
//insert into events...
}
}
如何在不违反OOP封装原则的情况下做到这一点?
存储库需要知道 Event
的 date
才能保留它。添加 getDate
方法将违反封装原则。
How is this done without violating OOP encapsulation principle?
你改变对封装原则的解释。
在我们系统的某处,我们需要两个函数:一个获取实体的内存表示并将其转换为实体的存储表示,另一个函数从存储的表示中重构实体。
例如,您可能在实体上有一个方法,该方法 returns 一个 JSON 纪念品的表示,然后实体上的一个构造函数接受 JSON 表示和用它来初始化对象。
实际上,这是 GoF 书中的 Memento 模式的应用。或者您可以将其视为发送给 from/to 实体的消息。
此消息不一定是实体数据结构的副本;它可能以不同的方式表达该信息,它可能不会共享所有信息,等等。但是如果你想稍后将信息取回一个实体,你现在必须能够将它复制出来。
另一种可能有帮助的思考方式:我们不存储 "entities",也不通过网络发送实体——我们通过网络发送 值 ;信息被打包到一些易于理解的模式中,以便我们稍后可以解压缩信息。
我们可以争辩说这不违反 "encapsulation" 的原因有两个:首先,纪念品是一个副本 -- 深 副本——信息。我们无法通过更改纪念品来更改现有实体的状态。其次,没有承诺或暗示保证纪念品与实体本身具有相同的数据结构。
所做的假设您可以在没有受持久性策略影响的代码的情况下实现实体,因为您需要能够获取信息以某种方式存储数据。
您需要将 Event
定义为可存储。 OOP 背后的整个思想是告诉对象执行行为。您想要存储 Event
的状态,但不能在不牺牲封装提供的灵活性的情况下公开状态。
这与序列化对象非常相似,其中它们的内部状态必须写入流。这是通过让每个可序列化的 class 定义一个 writeObject(ObjectOutputStream)
来指定它应该如何序列化来处理的。
您可以提供一个 saveTo(DataStorage storage)
,让客户知道事件中的数据可以存储在外部。与内部数据的所有存储一样,此泄漏实现,因为有人可以查看存储以查看 Event
决定存储的实现细节。要读回数据,Event
将提供 restoreFrom(DataStorage storage)
.
你不仅让实现对客户端隐藏(不包括写内部状态的泄漏,写内部状态时这是不可避免的),而且你可以提供不同形式的DataStorage
:也许你想保存对数据库的权利,或者您可能想要一个 DataStorageBuffer
来获取以后 use/parsing/storage.
的数据
class Event {
private final Long id;
private final Date date;
public Event(Long id, Date date) {
this.id = id;
this.date = date;
}
public void saveTo(DataStorage storage) {
// Add state to storage
}
public static Event readFrom(DataStorage storage) {
Long id = ...; // read from storage
Date date = ...;
return new Event(id, date);
}
}
我们不是请求存储信息 via a getter,而是告诉对象存储自己,与 OOP 哲学保持一致。
假设我们有一个 class Event
:
class Event {
private final Long id;
private final Date date;
//constructor and other stuff
public boolean hasExpired() {
return date > today();
}
}
如您所见,它的 public 接口只是一个方法 hasExpired
,它使用 Event
内部状态 (date
) 来执行逻辑。
现在,我们要使用存储库将此 Event
保存到数据库:
class EventRepository {
public void saveEvent(Event event) {
//insert into events...
}
}
如何在不违反OOP封装原则的情况下做到这一点?
存储库需要知道 Event
的 date
才能保留它。添加 getDate
方法将违反封装原则。
How is this done without violating OOP encapsulation principle?
你改变对封装原则的解释。
在我们系统的某处,我们需要两个函数:一个获取实体的内存表示并将其转换为实体的存储表示,另一个函数从存储的表示中重构实体。
例如,您可能在实体上有一个方法,该方法 returns 一个 JSON 纪念品的表示,然后实体上的一个构造函数接受 JSON 表示和用它来初始化对象。
实际上,这是 GoF 书中的 Memento 模式的应用。或者您可以将其视为发送给 from/to 实体的消息。
此消息不一定是实体数据结构的副本;它可能以不同的方式表达该信息,它可能不会共享所有信息,等等。但是如果你想稍后将信息取回一个实体,你现在必须能够将它复制出来。
另一种可能有帮助的思考方式:我们不存储 "entities",也不通过网络发送实体——我们通过网络发送 值 ;信息被打包到一些易于理解的模式中,以便我们稍后可以解压缩信息。
我们可以争辩说这不违反 "encapsulation" 的原因有两个:首先,纪念品是一个副本 -- 深 副本——信息。我们无法通过更改纪念品来更改现有实体的状态。其次,没有承诺或暗示保证纪念品与实体本身具有相同的数据结构。
所做的假设您可以在没有受持久性策略影响的代码的情况下实现实体,因为您需要能够获取信息以某种方式存储数据。
您需要将 Event
定义为可存储。 OOP 背后的整个思想是告诉对象执行行为。您想要存储 Event
的状态,但不能在不牺牲封装提供的灵活性的情况下公开状态。
这与序列化对象非常相似,其中它们的内部状态必须写入流。这是通过让每个可序列化的 class 定义一个 writeObject(ObjectOutputStream)
来指定它应该如何序列化来处理的。
您可以提供一个 saveTo(DataStorage storage)
,让客户知道事件中的数据可以存储在外部。与内部数据的所有存储一样,此泄漏实现,因为有人可以查看存储以查看 Event
决定存储的实现细节。要读回数据,Event
将提供 restoreFrom(DataStorage storage)
.
你不仅让实现对客户端隐藏(不包括写内部状态的泄漏,写内部状态时这是不可避免的),而且你可以提供不同形式的DataStorage
:也许你想保存对数据库的权利,或者您可能想要一个 DataStorageBuffer
来获取以后 use/parsing/storage.
class Event {
private final Long id;
private final Date date;
public Event(Long id, Date date) {
this.id = id;
this.date = date;
}
public void saveTo(DataStorage storage) {
// Add state to storage
}
public static Event readFrom(DataStorage storage) {
Long id = ...; // read from storage
Date date = ...;
return new Event(id, date);
}
}
我们不是请求存储信息 via a getter,而是告诉对象存储自己,与 OOP 哲学保持一致。