Pythonプロジェクトのメインクラスが少し「 God Object 」であるプロジェクトに取り組んでいます。so friggin 'の多くの属性があり、メソッド!
クラスをリファクタリングしたい。
最初のステップとして、私は比較的単純なことをしたいと思います。しかし、最も簡単な方法を試したところ、いくつかのテストと既存の例が破られました。
基本的に、クラスには属性のリストがたくさんありますが、私はそれらを明確に調べて考えることができます「これらの5つの属性は関連しています...これらの8つの属性も関連しています...そして残りがあります。」
基本的に、関連する属性をdictのようなヘルパークラスにグループ化したかっただけです。 __getattr__
はこの仕事に理想的だと感じました。そのため、属性を別のクラスに移動し、確かに、__getattr__
はその魔法を完全にうまく機能させました…
最初。
しかし、私は例の1つを実行してみました。サンプルのサブクラスは、これらの属性の1つを直接設定しようとします(クラスレベル)。しかし、属性が親クラスで「物理的に配置」されなくなったため、属性が存在しないというエラーが発生しました。
次に、@property
デコレータについて読みます。ただし、x
が親クラスのプロパティである場合、self.x = blah
を実行したいサブクラスに問題が発生することも読みました。
whatever
プロパティがクラス(またはインスタンス)自体に「物理的に」配置されていない場合でも、self.whatever
を使用してすべてのクライアントコードを引き続き機能させます。たとえば、私はこれを変更したいだけです:
larry = 2
curly = 'abcd'
moe = self.doh()
これに:
larry = something_else('larry')
curly = something_else('curly')
moe = yet_another_thing.moe()
…それはまだうるさいからです。これにより、simple属性がデータを管理できる何かに正常に変換されますが、元の属性には3つの変数があり、調整されたバージョンにはまだ3つの変数があります。
しかし、私はこのようなもので大丈夫でしょう:
stooges = Stooges()
そして、self.larry
の検索が失敗した場合、何かがstooges
をチェックし、larry
が存在するかどうかを確認します。 (ただし、サブクラスがクラスレベルでlarry = 'blah'
を実行しようとした場合にも機能する必要があります。)
larry = 'blah'
を使用する既存のクライアントコードで作業したいこれは可能ですか?または私は間違った木を吠えていますか?
python "God object"を記述してリファクタリングした後、私は同情しました。私が行ったのは、メソッドに基づいて元のオブジェクトをサブセクションに分割することです。たとえば、元のオブジェクトはこの疑似コードのように見えました:
method A():
self.bla += 1
method B():
self.bla += 1
do stuff():
self.bla = 1
method A()
method B()
print self.bla
スタッフメソッドは、自己完結型の作業の「単位」です。オリジナルをインスタンス化する新しいクラスに移行しました。これにより、必要なプロパティも引き出されました。一部はサブクラスでのみ使用され、まっすぐに移動できました。他のものは共有され、共有クラスに移されました。
「Godオブジェクト」は、起動時に共有クラスの新しいコピーを作成し、新しいサブクラスのそれぞれは、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="[email protected]"):
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
に置き換えます。これにより、現在のインターフェースを壊すことなく、サブプロジェクトに移行します。
他にすべきことは、「作業単位」を探すことだけです。 「God Object」の一部を独自のメソッドに変換する場合は、そうする。ただし、メソッドをoutsideに配置します。これにより、コンポーネント間のインターフェースを作成する必要があります。
その基礎を築くことは、他のすべてがそれに続くことを可能にします。たとえば、メイラーとのインターフェース方法を示すヘルパーオブジェクトの一部:
#!/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
可能な最小の個別作業単位に集中し、それを外に移動します。これは簡単に実行でき、セットアップをすばやく実行できます。移動するもののプロパティを見てはいけません。ほとんどの場合、それらはtasksで行われている処理の補助的なものです。メソッドを処理した後に残ったものは、共有状態の一部であるため、おそらく元のオブジェクトにstayする必要があります。
But、新しいオブジェクトは必要なプロパティを初期化変数として受け入れるようになり、呼び出し元オブジェクトのプロパティには触れません。次に、呼び出し側が必要に応じて共有プロパティを更新するために使用できる必要な値を返します。これはオブジェクトを分離するのに役立ち、より堅牢なシステムになります。