파이썬“신 클래스”를 리팩토링하는 방법?


10

문제

저는 주 클래스가“ God Object ” 인 Python 프로젝트를 진행하고 있습니다. 가 있습니다 너무 많은 속성과 메소드 '빌어 먹을!

수업을 리팩토링하고 싶습니다.

지금까지…

첫 번째 단계에서는 상대적으로 간단한 일을하고 싶습니다. 그러나 가장 간단한 접근 방식을 시도했을 때 일부 테스트와 기존 예제가 중단되었습니다.

기본적으로이 클래스에는 속성 목록이 많이 있습니다. 그러나 속성을 명확하게 살펴보고 “이 5 개의 속성은 관련이 있습니다.이 8 개의 속성도 관련이 있습니다… 나머지는 있습니다.” 라고 생각할 수 있습니다 .

getattr

기본적으로 관련 속성을 dict-like helper 클래스로 그룹화하고 싶었습니다. 나는 __getattr__직업에 이상적이라고 생각했다 . 그래서 속성을 별도의 클래스로 옮겼고 확실히 __getattr__마법을 완벽하게 작동했습니다 ...

에서 첫 번째 .

그러나 예제 중 하나를 실행 해 보았습니다. 서브 클래스 예제는 이러한 속성 중 하나를 직접 ( 클래스 레벨에서 ) 설정하려고합니다 . 그러나 속성이 더 이상 부모 클래스에서 "물리적으로"위치하지 않았으므로 속성이 존재하지 않는다는 오류가 발생했습니다.

@특성

그런 다음 @property데코레이터 에 대해 읽었습니다 . 그러나 나는 그것이 부모 클래스의 속성 일 self.x = blah때 하고 싶은 서브 클래스에 문제가 있다는 것을 읽었습니다 x.

원하는

  • self.whatever부모의 whatever속성이 클래스 (또는 인스턴스) 자체에 "물리적으로"위치하지 않더라도 모든 클라이언트 코드가 계속 사용 되도록하십시오.
  • 관련 속성을 dict-like 컨테이너로 그룹화하십시오.
  • 메인 클래스에서 코드의 극도의 노이즈를 줄입니다.

예를 들어, 나는 단순히 이것을 바꾸고 싶지 않습니다 .

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

답변:


9

파이썬 "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="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. 현재 인터페이스를 중단하지 않고 하위 프로젝트로 항목을 마이그레이션합니다.

해야 할 유일한 일은 "작업 단위"를 찾는 것입니다. "God Object"의 일부를 고유 한 방법으로 바꾸려고한다면 그렇게하십시오 . 그러나 메소드를 외부에 두십시오. 그러면 구성 요소 사이에 인터페이스가 만들어집니다.

그 토대를 마련하면 다른 모든 것이 따라갈 수 있습니다. 예를 들어, 헬퍼 오브젝트가 메일러와 인터페이스하는 방법을 보여주는 헬퍼 오브젝트의 일부입니다.

#!/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

@Zearin, 내 프로필에 내 이메일 주소가 있습니다. 이것은 회사 프로젝트를위한 것이 었으며 독점적 인 물건으로 인해 저장소의 완전한 사본을 줄 수는 없습니다. 충분한 시간이 주어지면 스냅 샷 전후에 정리할 수 있지만 얼마나 도움이 될지 확신 할 수 없습니다.
스펜서 Rathbun

프로필에 이메일 주소가 없습니다. 모든 종류의 정보가 있지만 연락처 정보는 없습니다. ☺ 어떻게 연락해야합니까?
Zearin

알았다. 사이버 맨 :“삭제! 지우다! 지우다!"
Zearin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.