【问题标题】:Benefits of using class func vs func vs no class declaration使用类 func 与 func 与无类声明的好处
【发布时间】:2015-05-12 17:20:15
【问题描述】:

好的,所以我的项目中有一堆帮助函数,我最初在一个名为 Animate 的类中拥有这些函数。我想知道声明 func vc 类 func 有什么好处。

让我们以这个类为例:

class Animate{
    func moveView(...){
        ...
    }
}

所以我相信如果我有一个类函数,我就不必实例化这个类。

Animate.moveView(...)

如果我只是用 func 声明函数,它将是:

Animate().moveView(...)

但是,如果我根本不将文件声明为类:

func moveView(...){
    ...
}

当我调用函数时,它只是:

moveView(...)

没有说明代码来自哪里,它可以像这样在项目的任何地方使用。

这三种方式的优缺点是什么?是不是宣布一个班级不好的做法?或者,是否有一些边缘情况表明这非常有用?例如,在我的情况下,我不需要类,因为我只是创建辅助函数而不是对象。

提前感谢您对此的任何见解!

【问题讨论】:

  • 除非 finc 在本地 mvc 中,否则我喜欢类,因为你知道它们来自哪里。尝试在混搭的地方调试其他人的代码,而您却一无所知。

标签: swift


【解决方案1】:

好的。实例方法 vs 类方法 vs 全局方法。

(术语方法和函数可以互换。方法意味着由对象实现的功能,因此我倾向于术语方法而不是术语函数。)

实例方法是由类的实例执行的方法。您必须拥有该类的实例才能调用实例方法。

实例方法可以访问它们所属对象的实例变量,因此对象可以在调用之间保存状态信息。 (在网络类中,您可以创建多个下载对象,每个下载对象管理从不同 URL 下载不同文件的单个文件,并且每个可能有不同的委托,它会在下载完成时通知它)

类方法由类本身调用,而不是由实例调用。这可以使调用辅助函数变得简单,而无需管理对象来为您完成这项工作。由于类方法不与类的实例对话,它们不能为每个对象保留不同的状态信息。例如,您可能有一个对字符串执行本地化功能的实用程序类。本地化过程是独立的。您调用一个类函数并传入一个字符串和您希望它本地化的语言,然后它会将结果返回给您。无需在调用之间保持状态。这样的调用可能看起来像

let frenchString = 
  LocalizationUtils.localizeString("English String", 
    toLanguage: "French")

全局函数不属于任何特定类。它们对于定义它们的整个模块是全局的。它们类似于类函数,只是它们不是特定于特定类的。

【讨论】:

  • 那么,在您看来,全局函数是一种代码味道,我应该改用类方法吗?
  • 不,这三个都有自己的位置。类函数确实具有为函数创建清晰的“名称空间”的优点,以便将它们逻辑地组合在一起。 Swift 中的运算符重载是全局函数的一个示例。如果你为一个类定义一个“+”运算符,它必须是一个全局函数,这样你就可以在任何上下文中调用它。
【解决方案2】:

我同意(并赞成)@Duncan C 的回答,但只是想我会提出其他几个优点/缺点。

比起类方法,我更喜欢全局函数,因为全局函数不会弄乱我的类。我喜欢让我的课保持精简。全局函数可以保存在一个单独的文件中,我可以根据需要将它们复制并粘贴或导入到给定的项目中。所以我的项目中可能有一个名为AnimateHelperFunctions 的文件,它只是与该类相关的全局函数。一个给定的项目可能只需要其中的几个,或者大多数,或者那些加上我发现我需要的更多。我可以从文件中删除我在给定项目中不使用的那些,以保持该文件整洁和修剪。

我只是认为全局函数更加模块化,并鼓励我为单个函数分解单个任务 - 一个很好的全局辅助函数,它只做一件事并且完美地完成它,有时也可以抽象或通用并用于其他情况也是如此。您可能在某个项目中意识到您不需要该类 - 您只需要它的辅助函数,它就在那里。

我更喜欢一百个简单的全局函数,我可以从一个庞大的臃肿类中挑选。

当然,您可以通过扩展完成许多相同的事情,并且在某种程度上这是一个品味问题,因为除了使用类方法之外,类方法和全局函数之间几乎没有区别(任何?)你必须拖着整个班级。

与全局状态不同,全局函数没有任何危险。当然,任何人都可以调用它,但类方法也是如此,全局函数只能对你传递给它的参数进行操作。

【讨论】:

  • 感谢您提供更多信息 :)
  • 对我来说听起来你需要更多地采用 OOP 心态。并不是你的主要观点是错误的,而是一个设计良好的 OOP 项目看起来比具有所有非常干净、完美的全局功能的同一个项目更干净、更容易完成 100 倍。我有一个我自己管理的项目,其中包含 300 多个文件和 200,000 行代码。每次我进行认真的重构时,项目总是看起来更好,并且变得更加面向对象。我有 0 个全局函数。我有很多静态函数。如果我想创建一个全局函数,作为通用类型的扩展几乎总是更好。
  • 对我来说,听起来您已经无意识地采用了函数式编程思维,这比 OOP 有很多好处。我有一个我自己管理的项目,仅包含 576 个源文件和超过 100K 行代码。每次我进行认真的重构时,它都会保持功能并减少 OOP。与它一起工作是一种乐趣。现在,我也有 0 个全局函数,但它们都是 all 静态的。我拥有的所有数据也是静态的。我认为这在保留函数式编程的灵活性、可组合性等的同时挑选了 OOP 的最佳部分。它避免了急躁!
【解决方案3】:

对我来说,我使用staticclass 方法来控制类级别的属性,或者如果我必须返回特定classstruct 的自定义实例。例如考虑我在struct下面。

struct Person {
  let firstName: String
  let lastName: String
}

现在,如果我正在编写一些测试用例,我需要在我的许多测试类中使用特定名称 John 初始化 Person 的实例,我可以创建一个帮助程序 static 方法。

extension Person {
    static func john() -> Person {
        return Person(firstName: "John", lastName: "Appleseed")
    }
}

let john = Person.john() // I could also create a static property instead, but it's a matter of personal choice and situation.

在上述情况下,我也可以将 john 设为全局函数,但对我来说,它会非常模糊且不可读。

我能想到的另一个我更喜欢静态方法的地方是返回 enum 的案例计数。

enum Mood {
    case happy
    case angry
    case lazy
    case high

    static func count() -> Int {
        return 4
    }
}

有些地方我使用全局函数。我使用全局函数进行日志记录。

func log(screenEvent name: String)  {
    let tracker = GAI.sharedInstance().defaultTracker
    tracker.set(kGAIScreenName, value: screenName)
    let builder = GAIDictionaryBuilder.createScreenView()
    tracker.send(builder.build() as [NSObject : AnyObject])
}

在内部,该方法使用sharedInstance,创建一个全局方法使其在项目中的任何地方都可以轻松访问,就像在控制台中记录输出的print函数一样,但这是在一些自定义服务中登录。

我通常在我的项目中包含的其他一些全局函数是 GCD 助手。

func delay(delay:Double, closure: dispatch_block_t) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}


func backgroundTask(closure: dispatch_block_t) {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), closure)
}

func mainThreadTask(closure: dispatch_block_t) {
    dispatch_async(dispatch_get_main_queue(), closure)
}

这些函数不需要关于类的任何信息,因此将它们设为全局而不是将它们包装在类中是有意义的。

关于instance 方法,正如@Duncan C 所回答的那样,当您想要保持状态时,它们会在实例上调用。下面的示例显示了静态方法和实例方法的用法。

enum TapType {
    case water
    case beer
}

struct Tap {
    let tapType: TapType

    //static method
    static func unlimitedBeer() -> Tap {
        let beer = Tap(tapType: .beer)
        beer.turnOn(forDuration: Float.greatestFiniteMagnitude)
        return beer
    }

    //instance method: will do operation on a particular instance of `Tap`.
    func turnOn(forDuration duration: Float) {
        //implementation
    }
}

let unlimitedBeer = Tap.unlimitedBeer()

您始终可以使用convenience 初始化程序来初始化具有自定义行为的对象,但同样,这是一个选择问题。在上面的例子中,我想不出任何convenience 初始化器可以给我无限量的啤酒。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-20
    • 2010-11-12
    相关资源
    最近更新 更多