একক দায়িত্ব নিয়ে বড় শ্রেণি


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ঠিক?
candied_orange

5
আপনার চরিত্রটি যদি সঠিক বিমূর্ত স্তরে থাকে তবে আমি কোনও সমস্যা দেখছি না। অন্যদিকে যদি এটি সত্যিই লোডিং এবং নিজেরাই টিকিয়ে রাখার সমস্ত বিবরণ পরিচালনা করে তবে আপনি একক দায়বদ্ধতা অনুসরণ করছেন না। ডেলিগেশন এখানে সত্যই কী। আপনার চরিত্রটি টাইমার এবং এই জাতীয় কিছু নিম্ন স্তরের বিশদ সম্পর্কে জানে বলে আমার মনে হয় যে এটি ইতিমধ্যে খুব বেশি জেনে গেছে।
ফিলিপ স্ট্যুইক

1
শ্রেণীর বিমূর্ততার একক স্তরে কাজ করা উচিত। এটি উদাহরণস্বরূপ রাজ্য সংরক্ষণের বিশদে নেবে না। আপনার অভ্যন্তরগুলির জন্য দায়ী ছোট অংশগুলি পচিয়ে নিতে সক্ষম হওয়া উচিত। কমান্ড প্যাটার্ন এখানে দরকারী হতে পারে। এছাড়াও google.pl/url?sa=t&source=web&rct=j&url=http://…
পাইওটর গুইয়াজদা

মন্তব্য এবং উত্তরের জন্য ধন্যবাদ। আমি মনে করি আমি কেবল জিনিসগুলিকে পর্যাপ্ত পরিমাণে দ্রবীভূত করছিলাম না, এবং বড় বড় নেবালাস ক্লাসে খুব বেশি রাখাতে আঁকড়ে ছিলাম। কমান্ড প্যাটার্ন ব্যবহার করা এখন পর্যন্ত একটি বড় সহায়তা হয়েছে। আমি বিচ্ছিন্নতার বিভিন্ন স্তরে (যেমন সকেট, গেম বার্তা, গেম কমান্ড) অপারেটরগুলিকে স্তরগুলিতে আলাদা করে আছি। আমি অগ্রগতি করছি!
ব্যবহারকারী 35358

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.

শেষ অবধি, একক দায়িত্ব অর্জন করতে আপনার কতদূর যেতে হবে সে সম্পর্কে কোনও কঠোর এবং দ্রুত নিয়ম নেই। জটিলতার জন্য জটিলতা কোনও ভাল জিনিস নয়, তবে মেগালিথিক ক্লাসগুলি তাদের মধ্যে মোটামুটি জটিল হতে থাকে। এসআরপি এবং প্রকৃতপক্ষে অন্যান্য সলিড নীতিগুলির একটি মূল লক্ষ্য হ'ল কাঠামো, ধারাবাহিকতা সরবরাহ করা এবং কোডটিকে আরও রক্ষণাবেক্ষণযোগ্য করে তোলা - যার ফলে প্রায়শই সরল কিছু হয়।


আমি মনে করি এই উত্তরটি আমার সমস্যার সূত্রপাত করেছে, আপনাকে ধন্যবাদ। আমি এটিকে আমার অ্যাপ্লিকেশনটির অংশগুলি রিফ্যাক্টর করার জন্য ব্যবহার করছি এবং জিনিসগুলি এখন পর্যন্ত অনেক পরিষ্কার দেখাচ্ছে looking
ব্যবহারকারী 35358

1
আপনার অ্যানমিক মডেলগুলি সম্পর্কে সতর্কতা অবলম্বন করতে হবে , চরিত্রের মডেলটির মতো আচরণ করা পুরোপুরি গ্রহণযোগ্য Walk, Attackএবং Duck। যা ঠিক নয়, তা হ'ল Saveএবং Load(দৃistence়তা)। এসআরপি জানিয়েছে যে একটি শ্রেণীর কেবল একটি দায়িত্ব থাকা উচিত তবে চরিত্রের দায়িত্ব একটি চরিত্র হওয়া, ডেটা ধারক নয়।
ক্রিস ওহলার্ট

1
@ ক্রিসওহলার্ট এই নামটির কারণ CharacterModel, যার দায়বদ্ধতা হ'ল বিজনেস লজিক স্তর থেকে ডেটা স্তর সম্পর্কিত উদ্বেগকে ডাকা করে দেওয়ার জন্য একটি ডেটা ধারক হওয়া। আচরণগত Characterশ্রেণীর পক্ষে কোথাও কোথাও উপস্থিত থাকা এখনও বাঞ্ছনীয় হতে পারে তবে ৮০ টি ক্রিয়াকলাপ এবং ৩০ টি কমান্ডের সাহায্যে আমি এটিকে আরও ভেঙে ফেলার দিকে ঝুঁকেছি। বেশিরভাগ সময় আমি দেখতে পেলাম যে সত্তা বিশেষ্যগুলি শ্রেণীর নামগুলির জন্য একটি "রেড হেরিং" কারণ কোনও সত্তা বিশেষ্য থেকে দায় বহির্ভূত করা কঠিন এবং এক ধরণের সুইস-আর্মি-ছুরিতে পরিণত করা তাদের পক্ষে খুব সহজ।
বেন কট্রেল 21

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শ্রেণীর একটি ছোট্ট অংশ দেখেছি । সুতরাং আপনার প্রশ্নের একটি অংশের উত্তর নেই: আপনার ক্লাসটি প্রায় অবশ্যই এসআরপি অনুসরণ করে না।

তবে আমি বলব যে এসআরপি-র অধীনে এমন একটি মামলা রয়েছে যেখানে ২,৫০০-লাইন বর্গ বা তার চেয়ে বেশি দীর্ঘ ক্লাস থাকতে পারে। কিছু উদাহরণ হতে পারে:

  • একটি অত্যন্ত জটিল তবে সু-সংজ্ঞায়িত গাণিতিক গণনা যা ভাল-সংজ্ঞায়িত ইনপুট নেয় এবং ভাল-সংজ্ঞায়িত আউটপুট দেয়। এটি অত্যন্ত অপ্টিমাইজড কোড হতে পারে যার হাজার হাজার লাইনের প্রয়োজন। সুসংজ্ঞাযুক্ত গণনার জন্য প্রমাণিত গাণিতিক পদ্ধতিগুলির পরিবর্তনের অনেকগুলি কারণ নেই।
  • একটি ক্লাস যা ডেটা স্টোর হিসাবে কাজ করে, যেমন yield return <N>প্রথম শ্রেণীর প্রথম ১০,০০০ মৌলিক সংখ্যার জন্য সবেমাত্র একটি শ্রেণি বা শীর্ষস্থানীয় 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)

এখানে আপনি একটি 'অ্যাটাকরেসলভার' বা সেই লাইনের পাশাপাশি এমন কিছু উপস্থাপন করতে পারেন যা পরিসংখ্যান প্রেরণ এবং সংগ্রহ করা পরিচালনা করে। এখানে অন_ট্যাক কি কেবল চরিত্রের অবস্থা সম্পর্কে আরও বেশি কাজ করছে?

আপনি রাষ্ট্রটি পুনর্বিবেচনা করতে এবং নিজেকে জিজ্ঞাসা করতে পারেন আপনার সত্যিকারের কিছু রাজ্যের চরিত্রটিতে থাকা দরকার কিনা। 'সফল_ট্যাক' এমন কিছু মনে হচ্ছে যা আপনি সম্ভবত অন্য কোনও শ্রেণিতেও ট্র্যাক করতে পারেন।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.