Genel nesneleri bir kapta depolamak ve ardından nesneyi alıp kapsayıcıdan nesneleri indirmek kod kokusu mu?


34

Örneğin, Player'ın yeteneğini artıracak bazı araçlara sahip bir oyunum var:

Tool.h

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

Ve bazı araçlar:

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;
};

Ve sonra bir oyuncu saldırı için bazı araçlar tutabilir:

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
    }
};

Araçlardaki if-elsesanal yöntemlerle değiştirmenin zor olduğunu düşünüyorum , çünkü her bir aracın farklı özellikleri vardır ve her bir araç oyuncunun saldırısını ve savunmasını etkiler, bunun için oyuncu saldırısının ve savunmanın güncellenmesinin Player nesnesinin içinde yapılması gerekir.

Fakat bu tasarımdan memnun kalmadım, çünkü uzun süren bir if-elseaçıklama ile aşağı havayı içeriyor . Bu tasarımın "düzeltilmesi" gerekiyor mu? Eğer öyleyse, düzeltmek için ne yapabilirim?


4
Belirli bir alt sınıfın (ve sonraki alt kayıtların) testlerini kaldırmak için kullanılan standart bir OOP tekniği, if zinciri ve atmaları yerine kullanılacak baz sınıfında bir veya bu durumda belki iki sanal yöntem (ler) oluşturmaktır. Bu, if'nin tamamını kaldırın ve işlemi uygulamak için alt sınıflara devretmek için kullanılabilir. Ayrıca, her yeni alt sınıfı eklediğinizde if ifadelerini düzenlemek zorunda kalmazsınız.
Erik Eidt

2
Ayrıca, İkili Gönderimi de göz önünde bulundurun.
Örümcek Boris,

Neden Tool sınıfınıza bir nitelik türü sözlüğü (yani saldırı, savunma) ve buna atanan bir değer içeren bir özellik eklemiyorsunuz. Saldırı, savunma numaralandırılmış değerler olabilir. Ardından, numaralandırılan sabit tarafından yalnızca değeri Araçtan arayabilirsiniz.
user1740075


1
Ayrıca Ziyaretçi desenine bakınız.
JDługosz

Yanıtlar:


63

Evet, bir kod kokusudur (birçok durumda).

İf-else'in araçlarda sanal yöntemlerle değiştirilmesinin zor olduğunu düşünüyorum.

Örneğinizde, if / else öğesini sanal yöntemlerle değiştirmek oldukça kolaydır:

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;}
};

Artık ifbloğunuz için daha fazla gerek yok , arayan sadece onu kullanabilir

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

Elbette, daha karmaşık durumlar için, böyle bir çözüm her zaman çok açık değildir (ancak, hemen hemen her zaman mümkün olabilir). Ancak, vakanın nasıl sanal yöntemlerle çözüleceğini bilmediğiniz bir duruma gelirseniz, burada tekrar "Programcılar" (ya da dile veya uygulamaya özgü ise Stackoverflow'ta) sorunlarına yeni bir soru sorabilirsiniz.


4
veya, bu konuda, gamedev.stackexchange.com
Kromster

7
SwordKod tabanınızda bu şekilde kavramı bile gerekmez . Sadece new Tool("sword", swordAttack, swordDefense)bir JSON dosyasından olabilir.
AmazingDreams

7
@AmazingDreams: bu doğru (burada gördüğümüz kodun bölümleri için), ancak OP'nin tartışmak istediği konuya odaklanması sorusu için gerçek kodunu basitleştirdiğini tahmin ediyorum.
Doktor Brown,

3
Bu orijinal koddan çok daha iyi değil (peki, biraz). Ek özelliklere sahip hiçbir araç, ek yöntemler eklenmeden oluşturulamaz. Sanırım bu durumda mirasa ilişkin bir terfi lehte olmalı. Evet, şu anda sadece saldırı ve savunma var, ancak bu şekilde kalması gerekmiyor.
Polygnome

1
@DocBrown Evet, doğru, bir karakterin araçlar tarafından değiştirilen veya daha fazla donanıma sahip bazı istatistiklere sahip olduğu bir RPG'ye benziyor olsa da. ToolOlası tüm değiştiricileri ile bir jenerik yapabilirim vector<Tool*>, bir veri dosyasından okunan bazı şeyleri doldurabilirim , sonra sadece üzerlerine geçebilir ve şimdiki gibi istatistikleri değiştirebilirim. Bir öğenin saldırı için% 10 bonus vermesini istediğinizde başınız belaya girer. Belki bir tool->modify(playerStats)başka seçenek.
AmazingDreams

23

Kodunuzla ilgili en büyük sorun, herhangi bir yeni öğeyi ne zaman tanıtırsanız, yalnızca öğenin kodunu yazmanız ve güncellemenizin yanı sıra, oynatıcınızı (veya öğenin kullanıldığı her yeri) değiştirmek zorunda kalmanızdır; çok daha karmaşık.

Genel bir kural olarak, normal bir alt sınıflamaya / mirasa güvenemeyeceğiniz ve kendinizi büyütmek zorunda kalacağınız zaman, her zaman balık gibi olduğunu düşünüyorum.

Her şeyi daha esnek hale getirmek için iki olası yaklaşım düşünebilirim:

  • Diğerlerinin de belirttiği gibi, attackve defenseüyelerini temel sınıfa taşıyın ve bunları basitçe başlatın 0. Bu aynı zamanda, bir saldırı için öğeyi gerçekten sallayıp saldıramadığınızı veya atakları engellemek için kullanıp kullanamayacağınızı kontrol olarak ikiye katlayabilir.

  • Bir tür geri çağırma / etkinlik sistemi oluşturun. Bunun için farklı olası yaklaşımlar var.

    Basit tutmaya ne dersin?

    • virtual void onEquip(Owner*) {}Ve gibi bir temel sınıf üyesi oluşturabilirsiniz virtual void onUnequip(Owner*) {}.
    • Aşırı yükleri aranacak ve örneğin ( virtual void onEquip(Owner *o) { o->modifyStat("attack", attackValue); }ve ) öğeyi donatırken istatistikleri değiştireceklerdir virtual void onUnequip(Owner *o) { o->modifyStat("attack", -attackValue); }.
    • İstatistiklere, örneğin anahtar olarak kısa bir dize veya sabit kullanarak, dinamik bir şekilde erişilebilir, böylece oyuncuya veya özel olarak "sahibine" tabi tutmak zorunda kalmayacağınız yeni teçhizata özel değerler veya bonuslar bile ekleyebilirsiniz.
    • Saldırı / savunma değerleri tam zamanında istemekle karşılaştırıldığında, bu sadece her şeyi daha dinamik hale getirmekle kalmaz, aynı zamanda gereksiz çağrılardan tasarruf etmenizi sağlar ve hatta karakterinizi kalıcı olarak etkileyebilecek öğeler oluşturmanıza olanak tanır.

      Örneğin, karakterinizi kalıcı olarak lanetli olarak işaretleyerek, sadece bir kez teçhiz edildiğinde gizli bir stat ayarlayabilecek lanetli bir halka düşünün.


7

@DocBrown iyi bir cevap vermiş olsa da, yeterince ileri gitmiyor, imho. Cevapları değerlendirmeye başlamadan önce ihtiyaçlarınızı değerlendirmelisiniz. Gerçekten neye ihtiyacın var ?

Aşağıda farklı ihtiyaçlar için farklı avantajlar sunan iki olası çözüm göstereceğim.

İlki çok basit ve gösterdiklerinize özel olarak uyarlanmış:

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;
    }
}

Bu , araçların seri hale getirilmesine / seri hale getirilmesine (örn. Kaydetme veya ağ oluşturma) çok kolay bir şekilde izin verir ve sanal olarak gönderilmesine gerek yoktur. Eğer kodunuz gösterdiğiniz tek şeyse ve başka isimlerle daha fazla gelişmesini beklemiyorsanız, farklı isimlerle ve bu istatistiklerle daha farklı araçlara sahip olmanız, sadece farklı miktarlarda, o zaman bu yoludur.

@DocBrown, hala sanal gönderime dayanan bir çözüm önerdi ve kodunuzu gösterilmemiş olan bölümler için araçları bir şekilde uzmanlaştırırsanız, bu bir avantaj olabilir. Bununla birlikte, diğer davranışları gerçekten de değiştirmek veya değiştirmek isterseniz, aşağıdaki çözümü öneririm:

Kalıtım Üzerine Kompozisyon

Ya sonradan çevikliği değiştiren bir araç istersen ? Veya hız koşmak ? Bana göre, bir RPG yapıyorsun. RPG'ler için önemli olan şeylerden biri uzatma için açık olmaktır . Şimdiye kadar gösterilen çözümler bunu sunmuyor. ToolHer yeni özelliğe ihtiyaç duyduğunuzda sınıfı değiştirmeniz ve ona yeni sanal yöntemler eklemeniz gerekir.

Gösterdiğim ikinci çözüm, daha önce bir yorumda bahsettiğim çözümdür - miras yerine kompozisyon kullanır ve "değişiklik için kapalı, uzantı için açık * ilkesini izler. Varlık sistemlerinin nasıl çalıştığını bilirseniz, bazı şeyler tanıdık gelecektir (kompozisyonu ES'nin küçük kardeşi olarak düşünmeyi severim).

Aşağıda gösterdiklerimin Java ya da C # gibi çalışma zamanı tür bilgisi olan dillerde daha zarif olduğunu unutmayın. Bu nedenle, gösterdiğim C ++ kodu, kompozisyonun çalışmasını sağlamak için gerekli olan bazı "defter tutma" leri içermelidir. Belki daha fazla C ++ deneyimi olan biri daha iyi bir yaklaşım önerebilir.

İlk önce, arayanın tarafına tekrar bakıyoruz . Örneğinizde, attackyöntem içindeki arayan kişi olarak araçları hiç umursamıyorsunuz. Önemsediğiniz iki özellik - saldırı ve savunma noktaları. Sen yok gerçekten o nereden geldiğini bakım ve diğer özellikleri (örneğin koşma hızının, çeviklik) umurumda değil.

İlk önce yeni bir sınıf tanıtıyoruz.

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

Ve sonra, ilk iki bileşenimizi yarattık.

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;
};

Daha sonra, bir Aracı bir takım özellikler tutturur ve özellikleri başkaları tarafından sorgulanabilir hale getiririz.

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();
    }
};

Bu örnekte, her türden yalnızca bir bileşene sahip olduğumu destekliyoruz - bu işleri kolaylaştırır. Teoride, aynı tipteki birden fazla bileşene de izin verebilirsiniz, ancak bu çirkin hale gelir. Önemli bir husus: Toolartık değişiklik için kapatıldı - asla bir daha asla kaynağına dokunmayacağız Tool- ama uzatma için aç - bir aracın davranışını başka şeyleri değiştirerek ve sadece diğer Bileşenleri içine geçirerek uzatabiliriz.

Şimdi, araçları bileşen türlerine göre almak için bir yola ihtiyacımız var. Kod örneğinizde olduğu gibi, araçlar için hala bir vektör kullanabilirsiniz:

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 << "!";
        }
};

Bunu kendi Inventorysınıfınıza yeniden değerlendirebilir ve araçları bileşen türüne göre almayı büyük ölçüde kolaylaştıran ve tüm koleksiyon boyunca tekrar tekrar yinelemeyi önleyen arama tablolarını depolayabilirsiniz.

Bu yaklaşımın hangi avantajları var? Gelen attacksen işlemek iki bileşeni vardır Araçlar - başka bir şey umurumda değil.

Bir walkToyöntemin olduğunu düşünelim ve şimdi bir aracın yürüme hızını değiştirme yeteneği kazanıp kazanmayacağının iyi bir fikir olduğuna karar verdin. Sorun değil!

İlk önce yenisini oluşturun Component:

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

Ardından, uyanma hızınızı artırmak istediğiniz araca bu bileşenin bir örneğini ekleyin ve WalkToyeni oluşturduğunuz bileşeni işlemek için yöntemi değiştirin :

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;
    }
}

Tools sınıfını değiştirmeden Tools'umuza bazı davranışlar eklediğimizi unutmayın.

Dizeleri bir makro veya statik const değişkenine taşıyabilir (ve gerekir), böylece tekrar tekrar yazmak zorunda kalmazsınız.

Bu yaklaşımı daha ileri götürürseniz - örneğin, oynatıcıya eklenebilecek Combatbileşenler yapın ve oynatıcıyı savaşa katılabilecek şekilde işaretleyen bir bileşen yapın, o zaman attackyöntemden de kurtulabilirsiniz ve bunun ele alınmasına izin verin Bileşen tarafından veya başka bir yerde işlenebilir.

Oyuncunun Bileşen alabilmesinin avantajı, o zaman, oyuncuya, ona farklı davranışlar vermesi için değiştirmenize gerek kalmayacağıdır. Benim örneğimde, bir Movablebileşen yaratabilirsiniz , bu şekilde walkTooynatıcıda onu hareket ettirmek için yöntemi uygulamanıza gerek kalmaz . Sadece bileşeni yaratır, oynatıcıya ekler ve bir başkasının işlemesine izin verirsiniz.

Bu örnekte tam bir örnek bulabilirsiniz: https://gist.github.com/NetzwergX/3a29e1b106c6bb9c7308e89dd715ee20

Bu çözüm açık olduğu gibi diğerlerine göre biraz daha karmaşık. Ancak ne kadar esnek olmak istediğinize, ne kadar uzağa çekmek istediğinize bağlı olarak, bu çok güçlü bir yaklaşım olabilir.

Düzenle

Bazı diğer cevaplar doğrudan miras önermektedir (Kılıçların Aracı uzatması, Kalkanın Aracı uzatması). Mirasın çok işe yaradığı bir senaryo olduğunu sanmıyorum. Bir kalkanla belirli bir şekilde engellemenin, saldırgana da zarar verebileceğine karar verirseniz ne olur? Benim çözümümle, bir kalkanı bir Attack bileşeni ekleyebilir ve kodunuzda herhangi bir değişiklik yapmadan bunu fark edebilirsiniz. Kalıtım ile bir problemin olur. RPG'lerdeki ürünler / Araçlar, kompozisyon için en uygun adaylar veya hatta en baştan varlık sistemlerini kullanarak doğrudan adaylardır.


1

Genel olarak konuşursak, ifherhangi bir OOP dilinde (örnek türünü istemekle birlikte) kullanma gereksiniminiz varsa , bu bir kokunun devam ettiğini gösteren bir işarettir. En azından, modellerine yakından bakmalısın.

Etki alanınızı farklı şekillendiririm.

Sizin yararınız için Tool, hem tüy gibi hem de bunun gibi bir şeyle savaşmanızın bir faydası olma ihtimali olan bir AttackBonusve a vardır.DefenseBonus0

Saldırı için, kullanılan silahtan baserate+ a sahipsin bonus. Aynı savunma baserate+ için de geçerli bonus.

Sonuç olarak , saldırı / savunma boni hesaplamak için Toolbir virtualyöntem olması gerekir .

tl; Dr.

Daha iyi bir tasarıma sahip hack lerden kaçınabilirsin if.


Bazen skaler değerleri karşılaştırırken, örneğin bir if gereklidir. Nesne tipi değiştirme için, çok değil.
Andy,

Haha, eğer oldukça gerekli bir operatör ise ve sadece kullanmanın kod kokusu olduğunu söyleyemezsiniz.
tymtam

1
@Tymski haklı olarak biraz saygı göstererek. Kendimi daha net hissettim. Daha ifaz programlamayı savunmuyorum . Çoğunlukla, instanceofya da bunun gibi bir şey gibi kombinasyonlarda . Ancak, ifkodlamanın bir kod olduğunu iddia eden bir pozisyon var ve bunun üstesinden gelmek için yollar var. Ve haklısın, bu kendi hakkı olan temel bir operatör.
Thomas Junk,

1

Yazıldığı gibi, "kokuyor" ama bu sadece verdiğiniz örnekler olabilir. Verileri genel nesne konteynerlerinde saklamak, daha sonra verilere erişmek için yayın yapmak otomatik olarak kod kokusu değildir . Birçok durumda kullanıldığını göreceksiniz. Ancak, onu kullanırken, ne yaptığınızı, nasıl yaptığınızı ve nedenini bilmelisiniz. Örneğe baktığımda, bana hangi nesnenin ne olduğunu söylemek için dizge bazlı karşılaştırmaların kullanılması, kişisel koku sayacımı ne kadar tetikler ki. Burada ne yaptığınızdan tam olarak emin olmadığınızı gösterir (bu iyi, çünkü programcılar için buraya gelmek konusunda bilgeliğiniz var. " beni dışarı! ").

Bunun gibi jenerik konteynerlerden veri dökümü modelinin temel sorunu, verinin üreticisi ve verinin tüketicisinin birlikte çalışması gerektiğidir, ancak ilk bakışta yaptıkları açık olmayabilir. Bu paternin her örneğinde, koklamak ya da koklamak değil, temel sorun budur. Bir sonraki geliştiricinin bu kalıbı yaptığınızdan habersiz olması ve kazara kırması çok olasıdır, bu yüzden bu kalıbı kullanırsanız bir sonraki geliştiriciye yardım etmeye özen göstermelisiniz. Var olmadığını bilmesi gereken bazı detaylar nedeniyle, istemeden kodları kırmamasını kolaylaştırmanız gerekir.

Örneğin, bir oyuncu kopyalamak istersem? Sadece oynatıcı nesnesinin içeriğine bakarsam, oldukça kolay görünüyor. Sadece kopyalamak zorunda attack, defenseve toolsdeğişkenleri. Pasta kadar kolay! İşaretçileri kullanımınızın onu biraz zorlaştırdığını çabucak öğreneceğim (bir noktada akıllı işaretçilere bakmaya değer, ama bu başka bir konu). Bu kolayca çözüldü. Her aracın yeni kopyalarını oluşturacağım ve bunları yeni toolslisteme koyacağım . Ne de olsa, Toolsadece bir üyeyle gerçekten basit bir sınıftır. Bu yüzden bir kopyasını da içeren bir sürü kopya oluşturdum Sword, ama bunun bir kılıç olduğunu bilmiyordum, o yüzden sadece kopyaladım name. Daha sonra, attack()fonksiyon isme bakar, bir "kılıç" olduğunu görür, atar ve kötü şeyler olur!

Bu örneği, aynı modeli kullanan soket programlamasında başka bir vaka ile karşılaştırabiliriz. Bunun gibi bir UNIX soket işlevi kurabilirim:

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));

Neden bu aynı kalıp? Çünkü binda kabul etmiyor sockaddr_in*, daha genel kabul ediyor sockaddr*. Bu sınıfların tanımlarına bakarsanız, * sockaddrailesine yalnızca bir üyemizin atandığını görüyoruz sin_family. Aile, hangi alt türü kullanmanız gerektiğini söylüyor sockaddr. AF_INETAdres yapısının gerçekte bir olduğunu söyler sockaddr_in. Öyle AF_INET6olsaydı, adres sockaddr_in6daha büyük IPv6 adreslerini desteklemek için daha geniş alanlara sahip bir a olurdu .

Bu, Toolörneğinizle aynıdır , ancak hangi aileyi a'dan ziyade belirlemek için bir tam sayı kullanır std::string. Ancak, koku almadığını iddia edeceğim ve bunu "soket yapmanın standart bir yolu, bu yüzden" kokmamalı "dı. Nedenler dışında yapmaya çalışacağım. neden genel nesnelerde veri depolamanın ve onu yayınlamanın otomatik olarak kod kokusu olmadığını iddia ediyorum , ancak nasıl güvenli hale getireceklerini yapmada bazı farklılıklar var.

Bu modeli kullanırken, en önemli bilgi, alt sınıfla ilgili bilgilerin üreticiden tüketiciye aktarımını yakalamaktır. Bu, namealanla yaptığınız şeydir ve UNIX soketleri kendi sin_familyalanlarıyla yapar. Bu alan, üreticinin gerçekte ne yarattığını anlamak için tüketicinin ihtiyaç duyduğu bilgidir. Olarak tüm bu desen durumlarda, bir numaralandırma olmalıdır (ya da en azından, bir tam sayıdır, bir numaralandırma gibi hareket). Niye ya? Tüketicinizin bilgilerle ne yapacağını düşünün. Büyük bir ifaçıklama yazmaları gerekecek ya daswitchdeyim, yaptığınız gibi, doğru alt türü belirledikleri yerde, onu kullanıp verileri kullanmaktadır. Tanım olarak, bu türlerden yalnızca az sayıda olabilir. Yaptığınız gibi bir dizgide saklayabilirsiniz, ancak bunun birçok dezavantajı vardır:

  • Yavaş - std::stringdizgiyi korumak için tipik olarak biraz dinamik bellek gerekir. Ayrıca, hangi alt sınıfa sahip olduğunuzu bulmak istediğinizde, adı eşleştirmek için tam metin karşılaştırması yapmanız gerekir.
  • Çok yönlü - Aşırı derecede tehlikeli bir şey yaparken kendinize kısıtlamalar koymak için söylenecek bir şey var. Ne tür bir nesneye baktığını anlatmak için bir alt dize arayan bu tür sistemlere sahibim . Bu , alt nesneyi yanlışlıkla bu alt dizgiyi içeren ve korkunç şifreli bir hata oluşturana kadar çok çalıştı . Yukarıda da belirttiğimiz gibi, sadece az sayıda vakaya ihtiyacımız olduğundan, dizgiler gibi devasa bir şekilde güçlendirilmiş bir araç kullanmak için hiçbir neden yoktur. Bu yol açar ...
  • Hata eğilimli - Diyelim ki, bir tüketici yanlışlıkla bir sihirli bez adını koyduğunda işlerin neden işe yaramadığını ayıklamaya çalışan cinayet dolu bir öfkeye gitmek isteyeceksiniz MagicC1oth. Ciddi, böyle hatalar alabilir gün ne olduğunu fark etmeden önce kafa kaşıma.

Bir numaralandırma çok daha iyi çalışıyor. Hızlı, ucuz ve daha az hata eğilimli:

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!");
        }
   }
};

Bu örnek ayrıca, switchbu kalıbın en önemli kısmı olan enums ile ilgili bir ifadeyi gösterir: fırlayan bir defaultdurum. İşleri mükemmel yaparsan , asla böyle bir duruma girmemelisin. Ancak, birisi yeni bir araç türü eklerse ve kodunuzu desteklemek için güncellemeyi unutursanız, hatayı yakalamak için bir şey istersiniz. Aslında, onlara ihtiyacım olmasa bile onları eklemeniz gerekenleri tavsiye ederim.

Bunun diğer büyük avantajı enum, bir sonraki geliştiriciye, tam olarak geçerli araç tiplerinin tam bir listesini vermesidir. Bob'un epik patron savaşında kullandığı uzman Flüt sınıfını bulmak için koda girmeye gerek yok.

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
    }
}

Evet, "boş" bir varsayılan ifade koydum, yalnızca yeni bir beklenmeyen tür benim için geldiğinde ne olacağını umduğum bir sonraki geliştiriciye açık yapmak için.

Bunu yaparsanız, desen daha az kokacak. Ancak, kokusuz olmak için yapmanız gereken son şey diğer seçenekleri göz önünde bulundurmaktır. Bu yayınlar, C ++ repertuarında sahip olduğunuz daha güçlü ve tehlikeli araçlardan bazılarıdır. İyi bir nedeniniz yoksa, onları kullanmamalısınız.

Çok popüler bir alternatif, "sendika yapısı" veya "sendika sınıfı" dediğim şey. Örneğin, bu aslında çok iyi bir seçim olacaktır. Bunlardan birini yapmak için, daha Toolönce olduğu gibi bir numaralandırma ile bir sınıf yaratırsınız, ancak alt sınıflandırma yerine, Tooltüm alanları sadece her alt tipten koyarız.

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

    int   attack;
    int   defense;
};

Şimdi hiç alt sınıfa ihtiyacınız yok. typeHangi alanların gerçekte geçerli olduğunu görmek için alana bakmanız yeterlidir. Bu çok daha güvenli ve anlaşılması daha kolay. Ancak, dezavantajları vardır. Bunu kullanmak istemediğin zamanlar vardır:

  • Nesneler birbirinden çok farklı olduğunda - Bir çamaşırhane listesi ile sonuçlanabilir ve hangilerinin her nesne türü için geçerli olduğu belirsiz olabilir.
  • Hafıza kritik bir durumda çalışırken - 10 araç yapmanız gerekiyorsa, hafızayla tembel olabilirsiniz. 500 milyon araç yapmanız gerektiğinde, bit ve baytları önemsemeye başlayacaksınız. Sendika yapıları her zaman olması gerekenden daha büyüktür.

Bu çözüm, API'nin açık uçluluğu ile birleştirilen farklılık sorunu nedeniyle UNIX soketleri tarafından kullanılmaz. UNIX soketlerinin amacı, UNIX'in her lezzetinin işe yarayabileceği bir şey yaratmaktı. Her lezzet, destekledikleri ailelerin listesini tanımlayabilir, beğenir AF_INETve her biri için kısa bir liste olur. Bununla birlikte, yeni bir protokol gelirse, olduğu gibi AF_INET6, yeni alanlar eklemeniz gerekebilir. Bunu bir sendika yapısıyla yaptıysanız, yapının yeni bir sürümünü aynı ada sahip yeni bir sürüm oluşturup bitmeyen uyumsuzluk sorunları yaratabileceksiniz. Bu nedenle UNIX soketleri, birleşim yapısı yerine döküm kalıbını kullanmayı seçti. Bunu düşündüklerinden eminim ve bunu düşündükleri gerçeği, kullandıklarında koku almadıklarının bir parçası.

Bir sendikayı da gerçek anlamda kullanabilirsiniz. Sendikalar, yalnızca en büyük üye kadar büyük olmakla hafızayı korurlar, ancak kendi sorunlarıyla gelirler. Bu muhtemelen kodunuz için bir seçenek değildir, ancak her zaman göz önünde bulundurmanız gereken bir seçenek.

Başka bir ilginç çözüm boost::variant. Boost , yeniden kullanılabilir çapraz platform çözümleriyle dolu harika bir kütüphanedir. Muhtemelen şimdiye kadar yazılmış en iyi C ++ kodlarından bazıları. Boost.Variant , temelde sendikaların C ++ versiyonudur. Birçok farklı tipte olabilen, ancak bir seferde yalnızca bir tanesi bulunan bir kaptır. Sen yapabilir Sword, Shieldve MagicClothdaha sonra aracı bir olmak yapmak, sınıfları boost::variant<Sword, Shield, MagicCloth>bu o üç türden birini ihtiva anlam. Bu, UNIX soketlerinin kullanılmasını önleyen gelecekteki uyumluluk ile aynı sorundan muzdariptir (UNIX soketlerinin C olduğundan bahsetmeden değilboostbiraz tarafından!), ama bu desen inanılmaz derecede yararlı olabilir. Varyant, örneğin, bir metin dizisi alan ve kurallar için bir gramer kullanarak ayrılan ayrıştırma ağaçlarında sıklıkla kullanılır.

Bir dalma yapmadan ve genel nesne döküm yaklaşımını kullanmadan önce bakmayı önerdiğim son çözüm Ziyaretçi tasarım deseni. Ziyaretçi, sanal bir işlev çağırmanın, ihtiyaç duyduğunuz döküm işlemini etkili bir şekilde yaptığı ve sizin için yaptığı gözleminden yararlanan güçlü bir tasarım desenidir. Derleyici bunu yaptığından, asla yanlış olamaz. Bu nedenle, bir enum depolamak yerine, Ziyaretçi nesnenin ne tür olduğunu bilen bir oy hakkına sahip olan soyut bir temel sınıf kullanır. Daha sonra işi yapan, temiz, küçük, çift yönlü bir çağrı yaratırız:

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;
};

Peki bu tanrı aweful deseni nedir? Eh, Toolbir sanal işlevi vardır accept. Bir ziyaretçi iletirseniz, geri dönmesi ve visitbu ziyaretçi için türdeki doğru işlevi çağırması beklenir . Bu ne visitor.visit(*this);her alt tipine yapar. Karmaşık, ancak bunu yukarıdaki örneğinizle gösterebiliriz:

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
}

Peki burada ne olacak? Ne tür bir nesne ziyaret ettiğini bildiğinde, bizim için bazı işler yapacak bir ziyaretçi yaratıyoruz. Daha sonra araçların listesini tekrarlıyoruz. Argüman uğruna, diyelim ki ilk nesne bir diğeridir Shield, fakat kodumuz henüz bunu bilmiyor. Çağrıyor t->accept(v), sanal bir işlev. İlk nesne bir kalkan olduğu için, çağırarak biter void Shield::accept(ToolVisitor& visitor), hangi aramaları visitor.visit(*this);. Biz hangi yukarı ararken Şimdi visitaramaya biz çağırarak sona erecek bu yüzden, zaten, (bu fonksiyon çağrıldım çünkü) biz Kalkanı olduğunu biliyoruz void ToolVisitor::visit(Shield* shield)sayfamızda yer AttackVisitor. Bu şimdi savunmamızı güncellemek için doğru kodu çalıştırıyor.

Ziyaretçi hacimlidir. O kadar tuhaf ki neredeyse kendine has bir kokusu olduğunu düşünüyorum. Kötü ziyaretçi kalıpları yazmak çok kolaydır. Ancak, diğerlerinin hiçbirinin sahip olmadığı büyük bir avantaja sahiptir. Yeni bir araç tipi eklersek ToolVisitor::visit, bunun için yeni bir işlev eklemeliyiz . Bunu yaptığımız anda , programdaki her ToolVisitor biri derleme yapmayı reddedecek çünkü sanal bir işlevi eksik. Bu, bir şeyi özlediğimiz tüm olayları yakalamayı çok kolaylaştırıyor. İşi yapmak için açıklamalar ifveya switchifadeler kullanırsanız garanti etmek daha zordur . Bu avantajlar, Ziyaretçi'nin 3d grafik sahne jeneratörlerinde hoş küçük bir yer bulmasına yetecek kadar iyi. Tam olarak Ziyaretçi'nin sunduğu davranışa ihtiyaç duyuyorlar, bu yüzden harika çalışıyor!

Sonuç olarak, bu kalıpların bir sonraki geliştiriciyi zorlaştırdığını unutmayın. Onlar için kolaylaştırmak için zaman harcayın ve kod kokmaz!

* Teknik olarak, spekere bakarsanız, sockaddr isimli bir üyeye sahiptir sa_family. Burada bizim için önemli olmayan C düzeyinde bazı hileler yapılıyor. Asıl uygulamaya bakmaya açıksınız, ancak bu cevap sa_family sin_familyiçin, C kandırmasının önemsiz ayrıntılarla ilgilendiğine güvenerek, hangisini nesir için en sezgisel olanı kullanarak tamamen birbirinin yerine kullanacağım .


Ardışık olarak saldırmak, oynatıcıyı örneğinizde sınırsız şekilde güçlendirir. Ve ToolVisitor kaynağını değiştirmeden yaklaşımınızı genişletemezsiniz. Yine de harika bir çözüm.
Polygnome

@ Polygnome Örnek hakkında haklısın. Kodun tuhaf göründüğünü düşündüm, ancak tüm bu metin sayfalarını kaydırırken hatayı özledim. Şimdi tamir ediyorum. ToolVisitor'un kaynağının değiştirilmesi gerekliliğine gelince, bu, Ziyaretçi deseninin karakteristik bir tasarımıdır. Bu bir nimettir (yazdığım gibi) ve bir lanettir (senin yazdığın gibi). İsteğe bağlı olarak genişletilebilir bir sürümün olmasını istediğiniz durumun ele alınması çok daha zordur ve sadece değerleri yerine değişkenlerin anlamını kazmaya başlar ve zayıf yazılmış değişkenler ve sözlükler ve JSON gibi diğer kalıpları açar.
Cort Ammon

1
Evet, ne yazık ki OP'lerin tercihleri ​​ve gerçekten bilinçli bir karar vermek için hedefler hakkında yeterince bilgimiz yok. Ve evet, tamamen esnek bir çözümün uygulanması zor, neredeyse 3 saat boyunca cevabım üzerinde çalıştım, çünkü C ++ oldukça paslı :(
Polygnome

0

Genel olarak, sadece veri iletimi içinse birkaç sınıf uygulamaktan / miras almaktan kaçınırım. Tek bir sınıfa sadık kalarak her şeyi oradan uygulayabilirsiniz. Örneğin, bu yeterli

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

Muhtemelen, oyununuzun birkaç kılıç kullanacağını tahmin ediyorsunuz, ancak bunu uygulamak için başka yollara sahip olacaksınız. Sınıf patlaması nadiren en iyi mimaridir. Basit tut.


0

Daha önce de belirtildiği gibi, bu ciddi bir kod kokusu. Ancak, bir problemin kaynağını, tasarımınızdaki kompozisyon yerine miras kullanmakta olduğu düşünülebilir.

Örneğin, bize gösterdiklerinize göre, açıkça 3 konseptiniz var:

  • madde
  • Saldırı yaratabilecek öğe.
  • Savunması olabilecek eşya.

Dördüncü sınıfınızın sadece son iki kavramın bir birleşimi olduğunu unutmayın. Bu yüzden bunun için kompozisyon kullanmanızı öneririm.

Saldırı için gereken bilgiyi temsil etmek için bir veri yapısına ihtiyacınız var. Savunma için ihtiyacınız olan bilgiyi temsil eden bir veri yapısına ihtiyacınız var. Son olarak, bu özelliklerden birine veya her ikisine birden sahip olabilecek veya bulunmayacak şeyleri temsil etmek için bir veri yapısına ihtiyacınız vardır:

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_;}
};

Ayrıca: bir oluşturma-her zaman yaklaşımını kullanmayın :)! Bu durumda neden kompozisyon kullanılmalı? Ben alternatif bir çözüm, ama bir alan ... Bu durumda garip görünüyor ( " "unutmayın) "enkapsüle" için bir sınıf oluşturarak konusunda hemfikir
AilurusFulgens

@AilurusFulgens: Bugün "alan" dır. Yarın ne olacak? Bu tasarım sağlar Attackve Defensedaha fazla arayüz değiştirmeden komplike olmaya Tool.
Nicol Bolas,

1
Bununla Aracı yine de çok iyi genişletemezsiniz - elbette, saldırı ve savunma daha karmaşık hale gelebilir, ama bu kadar. Kompozisyonu tam gücüyle kullanırsanız, Tooluzatma için açık bırakmaya devam ederken değişiklik için tamamen kapalı yapabilirsiniz .
Polygnome

@ Polygnome: Böyle önemsiz bir durum için keyfi bir bileşen sistemi oluşturma zorluğundan geçmek istiyorsanız, bu size kalmış. Kişisel olarak Tool, değiştirmeden genişletmek istemem için hiçbir neden göremiyorum . Ve bunu değiştirme hakkım varsa, o zaman keyfi bileşenlere ihtiyaç duymuyorum.
Nicol Bolas,

Aracı kendi kontrolünüz altında olduğu sürece , değiştirebilirsiniz. Ancak “değişiklik için kapalı, uzatma için açık” ilkesi, iyi bir nedenden ötürü (burada ayrıntılandırılması çok uzun) vardır. Yine de bu kadar önemsiz olduğunu sanmıyorum. Bir RPG için esnek bir bileşen sistemi planlamak için doğru zaman harcıyorsanız , uzun vadede muazzam ödüller kazanırsınız . Bu tür kompozisyonda, sadece düz alanlar kullanmanın yararını görmüyorum . Saldırı ve savunmayı daha da uzmanlaştırabilmek çok teorik bir senaryo gibi görünüyor. Ancak yazdığım gibi, OP'nin kesin gereksinimlerine bağlıdır.
Polygnome

0

Neden soyut calışmalarımız değil modifyAttackve modifyDefensede Toolsınıfta? O zaman her çocuğun kendine göre bir uygulaması olacak ve siz buna zarif bir yöntem diyorsunuz:

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

Değerleri referans olarak iletmek, şunları yapabiliyorsanız kaynakları koruyacaktır:

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

0

Eğer polimorfizm kullanılıyorsa, hangi sınıfın kullanıldığına önem veren tüm kodların sınıfın içinde olması her zaman en iyisidir. Bunu nasıl kodlardım:

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;
};

Bunun aşağıdaki avantajları vardır:

  • yeni sınıflar eklemek daha kolay: Sadece tüm soyut yöntemleri uygulamak zorundasınız ve kodun geri kalanı sadece çalışıyor
  • sınıfları çıkarmak daha kolay
  • yeni istatistikler eklemek daha kolay (stat ile ilgilenmeyen sınıflar sadece bunu yoksayıyorlar)

En azından bonusu oyuncudan kaldıran bir unequip () yöntemi de eklemelisiniz.
Polygnome

0

Bence bu yaklaşımdaki kusurları tanımanın bir yolu fikrinizi mantıklı bir şekilde geliştirmektir.

Bu bir oyuna benziyor, bu nedenle bazı aşamalarda muhtemelen performans konusunda endişelenmeye başlayacak ve bu dizgelerin karşılaştırmasını bir intveya enum. Maddelerin listesi uzadıkça, bu if-elseoldukça hantallaşmaya başlar, bu yüzden onu yeniden düzenlemeyi düşünebilirsiniz switch-case. Ayrıca, bu noktada oldukça fazla bir metin duvarı var, böylece her bir eylemden kurtulursunuz.case ayrı bir işleve ayırabilirsiniz.

Bu noktaya ulaştığınızda, kodunuzun yapısı tanıdık görünmeye başlar - bir homebrew gibi görünmeye başlar, elle döndürülebilir bir vtable * - üzerine sanal yöntemlerin uygulandığı temel yapı. Bunun dışında, bir öğe türünü her eklediğinizde veya değiştirdiğinizde kendiniz el ile güncellemeniz ve bakımını yapmanız gereken bir kararsızlık söz konusudur.

"Gerçek" sanal işlevlere bağlı kalarak, her bir öğenin davranışının uygulanmasını öğenin içinde tutabilirsiniz. Daha fazla kendine yeten ve tutarlı bir şekilde ek öğeler ekleyebilirsiniz. Ve bütün bunları yaptığınız gibi, dinamik gönderiminizin yerine sizden ziyade özen gösterecek olan derleyicidir.

Özel probleminizi çözmek için: Saldırı ve savunmayı güncellemek için basit bir sanal fonksiyon çifti yazmakta zorlanıyorsunuz, çünkü bazı eşyalar sadece saldırıyı etkilerken, bazı eşyalar sadece savunmayı etkiliyor. Bunun gibi basit bir davadaki hile, her iki davranışı da yine de uygulamak, ancak bazı durumlarda hiçbir etkisi yoktur. GetDefenseBonus()dönebilir 0veya ApplyDefenseBonus(int& defence)sadece defencedeğişmeden kalabilir . Nasıl yürüdüğünüz, etkisi olan diğer eylemleri nasıl ele almak istediğinize bağlı olacaktır. Daha karmaşık davranışlarda, daha çeşitli davranışların gerçekleştiği yerlerde, etkinliği tek bir yöntemle birleştirebilirsiniz.

* (Her ne kadar tipik uygulamaya göre aktarılmış olsa da)


0

Mümkün olan tüm "araçlar" ı tanıyan bir kod bloğuna sahip olmak harika bir tasarım değildir (özellikle kodunuzda bu kadar çok blokla karşılaşacağınız için); ancak hiçbiri, Toololası tüm araç özellikleri için taslakları olan bir temele sahip değildir : şimdi Toolsınıf, olası tüm kullanımlar hakkında bilgi sahibi olmalıdır.

Ne her aracı bilir onu kullanır karakterine katkıda bulunabilir budur. Yani tüm araçlar için bir yöntem sağlayın giveto(*Character owner). Oyuncunun istatistiklerini, diğer araçların neler yapabileceğini ve en iyisinin de karakterin alakasız özelliklerini bilmesi gerekmeksizin bildiği gibi ayarlayacaktır. Örneğin, bir kalkan bile nitelikler hakkında bilmek gerekmez attack, invisibility, healthvb karakter nesne gerektirdiğini özelliklerini desteklemesi için bir araçtır uygulamak için ihtiyaç duyulan All. Bir eşeğe kılıç vermeyi denerseniz ve eşeğin hiçbir attackistatistiği yoksa, bir hata alırsınız.

Araçların remove(), mal sahibi üzerindeki etkilerini tersine çeviren bir yöntemi de olmalıdır . Bu biraz aldatıcıdır (verildiğinde ve alındığında sıfır olmayan bir etki bırakan araçlarla sona ermek mümkündür), ancak en azından her bir araca yerleştirilmiştir.


-4

Kokusu olmadığını söyleyen bir cevap yok, bu yüzden bu görüşün anlamına gelen ben olacağım; Bu kod tamamen iyi! Benim düşünceme göre, ilerlemenin bazen daha kolay olduğu ve yeni şeyler yaratırken becerilerinizin kademeli olarak artmasına izin verdiği gerçeğine dayanıyor. Mükemmel bir mimarlık yapmak için günlerce takılıp kalabilirsiniz, ancak muhtemelen hiç kimse onu çalışırken bile göremez, çünkü projeyi hiç bitirmediniz. Şerefe!


4
Kişisel deneyim ile yeteneklerinizi geliştirmek elbette iyidir. Ancak, zaten bu kişisel deneyime sahip olan insanlara sorarak becerilerini geliştirmek, böylece kendi başınıza deliğe girmenize gerek kalmaz. Elbette, insanların burada ilk başta soru sorma nedeni bu değil mi?
Graham

Katılmıyorum Ancak anlıyorum ki bu site tamamen derinleşmekle ilgili. Bazen bu aşırı bilgiçlik olmak anlamına gelir. Bu yüzden bu görüşü yayınlamak istedim, çünkü gerçekte bağdaşır ve daha iyi olmak için ipuçları arıyor ve yeni başlayanlar için yardım ediyorsanız, yeni başlayanlar için oldukça yararlı olan "yeterince iyi" hakkındaki tüm bu bölümü kaçırıyorsunuz.
Ostmeistro
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.