如何重构一个Python“神类”?


10

问题

我正在一个Python项目中,该项目的主类有点“ God Object ”。有这么受诅咒的多属性和方法!

我想重构班级。

至今…

第一步,我想做一些相对简单的事情。但是当我尝试最直接的方法时,它破坏了一些测试和现有示例。

基本上,该类具有大量的属性列表,但是我可以清楚地查看它们,并认为:“这5个属性是相关的……这8个属性也都是相关的……剩下的就是这些。”

getattr

我基本上只是想将相关属性分组为类似dict的帮助器类。我有种__getattr__理想的工作选择。因此,我将属性移到了一个单独的类,并且确实可以__getattr__很好地发挥其魔力……

开始

但是后来我尝试运行其中一个示例。示例子类尝试直接设置这些属性之一(在类级别)。但是由于该属性不再在父类中“物理定位”,所以我收到一个错误消息,指出该属性不存在。

@属性

然后,我阅读了有关@property装饰器的信息。但是后来我也读到,self.x = blahx父类的属性是子类时,它会给想要做的子类带来问题。

期望的

  • self.whatever即使父类的whatever属性不在类(或实例)本身的“实体位置” ,也要让所有客户端代码继续使用。
  • 将相关属性分组到类似dict的容器中。
  • 减少主类中代码的极端噪音。

例如,我只是想更改此内容:

larry = 2
curly = 'abcd'
moe   = self.doh()

变成这个:

larry = something_else('larry')
curly = something_else('curly')
moe   = yet_another_thing.moe()

…因为那仍然很吵。尽管这成功地将简单的属性赋予可以管理数据的属性,但是原始属性具有3个变量,而经过调整的版本仍具有3个变量。

但是,我可以这样:

stooges = Stooges()

如果查找self.larry失败,将检查stooges并查看是否larry存在。(但是,如果子类尝试larry = 'blah'在类级别执行此操作,那么它也必须起作用。)

摘要

  • 想要用单个属性替换父类中相关属性组,该单个属性将所有数据存储在其他位置
  • 想要与larry = 'blah'在类级别使用(例如)的现有客户端代码一起使用
  • 希望继续允许子类扩展,覆盖和修改这些重构的属性,而无需知道任何更改


这可能吗?还是我吠错了树?


6
即使您将实现的各个部分分开,但仍然坚持拥有巨大的类似于上帝的接口,就会失去一半的好处。您可以提供快捷方式,但是只要将变量放入不同的名称空间中并完全重定向到这些名称空间,就几乎没有任何东西。

1
@delnan:好的,那么您会推荐什么呢?
Zearin 2012年

Answers:


9

我表示同情,编写并重构了python“上帝对象”。我所做的就是根据方法将原始对象分解为几个子部分。例如,原始代码看起来像下面的伪代码:

method A():
    self.bla += 1

method B():
    self.bla += 1

do stuff():
    self.bla = 1
    method A()
    method B()
    print self.bla

填充方法是工作的独立“单元”。我将其迁移到原始实例化的新类。这也拉出了必要的属性。有些仅由子类使用,可以直接移动。其他人被共享,并进入共享类。

“上帝对象”在启动时会创建共享类的新副本,并且每个新的子类都将指针作为其init方法的一部分。例如,这是邮件的剥离版本:

#!/usr/bin/env python
# -*- coding: ascii -*-
'''Functions for emailing with dirMon.'''

from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders
import os
import smtplib
import datetime
import logging

class mailer:
    def __init__(self,SERVER="mail.server.com",FROM="support@server.com"):
        self.server = SERVER
        self.send_from = FROM
        self.logger = logging.getLogger('dirMon.mailer')

    def send_mail(self, send_to, subject, text, files=[]):
        assert type(send_to)==list
        assert type(files)==list
        if self.logger.isEnabledFor(logging.DEBUG):
            self.logger.debug(' '.join(("Sending email to:",' '.join(send_to))))
            self.logger.debug(' '.join(("Subject:",subject)))
            self.logger.debug(' '.join(("Text:",text)))
            self.logger.debug(' '.join(("Files:",' '.join(files))))
        msg = MIMEMultipart()
        msg['From'] = self.send_from
        msg['To'] = COMMASPACE.join(send_to)
        msg['Date'] = formatdate(localtime=True)
        msg['Subject'] = subject
        msg.attach( MIMEText(text) )
        for f in files:
            part = MIMEBase('application', "octet-stream")
            part.set_payload( open(f,"rb").read() )
            Encoders.encode_base64(part)
            part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))
            msg.attach(part)
        smtp = smtplib.SMTP(self.server)
        mydict = smtp.sendmail(self.send_from, send_to, msg.as_string())
        if self.logger.isEnabledFor(logging.DEBUG):
            self.logger.debug("Email Successfully Sent!")
        smtp.close()
        return mydict

它一次创建,并在需要邮件功能的不同类之间共享。

因此,为您创建larry具有所需属性和方法的类。客户说到处larry = blah都用larryObj.larry = blah。这样可以在不中断当前界面的情况下将内容迁移到子项目。

唯一要做的另一件事是寻找“工作单元”。如果您打算将“上帝对象”的一部分变成它自己的方法,请这样做。但是,将方法放在外面。这迫使您在组件之间创建接口。

奠定基础使其他所有事情都可以遵循。例如,帮助对象的一部分演示了它如何与邮件程序交互:

#!/usr/bin/env python
'''This module holds a class to spawn various subprocesses'''
import logging, os, subprocess, time, dateAdditionLib, datetime, re

class spawner:
    def __init__(self, mailer):
        self.logger = logging.getLogger('dirMon.spawner')
        self.myMailer = mailer

专注于最小的单个工作单元,然后将其移出。这更容易实现,并且可以让您快速进行设置。不要看待移动的属性,在大多数情况下,它们是使用它们完成的任务的辅助工具。处理完方法后剩下的所有内容都应该保留在原始对象中,因为它是共享状态的一部分。

但是,现在,新对象应该将它们需要的属性作为初始变量接受,而不涉及调用方对象的属性。然后,它们返回任何必要的值,调用者可以根据需要使用这些值来更新共享属性。这有助于将对象分离,从而使系统更坚固。


1
很棒的答案,斯潘塞。谢谢!我有一些后续问题,这些问题本质上过于具体,无法在此处使用。我可以私下联系您讨论这些吗?
Zearin 2012年

@Zearin当然,我的个人资料中有我的电子邮件地址。但是,这是用于公司项目的,由于其中存在专有内容,因此我无法为您提供存储库的完整副本。如果有足够的时间,我可以在快照之前/之后进行清理,但是我不确定这对您有什么帮助。
Spencer Rathbun 2012年

我在您的个人资料上看不到任何电子邮件地址。有各种各样的信息,但没有联系信息。I我应该如何与您联系?
Zearin '04年

得到它了。网络人:“删除!删除!删除!”
Zearin 2012年
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.