让接口程序化而不是语义化意味着什么

What does it mean to make an interface programmatic instead of semantic?

本文关键字:语义 意味着 什么 接口 程序化      更新时间:2023-10-16

现在我正在读《代码完整,第二版》一书。在第6.2章中,作者讨论了类和接口,并给出了以下建议:

尽可能使接口程序化而非语义化
每个接口由一个程序部分和一个语义部分组成。程序部分由数据类型和其他属性组成可以由编译器强制执行的接口的。语义界面的一部分包括关于将使用接口,而编译器无法强制执行该接口。这个语义接口包括诸如"例程A必须在RoutineB之前调用"或"如果dataMember1不是,则RoutineA将崩溃在传递给例程A之前已初始化。"语义接口应该在注释中记录,但尽量减少接口取决于文件。接口的任何方面编译器强制执行是一个可能被误用的方面。寻找将语义接口元素转换为程序化的方法接口元素。

我理解作者所说的语义和编程的含义,但我不明白的是如何将语义接口函数转换为编程函数。他提到使用断言或其他技术来实现这一点。

让我们以作者为例:

必须在例程B之前调用例程A。我假设这些例程是接口(公共函数)的一部分,因为这就是丑陋的接口。

所以,如果在调用RoutineB之前确实必须调用RoutineA,您将如何使用断言或其他技术重新组织这个接口?

我对此有一些想法,但我不确定这些想法是否正确。

假设RoutineA和RoutineB都是公共函数,这意味着它们应该彼此独立可用,但唯一的限制是,在独立调用RoutineNB之前,必须首先调用RoutineA。

如果确实是这样的话,那么你将如何使用断言或其他技术来解决这个问题?

如果我的假设有错误,请随时更正。

此外,我有意将其发布在当前标签下,因为像面向对象编程/设计/接口这样的标签点击率很低,这意味着我的问题可能不会经常出现。

两个可能的答案:

  • assert( a_called );放入例程RoutineB
  • 从类的接口中删除RoutineB,并使RoutineA返回一个具有RoutineB成员的新对象

在后一种情况下,您可能希望将外部类作为指向内部类的智能指针的精简包装器,然后RoutineA只复制指针。

class Impl;
class SecondClass;
class FirstClass
{
    std::shared_ptr<Impl> pimpl;
public:
    FirstClass();
    SecondClass RoutineA(...);
    ...
}
class SecondClass
{
    std::shared_ptr<Impl> pimpl;
    friend class FirstClass;
    SecondClass(const std::shared_ptr<Impl>& impl) : pimpl(impl);
public:
    void RoutineB(....);
}
SecondClass FirstClass::RoutineA(...)
{
    // Do stuff
    return SecondClass(pimpl);
}

您也可以使用unique_ptr来完成此操作,但代码要长一些。

我认为您应该在运行时检查限制("RoutineA必须在RoutineB之前调用")。标记中的编程语言在编译时无法检查这样的限制。您的代码可能看起来像:

RoutineA()
{
  aCalled = true;
  //some operations..
}
RoutineB()
{
  if(!aCalled) // or an assertion
  {
    throw NotReadyException("RoutineA must be called");
  }
  //some operations..
}