如何定义一个对对象状态做出假设的方法?

时间:2014-04-04 02:11:46

标签: c#

在我们的几何库中,我们有一个Polygon类,当然包含一个检查此多边形是否包含另一个多边形的方法:

public bool Contains(Polygon other) {
    //complex containment checking code goes here
}

需要注意的其他重要事项是多边形是可变的,并且无法通知其形状何时发生变化。

现在,在我们的一个项目中,我们有一个验证函数,它检查大量的多边形是否全部包含在一个外边界内。

Polygon boundary = ...;
foreach (var poly in _myMassiveListOfPolygons)
    if (!boundary.Contains(poly))
        //error handling code goes here

剖析已经确定这是一个相当大的瓶颈。幸运的是,有一种方法可以大大加快计算速度。我们知道边界多边形总是凸的,所以我们可以编写一个假定外多边形是凸的专用方法,而不是使用通用的Contains方法。

最初我在客户端代码中添加了类似的东西:

Func<Polygon, bool> containsFunc;
// Keep both options, in case my assumption about being always convex is wrong
if (boundary.IsConvex())
{
    containsFunc = p => { ... }; //efficient code for convex boundary goes here
}
else
{
    containsFunc = p => boundary.Contains(p);
}
foreach (var poly in _myMassiveListOfPolygons)
    if (!containsFunc(poly))
        //error handling code goes here

四处欢乐!表现增加了十倍!

但是,此替代Contains方法的代码位于客户端项目中似乎不对,其他人无法重用它。

所以我尝试将它移动到Polygon类本身。

public bool Contains(Polygon other) {
    if (this.IsConvex())
        return ConvexContains(other);
    else
        return GenericContains(other);
}
private bool GenericContains(Polygon other) { ...}
private bool ConvexContains(Polygon other) { ...} 

使用此方法,客户端代码将返回原始代码。不幸的是,与前面的代码相比有一个很大的区别:在有一次调用boundary.IsConvex()之前选择要使用的正确函数,现在在每次调用Contains时调用此方法!虽然这仍然比使用通用的Contains方法更快,但大多数性能改进都会再次丢失(更不用说凹面多边形的性能下降)。

第三种可能性是有两个公共方法来检查包含,其中一个假定多边形是凸的(当然不做检查本身,否则我们回到之前的开销问题)。这似乎不是一个好主意,因为用凹形多边形意外调用它可能会产生意想不到的结果,这使得很难找到错误。

所以最后,我的问题是,有什么好方法可以解决这种情况吗?我们应该在哪里放置替代包含方法,我们应该如何调用它?

修改

我喜欢缓存多边形是否凸出的想法。但我无法改变界面或类的行为,这在以下情况下会出现问题:

//relevant part of the polygon interface
public List<Point> Points { get; set; }


//client code
var poly = new Polygon();
var list = new List<Point> 
{
    new Point(0,0),
    new Point(10,0),
    new Point(0,10)
};
poly.Points = list;
list.Add(new Point(1, 1));

最后一行代码是在常规List<Point>上执行的,我无能为力。但是,现在有2个要求:

  1. 此多边形现在必须包含添加的点。这是为了确保现有代码不会中断。这意味着我们无法将Points复制到我们自己的可观察列表中。
  2. 我们必须通知这个更改点的列表(因为我们的多边形从凸面变为凹面)。这意味着我们无法通过我们自己的可观察列表来包装列表,因为只有通过包装器进行的更改才会导致通知。

3 个答案:

答案 0 :(得分:2)

一种选择是在几何库中创建另一个概念,比如FixedBoundary,它可以封装该逻辑。它将是不可变的,因此您可以安全地缓存凸性。一个例子:

public class FixedBoundary
{
    private Polygon boundary;
    private bool isConvex;

    public FixedBoundary(Polygon boundary)
    {
        // deep clone so we don't have to worry about the boundary being modified later.
        this.boundary = boundary.Clone(); 
        this.isConvex = this.boundary.IsConvex();
    }

    public bool Contains(Polygon p)
    {
        if (isConvex)
        {
            // efficient convex logic here
        }
        else   
        {
            return this.boundary.Contains(p);
        }
    }
}

这当然会为几何库增加一些概念上的重量。另一种选择是添加一个ImmutablePolygon(和扩展名为ImmutablePoint),它可以做同样的事情,但是转换可能很麻烦并影响性能。

答案 1 :(得分:1)

您可以使用Func<T, bool>委托复制您已经完成的工作.. Polygon内部..也许是这样的?

private Func<Polygon, bool> _containsFunc;

// ...

public bool Contains(Polygon other) {
    if (_containsFunc == null) {
       if (this.IsConvex())
           _containsFunc = ConvexContains;
        else
            _containsFunc = GenericContains;
    }

    return _containsFunc(other);
}

第一次拨打Contains后的每次通话都不会拨打IsConvex。除非我误解了你......这听起来像是你所追求的。

答案 2 :(得分:0)

好吧......你们在公共界面中使用List,真的开枪了。以下是您可以尝试解决它的方法(有一个小的非零机会,这将错误地缓存,但它是1英寸(1 <&lt; 32)的机会。

正如您所注意到的,我们可以缓存多边形的凸面...但问题就变成了缓存失效的情况。

无法在对象更改时无效缓存。您必须在每次调用时检查缓存一致性。这就是哈希的用武之地。

默认情况下,列表的哈希很无用,你想使用this solution for getting a usable hash

所以你想要做的是在你的多边形上添加nullable<int> _cachedHashCode。当您致电Polygon.IsConvex时,您会将List<Point>哈希,与_cachedHashCode进行比较,如果它们不相等,请重新计算您的IsConvex。