假设我们有一个类 Event
:
class Event {
private final Long id;
private final Date date;
//constructor and other stuff
public boolean hasExpired() {
return date > today();
}
}
正如你所看到的,它的公共接口只是一个方法。hasExpired
而这使用的是 Event
内部状态(date
)来执行逻辑。
现在,我们要保存这个 Event
到数据库,使用仓库。
class EventRepository {
public void saveEvent(Event event) {
//insert into events...
}
}
如何在不违反OOP封装原则的情况下完成?
存储库需要知道 Event
's date
以使其持久化。添加一个 getDate
方法会违反封装原则。
如何在不违反OOP封装原则的情况下做到这一点呢?
你改变你对封装原则的解释。
某处 在我们的系统中,我们需要有两个函数:一个是接受实体的内存表示,并将其转换为实体的存储表示,另一个是将实体从存储表示中重构出来的函数。
例如,你可能在实体上有一个方法,返回一个纪念品的JSON表示,然后在实体上有一个构造函数,接受JSON表示,并使用它来初始化对象。
实际上,这就是GoF书中Memento模式的应用。 或者你可以把它看作是一个从实体上发送的消息。
这个消息不一定是实体的数据结构的副本,它可能用不同的方式来表达这个信息,它可能没有共享所有的信息,等等。 但是,如果你以后想把信息重新放到实体中,你必须现在就能把它复制出来。
另一种思考方式可能会有帮助:我们不存储 "实体",也不通过网络发送实体--我们发送的是 价值观 网络上的信息;信息被装入一些很好理解的模式中,以便我们以后可以解开信息的包装。
我们可以说这并不违反 "封装",原因有二:第一,纪念品是一个副本 -- -- 是一个 "封装"。深入 信息的副本。 我们不能通过改变备忘录来改变现有实体的状态。 其次,没有任何承诺或默示保证纪念品具有与实体本身相同的数据结构。
什么?是否 摔倒是假设你可以实现实体的时候不需要任何受你的持久化策略影响的代码,因为你需要能够以某种方式从数据存储中获取信息。
你需要定义 Event
来进行存储。OOP背后的整个思想是告诉对象执行行为。你想存储 Event
但不能在不牺牲封装提供的灵活性的情况下暴露状态。
这与序列化对象非常相似,对象的内部状态必须被写入流。这是由每个可序列化的类定义一个 writeObject(ObjectOutputStream)
来指定它应该如何被序列化。
你可以提供一个 saveTo(DataStorage storage)
,让客户知道事件中的数据可以被外部存储。与所有内部数据的存储一样,这泄露了实现,因为有人可以查看存储,查看什么实现细节。Event
决定存储。要读回数据。Event
将提供 restoreFrom(DataStorage storage)
.
不仅可以让实现对客户端隐藏(不包括写内部状态的泄漏,那是写内部状态时不可避免的),而且可以提供不同形式的 DataStorage
:也许你想直接保存在数据库中,也许你想用一个 DataStorageBuffer
以获取数据供以后使用eparsingstorage。
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);
}
}
而不是要求将信息存储在 通过获取者我们告诉对象要自己存储,与OOP理念保持一致。