类与函数[关闭]


73

即使对于没有任何编程经验但具有一定数学背景的人,函数也易于理解。另一方面,类似乎更难掌握。

假设我要创建一个类/函数,计算给定生日和当前年份的人的年龄。我应该为此创建类还是函数?还是选择取决于方案?

PS我正在使用Python,但是我想这个问题很普遍。


类用于更大的产品。简而言之,将汽车的螺母和螺栓视为对象,甚至汽车也是对象。如果您在编写示例程序很有趣,请坚持使用函数。
Saran-san

1

1
那么这种方法呢:从函数开始。如果您发现编写的几个函数始终需要相同的输入参数,则可以将这些函数实现为类的方法。
KevinSüdmersen20年

Answers:


137

创建一个函数。函数特定的事情,类特定的事情。

类通常具有方法,这些方法是与特定类相关联的函数,并且可以执行与该类所具有的事物相关联的操作-但是,如果您只想做某事,那么功能就是您所需要的。

本质上,类是一种将功能(作为方法)和数据(作为属性)分组为围绕某种事物的逻辑单元的方法。如果您不需要该分组,则无需上课。


9
这不是真的。类还允许按类型分派。如果您有一组或一组协作功能模板,那么很可能只是一次执行,但是使用类会使您有选择地在组中实现不同功能的可能性,以便获得模板的专业化。用普通函数很难做到这一点,因为没有类型可以充当它们之间的链接并允许
对子

...强制执行逻辑,可以将类函数(例如,作为vtable)包括在实例状态或数据中。函数本身不存储在实例中,而是对它们的某种间接引用。因此,最重要的是,您可能没有数据(指向vtable的指针除外),可能只有一个实例,但是如果您需要实现由以下各项组成的一系列相关算法,则类仍然是一个好主意协作功能。
memeplex'3

1
总结一下:将class功能和数据分组。在任何情况下,这样做都没有好处。我们具有将它们组合在一起的功能,数据类型和模块。即使您需要特殊的函数多态性:针对不同数据类型的不同函数实现,也比OOP类有更好的解决方案。考虑最终与语言无关的Haskell样式类型类。
粘土人

21

就像Amber在回答中所说的那样:创建一个函数。实际上,如果您有以下类似的事情,则不必上课:

class Person(object):
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

    def compute(self, other):
        """ Example of bad class design, don't care about the result """
        return self.arg1 + self.arg2 % other

在这里,您只是将一个函数封装在一个类中。这只会使代码的可读性和效率降低。实际上,该函数compute可以这样编写:

def compute(arg1, arg2, other):
     return arg1 + arg2 % other

仅当您拥有多个功能且保持内部状态(带有属性)有意义时,才应使用类。否则,如果要重新组合功能,只需在新.py文件中创建一个模块。

您可能会看这段视频(Youtube,大约30分钟),这解释了我的观点。Jack Diederich展示了为什么在这种情况下类是邪恶的,以及为什么它是如此糟糕的设计,尤其是在API之类的情况下。
这是一个很长的视频,但这是必看的。


4
如果需要100个功能,请编写100个功能。我认为完全没有必要或不会受益于课堂。除非您陷于OOP思维中。
粘土人

感谢您发布视频:这就是我的想法:不要创建只有两个方法且其中一个是init的类
KevinSüdmersen20年

9

类(或更确切地说,它们的实例)用于表示事物。类用于定义特定对象类(其实例)支持的操作。如果您的应用程序需要跟踪人员,则Person可能是一类;该类的实例代表您要跟踪的特定人员。

函数用于计算事物。他们接收输入并产生输出和/或产生影响。

类和函数并不是真正的替代品,因为它们不是相同的东西。考虑上一堂课以“根据给定他/她的生日和当前年份来计算一个人的年龄”确实没有任何意义。您可能会或可能不会有类以表示任何的概念PersonAgeYear,和/或Birthday。但是,即使Age是一堂课,也不应将其视为计算一个人的年龄;而一个人的年龄的计算结果中的实例Age类。

如果您在应用程序中对人员建模并且您有一个Person类,则将年龄计算作为该类的一种方法可能是有意义的Person。方法本质上是一个定义为类一部分的函数。正如我前面提到的,这就是“定义特定对象类支持的操​​作”的方式。

因此,您可以在人员类上创建一种用于计算人员年龄的方法(它可能会从人员对象中检索生日,并将当前年份作为参数)。但是计算仍然是由一个函数完成的(只是一个恰好是类上方法的函数)。

或者,您可以简单地创建一个接收参数的独立函数(可以从中检索出生年份的个人对象,也可以只是出生年份本身)。如您所注意到的,如果您还没有该方法自然所属的类,这会简单得多!您绝不应该仅仅为了进行操作而创建一个类。如果仅是该类的全部,那么该操作应该只是一个独立的函数。


1
类可以基于一组协作函数对复杂的计算进行建模。如果您有算法的模板,则可以将占位符表示为方法。然后,子类型化将使您可以专门化一些方法来获得可扩展的实现系列。请记住,多态类实例始终至少具有一些数据:某种vtable。因此,类不是用于概念而函数是用于计算是不正确的。
memeplex'3

1
您可以定义Person为anamedtuple并编写所需的任何功能。
粘土人

8

这取决于场景。如果你需要计算一个人的年龄,那么既然你想实现一个使用功能单一的特定行为。

但是,如果您要创建一个对象,其中包含一个人的出生日期(可能还有其他数据),并允许对其进行修改,那么计算年龄可能是与该人有关的许多操作之一,使用一个类代替。

类提供了一种将某些数据和相关操作合并在一起的方法。如果您仅对数据执行一项操作,则使用函数并将数据作为参数传递,您将获得等效的行为,而代码却不那么复杂。

请注意此类:

class A(object):
    def __init__(self, ...):
        #initialize
    def a_single_method(self, ...):
        #do stuff

实际上不是一个类,它只是一个(复杂的)函数。一个合法的类应该总是至少有两个方法(不计数__init__)。


我以前没有将围绕一种方法的类认为是不必要的复杂(我同意它们是这样,我在编码时有时只是在auto-OOPilot上),而只是发现了pylint,如果您的方法较少,它将标记为警告。而不是您的类上的两个(公共)方法。一旦我真正停止思考,这将是非常合理的。
dwanderson

在您的示例中,如果您希望可以修改和计算诸如age的值的Person记录,我更喜欢使用Person记录(Python中的namedtuple)并编写所需的任何函数。
粘土人

7

我知道这是一个有争议的话题,现在我很可能会被淘汰。但是这是我的想法。

对于我自己,我认为最好是尽可能避免上课。如果我需要复杂的数据类型,则可以使用简单的struct(C / C ++),dict(python),JSON(js)或类似的方法,即没有构造函数,没有类方法,没有运算符重载,没有继承等。使用类时,您会被OOP本身所迷惑(什么设计模式,什么应该是私有的,等等),而将注意力集中在您想首先编写的基本内容上。

如果您的项目变得又大又混乱,那么OOP就变得有意义了,因为需要某种直升机视图系统架构。“功能与类别”还取决于您要完成的任务。

功能

  • 目的:处理数据,操纵数据,创建结果集。
  • 何时使用:如果要执行此操作,请始终对函数进行编码:“ y = f(x)”

struct / dict / json / etc(而不是类)

  • 目的:存储属性/参数,维护属性/参数,重用属性/参数,使用属性/参数。后来。
  • 何时使用:如果您要处理一组属性/参数(最好是不可变的)
  • 不同的语言是相同的:struct(C / C ++),JSON(js),dict(python)等。
  • 总是比复杂的类更喜欢简单的struct / dict / json / etc(保持简单!)

类(如果它是新的数据类型)

  • 一个简单的角度:是带有附加方法的struct(C),dict(python),json(js)等。
  • 该方法仅应与存储在类中的数据/参数结合使用。
  • 我的建议:永远不要在类方法中编写复杂的东西(而是调用外部函数)
  • 警告:请勿将类误用作函数的伪命名空间!(这种情况经常发生!)
  • 其他用例:如果您想进行很多运算符重载,请使用类(例如,您自己的矩阵/向量乘法类)
  • 问自己:这真的是一种新的“数据类型”吗?(是=>类|否=>您可以避免使用类)

数组/向量/列表(用于存储大量数据)

  • 目的:存储许多相同数据类型的同类数据,例如时间序列
  • 建议#1:只使用您的编程语言已经拥有的。不要重新发明
  • 建议#2:如果您真的想要“ mysupercooldatacontainer类”,请重载现有的array / vector / list / etc类(例如“ class mycontainer:public std :: vector…”)

枚举(枚举类)

  • 我只说它
  • 建议#1:使用枚举加开关盒而不是过于复杂的OOP设计模式
  • 建议#2:使用有限状态机

除了命名空间重要。函数闭包可以在大多数时间(但并非总是)将名称空间私有化。类的主要用途是支持集体(以命名空间为中心)的模板,扩展和DRY驱动的多态性。
Cowbert

3

在回答您的问题之前:

如果您没有Person课程,那么首先必须考虑是否要创建Person课程。您打算经常重用概念Person吗?如果是这样,您应该创建一个Person类。(您可以通过传入变量的形式访问此数据,而不必担心凌乱和草率。)

要回答您的问题:

您可以访问他们的出生年份,因此在这种情况下,您可能要上一Person堂课someperson.birthdate。在这种情况下,您必须问自己,someperson.age一个可重用的值吗?

答案是肯定的。我们通常比出生日期更关心年龄,因此,如果出生日期是一个字段,则年龄绝对应该是派生字段。(在这种情况下,我们不会这样做:如果我们正在计算someperson.chanceIsFemalesomeperson.positionToDisplayInGrid或其他不相关的值,我们将不会扩展Person该类;您只是问自己:“另一个程序是否会关心我正在考虑使用的类来扩展该类的字段? “”这个问题的答案将决定您是否扩展原始类,或创建一个函数(或您自己的类,诸如此类PersonAnalysisData)。


假设您要一个Person“类型”。为什么要使用“类”而不是记录类型或已定义的命名元组?OOP是基于类的,人们已经习惯了,但是从风格上讲,许多人更喜欢简单的类型。
粘土

2

我将在这一方面与众不同,并提出另一种观点:

永远不要创建类。

依赖类有一种明显的趋势,导致编码人员创建肿且缓慢的代码。传递类(因为它们是对象)比调用一个函数并传递一两个字符串要多得多。正确的函数命名约定几乎可以完成创建类所能做的所有事情,并且只有一小部分的开销和更好的代码可读性。

但这并不意味着您不应该学习理解课程。如果您与其他人一起编码,人们会一直使用它们,并且您需要知道如何处理这些类。编写代码以依赖函数意味着代码将更小,更快,更易读。我已经看到了巨大的站点,这些站点仅使用快速且灵活的函数编写而成,并且我已经看到了具有最小功能的微型站点,这些函数严重依赖类并且不断崩溃。(当您拥有扩展包含作为其类的一部分的类的类的类时,您知道您已经失去了易于维护的所有外观。)

归根结底,您要传递的所有数据都可以轻松地由现有数据类型处理。

类被创建为精神上的拐杖,并且没有提供任何实际的额外功能,而从长远来看,它们过于复杂的代码倾向于创建该拐杖。


评论不作进一步讨论;此对话已转移至聊天
meagar

2

永远不要创建类。至少正在讨论Python中的OOP类。

考虑这个简单的类:

class Person(object):
    def __init__(self, id, name, city, account_balance):
        self.id = id
        self.name = name
        self.city = city
        self.account_balance = account_balance

    def adjust_balance(self, offset):
        self.account_balance += offset


if __name__ == "__main__":
    p = Person(123, "bob", "boston", 100.0)
    p.adjust_balance(50.0)
    print("done!: {}".format(p.__dict__))

与这个namedtuple版本:

from collections import namedtuple

Person = namedtuple("Person", ["id", "name", "city", "account_balance"])


def adjust_balance(person, offset):
    return person._replace(account_balance=person.account_balance + offset)


if __name__ == "__main__":
    p = Person(123, "bob", "boston", 100.0)
    p = adjust_balance(p, 50.0)
    print("done!: {}".format(p))

namedtuple方法更好,因为:

  • namedtuple具有更简洁的语法和标准用法。
  • 就理解现有代码而言,命名元组基本上是毫不费力地理解的。类更复杂。而且对于人类而言,课程可能变得非常复杂。
  • namedtuple是不可变的。管理可变状态会增加不必要的复杂性。
  • inheritance增加了复杂性,并隐藏了复杂性。

我看不出使用OOP类有什么优势。显然,如果您习惯了OOP,或者必须与需要Django之类的代码进行交互。

顺便说一句,大多数其他语言都有一些记录类型功能,例如namedtuples。例如,Scala具有案例类。这种逻辑在那里同样适用。


5
投票失败。namedtuple您所倡导的方法似乎没有提供任何方法封装或数据隐藏(除非我遗漏了一些东西)。真正的OO方法的两个主要优点是:您可以将数据以及对其执行作用的功能打包在一起,并且可以在对象中维护私有状态。如果您不这样做,那么您将只能重塑OOP,并且代码将更难使用,而不是更简单。
Marnen Laibow-Koser
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.