【发布时间】:2010-09-30 19:30:03
【问题描述】:
什么是流畅的界面?我找不到一个好的定义,但我得到的只是我不太熟悉的语言(例如 C++)的长代码示例。
另外,什么是泄漏抽象?
谢谢
【问题讨论】:
-
这应该作为两个单独的问题发布。
标签: oop fluent-interface leaky-abstraction
什么是流畅的界面?我找不到一个好的定义,但我得到的只是我不太熟悉的语言(例如 C++)的长代码示例。
另外,什么是泄漏抽象?
谢谢
【问题讨论】:
标签: oop fluent-interface leaky-abstraction
泄漏抽象是底层现实的细节经常“泄漏”的抽象。
All abstractions lie 或多或少,但有时抽象与底层现实非常不匹配,导致弊大于利。
抽象中“泄漏”的一个简单示例可能是通常的浮点类型。它似乎代表一般实数,您可以使用它来执行基本计算。但有时您会遇到 1/3*3 != 1 或 1 + 10^-20 = 1 的情况。这就是实际实现细节泄漏并且抽象中断的情况。
【讨论】:
Neal Ford 在他的“高效程序员”一书中很好地解释和提供了流畅的接口示例。
带有 getter/setter 的传统对象或“bean”:
Car car = new CarImpl();
MarketingDescription des = new MarketingDescriptionImpl();
desc.setType("Box");
desc.setSubtype("Insulated");
desc.setAttribute("length", "50.5");
desc.setAttribute("ladder", "yes");
desc.setAttribute("lining type", "cork");
car.setDescription(desc);
用流畅的界面满足同样的需求:
Car car = Car.describedAs()
.box()
.length(50.5)
.type(Type.INSULATED)
.includes(Equipment.LADDER)
.lining(Lining.CORK);
【讨论】:
谢谢大家。
很好的描述。
我对流畅界面的想法是为了提高可读性。我总是可以阅读一系列方法以及其中一个与上一个/下一个方法的关系。
例如就像发布验证示例的发帖人(我之前写过类似的代码)。
【讨论】:
如果为副作用而执行的方法返回self,则面向对象的接口是流畅的,因此可以将这些方法链接在一起。
我第一次遇到流畅的接口是在 1990 年,当时 Modula-3 接口警察(我没有编造这个)要求所有初始化方法返回已初始化的对象。我相信这种用法早于“流畅界面”一词的诞生。
【讨论】:
Eric Evans 创造了一个流畅的接口术语,它只是方法链的另一个名称。 Martin Fowler 就这个主题写了一篇couple 的articles,但大致是这样的:
m_Window = window::with()
.width(l_Width)
.height(l_Height)
.title("default window")
.left(200)
.top(200)
.create();
Fluent 接口通常用于在不支持它们的语言中创建命名参数(例如 C++ 中的命名参数惯用语),或者在特定领域语言中创建命名参数,以使代码更流畅地阅读。
我已经看到它们被用于从图像处理库到正则表达式库和 3D 库的所有内容。其他示例包括树结构、列表或其他数据结构的构建。所有需要构造复杂对象(加载参数)的东西都可以使用 Fluent Interfaces 使其更具可读性。例如,将前面的示例与 CreateWindow 函数调用进行比较:
::CreateWindow(
"Window class",
"Window title",
dwStyle, X, Y,
nWidth, nHeight,
hWndPant, hMenu,
hInstance, NULL
);
【讨论】:
在流畅的接口中,对象的方法将返回对该对象的引用,以便您可以将方法调用链接在一起。
例如,在 NValidate 中,我这样做是为了简化参数验证:
public City GetCity(string zipCode)
{
zipCode.Assert("zipCode").IsNotNullOrEmpty().HasLength(5,10).Matches("\\d[5]-\\d[4]");
// Continue processing
}
不过,我不能谈论有漏洞的抽象。
【讨论】:
这是一个常规的日常界面:
public interface NotFluent
{
void DoA();
void DoB();
void DoC();
}
这是一个流畅的界面:
public interface Fluent
{
Fluent DoA();
Fluent DoB();
Fluent DoC();
}
最明显的区别是当我们返回一个 void 时,我们返回的是一个接口类型的实例。可以理解的是,返回的接口是CURRENT INSTANCE,而不是同类型的新实例。当然,这是不可强制执行的,在不可变对象(如字符串)的情况下,它是一个不同的实例,但可以认为是同一个实例,只是更新了。
以下是它们的使用示例:
NotFluent foo = new NotFluentImpl();
foo.DoA();
foo.DoB();
foo.DoC();
Fluent bar = new FluentImpl();
bar.DoA().DoB().DoC();
请注意,在链接不同的调用时,流畅的界面更易于使用。 IRL,请查看 Linq 扩展方法以及每个调用是如何设计为流入另一个调用的。没有任何方法返回 void,即使它是一个有效的结果。
【讨论】: