91

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

假设我想创建一个类/函数,根据他/她的生日年份和当前年份计算一个人的年龄。我应该为此创建一个类还是一个函数?还是选择取决于场景?

PS我正在研究Python,但我想这个问题是通用的。

4

8 回答 8

178

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

类通常具有方法,这些方法是与特定类相关联的函数,并执行与该类的事物相关联的事情 - 但如果您只想做某事,那么您只需要一个函数。

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

于 2013-08-13T07:15:39.417 回答
29

就像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

只有当你有超过 1 个函数并且保持内部状态(带有属性)有意义时,你才应该使用类。.py否则,如果您想重新组合功能,只需在新文件中创建一个模块。

你可能会看这个视频(Youtube,大约 30 分钟),它解释了我的观点。Jack Diederich 说明了为什么类在这种情况下是邪恶的,以及为什么它的设计如此糟糕,尤其是在 API 等方面。
这是一个相当长的视频,但它是必须看到的。

于 2013-08-13T07:26:54.910 回答
11

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

就我自己而言,我认为最好尽可能长时间地避免上课。如果我需要一个复杂的数据类型,我会使用简单的结构(C/C++)、字典(python)、JSON(js)或类似的,即没有构造函数、没有类方法、没有运算符重载、没有继承等。使用类时,您可能会被 OOP 本身(什么设计模式,什么应该是私有的,等等)迷住了,而将注意力集中在您想要编码的基本内容上。

如果您的项目变得庞大而混乱,那么 OOP 开始变得有意义,因为需要某种直升机视图系统架构。“功能与类”也取决于您面前的任务。

功能

  • 目的:处理数据、操作数据、创建结果集。
  • 何时使用:如果您想这样做,请始终编写一个函数:“y=f(x)”</li>

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

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

类(如果是新数据类型)

  • 一个简单的观点:是一个结构(C),字典(python),json(js)等,附加了方法。
  • 该方法仅应与存储在类中的数据/参数结合使用才有意义。
  • 我的建议:永远不要在类方法中编写复杂的代码(而是调用外部函数)
  • 警告:不要滥用类作为函数的假命名空间!(这种情况经常发生!)
  • 其他用例:如果你想做很多运算符重载,那么使用类(例如你自己的矩阵/向量乘法类)
  • 问问自己:它真的是一种新的“数据类型”吗?(是 => 类 | 否 => 你能避免使用类)

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

  • 用途:存储大量相同数据类型的同质数据,例如时间序列
  • 建议#1:只需使用您的编程语言已有的内容。不要重新发明它
  • 建议#2:如果你真的想要你的“class mysupercooldatacontainer”,那么重载一个现有的array/vector/list/etc 类(例如“class mycontainer : public std::vector...”)

枚举(枚举类)

  • 我只是提一下
  • 建议#1:使用枚举加 switch-case 而不是过于复杂的 OOP 设计模式
  • 建议#2:使用有限状态机
于 2016-04-03T13:17:41.880 回答
10

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

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

类和函数并不是真正的替代品,因为它们的用途不同。考虑创建一个类来“根据他/她的生日年份和当前年份计算一个人的年龄”真的没有意义。您可能有也可能没有类来表示PersonAgeYear和/或的任何概念Birthday。但即使Age是一个班级,也不应该被认为是计算一个人的年龄;相反,计算一个人的年龄会产生一个类的实例Age

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

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

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

于 2013-08-13T07:56:16.617 回答
9

这取决于场景。如果您只想计算一个人的年龄,请使用函数,因为您想实现单个特定行为。

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

类提供了一种将一些数据和相关操作合并在一起的方法。如果您对数据只有一个操作,那么使用函数并将数据作为参数传递,您将获得等效的行为,并且代码不太复杂。

请注意,此类:

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

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

于 2013-08-13T07:23:53.983 回答
3

Before answering your question:

If you do not have a Person class, first you must consider whether you want to create a Person class. Do you plan to reuse the concept of a Person very often? If so, you should create a Person class. (You have access to this data in the form of a passed-in variable and you don't care about being messy and sloppy.)

To answer your question:

You have access to their birthyear, so in that case you likely have a Person class with a someperson.birthdate field. In that case, you have to ask yourself, is someperson.age a value that is reusable?

The answer is yes. We often care about age more than the birthdate, so if the birthdate is a field, age should definitely be a derived field. (A case where we would not do this: if we were calculating values like someperson.chanceIsFemale or someperson.positionToDisplayInGrid or other irrelevant values, we would not extend the Person class; you just ask yourself, "Would another program care about the fields I am thinking of extending the class with?" The answer to that question will determine if you extend the original class, or make a function (or your own class like PersonAnalysisData or something).)

于 2013-08-13T07:24:35.107 回答
3

我将在这个问题上脱颖而出 (7年后编辑:我不再是这个问题的唯一声音,有一个完整的编码运动可以做到这一点,称为“函数式编程”)并提供一个替代方案观点看法:

永远不要创建类。始终使用函数。

编辑:研究一再表明,类是一种过时的编程方法。几乎所有关于该主题的研究论文都与函数式编程而不是面向对象编程有关。

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

这并不意味着你不应该学习理解类。如果你和其他人一起编码,人们会一直使用它们,你需要知道如何处理这些类。编写依赖函数的代码意味着代码将更小、更快、更易读。我见过只使用快速而快速的函数编写的大型网站,也见过一些功能极少的小型网站,这些网站严重依赖类并且经常中断。(当您的类扩展包含作为其类的一部分的类时,您知道您已经失去了所有易于维护的外表。)

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

类是作为精神支柱而创建的,并没有提供实际的额外功能,从长远来看,它们倾向于创建的过于复杂的代码会破坏该拐杖的意义。

编辑:7 年后更新...... 最近,编码方面的新运动一直在验证我提出的这一点。它是用函数式编程取代面向对象编程 (OOP) 的运动,它基于 OOP 的许多这些确切问题。有很多研究论文展示了函数式编程相对于面向对象编程的好处。除了我提到的几点之外,它还使重用代码变得更加容易,使错误修复和单元测试更快更容易。老实说,有很多好处,使用 OOP 而不是功能的唯一原因是与尚未更新的遗留代码的兼容性。

于 2015-08-13T23:25:29.560 回答
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__))

vs 这个 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 方法更好,因为:

  • namedtuples 具有更简洁的语法和标准用法。
  • 在理解现有代码方面,namedtuples 基本上是不费吹灰之力的。类更复杂。对于人类来说,类可能会变得非常复杂。
  • 命名元组是不可变的。管理可变状态会增加不必要的复杂性。
  • inheritance增加了复杂性,并隐藏了复杂性。

我看不到使用 OOP 类的单一优势。显然,如果您习惯于 OOP,或者您必须与需要类的代码(如 Django)进行交互。

顺便说一句,大多数其他语言都有一些记录类型功能,比如命名元组。例如,Scala 就有案例类。这个逻辑同样适用于那里。

于 2016-05-22T00:13:20.420 回答