Python类的使用风格与单一职责原则
我已经学习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__
方法应该用来计算最终结果吗?这样会不会让这个类变得难以测试?- 对于Python程序员来说,有哪些规则呢?
在得到答案后编辑:
所以,阅读了Augusto的理论后,我会得到这样的结果:
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的小错误。)
4 个回答
我什么时候应该使用类变量,或者在类函数中传递参数?
在你的例子中,我会把 item
作为参数传递给 do
方法。此外,这个原则适用于任何编程语言:给类提供它所需的信息(最小权限),而把那些不是你算法内部需要的东西通过参数传递(依赖注入)。所以,如果 ComplicatedOperations
不需要 item
来初始化自己,就不要把它作为初始化参数;如果它需要 item
来完成工作,那就把它作为参数传递。
ComplicatedOperations
类应该是一个类,还是只是一堆函数?
这要看情况。如果你在使用各种操作,并且它们有一些共同的接口或约定,那当然可以。如果这些操作反映了某个概念,并且所有方法都和这个类相关,那也没问题。但如果它们之间关系不大,你可能只需要用函数,或者重新考虑一下单一职责原则,把这些方法拆分到其他类中。
初始化方法应该用来计算最终结果吗?这样会让这个类难以测试吗?
不会,初始化方法是用来初始化的,你应该把具体的工作放在一个单独的方法里。
另外,由于缺乏上下文,我不太明白 CreateOption
的作用。如果它只是像上面那样使用,你可能可以直接把它去掉……
类是一种组织结构。所以,如果你不利用它们来进行组织,那就是在走错路。 :)
你可以用类来做几件不同的事情来帮助组织:
- 把数据和使用这些数据的方法放在一起,这样代码就能在一个地方处理这些数据。
- 把相似的功能放在一起,这样就能提供一个易于理解的接口,因为“大家都知道”所有的数学功能都在数学对象里。
- 提供方法之间的明确沟通,建立一个“传送带”式的操作流程,每个操作都是一个黑箱,只要遵循标准,它们可以随意改变。
- 抽象一个概念。这可以包括子类、数据、方法等等,围绕某个中心思想,比如数据库访问。这个类就成了你在其他项目中可以用的组件,修改的工作量很小。
如果你不需要像上面那样进行组织,那就应该追求简单,采用过程式或函数式编程。Python的核心在于拥有一个工具箱,而不是一把锤子。
我个人觉得类就像是一些概念。我会定义一个操作类(Operation),它的行为就像一个操作,所以里面会有一个 do() 方法,还有其他可能让它独特的方法和属性。
正如 mgilson 所说的,如果你无法清晰地定义和区分某个概念,也许用简单的函数式方法会更好。
来回答你的问题:
- 当某个属性在多个实例之间是共享的时候,你应该使用类属性(在 Python 中,类属性是在编译时初始化的,所以不同的对象会看到相同的值。通常,类属性应该是常量)。使用实例属性来存储特定于对象的属性,这样在方法中使用时就不需要传递它们。这并不是说你应该把所有东西都放在 self 里,而是只放那些你认为能代表这个对象的属性。使用传入的变量来存储那些与对象无关的值,这些值可能依赖于外部对象的状态(或者程序的执行情况)。
- 如上所述,我会保持一个单独的操作类(Operation),然后用一个操作对象的列表来进行计算。
- init 方法只是用来实例化对象,并进行所有必要的处理,以确保对象的正常行为(换句话说,就是让它准备好使用)。
- 只需考虑你想要建模的那些想法。