एकल जिम्मेदारी के साथ बड़ी कक्षा


13

मेरे पास एक 2500 लाइन Characterक्लास है:

  • खेल में चरित्र की आंतरिक स्थिति को ट्रैक करता है।
  • भार और उस अवस्था को बनाए रखता है।
  • ~ 30 आने वाली कमांड्स (आमतौर पर = उन्हें आगे की ओर देती हैं Game, लेकिन कुछ रीड-ओनली कमांड्स को तुरंत जवाब दिया जाता है)।
  • प्राप्त Gameक्रियाओं के बारे में ~ 80 कॉल प्राप्त करता है और दूसरों के प्रासंगिक कार्य करता है।

यह मुझे लगता है कि Characterएक ही जिम्मेदारी है: चरित्र की स्थिति का प्रबंधन करने के लिए, आने वाली कमांड और गेम के बीच मध्यस्थता करना।

कुछ अन्य जिम्मेदारियां हैं जो पहले ही टूट चुकी हैं:

  • Characterएक है Outgoingजो इसे में कॉल क्लाइंट अनुप्रयोग के लिए बाहर जाने वाले अपडेट उत्पन्न करने के लिए।
  • Characterएक है Timerजब यह अगले कुछ करने के लिए अनुमति दी है जो पटरियों। इसके विरुद्ध आवक आदेशों को मान्य किया जाता है।

तो मेरा सवाल यह है कि क्या एसआरपी और इसी तरह के सिद्धांतों के तहत इतना बड़ा वर्ग होना स्वीकार्य है? क्या इसे कम बोझिल बनाने के लिए कोई सर्वोत्तम अभ्यास है (जैसे। शायद अलग-अलग फ़ाइलों में विभाजित करने के तरीके)? या मैं कुछ याद कर रहा हूं और क्या वास्तव में इसे विभाजित करने का एक अच्छा तरीका है? मुझे लगता है कि यह काफी व्यक्तिपरक है और दूसरों से प्रतिक्रिया चाहेंगे।

यहाँ एक नमूना है:

class Character(object):
    def __init__(self):
        self.game = None
        self.health = 1000
        self.successful_attacks = 0
        self.points = 0
        self.timer = Timer()
        self.outgoing = Outgoing(self)

    def load(self, db, id):
        self.health, self.successful_attacks, self.points = db.load_character_data(id)

    def save(self, db, id):
        db.save_character_data(self, health, self.successful_attacks, self.points)

    def handle_connect_to_game(self, game):
        self.game.connect(self)
        self.game = game
        self.outgoing.send_connect_to_game(game)

    def handle_attack(self, victim, attack_type):
        if time.time() < self.timer.get_next_move_time():
            raise Exception()
        self.game.request_attack(self, victim, attack_type)

    def on_attack(victim, attack_type, points):
        self.points += points
        self.successful_attacks += 1
        self.outgoing.send_attack(self, victim, attack_type)
        self.timer.add_attack(attacker=True)

    def on_miss_attack(victim, attack_type):
        self.missed_attacks += 1
        self.outgoing.send_missed_attack()
        self.timer.add_missed_attack()

    def on_attacked(attacker, attack_type, damage):
        self.start_defenses()
        self.take_damage(damage)
        self.outgoing.send_attack(attacker, self, attack_type)
        self.timer.add_attack(victim=True)

    def on_see_attack(attacker, victim, attack_type):
        self.outgoing.send_attack(attacker, victim, attack_type)
        self.timer.add_attack()


class Outgoing(object):
    def __init__(self, character):
        self.character = character
        self.queue = []

    def send_connect_to_game(game):
        self._queue.append(...)

    def send_attack(self, attacker, victim, attack_type):
        self._queue.append(...)

class Timer(object):
    def get_next_move_time(self):
        return self._next_move_time

    def add_attack(attacker=False, victim=False):
        if attacker:
            self.submit_move()
        self.add_time(ATTACK_TIME)
        if victim:
            self.add_time(ATTACK_VICTIM_TIME)

class Game(object):
    def connect(self, character):
        if not self._accept_character(character):
           raise Exception()
        self.character_manager.add(character)

    def request_attack(character, victim, attack_type):
        if victim.has_immunity(attack_type):
            character.on_miss_attack(victim, attack_type)
        else:
            points = self._calculate_points(character, victim, attack_type)
            damage = self._calculate_damage(character, victim, attack_type)
            character.on_attack(victim, attack_type, points)
            victim.on_attacked(character, attack_type, damage)
            for other in self.character_manager.get_observers(victim):
                other.on_see_attack(character, victim, attack_type)

1
मुझे लगता है कि यह एक टाइपो है: db.save_character_data(self, health, self.successful_attacks, self.points)आप self.healthसही मतलब है?
कैंडिड_ओरेंज

5
यदि आपका चरित्र सही अमूर्त स्तर पर रहता है, तो मुझे कोई समस्या नहीं दिखती है। यदि दूसरी ओर यह वास्तव में लोड करने और खुद को बनाए रखने के लिए कह रहा है, तो आप एकल जिम्मेदारी का पालन नहीं कर रहे हैं। प्रत्यायोजन वास्तव में यहाँ महत्वपूर्ण है। यह देखते हुए कि आपके चरित्र को टाइमर जैसे कुछ निम्न स्तर के विवरण के बारे में पता है, और मुझे लगता है कि यह पहले से ही बहुत अधिक जानता है।
फिलिप स्टीक

1
वर्ग को अमूर्तता के एकल स्तर पर काम करना चाहिए। इसे राज्य के भंडारण के उदाहरण के लिए नहीं जाना चाहिए। आपको आंतरिक लोगों के लिए जिम्मेदार छोटे विखंडू को विघटित करने में सक्षम होना चाहिए। कमांड पैटर्न यहां उपयोगी हो सकता है। इसके अलावा google.pl/url?sa=t&source=web&rct=j&url=http://…
Piotr Gwiazda

टिप्पणियों और उत्तरों के लिए धन्यवाद। मुझे लगता है कि मैं अभी चीजों को पर्याप्त रूप से विघटित नहीं कर रहा था, और बड़ी नेबुलस कक्षाओं में रखने के लिए जकड़ रहा था। कमांड पैटर्न का उपयोग करना अब तक एक बड़ी मदद रही है। मैं चीज़ों को अलग-अलग स्तरों में विभाजित कर रहा हूं जो कि अमूर्त के विभिन्न स्तरों पर काम करती हैं (जैसे। सॉकेट, गेम संदेश, गेम कमांड)। मैं प्रगति कर रहा हूँ!
user35358

1
इससे निपटने का एक और तरीका है कि एक क्लास के रूप में "कैरेक्टरस्टेट" हो, दूसरे के रूप में "कैरेक्टर इनपुटहैंडलर", दूसरे के रूप में "कैरेक्टरपर्सनस्टेंस" ...
टी। सारा

जवाबों:


14

एसआरपी को एक समस्या में लागू करने के मेरे प्रयासों में मुझे आमतौर पर लगता है कि एकल-जिम्मेदारी-प्रति-वर्ग से चिपके रहने का एक अच्छा तरीका वर्ग के नाम चुनना है, जो उनकी जिम्मेदारी के लिए है, क्योंकि यह अक्सर कुछ फ़ंक्शन के बारे में अधिक स्पष्ट रूप से सोचने में मदद करता है वास्तव में उस वर्ग में 'संबंधित' है।

इसके अलावा, मैं इस तरह के रूप में है कि सरल संज्ञाओं महसूस Character(या Employee, Person, Car, Animal, आदि) अक्सर बहुत गरीब वर्ग नाम एक जैसे हैं क्योंकि वे वास्तव में वर्णन संस्थाओं अपने आवेदन में (डेटा), और जब कक्षाओं के रूप में व्यवहार में यह अक्सर बहुत आसान के साथ समाप्त करने के लिए है कुछ बहुत फूला हुआ।

मुझे लगता है कि 'अच्छा' वर्ग के नाम लेबल होते हैं जो सार्थक रूप से आपके कार्यक्रम के व्यवहार के कुछ पहलू को व्यक्त करते हैं - अर्थात जब कोई अन्य प्रोग्रामर आपकी कक्षा का नाम देखता है तो वे पहले से ही उस कक्षा के व्यवहार / कार्यक्षमता के बारे में एक मूल विचार प्राप्त करते हैं।

अंगूठे के एक नियम के रूप में, मैं एंटिटीज को डेटा मॉडल, और क्लासेस को व्यवहार के प्रतिनिधि के रूप में सोचता हूं । (हालांकि बेशक अधिकांश प्रोग्रामिंग भाषाएं classदोनों के लिए एक कीवर्ड का उपयोग करती हैं, लेकिन एप्लिकेशन व्यवहार से अलग 'सादा' संस्थाओं को रखने का विचार भाषा-तटस्थ है)

आपके चरित्र वर्ग के लिए आपके द्वारा बताई गई विभिन्न जिम्मेदारियों के टूटने को देखते हुए, मैं उन वर्गों की ओर झुकाव करना शुरू करूँगा, जिनके नाम उस आवश्यकता पर आधारित हैं जो उन्हें पूरा करते हैं। उदाहरण के लिए:

  • एक CharacterModelऐसी इकाई पर विचार करें जिसका कोई व्यवहार नहीं है और बस आपके चरित्र की स्थिति (डेटा रखती है) को बनाए रखती है।
  • के लिए हठ / आईओ, जैसे नाम पर विचार CharacterReaderऔर CharacterWriter (या शायद एक CharacterRepository/ CharacterSerialiser/ आदि)।
  • इस बारे में सोचें कि आपके आदेशों के बीच किस तरह के पैटर्न मौजूद हैं; यदि आपके पास 30 कमांड हैं, तो आपके पास संभावित रूप से 30 अलग-अलग जिम्मेदारियां हैं; जिनमें से कुछ ओवरलैप हो सकते हैं, लेकिन वे अलगाव के लिए एक अच्छे उम्मीदवार की तरह लगते हैं।
  • इस बात पर विचार करें कि क्या आप अपने क्रियाकलापों के साथ-साथ फिर से उसी तरह के रिफैक्टरिंग को लागू कर सकते हैं, 80 कार्यों में 80 अलग-अलग जिम्मेदारियां हो सकती हैं, संभवतः कुछ ओवरलैप के साथ भी।
  • आदेशों और कार्यों को अलग करने से एक अन्य वर्ग भी हो सकता है जो उन आदेशों / कार्यों को चलाने / फायर करने के लिए जिम्मेदार है; शायद किसी प्रकार का CommandBrokerया ActionBrokerजो आपके एप्लिकेशन के "मिडलवेयर" की तरह व्यवहार करता है / विभिन्न वस्तुओं के बीच उन कमांड और कार्यों को भेजने / प्राप्त करने / निष्पादित करने का

यह भी याद रखें कि व्यवहार से जुड़ी हर चीज को एक वर्ग के हिस्से के रूप में मौजूद होना जरूरी नहीं है; उदाहरण के लिए, आप दर्जनों स्टैटलेस एकल-विधि कक्षाओं को लिखने के बजाय अपने कार्यों / आदेशों को संक्षिप्त करने के लिए फ़ंक्शन पॉइंटर्स / डेलीगेट्स / क्लोजर के नक्शे / शब्दकोश का उपयोग करने पर विचार कर सकते हैं।

यह किसी भी वर्ग को लिखे बिना 'कमांड पैटर्न' समाधानों को देखने के लिए काफी सामान्य है, जो एक हस्ताक्षर / इंटरफ़ेस साझा करने वाले स्थिर तरीकों का उपयोग करके बनाए गए हैं:

 void AttackAction(CharacterModel) { ... }
 void ReloadAction(CharacterModel) { ... }
 void RunAction(CharacterModel) { ... }
 void DuckAction(CharacterModel) { ... }
 // etc.

अंत में, कोई भी कठिन और तेज़ नियम नहीं हैं कि आपको एकल जिम्मेदारी हासिल करने के लिए कितनी दूर जाना चाहिए। जटिलता के लिए जटिलता एक अच्छी बात नहीं है, लेकिन मेगालिथिक कक्षाएं अपने आप में काफी जटिल हैं। एसआरपी और वास्तव में अन्य ठोस सिद्धांतों का एक प्रमुख लक्ष्य संरचना, स्थिरता प्रदान करना और कोड को अधिक बनाए रखना है - जिसके परिणामस्वरूप अक्सर कुछ सरल होता है।


मुझे लगता है कि इस जवाब ने मेरी समस्या की जड़ को संबोधित किया, धन्यवाद। मैं इसे अपने एप्लिकेशन के रीफ्रैक्टिंग भागों में उपयोग करने के लिए डाल रहा हूं और चीजें अब तक बहुत साफ दिख रही हैं।
user35358

1
आपको एनीमिक मॉडल से सावधान रहना होगा , कैरेक्टर मॉडल के लिए व्यवहार की तरह Walk, Attackऔर यह पूरी तरह से स्वीकार्य है Duck। जो ठीक नहीं है, वह है ( Saveऔर Loadदृढ़ता)। एसआरपी में कहा गया है कि एक वर्ग के पास केवल एक जिम्मेदारी होनी चाहिए, लेकिन चरित्र की जिम्मेदारी एक चरित्र होना है, न कि डेटा कंटेनर।
क्रिस वोहलर्ट

1
@ChrisWohlert उस नाम का कारण है CharacterModel, जिसका जिम्मेदारी है व्यापार तर्क परत से दसगुणा डेटा स्तर की चिंताओं के एक डेटा कंटेनर किया जाना है। यह वास्तव में अभी भी व्यवहार Characterवर्ग के लिए कहीं न कहीं मौजूद होने के लिए वांछनीय हो सकता है , लेकिन 80 कार्यों और 30 आदेशों के साथ मैं इसे आगे तोड़ने के लिए झुकूंगा। ज्यादातर समय मुझे लगता है कि इकाई संज्ञाएं वर्ग नामों के लिए एक "लाल हेरिंग" हैं क्योंकि यह एक इकाई संज्ञा से जिम्मेदारी को अलग करना मुश्किल है, और यह सभी के लिए स्विस-सेना-चाकू का एक प्रकार में बदलना बहुत आसान है।
बेन कॉटरेल

10

आप हमेशा "जिम्मेदारी" की अधिक सार परिभाषा का उपयोग कर सकते हैं। इन स्थितियों को आंकने का यह बहुत अच्छा तरीका नहीं है, कम से कम तब तक जब तक आपको इसका बहुत अनुभव न हो। ध्यान दें कि आपने आसानी से चार बुलेट पॉइंट बनाए हैं, जिन्हें मैं आपकी कक्षा की ग्रैन्युलैरिटी के लिए एक बेहतर शुरुआती बिंदु कहूँगा। यदि आप वास्तव में एसआरपी का पालन कर रहे हैं, तो इस तरह से बुलेट पॉइंट बनाना मुश्किल है।

दूसरा तरीका यह है कि अपने वर्ग के सदस्यों को देखें और उन तरीकों के आधार पर अलग हो जाएं जो वास्तव में उनका उपयोग करते हैं। उदाहरण के लिए, उन सभी विधियों में से एक वर्ग बनाएं जो वास्तव में उपयोग करते हैं self.timer, अन्य सभी विधियों में से self.outgoingएक अन्य वर्ग जो वास्तव में उपयोग करते हैं , और शेष में से एक अन्य वर्ग। अपने तरीकों में से एक और वर्ग बनाएं जो एक तर्क के रूप में डीबी संदर्भ लेता है। जब आपकी कक्षाएं बहुत बड़ी होती हैं, तो अक्सर इस तरह के समूह होते हैं।

एक प्रयोग के रूप में आपको लगता है कि यह उचित है कि इसे छोटे से विभाजित करने से डरो मत। यही संस्करण नियंत्रण के लिए है। सही संतुलन बिंदु को बहुत दूर ले जाने के बाद देखना बहुत आसान है।


3

"जिम्मेदारी" की परिभाषा कुख्यात अस्पष्ट है, लेकिन यह थोड़ा कम अस्पष्ट हो जाता है यदि आप इसे "बदलने का कारण" मानते हैं। अभी भी अस्पष्ट है, लेकिन कुछ आप सीधे अधिक थोड़ा विश्लेषण कर सकते हैं। परिवर्तन के कारण आपके डोमेन पर निर्भर करते हैं और आपके सॉफ़्टवेयर का उपयोग कैसे किया जाएगा, लेकिन गेम इसके उदाहरण हैं क्योंकि आप इसके बारे में उचित अनुमान लगा सकते हैं। आपके कोड में, मैं पहली पाँच पंक्तियों में पाँच अलग-अलग जिम्मेदारियाँ गिनता हूँ:

self.game = None
self.health = 1000
self.successful_attacks = 0
self.points = 0
self.timer = Timer()

खेल आवश्यकताओं को इनमें से किसी भी तरीके से बदलने पर आपका कार्यान्वयन बदल जाएगा:

  1. एक "खेल" परिवर्तन की धारणा। इसकी संभावना कम से कम हो सकती है।
  2. आप स्वास्थ्य बिंदुओं को कैसे मापते और ट्रैक करते हैं
  3. आपकी आक्रमण प्रणाली बदल जाती है
  4. आपका पॉइंट सिस्टम बदल जाता है
  5. आपकी टाइमिंग प्रणाली बदल जाती है

आप डेटाबेस से लोड कर रहे हैं, हमलों को हल कर रहे हैं, गेम, टाइमिंग चीजों के साथ लिंक कर रहे हैं; मुझे लगता है कि जिम्मेदारियों की सूची बहुत पहले ही है, और हमने केवल आपकी Characterकक्षा का एक छोटा हिस्सा देखा है । तो आपके प्रश्न के एक हिस्से का उत्तर नहीं है: आपकी कक्षा लगभग निश्चित रूप से एसआरपी का पालन नहीं करती है।

हालांकि, मैं कहूंगा कि ऐसे मामले हैं जहां SRP के तहत 2,500-लाइन क्लास या उससे अधिक समय के लिए स्वीकार्य है। कुछ उदाहरण हो सकते हैं:

  • एक उच्च जटिल लेकिन अच्छी तरह से परिभाषित गणितीय गणना जो अच्छी तरह से परिभाषित इनपुट लेती है और अच्छी तरह से परिभाषित आउटपुट देती है। यह अत्यधिक अनुकूलित कोड हो सकता है जिसे हजारों लाइनों की आवश्यकता होती है। अच्छी तरह से परिभाषित गणना के लिए सिद्ध गणितीय तरीके बदलने के कई कारण नहीं हैं।
  • एक वर्ग जो एक डेटा स्टोर के रूप में कार्य करता है, जैसे कि एक वर्ग जो yield return <N>पहले 10,000 अभाज्य संख्याओं या शीर्ष 10,000 सबसे आम अंग्रेजी शब्दों के लिए है। संभावित कारण हैं कि यह कार्यान्वयन डेटा स्टोर या टेक्स्ट फ़ाइल से खींचने पर पसंद किया जाएगा। इन वर्गों को बदलने के बहुत कम कारण हैं (जैसे आपको लगता है कि आपको 10,000 से अधिक की आवश्यकता है)।

2

जब भी आप किसी अन्य इकाई के खिलाफ काम करते हैं तो आप एक तीसरी वस्तु पेश कर सकते हैं जो इसके बजाय हैंडलिंग करता है।

def on_attack(victim, attack_type, points):
    self.points += points
    self.successful_attacks += 1
    self.outgoing.send_attack(self, victim, attack_type)
    self.timer.add_attack(attacker=True)

यहाँ आप एक 'AttackResolver' या उन लाइनों के साथ कुछ पेश कर सकते हैं जो सांख्यिकी के प्रेषण और एकत्रीकरण को संभालती हैं। यहाँ on_attack केवल चरित्र की स्थिति के बारे में है क्या यह अधिक कर रहा है?

आप राज्य को फिर से देख सकते हैं और अपने आप से पूछ सकते हैं कि क्या आपके पास राज्य के कुछ चरित्रों की आवश्यकता है। 'successful_attack' कुछ ऐसा लगता है जिसे आप संभवतः किसी अन्य वर्ग पर भी ट्रैक कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.