2017-01-13 68 views
1

假设我们有一个巨大的数值笛卡尔坐标列表(5; 3)(1; -9)等。为了表示oop中的一个点,我创建了一个struct/object(c#):验证对象/结构没有失败

public struct Point 
{ 
    public int X, Y { get; } 

    public Point(int x, int y) 
    { 
     // Check if x,y falls within certain boundaries (ex. -1000, 1000) 
    } 
} 

这可能是错误的我如何使用结构。我猜你通常不会使用构造函数,但这不是重点。

假设我想添加一个1000点的列表,并且不能保证这些坐标位于边界内。简单地说,如果这个观点是无效的,就转到下一个观点而不失败,并告知用户。至于对象,我认为Point本身应该负责实例化和验证,但我不确定在这个特定情况下如何处理它。调用者预先检查x,y将是最简单的方法,但它不适用,因为调用者必须处理应驻留在Point中的逻辑。

验证和处理不正确的坐标而不失败和违反SRP的最合适方法是什么?

+0

[System.Drawing.Point(https://msdn.microsoft.com/en-us/ library/system.drawing.point(v = vs.110).aspx),不需要重新发明轮子。 – TheLethalCoder

+0

@TheLethalCoder'System.Drawing.Point'是一个可变结构。啊!它应该被绞死干燥并尽可能深埋;)。就我个人而言,我避免使用'Point',除非我与API交互的API迫使我使用。 – InBetween

+0

@InBetween只是将它作为替代选项发布,而不是重新将其重新写入。但是你的观点是我评论它的原因,而没有把它写成答案 – TheLethalCoder

回答

4

你不能在构造函数中这样做;构造函数要么成功运行,要么不运行。如果它不是因为一个例外被提出,那么,这么多是因为默默地失败。你可能会捕获异常,但这基本上意味着你使用异常作为控制流机制,这是一个很大的不,不这样做!

一个解决方案类似于你在想什么是使用静态工厂方法:

public struct Point 
{ 
    public static bool TryCreatePoint(int x, int y, Bounds bounds, out Point point) 
    { 
     if (x and y are inside bounds) 
     { 
      point = new Point(x, y); 
      return true; 
     } 

     point = default(Point); 
     return false; 
    } 

    //... 
} 

和代码加分名单应该采取行动基于创建成功。

有趣的事实:如果你正在使用C#7的代码可能看起来会更加清晰:

public static (bool Succesful, Point NewPoint) TryCreatePoint(int x, int y, Bounds bounds) 
{ 
    if (x and y are inside bounds) 
     return (true, new Point(x, y)); 

    return (false, default(Point)); 
} 
+0

C#7特性的良好用法。 – HimBromBeere

+0

@HimBromBeere我喜欢c#7中的新特性。如果你曾经用过功能语言,模式匹配和本地元组支持将会非常棒。 – InBetween

+0

只是为了好奇:如何调用该方法? – HimBromBeere

1

你想做的事情根本不可能,一个类的实例要么完全创建,要么根本不创建。如果构造函数被调用的唯一方法是而不是实例化一个实例是通过抛出一个异常。

所以,你有这两个机会做到这一点:

  1. 提取物的方法Validate返回一个布尔值,可以从你的类的调用者调用。

    public struct Point 
    { 
        public int X, Y { get; } 
    
        public Point(int x, int y) 
        { 
        } 
    } 
    public bool Validate() { return -1000 <= X && X <= 1000 && -1000 <= Y and Y <= 1000; } 
    

    当然,你可以使用属性来做同样的事情。

  2. 在构造

    public Point(int x, int y) 
    { 
        if(x > 1000) throw new ArgumentException("Value must be smaller 1000"); 
        // ... 
    } 
    

但是最好的解决方案恕我直言,是验证输入之前,你甚至想创建一个点抛出一个例外,那就是检查传递给构造事先的参数:

if(...) 
    p = new Point(x, y); 
else 
    ... 
1

我能想到的三个选项:

  1. 让构造函数抛出你捕获的异常。如果你预计会有很多失败,这并不是很好。

  2. 在结构上有一个IsValid属性,您可以使用该属性在创建后将其过滤掉。

  3. 加载数据的事情也需要负责验证数据。这将是我的首选。你说“它感觉不对,因为调用者必须处理应该驻留在Point中的逻辑”,但我认为检查加载数据的责任是正确的,是加载数据而不是数据类型。你可以有它在构造函数中抛出一个ArgumentOutOfRangeException如果输入无效,现在你不再期待任何无效的东西作为皮带和护腕进行传递的东西。

0

说实话,点应不检查边界,所以调用者应该这样做。一个点在它们的X和Y可以操作的范围内是有效的(int.MinValue和int.MaxValue)。所以一个-1000000,2000000是一个有效的点。问题在于,这一点对您的应用程序无效,因此您的应用程序(调用者),即使用点的人应该具有该逻辑,而不是在点构造函数内部。

+1

恕我直言。那么,Point是一个自定义对象,应该代表我想要的东西。我可以说(5000; 5000)不是一个点,因为它不符合规则。我不会将int的技术限制与商业规则混合在一起。 – DasBoot

+2

是的,这是事实,但一点是一个点,(5000; 5000)仍然是一个有效的点。你的规则是商业规则,对我而言,这不应该影响Point(作为一个泛型类)。如果你想重复使用同一个类(在这个例子中是Point),你会怎么做?在另一个项目中,边界是不同的? –

+0

它确实取决于给定的域规则。如果你有一个明确的说法,即一个点决不能超过某个值,就是这样。这意味着没有这样或那样的情况(这是我的情况)。在其他情况下,我会完全同意你的观点,并且可以通过引入Point的子类型(例如LimitedPoint)来澄清。 – DasBoot

0

的Structs在C#是有趣的,所以我会添加一个 “有趣” 的方法来检查:

struct Point 
{ 
    int _x; 
    public int X 
    { 
     get { return _x; } 
     set { _x = value; ForceValidate(); } 
    } // simple getter & setter for X 

    int _y; 
    public int Y 
    { 
     get { return _y; } 
     set { _y = value; ForceValidate(); } 
    } // simple getter & setter for Y 

    void ForceValidate() 
    { 
     const MAX = 1000; 
     const MIN = -1000; 

     if(this.X >= MIN && this.X <= MAX && this.Y >= MIN && this.Y <= MAX) 
     { 
      return; 
     } 
     this = default(Point); // Yes you can reasign "this" in structs using C# 
    } 
} 
+0

WTF ?!难以想象的这实际上起作用。看起来对我来说很难看,但是它的工作。 – HimBromBeere

+0

@HimBromBeere我知道这很丑陋:D但是当我看到'this'的任务时,它就会让我大笑。 –

+0

你很明显应该得到这个奇怪的C#特征的+1(并且来自MS的球员值得在屁股中踢球)。 – HimBromBeere