小功能与在相同功能中保持依赖功能


15

我有一个类,它设置节点数组并以类似图形的结构将它们彼此连接。最好是:

  1. 保留用于初始化和连接节点的功能
  2. 在两个不同的函数中具有初始化和连接功能(并具有必须调用这些函数的依赖顺序-尽管请记住,这些函数是私有的。)

方法1 :(因为一个功能要做两件事,但它会将依赖的功能分组在一起-除非先进行初始化,否则切勿连接节点。)

init() {
    setupNodes()
}

private func setupNodes() {
    // 1. Create array of nodes
    // 2. Go through array, connecting each node to its neighbors 
    //    according to some predefined constants
}

方法2 :(从某种意义上说,这是自记录的,但决不要在setupNodes()之前调用BUT connectNodes(),因此使用类内部知识的任何人都需要了解此顺序。)

init() {
    setupNodes()
}

private func setupNodes() {
    createNodes()
    connectNodes()
}

private func createNodes() {
    // 1. Create array of nodes
}

private func connectNodes() {
    // 2. Go through array, connecting each node to its neighbors 
    //    according to some predefined constants
}

兴奋地听到任何想法。



解决此问题的一种方法是定义只能用于创建最终对象的中间对象。这并不总是正确的解决方案,但是如果界面用户需要以某种方式操纵中间状态,则很有用。
乔尔·科内特

Answers:


23

您正在处理的问题称为时间耦合

您应该担心此代码的可理解性:

private func setupNodes() {
    createNodes();
    connectNodes();
}

我可以猜测发生了什么,但请告诉我这是否会使发生的事情更加清晰:

private func setupNodes() {
    self.nodes = connectNodes( createNodes() );
}

这具有额外的好处,即与修改实例变量的耦合较少,但对我而言可读性是第一。

这使得connectNodes()明确依赖于节点。


1
感谢您的链接。由于我的函数是私有函数,并且是从构造函数中调用的(在Swift中为init()),因此我认为我的代码不会像您链接的示例那样糟糕(外部客户端无法使用实例化实例空实例变量),但我有类似的气味。
mcfroob

1
您添加的代码更具可读性,因此我将以这种样式进行重构。
mcfroob

10

功能分开,有两个原因:

1.专用功能在这种情况下是专用的。

您的init函数是公共的,它的接口,行为和返回值是您需要担心的保护和更改。无论使用哪种实现,您都希望使用该方法得到的结果是相同的。

由于其余功能都隐藏在该私有关键字的后面,因此您可以根据自己的喜好实现它……因此,即使一点一点依赖于另一个首先调用,您也可以使其变得更好且模块化。

2.将节点彼此连接可能不是私有功能

如果您想在某个时候将其他节点添加到阵列怎么办?您是否销毁了现有的设置,然后将其完全重新初始化?还是将节点添加到现有阵列中,然后connectNodes再次运行?

connectNodes如果尚未创建节点数组,则可能会有理智的响应(引发异常?返回空集?您必须决定哪种方法对您的情况有意义)。


我以与1相同的方式进行思考,如果未初始化节点,则可能引发异常或某些异常,但这并不是特别直观。感谢您的回复。
mcfroob

4

您可能还会发现(取决于每项任务的复杂程度),这是拆分另一个课程的好方法。

(不知道Swift是否以这种方式工作,而是伪代码:)

class YourClass {
    init(generator: NodesGenerator) {
        self.nodes = connectNodes(generator.make())
    }
    private func connectNodes() {

    }
}

class NodesGenerator {
    public func make() {
        // Return some nodes from storage or make new ones
    }
}

这将创建和修改节点的职责划分为单独的类:NodeGenerator仅关心创建/检索节点,而YourClass仅关心连接给定的节点。


2

除了这是私有方法的确切目的之外,Swift还使您能够使用内部函数。

内部方法非常适合仅具有一个调用站点的函数,但是感觉它们没有理由证明它们是单独的私有函数。

例如,通常有一个公共的递归“入口”函数,该函数检查先决条件,设置一些参数并委托一个私有的递归函数来完成工作。

这是在这种情况下的外观示例:

init() {
    self.nodes = setupNodes()

    func setupNodes() {
        var nodes = createNodes()
        connect(Nodes: nodes)
    }

    private func createNodes() -> [Node]{
        // 1. Create array of nodes
    }

    func connect(Nodes: [Node]) {
        // 2. Go through array, connecting each node to its neighbors 
        //    according to some predefined constants
    }
}

注意如何使用返回值和参数传递数据,而不是改变共享状态。乍一看,这使得数据流更加明显,而无需跳入实施过程。


0

您声明的每个函数都带有增加文档并使文档泛化的负担,以便程序的其他部分可以使用它。它也带来了理解文件中其他功能如何为读取代码的人使用它的负担。

但是,如果程序的其他部分未使用它,则不会将其作为单独的函数公开。

如果您的语言支持,则通过使用嵌套函数,您仍然可以一事无成

function setupNodes ()  {
  function createNodes ()  {...} 
  function connectNodes ()  {...}
  createNodes() 
  connectNodes() 
} 

声明的位置非常重要,在上面的示例中,很明显,不需要任何其他线索,就可以将内部函数仅在外部函数的主体内使用。

即使您将它们声明为私有函数,我也认为它们对于整个文件仍然可见。因此,您需要在主函数的声明附近声明它们,并添加一些文档,以阐明它们仅由外部函数使用。

我认为您不必严格执行任何一项。最好的做法视情况而定。

将其分解为多个功能肯定会增加理解为什么会有3个功能以及它们如何相互配合的开销,但是,如果逻辑很复杂,那么所增加的开销可能会比分解复杂的逻辑所带来的简单性要少得多。分成更简单的部分。


有趣的选择。就像您说的那样,我认为为什么这样声明函数可能会有些令人费解,但是它将使函数依赖性保持良好的状态。
mcfroob

要回答此问题中的一些不确定性:1)是,Swift支持内部功能,2)它具有两个级别的“私有”。private仅允许在封闭类型(结构/类/枚举)内fileprivate进行访问,而允许对整个文件进行访问
Alexander-Reinstate Monica
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.