জেনেরিক অবজেক্টগুলি কোনও পাত্রে সংরক্ষণ করা এবং তারপরে অবজেক্টটি পাওয়া এবং পাত্রে থেকে অবজেক্টগুলি ডাউনকাস্ট করা কি কোডের গন্ধ?


34

উদাহরণস্বরূপ, আমার একটি গেম রয়েছে, যার প্লেয়ারের ক্ষমতা বাড়ানোর জন্য কয়েকটি সরঞ্জাম রয়েছে:

Tool.h

class Tool{
public:
    std::string name;
};

এবং কিছু সরঞ্জাম:

Sword.h

class Sword : public Tool{
public:
    Sword(){
        this->name="Sword";
    }
    int attack;
};

Shield.h

class Shield : public Tool{
public:
    Shield(){
        this->name="Shield";
    }
    int defense;
};

MagicCloth.h

class MagicCloth : public Tool{
public:
    MagicCloth(){
        this->name="MagicCloth";
    }
    int attack;
    int defense;
};

এবং তারপরে কোনও খেলোয়াড় আক্রমণ করার জন্য কিছু সরঞ্জাম ধরে রাখতে পারে:

class Player{
public:
    int attack;
    int defense;
    vector<Tool*> tools;
    void attack(){
        //original attack and defense
        int currentAttack=this->attack;
        int currentDefense=this->defense;
        //calculate attack and defense affected by tools
        for(Tool* tool : tools){
            if(tool->name=="Sword"){
                Sword* sword=(Sword*)tool;
                currentAttack+=sword->attack;
            }else if(tool->name=="Shield"){
                Shield* shield=(Shield*)tool;
                currentDefense+=shield->defense;
            }else if(tool->name=="MagicCloth"){
                MagicCloth* magicCloth=(MagicCloth*)tool;
                currentAttack+=magicCloth->attack;
                currentDefense+=magicCloth->shield;
            }
        }
        //some other functions to start attack
    }
};

আমি মনে করি if-elseসরঞ্জামগুলিতে ভার্চুয়াল পদ্ধতিগুলি প্রতিস্থাপন করা কঠিন , কারণ প্রতিটি সরঞ্জামের বিভিন্ন বৈশিষ্ট্য রয়েছে এবং প্রতিটি সরঞ্জাম প্লেয়ারের আক্রমণ এবং প্রতিরক্ষাগুলিকে প্রভাবিত করে, যার জন্য প্লেয়ার আক্রমণ এবং প্রতিরক্ষা আপডেট প্লেয়ার অবজেক্টের মধ্যে করা দরকার।

তবে আমি এই নকশায় সন্তুষ্ট নই, যেহেতু এটিতে একটি দীর্ঘ if-elseবিবৃতি সহ ডাউন কাস্টিং রয়েছে । এই নকশাটি কি "সংশোধন" করা দরকার? যদি তা হয় তবে আমি এটি সংশোধন করতে কী করতে পারি?


4
নির্দিষ্ট সাবক্লাসের (এবং পরবর্তী নিম্নোক্ত স্থানগুলির) জন্য পরীক্ষা অপসারণের জন্য একটি স্ট্যান্ডার্ড ওওপি কৌশল হ'ল চেইন এবং ক্যাসেটের পরিবর্তে বেস ক্লাসে দু'টি, ভার্চুয়াল পদ্ধতি (গুলি) তৈরি করতে হবে। এটি সম্পূর্ণরূপে যদি হয় তবে তা সরান এবং প্রয়োগ করতে সাবক্লাসগুলিতে অপারেশনটি অর্পণ করতে পারেন। প্রতিবার নতুন সাবক্লাস যুক্ত করার সময় আপনাকে বিবৃতিগুলি সম্পাদনা করতে হবে না।
এরিক tদ

2
ডাবল ডিসপ্যাচ বিবেচনা করুন।
বোরিস স্পাইডার

আপনার সরঞ্জাম শ্রেণিতে এমন একটি সম্পত্তি কেন যুক্ত করবেন না যা বিশিষ্ট প্রকারের অভিধান (যেমন আক্রমণ, প্রতিরক্ষা) এবং এর জন্য নির্ধারিত একটি মান ধারণ করে। আক্রমণ, প্রতিরক্ষা মান গণনা করা যেতে পারে। তারপরে আপনি কেবল গণনা করা ধ্রুবক দ্বারা সরঞ্জাম থেকে মানটি কল করতে পারেন।
ব্যবহারকারী1740075

8
আমি কেবল এটি এখানে রেখে যাব: ericlippert.com/2015/04/27/wizards-and-warriors-part-one
আপনি

1
ভিজিটর ধরণটিও দেখুন।
জেডিগোগস

উত্তর:


63

হ্যাঁ, এটি একটি কোড গন্ধ (প্রচুর ক্ষেত্রে)।

আমি মনে করি যদি অন্যথায় সরঞ্জামগুলিতে ভার্চুয়াল পদ্ধতিতে প্রতিস্থাপন করা কঠিন is

আপনার উদাহরণে, ভার্চুয়াল পদ্ধতি দ্বারা যদি / অন্যটি প্রতিস্থাপন করা বেশ সহজ:

class Tool{
 public:
   virtual int GetAttack() const=0;
   virtual int GetDefense() const=0;
};

class Sword : public Tool{
    // ...
 public:
   virtual int GetAttack() const {return attack;}
   virtual int GetDefense() const{return 0;}
};

এখন আপনার ifব্লকের আর কোনও প্রয়োজন নেই , কলার কেবল এটির মতো ব্যবহার করতে পারেন

       currentAttack+=tool->GetAttack();
       currentDefense+=tool->GetDefense();

অবশ্যই, আরও জটিল পরিস্থিতির জন্য, এই জাতীয় সমাধানটি সর্বদা এতটা সুস্পষ্ট হয় না (তবে প্রায় যে কোনও সময় সম্ভব)। তবে আপনি যদি এমন পরিস্থিতিতে পৌঁছে যান যে ভার্চুয়াল পদ্ধতিতে কীভাবে মামলাটি সমাধান করতে হয় তা আপনি জানেন না, আপনি "প্রোগ্রামারস" (বা, এটি যদি ভাষা বা বাস্তবায়ন নির্দিষ্ট হয়ে যায়, স্ট্যাকওভারফ্লো) তে আবার একটি নতুন প্রশ্ন জিজ্ঞাসা করতে পারেন।


4
বা, এই বিষয়টির জন্য, গেমদেব.স্ট্যাকেক্সেক্সচেঞ্জ.কম
ক্রোমস্টার বলেছেন মনিকা

7
এমনকি Swordআপনার কোডবেসে আপনার এই ধরণের ধারণার প্রয়োজন হবে না । আপনি ঠিক new Tool("sword", swordAttack, swordDefense)যেমন একটি JSON ফাইল হতে পারে ।
অ্যামেজিংড্রিমস

7
@ অ্যামাজিংড্রাইমস: এটি সঠিক (আমরা এখানে কোডের কিছু অংশ দেখতে পাই) তবে আমার ধারণা, ওপিতে যে প্রশ্নটি আলোচনা করতে চেয়েছিলেন তার দিকে মনোনিবেশ করার জন্য তাঁর প্রশ্নের জন্য তাঁর আসল কোডটি আরও সহজ করেছেন।
ডক ব্রাউন

3
এটি মূল কোডের চেয়ে বেশি ভাল নয় (ভাল, এটি কিছুটা)। অতিরিক্ত বৈশিষ্ট্যযুক্ত যে কোনও সরঞ্জাম অতিরিক্ত পদ্ধতি যুক্ত না করে তৈরি করা যায় না। আমি মনে করি এক্ষেত্রে উত্তরাধিকারের চেয়েও রচনাকে সমর্থন করা উচিত। হ্যাঁ, বর্তমানে কেবল আক্রমণ এবং প্রতিরক্ষা রয়েছে, তবে সেভাবে থাকার দরকার নেই।
বহুবৃত্তাকার

1
@ ডকব্রাউন হ্যাঁ এটি সত্য, যদিও এটি একটি আরপিজির মতো দেখাচ্ছে যেখানে একটি চরিত্রের কিছু পরিসংখ্যান রয়েছে যা সরঞ্জাম বা বরং সজ্জিত আইটেমগুলির মাধ্যমে সংশোধিত হয়। আমি Toolসমস্ত সম্ভাব্য সংশোধকগুলির সাথে জেনেরিক তৈরি করব , vector<Tool*>একটি ডেটা ফাইল থেকে পড়া স্টাফ দিয়ে কিছু পূরণ করব , তারপরে কেবল লুপ করব এবং আপনার এখনকার মতো পরিসংখ্যান সংশোধন করব। আপনি যখন সমস্যায় পড়তে চাইবেন যখন কোনও আইটেম যেমন আক্রমণের জন্য 10% বোনাস দিতে চান। সম্ভবত একটি tool->modify(playerStats)অন্য বিকল্প।
অ্যামেজিংড্রিমস

23

আপনার কোডটির সাথে বড় সমস্যাটি হ'ল আপনি যখনই কোনও নতুন আইটেম প্রবর্তন করেন তখন আপনাকে কেবল আইটেমের কোডটি লিখতে এবং আপডেট করতে হয় না, আপনাকে আপনার প্লেয়ারকেও (বা আইটেমটি যেখানেই ব্যবহৃত হয়) পরিবর্তন করতে হয় যা পুরো জিনিসটিকে একটি করে তোলে আরও অনেক জটিল।

থাম্বের একটি সাধারণ নিয়ম হিসাবে, আমি মনে করি এটি সর্বদা কৃপাশীল, যখন আপনি সাধারণ সাবক্লাসিং / উত্তরাধিকারের উপর নির্ভর করতে পারেন না এবং নিজেকে উজাড় করে দিতে হয়।

পুরো জিনিসটিকে আরও নমনীয় করে তুলতে আমি দুটি সম্ভাব্য পদ্ধতির কথা ভাবতে পারি:

  • অন্যদের উল্লেখ করা হয়েছে, সরানো attackএবং defenseবেস বর্গ সদস্য ও কেবল তাদের আরম্ভ 0। আপনি আসলে আক্রমণটির জন্য আইটেমটি দুলিয়ে রাখতে সক্ষম হন বা আক্রমণগুলি অবরোধ করতে এটি ব্যবহার করতে সক্ষম কিনা তা যাচাই হিসাবে এটি দ্বিগুণও হতে পারে।

  • একরকম কলব্যাক / ইভেন্ট সিস্টেম তৈরি করুন। এর জন্য বিভিন্ন সম্ভাব্য পন্থা রয়েছে।

    কীভাবে সহজ রাখবেন?

    • আপনি যেমন বেস ক্লাসের সদস্য তৈরি করতে পারেন virtual void onEquip(Owner*) {}এবং virtual void onUnequip(Owner*) {}
    • তাদের overloads বলে অভিহিত করা হবে এবং পরিসংখ্যান যখন (আ -) আইটেম, যেমন equipping সংশোধন virtual void onEquip(Owner *o) { o->modifyStat("attack", attackValue); }এবং virtual void onUnequip(Owner *o) { o->modifyStat("attack", -attackValue); }
    • পরিসংখ্যানগুলি কিছু গতিশীল উপায়ে অ্যাক্সেস করা যেতে পারে, যেমন একটি শর্ট স্ট্রিং বা কী হিসাবে ধ্রুবক ব্যবহার করে, আপনি এমনকি নতুন গিয়ারের নির্দিষ্ট মান বা বোনাসগুলিও প্রবর্তন করতে পারেন যা আপনাকে আপনার প্লেয়ার বা "মালিক" কে বিশেষভাবে পরিচালনা করতে হবে না।
    • ঠিক সময়ে আক্রমণ / প্রতিরক্ষা মানগুলির অনুরোধের তুলনায় এটি কেবল পুরো জিনিসটিকে আরও গতিশীল করে তোলে না, এটি আপনাকে অপ্রয়োজনীয় কলগুলি সংরক্ষণ করে এমনকি এমন আইটেম তৈরি করতে দেয় যা স্থায়ীভাবে আপনার চরিত্রকে প্রভাবিত করবে।

      উদাহরণস্বরূপ, এমন কোনও অভিশপ্ত রিং কল্পনা করুন যা একবার সজ্জিত কিছু লুকানো স্ট্যাটাস সেট করবে, আপনার চরিত্রটিকে স্থায়ীভাবে অভিশপ্ত হিসাবে চিহ্নিত করবে।


7

@ ডকব্রাউন যখন একটি ভাল উত্তর দিয়েছে, তবুও এটি যথেষ্ট পরিমাণে যায় না im উত্তরগুলির মূল্যায়ন শুরু করার আগে আপনার নিজের প্রয়োজনগুলি মূল্যায়ন করা উচিত। আপনার আসলে কী দরকার ?

নীচে আমি দুটি সম্ভাব্য সমাধান দেখাব যা বিভিন্ন প্রয়োজনের জন্য বিভিন্ন সুবিধা দেয়।

প্রথমটি খুব সরল এবং আপনি যা দেখিয়েছেন তার সাথে বিশেষভাবে তৈরি:

class Tool {
    public:
        std::string name;
        int attack;
        int defense;
}

public void attack() {
    int attack = this->attack;
    int defense = this->defense;
    for (Tool* tool : tools){
        attack += tool->attack;
        defense += tool->defense;
    }
}

এটি সরঞ্জামগুলি (যেমন সংরক্ষণ বা নেটওয়ার্কিংয়ের জন্য) যেমন খুব সহজেই সিরিয়ালাইজেশন / ডিসেরায়ালাইজেশন করতে দেয় এবং একেবারে ভার্চুয়াল প্রেরণের দরকার হয় না। যদি আপনার কোডটি আপনি দেখানো সমস্ত কিছু হয় এবং আপনি এটির অন্য কোনও বিকাশ হওয়ার প্রত্যাশা করেন না তবে বিভিন্ন নাম এবং সেই পরিসংখ্যানগুলির সাথে আরও বিভিন্ন সরঞ্জাম রয়েছে, কেবলমাত্র বিভিন্ন পরিমাণে, তবে এই পথে যাওয়ার উপায়।

@ ডকব্রাউন একটি সমাধান সরবরাহ করেছে যা এখনও ভার্চুয়াল প্রেরণের উপর নির্ভর করে এবং যদি আপনি কোনওভাবে আপনার কোডের অংশগুলির জন্য সরঞ্জামগুলি বিশেষায়িত করেন যা দেখানো হয়নি। তবে, যদি আপনার সত্যিই প্রয়োজন হয় বা অন্য আচরণও পরিবর্তন করতে চান, তবে আমি নিম্নলিখিত সমাধানটির পরামর্শ দেব:

উত্তরাধিকার উপর রচনা

আপনি যদি পরে কোনও সরঞ্জাম চান যা তত্পরতা পরিবর্তন করে ? নাকি রান গতি ? আমার কাছে মনে হচ্ছে আপনি আরপিজি করছেন making আরপিজিগুলির জন্য গুরুত্বপূর্ণ একটি বিষয় এক্সটেনশনের জন্য উন্মুক্ত । এখনও অবধি দেখানো সমাধানগুলি সেগুলি দেয় না। আপনার Toolক্লাসটি পরিবর্তন করতে হবে এবং প্রতিবার যখন কোনও নতুন অ্যাট্রিবিউট লাগবে তখন এটিতে নতুন ভার্চুয়াল পদ্ধতি যুক্ত করতে হবে।

দ্বিতীয় সমাধানটি যা আমি দেখিয়েছি তা হ'ল আমি একটি মন্তব্যে ইঙ্গিত দিয়েছিলাম - এটি উত্তরাধিকারের পরিবর্তে রচনা ব্যবহার করে এবং "পরিবর্তনের জন্য বন্ধ, এক্সটেনশনের জন্য উন্মুক্ত" নীতি অনুসরণ করে entity সত্তা সিস্টেমগুলি কীভাবে কাজ করে আপনি যদি তার সাথে পরিচিত হন তবে কিছু জিনিস পরিচিত লাগবে (আমি ES এর ছোট ভাই হিসাবে রচনাটি ভাবতে চাই)।

নোট করুন যে আমি নীচে যা দেখাব তা জাভা বা সি # এর মতো রানটাইম টাইপের তথ্য রয়েছে এমন ভাষাগুলিতে উল্লেখযোগ্যভাবে আরও মার্জিত। অতএব, আমি যে সি ++ কোডটি দেখছি তাতে কিছু "বুককিপিং" অন্তর্ভুক্ত করতে হবে যা এখানে ঠিকঠাক রচনাটি তৈরি করার জন্য প্রয়োজনীয়। হয়তো আরও সি ​​++ অভিজ্ঞতার সাথে আরও ভাল পদ্ধতির পরামর্শ দিতে সক্ষম।

প্রথমে আমরা কলারের পাশে আবার তাকাই । আপনার উদাহরণে, attackপদ্ধতির অভ্যন্তরে আহ্বায়ক হিসাবে আপনি সরঞ্জামগুলির মোটেই পাত্তা দিচ্ছেন না। আপনি যা যত্ন করেন তা হ'ল দুটি বৈশিষ্ট্য - আক্রমণ এবং প্রতিরক্ষা পয়েন্ট points আপনি না সত্যিই গ্রাহ্য যেখানে থেকে আসা, এবং আপনি অন্যান্য বৈশিষ্ট্য (যেমন রান গতি, দ্রুততা) সম্পর্কে যত্ন না।

সুতরাং প্রথমে, আমরা একটি নতুন ক্লাস চালু করি

class Component {
    public:
        // we need this, in Java we'd simply use getClass()
        virtual std::string type() const = 0;
};

এবং তারপরে, আমরা আমাদের প্রথম দুটি উপাদান তৈরি করি

class Attack : public Component {
    public:
        std::string type() const override { return std::string("mygame::components::Attack"); }
        int attackValue = 0;
};

class Defense : public Component {
    public:
      std::string type() const override { return std::string("mygame::components::Defense"); }
      int defenseValue = 0;
};

এরপরে, আমরা একটি সরঞ্জাম বৈশিষ্ট্যের একটি সেট ধরে রাখি এবং অন্যদের দ্বারা বৈশিষ্ট্যগুলিকে ক্যোরি-সক্ষম করে তুলি।

class Tool {
private:
    std::map<std::string, Component*> components;

public:
    /** Adds a component to the tool */
    void addComponent(Component* component) { 
        components[component->type()] = component;
    };
    /** Removes a component from the tool */
    void removeComponent(Component* component) { components.erase(component->type()); };
    /** Return the component with the given type */
    Component* getComponentByType(std::string type) { 
        std::map<std::string, Component*>::iterator it = components.find(type);
        if (it != components.end()) { return it->second; }
        return nullptr;
    };
    /** Check wether a tol has a given component */
    bool hasComponent(std::string type) {
        std::map<std::string, Component*>::iterator it = components.find(type);
        return it != components.end();
    }
};

মনে রাখবেন যে এই উদাহরণে, আমি কেবল প্রতিটি ধরণের একটি উপাদান থাকা সমর্থন করি - এটি জিনিসগুলিকে সহজ করে তোলে। আপনি তাত্ত্বিকভাবে একই ধরণের একাধিক উপাদানকেও মঞ্জুরি দিতে পারেন তবে এটি খুব দ্রুতই কুৎসিত হয়। একটি গুরুত্বপূর্ণ বিষয়: Toolএখন পরিবর্তনের জন্য বন্ধ রয়েছে - আমরা আর কখনও উত্সটিকে স্পর্শ করব না Tool- তবে এক্সটেনশনের জন্য উন্মুক্ত - আমরা কোনও সরঞ্জামের আচরণ অন্য জিনিসগুলিকে সংশোধন করে এবং কেবল অন্যান্য উপাদানগুলিকে এর মধ্যে দিয়ে প্রসারিত করতে পারি।

এখন উপাদানগুলির ধরণের মাধ্যমে আমাদের সরঞ্জামগুলি পুনরুদ্ধার করার একটি উপায় প্রয়োজন। আপনার কোড উদাহরণের মতো আপনি এখনও সরঞ্জামগুলির জন্য ভেক্টর ব্যবহার করতে পারেন:

class Player {
    private:
        int attack = 0; 
        int defense = 0;
        int walkSpeed;
    public:
        std::vector<Tool*> tools;
        std::vector<Tool*> getToolsByComponentType(std::string type) {
            std::vector<Tool*> retVal;
            for (Tool* tool : tools) {
                if (tool->hasComponent(type)) { 
                    retVal.push_back(tool); 
                }
            }
            return retVal;
        }

        void doAttack() {
            int attackValue = this->attack;
            int defenseValue = this->defense;

            for (Tool* tool : this->getToolsByComponentType(std::string("mygame::components::Attack"))) {
                Attack* component = (Attack*) tool->getComponentByType(std::string("mygame::components::Attack"));
                attackValue += component->attackValue;
            }
            for (Tool* tool : this->getToolsByComponentType(std::string("mygame::components::Defense"))) {
                Defense* component = (Defense*)tool->getComponentByType(std::string("mygame::components::Defense"));
                defenseValue += component->defenseValue;
            }
            std::cout << "Attack with strength " << attackValue << "! Defend with strenght " << defenseValue << "!";
        }
};

আপনি এটিকে নিজের Inventoryশ্রেণিতে রিফ্যাক্টর করতে পারেন , এবং অনুসন্ধানের টেবিলগুলি সঞ্চয় করতে পারেন যা উপাদানগুলির ধরন অনুসারে পুনরুদ্ধার সরঞ্জামগুলিকে ব্যাপকভাবে সরল করে এবং পুরো সংগ্রহটি বারবার পুনরাবৃত্তি এড়াতে পারে।

এই পদ্ধতির কি সুবিধা রয়েছে? ইন attack, আপনি দুটি প্রক্রিয়াযুক্ত সরঞ্জামগুলিতে প্রক্রিয়া করেন - আপনার অন্য কোনও কিছুর জন্য যত্ন নেই।

আপনার কোনও walkToপদ্ধতি রয়েছে তা কল্পনা করতে দিন এবং এখন আপনি সিদ্ধান্ত নিয়েছেন যে কোনও সরঞ্জাম যদি আপনার চলার গতি পরিবর্তন করার ক্ষমতা অর্জন করে তবে এটি একটি ভাল ধারণা। সমস্যা নেই!

প্রথমে নতুনটি তৈরি করুন Component:

class WalkSpeed : public Component {
public:
    std::string type() const override { return std::string("mygame::components::WalkSpeed"); }
    int speedBonus;
};

তারপরে আপনি নিজের জাগরণের গতি বাড়াতে এবং WalkToআপনার সবেমাত্র তৈরি করা উপাদানটি প্রক্রিয়া করার পদ্ধতিটি পরিবর্তন করতে চান সেই সরঞ্জামটিতে আপনি কেবলমাত্র এই উপাদানটির একটি উদাহরণ যোগ করুন :

void walkTo() {
    int walkSpeed = this->walkSpeed;

    for (Tool* tool : this->getToolsByComponentType(std::string("mygame::components:WalkSpeed"))) {
        WalkSpeed* component = (WalkSpeed*)tool->getComponentByType(std::string("mygame::components::Defense"));
        walkSpeed += component->speedBonus;
        std::cout << "Walk with " << walkSpeed << std::endl;
    }
}

মনে রাখবেন যে আমরা সরঞ্জাম ক্লাসে কোনও পরিবর্তন না করেই আমাদের সরঞ্জামগুলিতে কিছু আচরণ যুক্ত করেছি।

আপনি স্ট্রিংগুলিকে ম্যাক্রো বা স্ট্যাটিক কনস্ট ভেরিয়েবলে সরিয়ে নিতে পারেন (এবং হওয়া উচিত), যাতে আপনাকে বারবার টাইপ করতে হবে না।

আপনি যদি এই পদ্ধতিটি আরও গ্রহণ করেন - যেমন প্লেয়ারে যুক্ত হতে পারে এমন উপাদানগুলি তৈরি করুন, এবং এমন একটি Combatউপাদান তৈরি করুন যা খেলোয়াড়কে লড়াইয়ে অংশ নিতে সক্ষম বলে চিহ্নিত করে, তবে আপনি সেই attackপদ্ধতিটি থেকেও মুক্তি পেতে পারেন , এবং এটি পরিচালনা করতে দিন উপাদান দ্বারা বা অন্য কোথাও প্রক্রিয়াজাত করা।

প্লেয়ারকে কম্পোনেন্টগুলি পেতে সক্ষম করার সুবিধাটিও ততক্ষণে আপনার প্লেয়ারকে অন্যরকম আচরণ দেওয়ার জন্য পরিবর্তন করতে হবে না। আমার উদাহরণস্বরূপ, আপনি কোনও Movableউপাদান তৈরি করতে পারেন , সেইভাবে walkToপ্লেয়ারকে তাকে সরানোর জন্য পদ্ধতিটি প্রয়োগ করার দরকার নেই । আপনি কেবল উপাদানটি তৈরি করবেন, এটি প্লেয়ারের সাথে সংযুক্ত করুন এবং অন্য কাউকে এটি প্রক্রিয়া করতে দিন।

আপনি এই গিস্টটিতে একটি সম্পূর্ণ উদাহরণ খুঁজে পেতে পারেন: https://gist.github.com/NetzwergX/3a29e1b106c6bb9c7308e89dd715ee20

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

সম্পাদন করা

কিছু অন্যান্য উত্তর সরাসরি উত্তরাধিকার প্রস্তাব (তরোয়াল প্রসারিত সরঞ্জাম তৈরি, ঝাল প্রসারিত সরঞ্জাম তৈরি)। আমার মনে হয় না এটি এমন একটি দৃশ্য যেখানে উত্তরাধিকার খুব ভালভাবে কাজ করে। আপনি যদি সিদ্ধান্ত নেন যে একটি নির্দিষ্ট উপায়ে ieldাল দিয়ে ব্লক করা আক্রমণকারীটির ক্ষতি করতে পারে? আমার সমাধানের সাহায্যে আপনি কেবল একটি Attালটিতে একটি আক্রমণ উপাদান যুক্ত করতে পারেন এবং বুঝতে পারবেন যে আপনার কোডে কোনও পরিবর্তন ছাড়াই। উত্তরাধিকার সহ, আপনার একটি সমস্যা হবে। আরপিজিতে আইটেম / সরঞ্জামগুলি রচনা বা এমনকি শুরু থেকে সত্তা সিস্টেমগুলি ব্যবহার করে সরাসরি প্রার্থী।


1

সাধারণভাবে বলতে গেলে, আপনার যদি কখনও ifকোনও ওওপি ভাষায় (উদাহরণের ধরণের প্রয়োজনের সাথে মিশ্রণে) ব্যবহারের প্রয়োজন হয় তবে এটি একটি চিহ্ন, এটি দুর্গন্ধযুক্ত কিছু চলছে। কমপক্ষে, আপনার আপনার মডেলগুলি ঘনিষ্ঠভাবে দেখতে হবে।

আমি আপনার ডোমেনটি অন্যভাবে মডেল করব।

আপনার usecase জন্য Toolএকটি হয়েছে AttackBonusএবং DefenseBonusযা উভয় হতে পারে - 0যদি এটা পালক বা ওই জাতীয় কিছু মত যুদ্ধ জন্য অনর্থক হবে।

আক্রমণ করার জন্য, আপনার ব্যবহৃত অস্ত্র থেকে আপনার baserate+ রয়েছে bonus। প্রতিরক্ষা baserate+ এর ক্ষেত্রেও এটি একই রকম bonus

ফলস্বরূপ আপনার আক্রমণ / প্রতিরক্ষা বনি গণনার জন্য Toolএকটি virtualপদ্ধতি থাকতে হবে ।

TL; ড

আরও ভাল ডিজাইনের সাহায্যে আপনি হ্যাকি এড়াতে পারবেন if


কখনও কখনও একটি যদি প্রয়োজন হয়, উদাহরণস্বরূপ যখন স্কেলারের মানগুলির তুলনা করা হয়। অবজেক্ট টাইপ স্যুইচিংয়ের জন্য, এত বেশি নয়।
অ্যান্ডি

হাহা, যদি একটি অপরিহার্য অপারেটর হয় এবং আপনি কেবল এটি বলতে পারেন না যে এটি ব্যবহার করা একটি কোড গন্ধ।
tymtam

1
@ শব্দের অর্থ কিছুটা শ্রদ্ধা আপনি ঠিক বলেছেন। আমি নিজেকে আরও স্পষ্ট করে তুলেছি। আমি ifকম প্রোগ্রামিং রক্ষা করছি না । বেশিরভাগ ক্ষেত্রে যেমন instanceofবা এর মতো কিছু সংমিশ্রণে । তবে একটি অবস্থান রয়েছে, যা দাবি করে ifযে এটি একটি কোডমেল এবং এর চারপাশে যাওয়ার উপায় রয়েছে। এবং আপনি ঠিক বলেছেন, এটি একটি অপরিহার্য অপারেটর যার নিজস্ব অধিকার রয়েছে।
টমাস জাঙ্ক

1

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

এর মতো জেনেরিক পাত্রে ডেটা ingালার প্যাটার্নের সাথে মৌলিক সমস্যাটি হ'ল ডেটা প্রযোজক এবং ডেটা গ্রাহককে অবশ্যই একসাথে কাজ করতে হবে, তবে এটি প্রথম নজরে তারা তা করেছে তা স্পষ্ট নয়। এই প্যাটার্নের প্রতিটি উদাহরণে, দুর্গন্ধযুক্ত বা দুর্গন্ধযুক্ত নয়, এটি মৌলিক বিষয়। পরবর্তী বিকাশকারীটির পক্ষে সম্পূর্ণ অজানা থাকা খুব সম্ভব যে আপনি এই প্যাটার্নটিটি করছেন এবং এটি দুর্ঘটনাক্রমে ভেঙে দিয়েছেন, সুতরাং আপনি যদি এই প্যাটার্নটি ব্যবহার করেন তবে আপনাকে অবশ্যই পরবর্তী বিকাশকারীকে সাহায্য করার জন্য যত্নবান হতে হবে। আপনার অবিচ্ছিন্নভাবে কোডটি না ভাঙতে আপনার পক্ষে আরও সহজ করে তুলতে হবে কিছু বিশদের কারণে যার অস্তিত্ব নেই।

উদাহরণস্বরূপ, আমি যদি কোনও খেলোয়াড়ের অনুলিপি করতে চাই? আমি যদি কেবল প্লেয়ার অবজেক্টের বিষয়বস্তুগুলি দেখি তবে এটি বেশ সহজ দেখাচ্ছে looks আমি শুধু কপি আছে attack, defenseএবং toolsভেরিয়েবল। পাই হিসাবে সহজ! ঠিক আছে, আমি দ্রুত জানতে পারব যে আপনার পয়েন্টারগুলির ব্যবহার এটি আরও শক্ত করে তোলে (কোনও এক সময়ে এটি স্মার্ট পয়েন্টারগুলির দিকে নজর দেওয়া ভাল তবে এটি অন্য বিষয়)। যে সহজে সমাধান করা হয়। আমি কেবল প্রতিটি সরঞ্জামের নতুন কপি তৈরি করব এবং সেগুলি আমার নতুন toolsতালিকায় রাখব । সর্বোপরি, Toolকেবলমাত্র একজন সদস্য সহ সত্যই সাধারণ শ্রেণি। সুতরাং আমি একটি অনুলিপি তৈরি করেছি Sword, এর একটি অনুলিপি সহ , তবে আমি জানতাম না যে এটি একটি তরোয়াল ছিল, তাই আমি কেবলমাত্র অনুলিপি করেছি name। পরে, attack()ফাংশনটি নামটি দেখে, এটি একটি "তরোয়াল" দেখেছে, এটি কাস্ট করে এবং খারাপ জিনিস ঘটে!

আমরা এই কেসটিকে সকেট প্রোগ্রামিংয়ের অন্য কেসের সাথে তুলনা করতে পারি, যা একই প্যাটার্নটি ব্যবহার করে। আমি এইভাবে একটি ইউনিক্স সকেট ফাংশন সেট আপ করতে পারি:

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(portno);
serv_addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));

কেন এটি একই প্যাটার্ন? যেহেতু bindএকটি গ্রহণ করে না sockaddr_in*, এটি আরও জেনেরিক গ্রহণ করে sockaddr*। আপনি যদি এই ক্লাসগুলির সংজ্ঞাটি দেখেন তবে আমরা দেখতে পাই যে আমাদের যে sockaddrপরিবারকে sin_family* অর্পণ করা হয়েছে তার কেবলমাত্র একজন সদস্য রয়েছেন । পরিবারটি বলে যে কোন উপ টাইপটি আপনার করা উচিত sockaddrAF_INETআপনাকে বলে যে ঠিকানা কাঠামো আসলে একটি sockaddr_in। যদি এটি হয় AF_INET6তবে ঠিকানাটি একটি হবে sockaddr_in6, যার বৃহত্তর IPv6 ঠিকানাগুলিকে সমর্থন করার জন্য বৃহত্তর ক্ষেত্র রয়েছে।

এটি আপনার Toolউদাহরণের সাথে সমান , এটি কোনও পরিবারের পরিবর্তে কোন পরিবার নির্দিষ্ট করার জন্য কোনও পূর্ণসংখ্যা ব্যবহার করে std::string। যাইহোক, আমি দাবি করছি যে এটি গন্ধযুক্ত নয়, এবং এটি "সকেটগুলি করার একটি স্ট্যান্ডার্ড উপায়, সুতরাং এটি 'গন্ধ পাওয়া উচিত নয়' ব্যতীত অন্য কারণে এটি করার চেষ্টা করুন, অবশ্যই এটি একই প্যাটার্ন, যা আমি কেন দাবি করি যে জেনেরিক বস্তুগুলিতে ডেটা সংরক্ষণ এবং এটি ingালাই স্বয়ংক্রিয়ভাবে কোড গন্ধ নয়, তবে তারা কীভাবে এটি ব্যবহার করে তাতে কিছুটা পার্থক্য রয়েছে যা এটি নিরাপদ করে তোলে।

এই নিদর্শনটি ব্যবহার করার সময়, সর্বাধিক গুরুত্বপূর্ণ তথ্য হ'ল উত্পাদক থেকে গ্রাহক পর্যন্ত সাবক্লাস সম্পর্কিত তথ্য পৌঁছে দেওয়া। আপনি nameমাঠের সাথে এটি করছেন এবং ইউনিক্স সকেটগুলি তাদের sin_familyক্ষেত্রের সাথে কি করছেন । সেই ক্ষেত্রটি হ'ল সেই তথ্য যা ভোক্তাকে বুঝতে হবে যে প্রযোজক আসলে কী তৈরি করেছিলেন। এই ধরণের সমস্ত ক্ষেত্রে, এটি একটি গণনা হওয়া উচিত (বা খুব কমপক্ষে, একটি পূর্ণসংখ্যার একটি গণনার মতো অভিনয় করা)) কেন? আপনার গ্রাহক তথ্যটি কী করতে চলেছেন তা ভেবে দেখুন। তারা কিছু বড় ifবিবৃতি বা ক লিখেছেন প্রয়োজন হবেswitchবিবৃতি, যেমনটি আপনি করেছেন, যেখানে তারা সঠিক উপপ্রকারটি নির্ধারণ করে, এটি ফেলে দেয় এবং ডেটা ব্যবহার করে। সংজ্ঞা অনুসারে, এই ধরণের কয়েকটি সংখ্যকই হতে পারে। আপনি এটির মতো স্ট্রিংয়ে সংরক্ষণ করতে পারেন তবে এর অনেক অসুবিধা রয়েছে:

  • ধীর - std::stringসাধারণত স্ট্রিং রাখতে কিছু গতিশীল মেমরি করতে হয়। প্রতিবার আপনার কী সাবক্লাস রয়েছে তা বের করতে চাইলে নামের সাথে মেলে তুলতে আপনাকে একটি সম্পূর্ণ পাঠ্য তুলনা করতে হবে।
  • খুব বহুমুখী - যখন আপনি অত্যন্ত বিপজ্জনক কিছু করছেন তখন নিজেকে বাধা দেওয়ার জন্য কিছু বলার আছে। আমি এর মতো সিস্টেম পেয়েছি যা এটি কোন ধরণের অবজেক্টের দিকে চেয়েছিল তা জানাতে একটি স্ট্রিংয়ের সন্ধান করেছিল। কোনও অবজেক্টের নাম দুর্ঘটনাক্রমে সেই সাবস্ট্রিংটি উপস্থিত না হওয়া পর্যন্ত এটি দুর্দান্ত কাজ করেছে এবং মারাত্মক ক্রিপ্টিক ত্রুটি তৈরি করেছে। যেহেতু, যেমন আমরা উপরে বলেছি, আমাদের কেবল কয়েকটি সংখ্যক কেস প্রয়োজন, স্ট্রিংয়ের মতো বৃহত্তর বিদ্যুতের সরঞ্জাম ব্যবহার করার কোনও কারণ নেই। এটাও বিশালাকার...
  • ত্রুটির প্রবণতা - আসুন কেবলমাত্র এটি বলে দিন যে কোনও গ্রাহক ঘটনাক্রমে কোনও যাদু কাপড়ের নাম স্থাপন করলে জিনিস কেন কাজ করছে না তা ডিবাগ করার চেষ্টা করে আপনি একটি হত্যাকারী ছটফট করতে যেতে চাইবেন MagicC1oth। সিরিয়াসলি, এর মতো বাগগুলি কী ঘটেছে তা বুঝতে পেরে মাথা ফাটিয়ে কয়েক দিন সময় নিতে পারে।

একটি গণনা অনেক ভাল কাজ করে। এটি দ্রুত, সস্তা এবং ত্রুটিযুক্ত প্রবণতা:

class Tool {
public:
    enum TypeE {
        kSword,
        kShield,
        kMagicCloth
    };
    TypeE type;

    std::string typeName() const {
        switch(type) {
            case kSword:      return "Sword";
            case kSheild:     return "Sheild";
            case kMagicCloth: return "Magic Cloth";

            default:
                throw std::runtime_error("Invalid enum!");
        }
   }
};

এই উদাহরণটি switchenums জড়িত একটি বিবৃতিও দেখায় , এই প্যাটার্নের একক সবচেয়ে গুরুত্বপূর্ণ অংশটি: এমন একটি defaultমামলা যা ছুড়ে ফেলে। আপনি যদি নিখুঁতভাবে কাজগুলি করেন তবে আপনার কখনই সেই পরিস্থিতিতে পড়া উচিত নয় । তবে, যদি কেউ একটি নতুন সরঞ্জাম প্রকার যুক্ত করে এবং আপনি এটি সমর্থন করার জন্য আপনার কোডটি আপডেট করতে ভুলে যান তবে আপনি ত্রুটিটি ধরার জন্য কিছু চাইবেন। আসলে, আমি তাদের এতটাই সুপারিশ করছি যে আপনার প্রয়োজন না হলেও আপনার এগুলি যুক্ত করা উচিত।

এর অন্য বিশাল সুবিধাটি enumহ'ল এটি পরবর্তী বিকাশকারীকে ঠিক সামনে সামনে বৈধ সরঞ্জামের ধরণের একটি সম্পূর্ণ তালিকা দেয়। বব'র বিশেষায়িত বাঁশি শ্রেণিটি যা তিনি তাঁর মহাকাব্যিক বসের যুদ্ধে ব্যবহার করেন তা খুঁজে পাওয়ার জন্য কোডটি ভেঙে যাওয়ার দরকার নেই।

void damageWargear(Tool* tool)
{
    switch(tool->type)
    {
        case Tool::kSword:
            static_cast<Sword*>(tool)->damageSword();
            break;
        case Tool::kShield:
            static_cast<Sword*>(tool)->damageShield();
            break;
        default:
            break; // Ignore all other objects
    }
}

হ্যাঁ, আমি একটি "খালি" ডিফল্ট বিবৃতি রেখেছি, কেবল পরবর্তী বিকাশকারীকে এটি স্পষ্ট করে তুলতে যদি আমার কাছে কিছু নতুন অপ্রত্যাশিত প্রকার আসে তবে আমি কী ঘটতে আশা করি।

আপনি যদি এটি করেন তবে প্যাটার্নটি কম গন্ধ পাবে। যাইহোক, গন্ধমুক্ত থাকতে আপনাকে শেষ বিকল্পটি করতে হবে অন্য বিকল্পগুলি বিবেচনা করা। এই ক্যাসেটগুলি আপনার সি ++ রিপোর্টারে থাকা আরও কয়েকটি শক্তিশালী এবং বিপজ্জনক সরঞ্জাম। আপনার যদি ভাল কারণ না থাকে তবে এগুলি ব্যবহার করা উচিত নয়।

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

class Tool {
    public:
        enum TypeE {
            kSword,
            kShield,
            kMagicCloth
        };
    TypeE type;

    int   attack;
    int   defense;
};

এখন আপনার মোটেও সাবক্লাসের দরকার নেই। typeঅন্যান্য ক্ষেত্রগুলি আসলে বৈধ কিনা তা দেখতে আপনাকে কেবল ক্ষেত্রটি দেখতে হবে। এটি অনেক বেশি নিরাপদ এবং বোঝা সহজ। তবে এর ঘাটতি রয়েছে। আপনি এটি ব্যবহার করতে চান না এমন সময় রয়েছে:

  • যখন বস্তুগুলি খুব ভিন্ন হয় - আপনি ক্ষেত্রগুলির লন্ড্রি তালিকার সাথে শেষ করতে পারেন এবং এটি প্রতিটি স্পেসের ধরণের ক্ষেত্রে প্রযোজ্য তা অস্পষ্ট হতে পারে।
  • কোনও স্মৃতি-সঙ্কটজনক পরিস্থিতিতে কাজ করার সময় - আপনার যদি 10 টি সরঞ্জাম তৈরি করতে হয় তবে আপনি স্মৃতিশক্তি নিয়ে অলস হতে পারেন। আপনার যখন 500 মিলিয়ন সরঞ্জাম তৈরি করতে হবে তখন আপনি বিট এবং বাইটগুলি দেখাশোনা করতে যাচ্ছেন। ইউনিয়ন স্ট্রাক্টগুলি তাদের প্রয়োজনের চেয়ে সর্বদা বড় are

ইউআইআইএক্স সকেটগুলির দ্বারা এই সমাধানটি ব্যবহার করা হয় না কারণ এপিআই-এর খোলামেলা সমাপ্তি দ্বারা মিশ্রিত ভিন্নতা ইস্যুটির কারণে। ইউএনআইএক্স সকেটগুলির উদ্দেশ্যটি এমন কিছু তৈরি করা ছিল যা ইউনিক্সের প্রতিটি স্বাদ কাজ করতে পারে। প্রতিটি স্বাদ তাদের সমর্থন করে এমন পরিবারগুলির তালিকা সংজ্ঞায়িত করতে পারে AF_INET, এবং তাদের প্রত্যেকের জন্য একটি সংক্ষিপ্ত তালিকা থাকবে। তবে, যদি একটি নতুন প্রোটোকল আসে, যেমনটি AF_INET6করে, আপনার নতুন ক্ষেত্র যুক্ত করতে পারে add আপনি যদি ইউনিয়ন কাঠামো দিয়ে এটি করেন, আপনি কার্যকরভাবে একই নামের সাথে স্ট্রাক্টের একটি নতুন সংস্করণ তৈরি করে অন্তহীন অসঙ্গতি সমস্যা তৈরি করে শেষ করবেন। এই কারণেই ইউনিক্স সকেটগুলি ইউনিয়ন কাঠামোর পরিবর্তে castালাই প্যাটার্নটি ব্যবহার করতে পছন্দ করেছিল। আমি নিশ্চিত যে তারা এটি বিবেচনা করেছে এবং তারা এটি সম্পর্কে ভেবে দেখেছিল যে তারা এটি ব্যবহার করার সময় কেন গন্ধ পায় না part

আপনি বাস্তবের জন্য একটি ইউনিয়নও ব্যবহার করতে পারেন। ইউনিয়নগুলি কেবল বৃহত্তম সদস্য হিসাবে বৃহত্তর হয়ে স্মৃতি সংরক্ষণ করে, তবে তারা তাদের নিজস্ব ইস্যু নিয়ে আসে। এটি সম্ভবত আপনার কোডের জন্য কোনও বিকল্প নয়, তবে এটি সর্বদা আপনার বিবেচনা করা উচিত।

আর একটি আকর্ষণীয় সমাধান হ'ল boost::variantবুস্ট একটি পুনরায় ব্যবহারযোগ্য ক্রস প্ল্যাটফর্ম সমাধানগুলিতে পূর্ণ একটি লাইব্রেরি। এটি সম্ভবত সেরা সি ++ কোডের কিছু লেখা। বুস্ট.ভেরিয়েন্ট মূলত ইউনিয়নগুলির সি ++ সংস্করণ। এটি এমন একটি ধারক যা বিভিন্ন ধরণের বিভিন্ন ধরণের ধারণ করতে পারে তবে একসাথে কেবলমাত্র একটি। আপনি আপনার Sword, Shieldএবং MagicClothক্লাসগুলি তৈরি করতে পারেন , তারপরে সরঞ্জামটিকে একটি হিসাবে পরিণত করতে পারেন boost::variant<Sword, Shield, MagicCloth>, যার অর্থ এই তিন ধরণের একটি রয়েছে। এটি এখনও ভবিষ্যতের সামঞ্জস্যতার সাথে একই সমস্যার সাথে ভুগছে যা ইউনিক্স সকেটগুলি ব্যবহার করা থেকে বিরত করে (ইউএনআইএক্স সকেটগুলি সি হিসাবে উল্লেখ করা হয় না, ভবিষ্যদ্বাণী করেboostকিছুটা হলেও!), তবে এই প্যাটার্নটি অবিশ্বাস্যভাবে কার্যকর হতে পারে। বৈকল্পিক প্রায়শই ব্যবহৃত হয়, উদাহরণস্বরূপ, পার্স গাছগুলিতে, যা পাঠ্যের একটি স্ট্রিং নেয় এবং নিয়মের জন্য ব্যাকরণ ব্যবহার করে এটি ভেঙে দেয়।

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

class Tool;
class Sword;
class Shield;
class MagicCloth;

class ToolVisitor {
public:
    virtual void visit(Sword* sword) = 0;
    virtual void visit(Shield* shield) = 0;
    virtual void visit(MagicCloth* cloth) = 0;
};

class Tool {
public:
    virtual void accept(ToolVisitor& visitor) = 0;
};

lass Sword : public Tool{
public:
    virtual void accept(ToolVisitor& visitor) { visitor.visit(*this); }
    int attack;
};
class Shield : public Tool{
public:
    virtual void accept(ToolVisitor& visitor) { visitor.visit(*this); }
    int defense;
};
class MagicCloth : public Tool{
public:
    virtual void accept(ToolVisitor& visitor) { visitor.visit(*this); }
    int attack;
    int defense;
};

তাহলে এই godশ্বর আশ্চর্য প্যাটার্নটি কী? ওয়েল, Toolএকটি ভার্চুয়াল ফাংশন আছে accept,। আপনি যদি এটি কোনও দর্শনার্থী পাস করেন তবে প্রত্যাশা করা visitহয় যে এই ধরণের জন্য দর্শকের পক্ষে সঠিক ফাংশনটি কল করা হবে । visitor.visit(*this);প্রতিটি উপ টাইপের ক্ষেত্রে এটিই হয় । জটিল, তবে আমরা উপরের উদাহরণ দিয়ে এটিকে প্রদর্শন করতে পারি:

class AttackVisitor : public ToolVisitor
{
public:
    int& currentAttack;
    int& currentDefense;

    AttackVisitor(int& currentAttack_, int& currentDefense_)
    : currentAttack(currentAttack_)
    , currentDefense(currentDefense_)
    { }

    virtual void visit(Sword* sword)
    {
        currentAttack += sword->attack;
    }

    virtual void visit(Shield* shield)
    {
        currentDefense += shield->defense;
    }

    virtual void visit(MagicCloth* cloth)
    {
        currentAttack += cloth->attack;
        currentDefense += cloth->defense;
    }
};

void Player::attack()
{
    int currentAttack = this->attack;
    int currentDefense = this->defense;
    AttackVisitor v(currentAttack, currentDefense);
    for (Tool* t: tools) {
        t->accept(v);
    }
    //some other functions to start attack
}

তাহলে এখানে কী ঘটে? আমরা একটি ভিজিটর তৈরি করি যা আমাদের জন্য কিছু কাজ করবে, এটি একবার জানলে এটি কী ধরণের অবজেক্টে ভিজিট করছে। তারপরে আমরা সরঞ্জামগুলির তালিকাটি নিয়ে পুনরাবৃত্তি করি। যুক্তি দানের জন্য, আসুন প্রথম বস্তুটি একটি Shield, তবে আমাদের কোডটি এখনও এটি জানে না। এটি কল করে t->accept(v), একটি ভার্চুয়াল ফাংশন। কারণ প্রথম অবজেক্টটি একটি ঝাল, এটি কলিংয়ের সমাপ্ত হয় void Shield::accept(ToolVisitor& visitor), যা কল করে visitor.visit(*this);। এখন, আমরা যখন কোন visitকলটি খুঁজছি , আমরা ইতিমধ্যে জানতে পারি যে আমাদের একটি ঝাল রয়েছে (কারণ এই ফাংশনটি ডেকেছে), তাই আমরা void ToolVisitor::visit(Shield* shield)আমাদের সাথে যোগাযোগ করব AttackVisitor। এটি এখন আমাদের প্রতিরক্ষা আপডেট করতে সঠিক কোড চালায় runs

দর্শনার্থী বিশাল। এটি এতটা ছোঁয়াচে যে আমি প্রায় এটির নিজস্ব গন্ধ আছে বলে মনে করি। খারাপ দর্শকদের নিদর্শনগুলি লেখা খুব সহজ। যাইহোক, এটির একটি বিশাল সুবিধা রয়েছে অন্যদের কোনওটিরই নয়। আমরা যদি কোনও নতুন সরঞ্জাম প্রকার যুক্ত করি তবে এর জন্য আমাদের একটি নতুন ToolVisitor::visitফাংশন যুক্ত করতে হবে। তাত্ক্ষণিকভাবে আমরা এটি করি, প্রোগ্রামের প্রতিটি ToolVisitor সংকলন করতে অস্বীকার করবে কারণ এতে কোনও ভার্চুয়াল ফাংশন অনুপস্থিত। এটি এমন কিছু ক্ষেত্রে ধরা খুব সহজ করে তোলে যেখানে আমরা কিছু মিস করেছি। আপনি যদি কাজটি করতে বিবৃতি ifবা switchবিবৃতি ব্যবহার করেন তবে গ্যারান্টি দেওয়া অনেক কঠিন । এই সুবিধাগুলি যথেষ্ট ভাল যে ভিজিটর 3 ডি গ্রাফিক্স দৃশ্যের জেনারেটরগুলির মধ্যে একটি দুর্দান্ত কুলুঙ্গি খুঁজে পেয়েছে। ভিজিটর যে ধরণের আচরণ দেয় তা তাদের ঠিক প্রয়োজন হয় তাই এটি দুর্দান্ত কাজ করে!

সব মিলিয়ে মনে রাখবেন যে এই নিদর্শনগুলি পরবর্তী বিকাশকারীকে শক্ত করে তোলে। তাদের জন্য এটিকে আরও সহজ করে দেওয়ার জন্য সময় ব্যয় করুন এবং কোডটি গন্ধ পাবে না!

* প্রযুক্তিগতভাবে, যদি আপনি অনুমানটি দেখুন, সোকাড্ডারের একটি সদস্যের নাম রয়েছে sa_family। সি স্তরে এখানে কিছু কৌতুকপূর্ণ কাজ করা হচ্ছে যা আমাদের পক্ষে গুরুত্বপূর্ণ নয়। আপনি প্রকৃত বাস্তবায়নটি দেখার জন্য স্বাগত , তবে এই উত্তরের জন্য আমি sa_family sin_familyএবং অন্যদের সম্পূর্ণরূপে বিনিময়যোগ্য হিসাবে ব্যবহার করতে যাচ্ছি , গদ্যের জন্য যে কোনও একটিই স্বজ্ঞাত, এটি বিশ্বাস করে যে সি ট্রিকরি গুরুত্বহীন বিবরণের যত্ন নেয়।


একটানা আক্রমণ করা খেলোয়াড়কে আপনার উদাহরণে অসীম শক্তিশালী করে তুলবে। এবং আপনি টুলভিজারের উত্সটি সংশোধন না করে আপনার পদ্ধতির প্রসারিত করতে পারবেন না। যদিও এটি একটি দুর্দান্ত সমাধান।
বহুবিধ

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

1
হ্যাঁ, দুঃখের বিষয় আমরা সত্যই অবহিত সিদ্ধান্ত নেওয়ার জন্য ওপিএসের পছন্দসমূহ এবং লক্ষ্যগুলি সম্পর্কে পর্যাপ্ত পরিমাণে জানি না। এবং হ্যাঁ, একটি সম্পূর্ণরূপে নমনীয় সমাধান কঠিন বাস্তবায়ন আমি প্রায় 3H জন্য আমার উত্তর কাজ হয়, যেহেতু আমার সি ++ চমত্কার মরিচা :( হয়
Polygnome

0

সাধারণভাবে আমি বেশিরভাগ শ্রেণি প্রয়োগ / উত্তরাধিকার সূত্রে এড়াতে পারি যদি এটি কেবল তথ্য যোগাযোগের জন্য হয়। আপনি একটি একক শ্রেণিতে লেগে থাকতে পারেন এবং সেখান থেকে সবকিছু বাস্তবায়ন করতে পারেন। আপনার উদাহরণস্বরূপ, এটি যথেষ্ট

class Tool{
    public:
    //constructor, name etc.
    int GetAttack() { return attack }; //Endpoints for your Player
    int GetDefense() { return defense };
    protected:
         int attack;
         int defense;
};

সম্ভবত, আপনি অনুমান করছেন যে আপনার গেমটি বিভিন্ন ধরণের তরোয়াল ইত্যাদি প্রয়োগ করবে তবে এটিকে প্রয়োগ করার জন্য আপনার অন্য উপায় থাকবে। ক্লাস বিস্ফোরণ খুব কমই সেরা স্থাপত্য। সহজবোধ্য রাখো.


0

আগেই বলা হয়েছে, এটি একটি গুরুতর কোড-গন্ধ। তবে, কেউ আপনার সমস্যার উত্সটিকে আপনার নকশায় রচনার পরিবর্তে উত্তরাধিকার ব্যবহার করা বিবেচনা করতে পারে।

উদাহরণস্বরূপ, আপনি আমাদের যা দেখিয়েছেন তা প্রদত্ত, আপনার কাছে স্পষ্টভাবে 3 টি ধারণা রয়েছে:

  • পদ
  • আইটেম যা আক্রমণ করতে পারে।
  • আইটেম যা প্রতিরক্ষা থাকতে পারে।

মনে রাখবেন যে আপনার চতুর্থ শ্রেণিটি শেষ দুটি ধারণার সংমিশ্রণ মাত্র। সুতরাং আমি এটির জন্য রচনাটি ব্যবহার করার পরামর্শ দেব।

আক্রমণ করার জন্য প্রয়োজনীয় তথ্য উপস্থাপন করতে আপনার একটি ডেটা কাঠামো প্রয়োজন। এবং আপনার একটি তথ্য কাঠামো প্রয়োজন প্রতিরক্ষা জন্য প্রয়োজনীয় তথ্য প্রতিনিধিত্ব করে। সবশেষে, আপনার কাছে এমন একটি ডেটা কাঠামো দরকার যা এমন দুটি বিষয় বা দুটি বৈশিষ্ট্যই থাকতে পারে বা না করতে পারে এমন জিনিসগুলি উপস্থাপন করতে:

class Attack
{
private:
  int attack_;

public:
  int AttackValue() const;
};

class Defense
{
private:
  int defense_

public:
  int DefenseValue() const;
};

class Tool
{
private:
  std::optional<Attack> atk_;
  std::optional<Defense> def_;

public:
  const std::optional<Attack> &GetAttack() const {return atk_;}
  const std::optional<Defense> &GetDefense() const {return def_;}
};

এছাড়াও: একটি রচনা-সর্বদা পদ্ধতির ব্যবহার করবেন না :)! এই ক্ষেত্রে রচনা কেন ব্যবহার করছেন? আমি একমত যে এটি একটি বিকল্প সমাধান, তবে একটি ক্ষেত্রকে "
এনপ্যাপসুলেটিং

@ আইলুরাসফুলজেন্স: এটি আজ "ক্ষেত্র"। আগামীকাল কী হবে? এই নকশা অনুমতি দেয় Attackএবং Defenseতার বেশি ইন্টারফেস পরিবর্তন না করে জটিল পরিণত Tool
নিকোল বোলাস

1
আপনি এটির সাথে এখনও সরঞ্জামটি খুব ভালভাবে প্রসারিত করতে পারবেন না - অবশ্যই, আক্রমণ এবং প্রতিরক্ষা আরও জটিল হতে পারে তবে তা এটি। আপনি যদি এর সম্পূর্ণ শক্তিতে রচনা ব্যবহার করেন Toolতবে এটি এক্সটেনশনের জন্য খোলার সময় আপনি সংশোধনের জন্য সম্পূর্ণ বন্ধ করতে পারেন ।
বহুবিবাহ

@ পলিগনোম: আপনি যদি এইরকম তুচ্ছ মামলার জন্য একটি সম্পূর্ণ স্বেচ্ছাসেবী উপাদান সিস্টেম তৈরির সমস্যার মধ্য দিয়ে যেতে চান তবে এটি আপনারই বিষয়। আমি ব্যক্তিগতভাবে কোনও কারণ দেখি না কেন আমি Toolএটি পরিবর্তন না করেই বাড়াতে চাই । এবং যদি আমার এটি সংশোধন করার অধিকার থাকে তবে আমি স্বেচ্ছাচারী উপাদানগুলির প্রয়োজন দেখছি না।
নিকল বোলাস

যতক্ষণ না টুল আপনার নিজের নিয়ন্ত্রণে থাকে আপনি এটিকে সংশোধন করতে পারেন । তবে "পরিবর্তনের জন্য বন্ধ, এক্সটেনশনের জন্য উন্মুক্ত" নীতিটি সঠিক কারণে উপস্থিত রয়েছে (এখানে বিশদভাবে জানাতে খুব দীর্ঘ)। যদিও আমি মনে করি এটি তুচ্ছ নয়। আপনি যদি কোনও আরপিজির জন্য নমনীয় উপাদান ব্যবস্থা করার উপযুক্ত সময় ব্যয় করেন, আপনি দীর্ঘমেয়াদে অসাধারণ পুরষ্কার অর্জন করবেন reward আমি কেবল সরল ক্ষেত্র ব্যবহার না করে এই ধরণের রচনাতে যুক্ত বেনিফিটটি দেখতে পাচ্ছি না । আরও আক্রমণ এবং প্রতিরক্ষা বিশেষজ্ঞ করতে সক্ষম হওয়া খুব তাত্ত্বিক দৃশ্য বলে মনে হচ্ছে। তবে আমি যেমন লিখেছি, এটি ওপির সঠিক প্রয়োজনীয়তার উপর নির্ভর করে।
বহুজনিত

0

কেন বিমূর্ত পদ্ধতি তৈরি করছে না modifyAttackএবং modifyDefenseToolবর্গ? তারপরে প্রতিটি শিশুর নিজস্ব প্রয়োগ হবে এবং আপনি এই মার্জিত উপায়ে কল করুন:

for(Tool* tool : tools){
    currentAttack = tool->recalculateAttack(currentAttack);
    currentDefense = tool->recalculateDefense(currentDefense);
}
// proceed with new values for currentAttack and currentDefense

রেফারেন্স হিসাবে মানগুলি পাস করা সংস্থানগুলি সংরক্ষণ করতে পারে যদি আপনি:

for(Tool* tool : tools){
    tool->recalculateAttack(&currentAttack);
    tool->recalculateDefense(&currentDefense);
}
// proceed with new values for currentAttack and currentDefense

0

যদি কেউ পলিমারফিজম ব্যবহার করে তবে সর্বদা সেরা যদি কোন কোডটি কোন শ্রেণীর জন্য ব্যবহৃত হয় সে সম্পর্কে যত্নশীল সকল শ্রেণীর মধ্যেই থাকে। এইভাবে আমি এটি কোড করব:

class Tool{
 public:
   virtual void equipTo(Player* player) =0;
   virtual void unequipFrom(Player* player) =0;
};

class Sword : public Tool{
  public:
    int attack;
    virtual void equipTo(Player* player) {
      player->attackBonus+=this->attack;
    };
    //unequipFrom = reverse equip
};
class Shield : public Tool{
  public:
    int defense;
    virtual void equipTo(Player* player) {
      player->defenseBonus+=this->defense;
    };
    //unequipFrom = reverse equip
};
//other tools
class Player{
  public:
    int baseAttack;
    int baseDefense;
    int attackBonus;
    int defenseBonus;

    virtual void equip(Tool* tool) {
      tool->equipTo(this);
      this->tools.push_back(tool)
    };

    //unequip = reverse equip

    void attack(){
      //modified attack and defense
      int modifiedAttack = baseAttack + this->attackBonus;
      int modifiedDefense = baseDefense+ this->defenseBonus;
      //some other functions to start attack
    }
  private:
    vector<Tool*> tools;
};

এর নিম্নলিখিত সুবিধা রয়েছে:

  • নতুন ক্লাস যুক্ত করা সহজ: আপনাকে কেবল সমস্ত বিমূর্ত পদ্ধতি প্রয়োগ করতে হবে এবং বাকী কোডটি কেবল কাজ করে
  • ক্লাস অপসারণ করা সহজ
  • নতুন পরিসংখ্যান যুক্ত করা সহজ (ক্লাসগুলি যে স্ট্যাটাসের যত্ন নেয় না কেবল এটিকে উপেক্ষা করে)

আপনার অন্ততপক্ষে একটি আনস্কিপ () পদ্ধতি অন্তর্ভুক্ত করা উচিত যা প্লেয়ার থেকে বোনাস সরিয়ে দেয়।
বহুদিনের

0

আমি মনে করি এই পদ্ধতির ত্রুটিগুলি চিহ্নিত করার একটি উপায় হ'ল আপনার ধারণাকে যৌক্তিক উপসংহারে বিকাশ করা।

এটি একটি গেমের মতো দেখায়, তাই কোনও পর্যায়ে আপনি সম্ভবত কর্মক্ষমতা সম্পর্কে চিন্তা করতে শুরু করবেন এবং একটি intবা এর জন্য এই স্ট্রিং তুলনাগুলি অদলবদল করবেন enum। আইটেমের if-elseতালিকাগুলি আরও দীর্ঘ হওয়ার সাথে সাথে এটি বেশ অল্পস্বল্প হয়ে উঠতে শুরু করে, তাই আপনি এটিকে একটিতে পুনরায় সজ্জিত করার বিষয়টি বিবেচনা করতে পারেন switch-case। আপনি এই মুহুর্তে পাঠ্যের একটি প্রাচীরও পেয়েছেন যাতে আপনি প্রতিটিটির মধ্যে caseক্রিয়াকে আলাদা ফাংশনে পরিণত করতে পারেন ।

আপনি এই মুহুর্তে পৌঁছে গেলে আপনার কোডের কাঠামোটি পরিচিত দেখা শুরু করে - এটির শুরুটি হোমব্রিউ, হ্যান্ড রোলড ভেটেবল * - এর মতো প্রাথমিক কাঠামোর মতো দেখা যা ভার্চুয়াল পদ্ধতিগুলি সাধারণত প্রয়োগ করা হয় typically ব্যতীত, এটি একটি vtable যা আপনাকে নিজেরাই নিজে আপডেট করতে এবং বজায় রাখতে হবে, প্রতিবার যখন আপনি কোনও আইটেমের প্রকার যুক্ত বা সংশোধন করেন।

"আসল" ভার্চুয়াল ফাংশনগুলিকে স্টিক করে আপনি প্রতিটি আইটেমের আচরণের প্রয়োগটি আইটেমের মধ্যেই রাখতে সক্ষম হন। আপনি আরও স্ব-অন্তর্ভুক্ত এবং ধারাবাহিক উপায়ে অতিরিক্ত আইটেম যুক্ত করতে সক্ষম হন। এবং আপনি এই সমস্ত কিছু করার সাথে সাথে, এটি সংকলকটি আপনার পরিবর্তে আপনার গতিশীল প্রেরণের প্রয়োগের যত্ন নেবে।

আপনার নির্দিষ্ট সমস্যা সমাধানের জন্য: আপনি আক্রমণ এবং প্রতিরক্ষা আপডেট করার জন্য ভার্চুয়াল ফাংশনগুলির একটি সহজ জুটি লিখতে সংগ্রাম করছেন কারণ কিছু আইটেম কেবল আক্রমণকে প্রভাবিত করে এবং কিছু আইটেম কেবল প্রতিরক্ষাকে প্রভাবিত করে। এই মত একটি সহজ ক্ষেত্রে কৌশলটি উভয় আচরণ যেভাবেই বাস্তবায়িত করতে পারে তবে কিছু ক্ষেত্রে তার কোনও প্রভাব নেই। GetDefenseBonus()ফিরে আসতে পারে 0বা ApplyDefenseBonus(int& defence)কেবল defenceঅপরিবর্তিত থাকতে পারে । আপনি কীভাবে এটির উপর নির্ভরশীল তার উপর নির্ভর করে আপনি কীভাবে কার্যকর হবেন সেই অন্যান্য ক্রিয়াগুলি কীভাবে পরিচালনা করতে চান। আরও জটিল ক্ষেত্রে, যেখানে আরও বৈচিত্রময় আচরণ রয়েছে, আপনি কেবল ক্রিয়াকলাপকে একটি পদ্ধতিতে সংযুক্ত করতে পারেন।

* (সাধারণত বাস্তবায়নের তুলনায় ট্রান্সপোসড)


0

সমস্ত সম্ভাব্য "সরঞ্জাম" সম্পর্কে জেনে থাকা কোডের একটি ব্লক থাকা দুর্দান্ত ডিজাইন নয় (বিশেষত যেহেতু আপনি আপনার কোডে এরকম অনেকগুলি ব্লক শেষ করবেন ); তবে Toolসমস্ত সম্ভাব্য সরঞ্জামের বৈশিষ্ট্যের জন্য স্টাবের সাথে কোনও বেসিক নেই : এখন Toolক্লাসে সমস্ত সম্ভাব্য ব্যবহার সম্পর্কে জানতে হবে।

কি প্রতিটি টুল জানেন এটা চরিত্র এটা ব্যবহার করে অবদান রাখতে পারেন হয়। সুতরাং সমস্ত সরঞ্জামের জন্য একটি পদ্ধতি সরবরাহ করুন giveto(*Character owner),। এটি অন্যান্য সরঞ্জামগুলি কী করতে পারে তা না জেনে এবং খেলোয়াড়ের পরিসংখ্যান যথাযথভাবে সামঞ্জস্য করবে এবং সেরাটি কী তা চরিত্রের অপ্রাসঙ্গিক বৈশিষ্ট্য সম্পর্কেও জানতে হবে না। উদাহরণস্বরূপ, একটি ঢাল বৈশিষ্ট্য গুলো সম্পর্কে জানা প্রয়োজন নেই attack, invisibility, healthইত্যাদি যে সমস্ত আবেদন করতে চরিত্র বৈশিষ্ট্য সমর্থন করা যে বস্তুর প্রয়োজন একটি টুল দরকার। যদি আপনি গাধাটির তরোয়াল দেওয়ার চেষ্টা করেন এবং গাধাটির কোনও attackপরিসংখ্যান নেই, আপনি একটি ত্রুটি পাবেন।

সরঞ্জামগুলির একটি remove()পদ্ধতিও থাকা উচিত , যা তাদের প্রভাবের মালিকের উপর বিপরীত হয়। এটি সামান্য কৌশলযুক্ত (এমন সরঞ্জামগুলির সাথে শেষ হওয়া সম্ভব যা প্রদত্ত এবং পরে তা ছিনিয়ে নেওয়ার সময় একটি শূন্য-প্রভাব ফেলে) তবে কমপক্ষে প্রতিটি সরঞ্জামে এটি স্থানীয়করণ হয়।


-4

এর কোনও উত্তর নেই যা বলে যে এটি গন্ধ পায় না তাই আমিই সেই মতামতের পক্ষে দাঁড়াব; এই কোড পুরোপুরি ঠিক আছে! আমার মতামত এই সত্যের ভিত্তিতে যে কখনও কখনও আপনি আরও নতুন স্টাফ তৈরি করার সাথে ধীরে ধীরে আপনার দক্ষতা ধীরে ধীরে বাড়তে দেওয়া সহজ হয়। আপনি একটি নিখুঁত আর্কিটেকচার তৈরির জন্য কয়েকদিন আটকে যেতে পারেন, তবে সম্ভবত কখনও কেউ এটিকে কার্যকর অবস্থায় দেখতে পাবেন না কারণ আপনি কখনও প্রকল্পটি শেষ করেননি। চিয়ার্স!


4
ব্যক্তিগত অভিজ্ঞতার সাথে দক্ষতা উন্নত করা ভাল, নিশ্চিত। তবে ইতিমধ্যে এমন ব্যক্তিগত অভিজ্ঞতা রয়েছে এমন লোকদের জিজ্ঞাসা করে দক্ষতা উন্নত করা, যাতে নিজেকে নিজেই গর্তে পড়ার দরকার নেই, এটি বুদ্ধিমান। নিশ্চয়ই এই কারণেই লোকেরা এখানে প্রথম স্থানে প্রশ্ন জিজ্ঞাসা করে, তাই না?
গ্রাহাম

আমি রাজি নই। তবে আমি বুঝতে পারি যে এই সাইটটি গভীরভাবে যাওয়ার বিষয়ে। কখনও কখনও এর অর্থ অত্যধিক পেডেন্টিক হওয়া। এই কারণেই আমি এই মতামতটি পোস্ট করতে চেয়েছিলাম, কারণ এটি বাস্তবে নোঙ্গর করা হয়েছে এবং আপনি যদি আরও ভাল হয়ে উঠতে এবং কোনও শিক্ষানবিশকে সহায়তা করার জন্য টিপস সন্ধান করছেন তবে আপনি "পুরোপুরি ভাল" সম্পর্কে এই পুরো অধ্যায়টি মিস করবেন যা কোনও শিক্ষানবিশকে জানতে যথেষ্ট কার্যকর।
ওস্টমিস্ট্রো
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.