此设计在多大程度上违反了封装

To what degree does this Design violate Encapsulation

我正在 Java 中设计图形对象。我是设计师,我怀疑这种设计违反了封装,但我希望得到其他人的一些见解。

在下面,我们有两个接口 Graph 和 Vertex。

图实现负责管理图中的顶点,包括创建、删除、确保施加的约束等。通用参数代表 Type,类型存储在顶点中的值的类型,以及 W8,这是存储在边中的权重类型:

public interface Graph<T, W>
{
    /** Creates and adds a vertex with the value given to this graph */
    Vertex<T> createVertex(T value);

    /** Performs some arbitrary modification to the vertex given */
    void modify(Vertex<T> vertex, ....);

    // some other methods...
}

Vertex 接口用于获取和修改顶点的属性,也用于保存引用;它的实现由图的实现决定——客户端对其内部实现一无所知。它可能看起来像这样:

public interface Vertex<T>
{
     int getValue();

     /** Performs some arbitrary modification to this vertex - same as
       * as in modify method in graph */
     void modify(...);

     // some other methods
}

我使用顶点接口的原因是客户端可以简单地保留对相关顶点的引用并将引用传递给图形。我也可以有这样的图形界面:

public interface Graph<T, W>
{
    /** Creates and adds a vertex with the value given to this graph */
    void createVertex(T value);

    /** Performs some arbitrary modification to the vertex given */
    void modify(T vertex);

    // some other methods...
}

在此图形界面中,'modify' 方法始终必须搜索顶点;这将是非常不切实际的,尤其是当我们想要经常修改同一个顶点时——相反,客户端可以像第一种方法一样只保留一个引用传递。

回到我的问题,我对封装很严格,这是否违反了封装的原则?

就我个人而言,我认为这不会违反封装,因为客户端不知道图对象或顶点在内部执行了什么。然而,我们确实泄露了图以某种方式使用顶点的信息(尽管 exact 内部结构没有暴露),但其他库没有这样做。示例可能是作为图形子集的数据结构,例如树和链表:核心 Java 库中的链表实现不会让应用程序注意到某些节点接口被使用;我在其他各种库中看到的树实现也不公开它们的节点。

那么在严格的面向对象术语下,这种设计是否违反了封装?我也欢迎对现有的面向对象库的其他参考,也不在 Java 的范围内,或者讨论该主题的文章。

我觉得不违反封装。图的目的通常是管理顶点和边,因此将这些概念作为类型公开和使用是非常有意义的。在您的问题中,您引用了 LinkedList,但其主要目的是成为 List 而不是暴露节点的数据结构。如果它愿意,它可以完全安全地公开它的节点,尽管大多数客户端应该只使用它的 List API.

破坏封装是指您以不安全的方式暴露了内部结构,例如返回一个可变集合(例如 List<Vertex<T>>),它是一个内部数据结构,而不是通过更受控的方式保护它 API(例如 add(Vertex<T>),等等)。如果你的内脏失控,全世界都可以玩弄你的私处,而你却无能为力。

一些 Map 接口例如公开表示键或值的集合,并将这些实现绑定到映射本身的支持以进行突变。这样既安全又方便。

考虑不暴露内部数据结构、布尔值或按位标志(可能在 API 中使用枚举或接口以及内部需要的更高效的东西)或具有棘手操作顺序的更改器,例如"first call init, then call setup, then call x or y or z then make sure to dispose"。那种你想用方便的结构和通用的设计模式融入你的 API 的东西。

我认为这类似于公开 Map 接口 Map.Entry。图的概念以顶点的概念为先决条件 - 缺一不可。因此,公开两者是完全合法的。

另一方面,您在这里公开了(或可能限制了)至少一些实施细节。你的

void modify(T vertex);

in Graph 假定 vetexes 可以通过其内容寻址。情况可能并非总是如此。我觉得这对T的限制太大了。

我想说的是,为了确保封装,对图本身的唯一询问操作应该是 Set<Vertex<T>> getVertices。其余的应该在顶点上。

创建操作应该return它创建的实体:

Vertex<T> createVertex(T value)

然后您可以添加 Graph.createEdge(Vertex<T> v1, Vertex<T> v2)removeEdge(Vertex<T> v1, Vertex<T> v2)