设计问题:如何最好地使用构图
Posted
技术标签:
【中文标题】设计问题:如何最好地使用构图【英文标题】:DESIGN PROBLEM: How to use composition the best 【发布时间】:2020-02-23 10:33:48 【问题描述】:我正在做一个项目,但对它的设计有些怀疑。 我怎样才能最好地设计以下问题(在 JAVA 中):
A 类具有以下属性:
像素哈希集,其中每个像素的 x,y 坐标和值 v 介于 0-1 之间。 B 类的实例。B类具有以下功能:
一个获取像素并返回其左邻居的函数。当我在 A 类中时,我想对 A 中的每个像素使用 B.function,并且仅当它不存在时才将其添加到 HashSet 中。问题是我不想将 HashSet 发送给函数,如果它可能已经存在,那么从函数返回新的 Pixel 实例有多糟糕(这个函数将在许多像素上运行并且会创建许多未使用的实例像素)。
我还有什么其他选择?
【问题讨论】:
如果你有一个HashSet<Pixel>
,查看一个像素是否在集合中的唯一方法是创建另一个像素,所以除非你改变这个方面,否则你真的没有任何其他的B 类中函数的选项,因为即使您确实传入了 HashSet,它也必须创建 Pixel
对象。
谢谢,所以我想知道如果在每次调用 B.function 时,该函数都会创建 10 个我不会使用的实例,那么性能会有多糟糕?这样做可以接受吗?
你不认为 double 的二维数组能更好地表示这一点吗?
@ErwinBolwidt 在我的例子中,给定一个图像,我可能只需要在其中保存一些像素,所以我认为保存一个代表所有图像的二维数组可能是浪费的。
一张大小的图片有多少像素?为每个像素创建对象、存储 X 和 y 以及分配 HashSet 都会产生一定的开销。如果您的像素位于矩形区域,您只需要该区域的矩阵。如果您仍然需要 Hash 查找,以 Point(x 和 y)对象作为键、Double 作为值的 HashMap 是一种更自然的表示
【参考方案1】:
由于您使用Set<Pixel>
,您必须创建新的 Pixel
实例来检查它是否存在于集合中。
如果 set 在调用 B.function
方法后包含 N
元素,您将创建额外的 N
Pixel
节点。如果所有元素都是新的,您只需将它们添加到 set 中,否则Garbage Collection
需要清除它们。缺点之一是我们需要创建m
(其中m <= N
- 集合中已经存在的Pixel
-s 的数量),然后我们需要通过GC
收集它们。 m/N
比率有多大取决于您的算法以及您实际在做什么。
让我们计算我们需要为集合中的N = 1_000_000
像素消耗多少内存。我们知道int
是4 bytes
和double
是8 bytes
,让我们为对象添加额外的8 bytes
并为引用添加8 bytes
。它为Pixel
对象的每个实例提供32 bytes
。我们需要创建提供32MB
的N
对象。假设我们的比率是50%
,所以我们分配16MB
只是为了检查它是不需要的。
如果这是您无法支付的成本,您需要开发算法,允许您按照left-to-right
的顺序迭代Set<Pixel>
。所以,Pixel
X
的左邻居在X
之前。
假设Pixel
X(x, y)
的左邻居是像素X'(x - 1, y)
。 Pixel
B(0, y)
没有左邻。您需要使用TreeSet
并在Pixel
类中实现Comparable<Pixel>
接口。简单的实现可能如下所示:
@Override
public int compareTo(Pixel o)
return this.y == o.y ? this.x - o.x : this.y - o.y;
这允许您按从左到右的顺序迭代集合:(0, 0), (1, 0), ...., (x - 1, y), (x, y), (x + 1, y), ... , (maxX, maxY)
。因此,当您迭代它时,您可以检查前一个元素是否是当前Pixel
的左邻居。示例实现如下所示:
void addNeighboursIfNeeded()
Set<Pixel> neighbours = new HashSet<>(pixels.size());
Pixel last = null;
for (Pixel p : pixels)
if (p.getX() == 0 || p.isLeftNeighbour(last))
// a left border pixel
// or last checked element is a left neighbour of current pixel.
last = p;
continue;
// last element was not our left-neighbour so we need to call b method
Pixel left = b.getLeft(p);
neighbours.add(left);
last = p;
// add all new neigbours
pixels.addAll(neighbours);
这应该允许您保存为重复的Pixel
对象分配的内存。
【讨论】:
【参考方案2】:我可以在这里看到一些关于面向对象编程的问题。
-
封装违规:当您从 A 调用 B 的函数时,该函数对 A 的数据进行操作(您通过不发送 HashMap 来避免这种情况),它违反了封装(如果有原因,虽然它是可以接受的)。是否可以将该函数(在 A 的 HashSet 上操作)移动到 A?这将保护 A 的状态不被暴露。
类的增殖:有可能会有大量的Point类型的对象。您可以考虑使用 Flyweight GOF 设计模式,它将每个点的状态外化并使其可重用。并将大幅减少数量。
将大量点传递给 B 中的方法:如果您可以将方法从 B 转移到 A,则该点得到解决。无论如何,java 将通过引用传递这个集合。但在这种情况下,它对外部类的修改开放(需要注意这方面)。
Point类型的抽象:如果Point类只有状态没有行为,会导致违反封装。你能把 getNeighbour() 方法改成 Point 吗?因为它将使 Point 不可变(这是必不可少的)。当然,实际的算法可以委托给另一个类(如果它的职责独立变化并且具有算法层次结构,请在此处考虑 GOF 策略模式)。
集合中点的唯一性:您的集合将适当注意适当的哈希和类 Point 的逻辑相等性。
【讨论】:
以上是关于设计问题:如何最好地使用构图的主要内容,如果未能解决你的问题,请参考以下文章