【问题标题】:Python classes, how to use them style-wise, and the Single Responsibility Principle [closed]Python 类,如何以样式方式使用它们,以及单一职责原则 [关闭]
【发布时间】:2013-02-11 14:56:21
【问题描述】:

我用 Python 编程已经有一段时间了,并且已经掌握了一些 Python 风格的知识,但在如何正确使用类方面仍然存在问题。 在阅读面向对象的讲座时,我经常发现像单一责任原则这样的规则

“单一职责原则说一个类应该有 一个,也是唯一一个改变的理由”

读到这里,我可能会想到将一个班级分成两个班级,例如:

class ComplicatedOperations(object):
    def __init__(self, item):
        pass

    def do(self):
        ...

    ## lots of other functions

class CreateOption(object):
    def __init__(self, simple_list):
        self.simple_list = simple_list

    def to_options(self):
        operated_data = self.transform_data(self.simple_list)
        return self.default_option() + operated_data

    def default_option(self):
        return [('', '')]

    def transform_data(self, simple_list):
        return [self.make_complicated_operations_that_requires_losts_of_manipulation(item)
                        for item in simple_list]

    def make_complicated_operations_that_requires_losts_of_manipulation(self, item):
            return ComplicatedOperations(item).do()

对我来说,这引发了许多不同的问题;喜欢:

  • 什么时候应该使用类变量或在类函数中传递参数?
  • ComplicatedOperations 类应该是一个类还是只是一组函数?
  • 是否应该使用__init__ 方法来计算最终结果。这是否会使该课程难以测试。
  • pythonists 的规则是什么?

回答后编辑:

所以,阅读奥古斯托的理论,我最终会得到这样的结论:

class ComplicatedOperations(object):
    def __init__(self):
        pass

    def do(self, item):
        ...

    ## lots of other functions

def default_option():
    return [('', '')]

def complicate_data(item):
    return ComplicatedOperations().do(item)

def transform_data_to_options(simple_list):
    return default_option() + [self.complicate_data(item)
                    for item in simple_list]

(还修正了 default_option 的一个小错误。)

【问题讨论】:

  • 也许这只是我的看法,但是如果类方法不共享任何 state —— 即如果你可以编写所有函数 没有self ,那么它没有理由成为一个类...
  • 重命名并标记tag:srp 以强调单一责任原则

标签: python class oop single-responsibility-principle


【解决方案1】:

什么时候应该使用类变量或在类函数中传递参数

在您的示例中,我会将item 传递给do 方法。此外,这与任何语言的编程有关,只给一个类它需要的信息(最小权限),并通过参数(依赖注入)传递不是算法内部的所有内容,所以,如果 ComplicatedOperations 没有需要 item 进行自身初始化,不要将其作为 init 参数提供,如果需要 item 来完成它的工作,请将其作为参数提供。

ComplicatedOperations 类应该是一个类还是只是一堆函数

我会说,取决于。如果您正在使用各种操作,并且它们绝对共享某种接口或合同。如果操作反映了一些概念,并且所有方法都与类相关,那么当然可以。但是,如果它们是松散且不相关的,您可能只是使用函数或重新考虑 Single Responsability 并将方法拆分为其他类

是否应该使用init方法来计算最终结果。这是否使该类难以测试。

不,init 方法是用于初始化的,你应该在一个单独的方法上完成它的工作。

顺便说一句,由于缺乏上下文,我不明白CreateOption 的作用是什么。如果只是如上图那样使用,还不如直接去掉……

【讨论】:

  • 附带说明,在 python 中,__init__ 确实不能用于计算最终结果,因为您无法从__init__返回任何内容。跨度>
  • 从来没有想过可能从来没有从 init 返回任何东西,但也不认为这是不可能的。
  • 编辑后有什么想法。
  • 你能看到这里吗?default_option 假设了一些关于 simple_list 的内容。该结构是一个列表,并且预先准备好的默认项目('','')是有意义的。这个 default_option 是否应该成为 simple_list 的一部分?
【解决方案2】:

我个人认为类是概念。我会定义一个 Operation 类,它的行为类似于操作,因此包含一个 do() 方法,以及可能使其唯一的所有其他方法/属性。

正如 mgilson 正确所说,如果您无法定义和隔离任何概念,那么简单的函数式方法可能会更好。

回答您的问题:

  • 当某个属性在实例之间共享时,您应该使用类属性(在 Python 中,类属性在编译时初始化,因此不同的对象将看到相同的值。通常类属性应该是常量)。使用实例属性在其方法中使用特定于对象的属性,而无需传递它们。这并不意味着您应该将所有内容都放在 self 中,而只是您考虑为您的对象表征的内容。使用传递的变量具有与您的对象无关的值,并且可能取决于外部对象的状态(或取决于程序的执行)。
  • 如上所述,我会保留一个类 Operation 并使用一组 Operation 对象来进行计算。
  • init 方法只会实例化对象并进行对象正确行为所需的所有处理(换句话说,使其可读可使用)。
  • 想想你试图模仿的想法。

【讨论】:

    【解决方案3】:

    一个类通常代表一种对象。类实例是该类型的特定对象。一个经典的例子是Animal 类。 cat 将是 Animal 的一个实例。类变量(我假设您的意思是那些属于实例而不是类对象本身的变量),应该用于实例的属性。例如,在这种情况下,colour 可以是一个类属性,可以设置为cat.colour = "white"bear.colour = "brown"。如果值可能来自类外的某个来源,则应使用参数。如果Animal 类有sleep 方法,它可能需要知道动物睡眠的持续时间和姿势。duration 将是该方法的参数,因为它与动物没有关系,但posture 将是一个类变量,因为它是由动物决定的。

    在python中,一个类通常用于将一组共享一个状态的函数和变量组合在一起。继续上面的例子,一个特定的动物有一个状态,它在它的方法之间共享并且由它的属性定义。如果您的类只是一组不依赖于类状态的函数,那么它们也可以很容易地成为单独的函数。

    如果使用__init__ 计算最终结果(由于__init__ 无法返回结果,因此必须将其存储在类的属性中),那么您还不如使用函数。然而,一个常见的模式是通过类的其他几个(有时是私有的)方法在__init__ 中进行大量处理。这样做的原因是,如果将大型复杂功能分解为更小的、不同的任务,然后每个任务都可以单独测试,那么它们通常更容易测试。但是,这通常只在需要类时才这样做。

    处理整个业务的一种方法是首先确定您需要什么功能。当你有一组函数或变量都作用于或应用于同一个对象时,是时候将它们移动到一个类中了。请记住,面向对象编程 (OOP) 是一种适用于某些任务的设计方法,但在本质上并不优于函数式编程(事实上,一些程序员会认为相反!),因此没有必要使用类,除非实际上存在需要。

    【讨论】:

      【解决方案4】:

      类是一种组织结构。所以,如果你不使用它们来组织,那你就做错了。 :)

      您可以将它们用于组织的几种不同的东西:

      1. 将数据与使用所述数据的方法捆绑在一起,定义代码将与该数据交互的一个点
      2. 将类似函数捆绑在一起,提供易于理解的 API,因为“每个人都知道”所有数学函数都在数学对象中
      3. 在方法之间提供定义的通信,设置具有定义接口的操作“传送带”。每一个操作都是一个黑盒子,可以任意改变,只要符合标准即可
      4. 抽象一个概念。这可以包括围绕数据库访问等中心思想的子类、数据、方法等等。然后这个类成为一个组件,您可以在其他项目中使用,只需最少的重组

      如果您不需要像上面那样做一些有组织的事情,那么您应该追求简单并以程序/功能风格进行编程。 Python 是关于拥有一个工具箱,而不是一把锤子。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-24
        • 2019-11-15
        相关资源
        最近更新 更多