Bagaimana cara merefleksikan "kelas dewa" Python?

10

Masalah

Saya sedang mengerjakan proyek Python yang kelas utamanya sedikit " God Object ". Ada begitu banyak atribut dan metode!

Saya ingin memperbaiki kelas.

Sejauh ini…

Untuk langkah pertama, saya ingin melakukan sesuatu yang relatif sederhana; tetapi ketika saya mencoba pendekatan yang paling mudah, itu memecahkan beberapa tes dan contoh yang ada.

Pada dasarnya, kelas memiliki daftar atribut yang sangat panjang — tetapi saya dapat dengan jelas melihat mereka dan berpikir, “5 atribut ini terkait ... 8 ini juga terkait… dan kemudian ada sisanya.”

getattr

Saya pada dasarnya hanya ingin mengelompokkan atribut terkait ke dalam kelas pembantu seperti dikt. Saya punya perasaan __getattr__akan ideal untuk pekerjaan itu. Jadi saya memindahkan atribut ke kelas yang terpisah, dan, tentu saja, __getattr__bekerja dengan sangat baik…

Pada awalnya .

Tetapi kemudian saya mencoba menjalankan salah satu contoh. Contoh subclass mencoba mengatur salah satu dari atribut ini secara langsung (di tingkat kelas ). Tetapi karena atribut tidak lagi "secara fisik terletak" di kelas induk, saya mendapat kesalahan mengatakan bahwa atribut tidak ada.

@Properti

Saya kemudian membaca tentang @propertydekorator. Tetapi kemudian saya juga membaca bahwa itu menciptakan masalah untuk subclass yang ingin dilakukan self.x = blahketika xmerupakan properti dari kelas induk.

Diinginkan

  • Biarkan semua kode klien terus bekerja menggunakan self.whatever, bahkan jika whateverproperti orangtua tidak "secara fisik terletak" di kelas (atau contoh) itu sendiri.
  • Kelompokkan atribut terkait ke dalam wadah mirip-dikt.
  • Mengurangi kebisingan ekstrim dari kode di kelas utama.

Misalnya, saya tidak hanya ingin mengubah ini:

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

Ke dalam ini:

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

... karena itu masih berisik. Meskipun itu berhasil membuat atribut hanya menjadi sesuatu yang dapat mengelola data, yang asli memiliki 3 variabel dan versi tweak masih memiliki 3 variabel.

Namun, saya akan baik-baik saja dengan sesuatu seperti ini:

stooges = Stooges()

Dan jika pencarian self.larrygagal, sesuatu akan memeriksa stoogesdan melihat apakah larryada. (Tetapi itu juga harus bekerja jika subclass mencoba melakukannya larry = 'blah'di tingkat kelas.)

Ringkasan

  • Ingin mengganti grup atribut terkait di kelas induk dengan satu atribut yang menyimpan semua data di tempat lain
  • Ingin bekerja dengan kode klien yang ada yang menggunakan (misalnya) larry = 'blah'di tingkat kelas
  • Ingin terus mengizinkan subclass untuk memperluas, menimpa, dan memodifikasi atribut-atribut yang dire-refaktasi ini tanpa mengetahui ada yang berubah


Apakah ini mungkin? Atau saya menggonggong pohon yang salah?

Zearin
sumber
6
Anda kehilangan setengah dari manfaat jika Anda bersikeras masih memiliki antarmuka seperti dewa besar ini, bahkan jika Anda memisahkan bagian dari implementasi. Anda dapat memberikan cara pintas, tetapi hanya dengan menempatkan variabel ke dalam ruang nama yang berbeda dan mengalihkan sepenuhnya ke yang memberi Anda sangat sedikit, jika ada.
1
@delnan: Oke, jadi apa yang akan Anda rekomendasikan?
Zearin

Jawaban:

9

Setelah menulis dan kemudian refactored python "objek Tuhan", saya bersimpati. Apa yang saya lakukan adalah memecah objek asli menjadi beberapa bagian berdasarkan metode. Misalnya, aslinya tampak seperti kode semu ini:

method A():
    self.bla += 1

method B():
    self.bla += 1

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

Metode barang adalah "unit" mandiri. Saya memigrasikannya ke kelas baru yang instantiate aslinya. Ini mengeluarkan properti yang diperlukan juga. Beberapa hanya digunakan oleh sub kelas dan bisa bergerak lurus ke seberang. Yang lain dibagikan, dan dipindahkan ke kelas bersama.

"Objek Tuhan" membuat salinan baru dari kelas bersama saat start up, dan masing-masing sub kelas baru menerima pointer sebagai bagian dari metode init mereka. Misalnya, inilah versi mailer yang dilucuti:

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

Ini dibuat sekali dan dibagi antara kelas-kelas yang berbeda yang membutuhkan kemampuan pengiriman surat.

Jadi buat Anda, buat kelas larrydengan properti dan metode yang Anda butuhkan. Di mana-mana klien mengatakan untuk larry = blahmenggantinya larryObj.larry = blah. Ini memigrasi berbagai hal ke sub proyek tanpa merusak antarmuka saat ini.

Satu-satunya hal yang harus dilakukan adalah mencari "unit kerja". Jika Anda akan mengubah bagian dari "Obyek Dewa" menjadi metode itu sendiri, lakukanlah . Tapi, letakkan metode di luar itu. Ini memaksa Anda untuk membuat antarmuka antar komponen.

Meletakkan landasan itu memungkinkan semua yang lain untuk mengikutinya. Misalnya, sepotong objek helper yang menunjukkan bagaimana ia berinteraksi dengan mailer:

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

Berkonsentrasilah pada unit kerja individu terkecil yang mungkin, dan pindahkan. Ini lebih mudah dilakukan, dan memungkinkan Anda bermain dengan pengaturan dengan cepat. Jangan melihat properti untuk memindahkan barang, mereka mendukung tugas yang dilakukan dengan mereka dalam banyak kasus. Apa pun yang tersisa setelah Anda berurusan dengan metode mungkin harus tetap di objek asli, karena itu adalah bagian dari keadaan bersama.

Tapi , objek baru sekarang harus menerima properti yang mereka butuhkan sebagai variabel init, bukan menyentuh properti objek pemanggil. Mereka kemudian mengembalikan nilai yang diperlukan, yang dapat digunakan oleh penelepon untuk memperbarui properti yang dibagikan sebagaimana diperlukan. Ini membantu decouple objek dan membuat sistem yang lebih kuat.

Spencer Rathbun
sumber
1
Jawaban yang fantastis, Spencer. Terima kasih! Saya punya beberapa pertanyaan lanjutan yang sifatnya terlalu spesifik untuk bisa diterima di sini. Bolehkah saya menghubungi Anda secara pribadi untuk membahas ini?
Zearin
@ Zearin yakin, profil saya memiliki alamat email saya. Ini untuk proyek perusahaan, dan saya tidak bisa memberi Anda salinan lengkap dari repositori karena barang-barang milik di sana. Dengan jumlah waktu yang cukup, saya dapat membersihkan sebelum / sesudah foto, tetapi saya tidak yakin seberapa banyak yang akan membantu Anda.
Spencer Rathbun
Saya tidak dapat melihat alamat email apa pun di profil Anda. Ada segala macam info, tetapi bukan info kontak. ☺ Bagaimana saya harus menghubungi Anda?
Zearin
Oke. Cybermen: "Hapus! Menghapus! Menghapus!"
Zearin