วิธีการสร้าง Python“ god class” ใหม่ได้อย่างไร


10

ปัญหา

ฉันกำลังทำงานในโครงการงูหลามซึ่งชั้นเรียนหลักเป็น“ พระเจ้าวัตถุ ” เล็กน้อย นอกจากนี้เพื่อให้ friggin' หลายลักษณะและวิธีการ!

ฉันต้องการปรับโครงสร้างห้องเรียนอีกครั้ง

จนถึงตอนนี้ ...

สำหรับขั้นตอนแรกฉันต้องการทำสิ่งที่ค่อนข้างง่าย แต่เมื่อฉันลองใช้วิธีที่ตรงไปตรงมาที่สุดมันก็ทำลายการทดสอบและตัวอย่างที่มีอยู่

โดยทั่วไปชั้นเรียนมีรายการคุณลักษณะที่ยาวมาก - แต่ฉันสามารถมองข้ามพวกเขาอย่างชัดเจนและคิดว่า“ คุณลักษณะทั้ง 5 นี้เกี่ยวข้องกัน… 8 สิ่งเหล่านี้สัมพันธ์กัน…แล้วก็มีส่วนที่เหลือ”

getattr

โดยทั่วไปฉันแค่ต้องการจัดกลุ่มคุณลักษณะที่เกี่ยวข้องลงในคลาสผู้ช่วยแบบ dict ฉันมีความรู้สึก__getattr__จะเหมาะสำหรับงาน ดังนั้นฉันจึงย้ายคุณสมบัติไปยังชั้นเรียนอื่นและแน่นอน__getattr__ใช้เวทย์มนตร์ของมันได้อย่างสมบูรณ์แบบดี ...

ในตอนแรก

แต่จากนั้นฉันลองใช้หนึ่งในตัวอย่าง คลาสย่อยตัวอย่างพยายามตั้งค่าหนึ่งในแอ็ตทริบิวต์เหล่านี้โดยตรง (ที่ระดับคลาส ) แต่เนื่องจากแอตทริบิวต์ไม่ได้“ อยู่ในสภาพร่างกาย” อีกต่อไปในชั้นผู้ปกครองฉันได้รับข้อผิดพลาดที่บอกว่าไม่มีแอตทริบิวต์

@property

ฉันอ่านเกี่ยวกับ@propertyมัณฑนากร แต่ฉันก็อ่านด้วยว่ามันสร้างปัญหาให้กับคลาสย่อยที่ต้องการทำself.x = blahเมื่อxเป็นคุณสมบัติของคลาสพาเรนต์

ที่ต้องการอยากมี

  • ให้รหัสลูกค้าทั้งหมดยังคงใช้งานได้ต่อไป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

คำตอบ:


9

หลังจากเขียนและปรับโครงสร้างงูเหลือม "พระเจ้าวัตถุ" ฉันก็เห็นอกเห็นใจ สิ่งที่ฉันทำคือแยกวัตถุต้นฉบับออกเป็นส่วนย่อยตามวิธีการ ตัวอย่างเช่นต้นฉบับดูเหมือนโค้ดหลอกนี้:

method A():
    self.bla += 1

method B():
    self.bla += 1

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

วิธีการสิ่งที่เป็น "หน่วย" ในตัวเองของการทำงาน ฉันย้ายออกไปยังคลาสใหม่ซึ่งอินสแตนซ์ดั้งเดิม สิ่งนี้ดึงคุณสมบัติที่จำเป็นออกมาเช่นกัน บางคนถูกใช้โดยชั้นย่อยเท่านั้นและสามารถเคลื่อนตัวตรงๆได้ มีการแชร์คนอื่นและย้ายเข้าชั้นเรียนที่ใช้ร่วมกัน

"God object" สร้างสำเนาใหม่ของคลาสที่แบ่งใช้เมื่อเริ่มต้นและคลาสย่อยใหม่แต่ละตัวจะยอมรับตัวชี้เป็นส่วนหนึ่งของวิธีการเริ่มต้น ตัวอย่างเช่นต่อไปนี้เป็นจดหมายที่ลอกแบบ:

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

@Zearin แน่นอนว่าโปรไฟล์ของฉันมีที่อยู่อีเมลของฉัน นี่เป็นโครงการของ บริษัท และฉันไม่สามารถให้สำเนาของที่เก็บได้เพราะสิ่งที่เป็นกรรมสิทธิ์ในนั้น ด้วยเวลาที่เพียงพอฉันสามารถล้างก่อน / หลังสแน็ปช็อต แต่ฉันไม่แน่ใจว่าจะช่วยคุณได้มากแค่ไหน
Spencer Rathbun

ฉันไม่เห็นที่อยู่อีเมลในโปรไฟล์ของคุณ มีข้อมูลทุกประเภท แต่ไม่ใช่ข้อมูลติดต่อ ☺ฉันควรติดต่อคุณอย่างไร
Zearin

เข้าใจแล้ว ไซเบอร์เมน:“ ลบ! ลบ! ลบ!"
เซี
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.