我正在编写一个用于创建几何形状的API,并且在编写我的方法时遇到了一些困难。
我们来看一个简单的例子:创建一个圆圈。我们大多数人可能熟悉像graphics.drawEllipse(x, y, w, h)
这样的方法。要绘制圆,您需要知道左上角坐标,以及圆的宽度和高度。
我的API旨在让开发人员可以轻松地使用各种信息绘制形状,而无需进行大量的数学运算 - 这对于圆形来说是微不足道的,但对于其他形状则更为复杂。例如,您还应该能够根据其中心坐标和半径,或者左上角和右下角坐标绘制圆形。
所以我有一个Circle
类,其工厂方法如下:
Circle.createWithCenterAndRadius(cx, cy, r)
Circle.createWithBoundingBox(x1, y1, x2, y2)
Circle.createWithWidthAndHeight(x, y, w, h)
我觉得这里可能有“代码味道”,但我不确定。一方面,这些工厂方法必然是描述性的。另一方面,我可以预见这些方法名称会失控。例如,我如何命名一个三角形工厂方法,它创建一个给定点的三角形,一边的长度,一个角度和另一边的长度? Triangle.createWithPointSideAngleAndSide(x, y, side1, angle, side2)
?那只是邪恶吗?
如果你要使用这个API,这样的方法名称会对你好吗?您对如何使方法名称更加清晰有建议吗?
答案 0 :(得分:14)
您可以将圈子方法更改为
Circle.FromCenterAndRadius(...)
Circle.FromBoundingBox(...)
Circle.FromWidthAndHeight(...)
这意味着你以一种简洁的方式从不同的表现形式创建圈子......
答案 1 :(得分:11)
任何不支持命名参数的语言都可以。如果语言支持命名参数,我更喜欢简短的Create,只有明显的参数名称。
对于具有命名参数的语言,您将:
Circle.Create(
centerX = cx,
centerY = cy,
radius = r
);
另一个更复杂的选择,就是一个流畅的界面(但可能太多了):
circleBuilder.Center(cx,cy).Radius(r)
circleBuilder.Center(x,y).Width(w).Height(y)
circleBuilder.BoundWith().Left(x1,y1).Right(x2,y2)
Center返回仅允许Radius或Width的中间类的实例。然后BoundWith返回一个只允许Left。
答案 2 :(得分:8)
我认为你的描述方法没有任何问题 - 它们是紧凑的,并准确描述了正在发生的事情。图书馆的用户将毫不怀疑您的方法的功能,也不会是维护程序员。
如果你真的担心暴露大量的工厂方法,你也可以在这里应用一些设计模式 - 比如让工厂方法有属性类。你可以拥有一个CircleProperties类,其中包含CenterX,CenterY,Radius,(bool)UseCenterX,(bool)UseCenterY等属性,然后将其传递给公共工厂方法,该方法将确定要使用的(私有)工厂方法。
假设C#:
var circleProperties = new CircleProperties()
{
CenterX = 10,
CenterY = -5,
Radius = 8,
UseCenterX = true,
UseCenterY = true,
UseCenterRadius = true
};
var circle = Circle.Create(circleProperties);
答案 3 :(得分:6)
我的第一直觉是拥有更多类型,这样可以实现更直观的方法重载。
// instead of Circle.createWithCenterAndRadius(cx, cy, r)
Circle.create( new Point(cx,xy), r);
// instead of Circle.createWithBoundingBox(x1, y1, x2, y2)
Circle.create( new Point(x1,y1), new Point(x1,y1) );
// or even...
Circle.create( new Box(p1,p2));
// instead of Circle.createWithWidthAndHeight(x, y, w, h)
Circle.create( new Point(x,y), w, h);
与Point一样,您可以定义距离(允许不同的单位)
如果这种风格适合你,请考虑为什么需要工厂方法而不是构造函数。
Circle c = new Circle(new Point(cx,xy), r);
答案 4 :(得分:1)
对于不支持命名参数的语言,将方法名称设置为非常简单,如 Circle.create ,然后只需添加一个额外的输入标志字符串(如“center”)就更清晰了吗?或“边界”)表示如何根据输入变量数和类型难以区分的情况解释输入值?对此的缺点是它需要方法内部的额外逻辑来处理不同类型的输入参数,并且还要求用户记住标志选项。
答案 5 :(得分:1)
我会使用CreateTriangle方法并让重载显示所需的不同信息。
E.g。
Circle.CreateCircle(cx, cy, r)
Circle.CreateCircle(point1, point2)
Circle.CreateCircle(point, width, height)
答案 6 :(得分:0)
是的,这更像是一个元答案,但我建议您先了解如何在Apple's Cocoa中完成命名。
答案 7 :(得分:0)
你的直觉是正确的 - 以这种方式创造事物的整个模式是 - 如果。
除非这些仅使用一次或两次,否则它们将变得非常混乱。如果你正在创建一个有5个圆圈和3个三角形的形状,那将是一团糟。
除了一个简单的例子之外,任何事情都可能最好用某种数据驱动的实现来完成。
为此,使用字符串,哈希或XML来定义形状可能非常有用。
但这一切都取决于你对它们的使用方式。
我在Java中创建Swing控件时遇到了同样的问题。你最后得到一行“new Button()”后跟一堆.set属性调用以及一行代码将值复制到一个对象(或添加一个监听器),以及一行来重置值..
这种样板应该永远不会出现在代码中,所以我通常会尝试找到一种方法来驱动数据,动态地将控件绑定到对象 - 为此,一个描述性的基于字符串的语言将非常有帮助
答案 8 :(得分:-1)
我知道,我知道。对于C / C ++ / Java人来说,这听起来非常疯狂,但问题和所有这些答案中给出的示例清楚地表明了CamelCaseNaming真正的糟糕,糟糕的惯例。
让我们再看一下原来的例子:
Circle.createWithCenterAndRadius(cx, cy, r)
Circle.createWithBoundingBox(x1, y1, x2, y2)
Circle.createWithWidthAndHeight(x, y, w, h)
现在让我们摆脱那种骆驼案例符号
Circle.create_with_center_and_radius(cx, cy, r)
Circle.create_with_bounding_box(x1, y1, x2, y2)
Circle.create_with_width_and_height(x, y, w, h)
这可能看起来非常不合适,但说实话:哪个版本更容易阅读?