Як переробити "боговий клас" Python?


10

Проблема

Я працюю над проектом Python, головним класом якого є “ God Object ”. Існує так багато атрибутів і методів!

Я хочу переробити клас.

Так далеко…

Для першого кроку я хочу зробити щось відносно просте; але коли я спробував найпростіший підхід, він зламав деякі тести та існуючі приклади.

В основному, у класу є список атрибутів, що перебувають у лунго, але я чітко переглядаю їх і думаю: «Ці 5 атрибутів пов’язані між собою… Ці 8 також пов’язані між собою… і тоді є решта».

гетатр

Я в основному просто хотів згрупувати пов'язані атрибути в клас, що нагадує дікт. У мене було відчуття, що __getattr__це буде ідеально для роботи. Тож я перенесла атрибути в окремий клас і, безумовно, добре __getattr__працювала над його магією ...

На першому .

Але потім я спробував запустити один із прикладів. Приклад підкласу намагається встановити один з цих атрибутів безпосередньо (на рівні класу ). Але оскільки атрибут більше не був "фізично розташований" у батьківському класі, я отримав помилку, сказавши, що атрибут не існує.

@ властивість

Потім я читав про @propertyдекоратора. Але потім я також прочитав, що це створює проблеми для підкласів, які хочуть робити, self.x = blahколи xце властивість батьківського класу.

Бажаний

  • Нехай весь код клієнта продовжує працювати self.whatever, навіть якщо whateverвластивість батьків не знаходиться "фізично" в самому класі (або екземплярі).
  • Атрибути, пов'язані з групою, у контейнери, що нагадують дикти.
  • Зменшіть надзвичайну галасливість коду в основному класі.

Наприклад, я не хочу просто змінити це:

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: Гаразд, то що б ти порадив замість цього?
Зерін

Відповіді:


9

Написавши, а потім відремонтувавши пітон "Бог об'єкт", я співчуваю. Що я зробив, це розбити оригінальний об’єкт вниз на підрозділи, засновані на методах. Наприклад, оригінал виглядав таким псевдокодом:

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

Сконцентруйтеся на найменшій можливій одиниці роботи та перемістіть її. Це зробити простіше, і ви можете швидко грати з налаштуваннями. Не дивіться на властивості для переміщення матеріалів, вони є допоміжними завданнями, які виконуються з ними в більшості випадків. Що б не залишилося після того, як ви попрацювали з методами, ймовірно, слід залишатися в початковому об'єкті, оскільки це частина спільного стану.

Але нові об’єкти тепер повинні сприймати потрібні їм властивості як змінні init, не торкаючись властивості об'єктів виклику. Потім вони повертають будь-які необхідні значення, які абонент може використовувати для оновлення спільних властивостей у міру необхідності. Це допомагає роз’єднати об'єкти і створить більш надійну систему.


1
Фантастична відповідь, Спенсер. Дякую! У мене є деякі подальші запитання, які є надто специфічними за своєю природою, щоб відповідати тут. Чи можу я зв’язатися з вами приватно, щоб обговорити це?
Зерін

@ Zearin впевнений, у моєму профілі є моя електронна адреса. Це було для проекту компанії, і я не можу надати вам повну копію сховища через патентовані речі там. Зважаючи на достатню кількість часу, я міг би прибрати до / після знімків, але я не впевнений, наскільки це допоможе вам.
Спенсер Ратбун

Я не бачу жодної адреси електронної пошти у вашому профілі. Є всіляка інформація, але не контактна інформація. ☺ Як я повинен зв’язатися з вами?
Зерін

Зрозумів. Кібермен: "Видаліть! Видалити! Видалити! "
Зерін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.