Birkaç sınıfın aynı verilere erişmesi gerektiğinde, veriler nerede bildirilmelidir?


38

C ++ 'da temel bir 2D kule savunma oyunum var.

Her harita GameState'ten miras alan ayrı bir sınıftır. Harita, mantık ve çizim kodunu oyundaki her nesneye dağıtır ve harita yolu gibi verileri ayarlar. Sahte kodda mantık bölümü şuna benzer olabilir:

update():
  for each creep in creeps:
    creep.update()
  for each tower in towers:
    tower.update()
  for each missile in missiles:
    missile.update()

Nesneler (sürünen, kuleler ve füzeler) işaretçiler vektöründe depolanır. Kuleler, yeni füzeler yaratmak ve hedefleri belirlemek için sürüngenlerin vektörüne ve füzelerin vektörüne erişebilmelidir.

Soru şudur: vektörleri nerede ilan ederim? Map sınıfının üyeleri olmalı ve tower.update () işlevine argüman olarak geçmeli mi? Ya da küresel olarak ilan etti? Yoksa tamamen özlediğim başka çözümler var mı?

Birkaç sınıfın aynı verilere erişmesi gerektiğinde, veriler nerede bildirilmelidir?


1
Küresel üyeler 'çirkin' sayılırlar ancak hızlıdırlar ve gelişmeleri kolaylaştırırlar, eğer küçük bir oyunsa sorun değil (IMHO). Ayrıca mantığı idare eden ( kulelerin neden bu vektörlere ihtiyaç duyduğu) ve tüm vektörlere erişimi olan bir dış sınıf oluşturabilirsiniz .
Jonathan Connell

-1, bu oyun programlama ile ilgiliyse, o zaman pizza yemek de olur. Kendine iyi bir yazılım tasarım kitabı al
Maik Semder

9
@Maik: Yazılım tasarımı ile oyun programlamanın ilişkisi nedir? Sadece diğer programlama alanları için de geçerli olduğu için konu dışı kalmaz.
BlueRaja - Danny Pflughoeft 28:11

@BlueRaja yazılım tasarım kalıplarının listeleri daha iyi SO için uygundur, sonuçta bunun için orada. GD.SE oyun programlaması içindir, yazılım tasarımı değil
Maik Semder

Yanıtlar:


52

Programınız boyunca tek bir sınıf örneğine ihtiyacınız olduğunda, bu sınıfa bir hizmet diyoruz . Programlarda hizmetlerin uygulanmasında birkaç standart yöntem vardır:

  • Global değişkenler . Bunlar uygulanması en kolay olan, fakat en kötü tasarım. Eğer çok fazla global değişken kullanıyorsanız, hızlı bir şekilde kendinize çok fazla dayanan ( güçlü eşleşme ) modüller yazarken , mantık akışını takip etmeyi çok zorlaştırırsınız. Global değişkenler okuyuculu-dostu değildir. Genel değişkenler, nesnelerin ömrünün izlenmesini zorlaştırır ve ad alanını karıştırır. Bununla birlikte, en performanslı seçenek budur, bu yüzden kullanılabilecek ve kullanılması gereken zamanlar vardır, ancak bunları yedek olarak kullanın.
  • Singletons . Yaklaşık 10-15 yıl önce, singletons, bilinmesi gereken en büyük tasarım deseniydi . Ancak, bugünlerde onlar üzerinde duruldu. Çoklu iş parçacığı için çok daha kolaydır, ancak kullanımlarını bir seferde bir iş parçacığıyla sınırlamanız gerekir; bu her zaman istediğiniz şey değildir. Yaşam sürelerini takip etmek, global değişkenler kadar zor.
    Tipik bir singleton sınıfı şöyle görünecek:

    class MyClass
    {
    private:
        static MyClass* _instance;
        MyClass() {} //private constructor
    
    public:
        static MyClass* getInstance();
        void method();
    };
    
    ...
    
    MyClass* MyClass::_instance = NULL;
    MyClass* MyClass::getInstance()
    {
        if(_instance == NULL)
            _instance = new MyClass(); //Not thread-safe version
        return _instance;
    
        //Note that _instance is *never* deleted - 
        //it exists for the entire lifetime of the program!
    }
  • Bağımlılık Enjeksiyonu (DI) . Bu sadece servisi yapıcı parametresi olarak geçirmek anlamına gelir. Bir sınıfa geçirebilmek için bir hizmet zaten mevcut olmalı, bu nedenle iki hizmetin birbirine güvenmesine imkan yok; vakaların% 98'inde istediğin budur (ve diğer% 2 için her zaman bir setWhatever()yöntem oluşturabilir ve daha sonra hizmete geçebilirsin) . Bu nedenle, DI diğer seçeneklerle aynı bağlantı problemlerine sahip değildir. Çoklu iş parçacığı ile kullanılabilir, çünkü her iş parçacığı her hizmetin kendi örneğine sahip olabilir (ve yalnızca kesinlikle ihtiyacı olanları paylaşır). Ayrıca, onu önemsiyorsanız kod birimini test edilebilir kılar.

    Bağımlılık enjeksiyonuyla ilgili sorun, daha fazla hafıza gerektirmesidir; Şimdi bir sınıfın her örneği, kullanacağı her hizmete referans gerektirir. Ayrıca, çok fazla hizmetiniz olduğunda kullanmak can sıkıcı hale gelir; Bu sorunu diğer dillerde azaltan çerçeveler var, fakat C ++ 'ın yansıma eksikliği nedeniyle, C ++' daki DI çerçeveleri sadece elle yapmaktan daha fazla iş yapma eğilimindedir.

    //Example of dependency injection
    class Tower
    {
    private:
        MissileCreationService* _missileCreator;
        CreepLocatorService* _creepLocator;
    public:
        Tower(MissileCreationService*, CreepLocatorService*);
    }
    
    //In order to create a tower, the creating-class must also have instances of
    // MissileCreationService and CreepLocatorService; thus, if we want to 
    // add a new service to the Tower constructor, we must add it to the
    // constructor of every class which creates a Tower as well!
    //This is not a problem in languages like C# and Java, where you can use
    // a framework to create an instance and inject automatically.

    Başka bir örnek için bu sayfaya bakın (Ninject belgelerine, bir C # DI çerçevesi).

    Bağımlılık enjeksiyonu, bu problem için olağan bir çözümdür ve StackOverflow.com'da bu gibi sorulara en çok cevap vereceğiniz cevaptır. DI, bir Kontrol Denetimi (IoC) türüdür .

  • Servis Bulucu . Temel olarak, sadece her hizmetin bir örneğini tutan bir sınıf. Yansımayı kullanarak yapabilir veya yeni bir servis oluşturmak istediğinizde ona yeni bir örnek ekleyebilirsiniz. Hala öncekiyle aynı problemin var - Sınıflar bu yer belirleyiciye nasıl erişiyor? - yukarıdaki yöntemlerden herhangi biriyle çözülebilir, ancak şimdi ServiceLocatoronlarca hizmet için değil, sadece sınıfınız için yapmanız yeterlidir . Bu yöntem, aynı zamanda böyle bir şeye önem veriyorsanız, test edilebilirdir.

    Servis Belirleyiciler, başka bir Kontrol Tersine Çevirme (IoC) şeklidir. Genellikle, otomatik bağımlılık enjeksiyonunu yapan çerçevelerde bir servis bulucu da bulunur.

    XNA (Microsoft'un C # oyun programlama çerçevesi) bir servis bulucu içerir; hakkında daha fazla bilgi edinmek için bu cevaba bakınız .


Bu arada, IMHO kulelerin sürüngenlerden haberi olmamalı. Her bir kulenin sürünme listesine basitçe döngü yapmayı planlamıyorsanız, muhtemelen bazı önemsiz alan bölümlendirmeleri uygulamak isteyeceksiniz ; ve bu tür bir mantık kule sınıfına ait değildir.


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
Josh

Şimdiye kadar okuduğum en iyi ve net cevaplardan biri. Aferin. Bir hizmetin her zaman olsa paylaşılması gerektiğini düşündüm.
Nikos

5

Şahsen ben burada polimorfizm kullanırdım. Neden bir missilevektöre, bir towervektöre ve bir creepvektöre sahipler ki ... hepsi aynı işlevi çağırdığında; update? Neden bazı temel sınıf için işaretçiler vektör yok Entityya GameObject?

Tasarım yapmanın iyi bir yolunu bulmaya çalışmak 'sahiplenme anlamında bir anlam ifade ediyor mu?' Açıkçası, bir kulenin kendisini güncellemenin bir yolu var, ama bir harita üzerindeki tüm nesnelere sahip mi? Globalleşmeye gidersen, kulelerin ve sürünenlerin hiçbir şeyinin olmadığını mı söylüyorsun? Global, genellikle kötü bir çözümdür - kötü tasarım modellerini teşvik eder, ancak çalışması çok daha kolaydır. Tartmayı düşünün 'bunu bitirmek ister miyim?' ve 'tekrar kullanabileceğim bir şey istiyorum mu?'

Bunun bir yolu mesajlaşma sisteminin bir şeklidir. towerBir mesaj gönderebilir mapbir isabet (? Sahibine o erişimi olan, belki bir başvuru) creepve mapdaha sonra söyler creepo hit oldu. Bu çok temiz ve verileri ayırıyor.

Başka bir yol da haritanın kendisinde ne istediğini araştırmaktır. Ancak, burada güncelleme siparişi ile ilgili sorunlar olabilir.


1
Polimorfizm hakkındaki öneriniz gerçekten alakalı değil. Bunları ayrı vektörlerde sakladım, böylece çizim kodunda (önce belirli nesnelerin çizilmesini isteyeceğim) veya çarpışma kodunda olduğu gibi, her tür üzerinde ayrı ayrı yineleme yapabiliyorum.
Juicy

Bana göre harita varlıklara sahip değil, çünkü buradaki harita 'seviye' ile aynı. Mesajlar hakkındaki fikrinizi değerlendireceğim, teşekkürler.
Juicy

1
Bir oyunda performans önemli. Dolayısıyla, aynı nesne zamanlarının vektörleri daha iyi referans konumuna sahiptir. Ayrıca, sanal işaretçiler içeren polimorfik nesnelerin performansı yüksektir, çünkü güncelleme döngüsüne dahil edilemezler.
Zan Lynx,

0

Bu, sıkı nesne yönelimli programlamanın (OOP) bozulduğu bir durumdur.

OOP ilkelerine göre, sınıfları kullanarak verileri ilgili davranışla gruplamanız gerekir. Ancak birbirinizle ilgili olmayan (kuleler ve sürüngenler) verilere ihtiyaç duyan bir davranışa (hedefleme) sahip olursunuz. Bu durumda, pek çok programcı davranışı ihtiyaç duyduğu verinin bir parçasıyla ilişkilendirmeye çalışacaktır (örneğin, kuleler hedeflemeyi ele alır, ancak sürüngenleri bilmez), ancak başka bir seçenek daha vardır: davranışı verilerle gruplama.

Hedefleme davranışını kule sınıfının bir yöntemi yapmak yerine, kuleleri kabul eden ve argüman olarak sürünen ücretsiz bir işlev haline getirin. Bu, kulede kalan üyelerin daha çoğunu ve sünnet sınıflarını herkese açık hale getirmeyi gerektirebilir ve bu sorun değil. Veri gizleme faydalıdır, ancak bu kendi başına bir amaç değil, bir araç olarak köle olmamalısınız. Ayrıca, özel üyeler verilere erişimi denetlemenin tek yolu değildir - veriler bir işleve geçirilmezse ve global değilse, bu işlevden etkin bir şekilde gizlenir. Bu tekniği kullanmak global verileri önlemenize izin veriyorsa, aslında kapsüllemeyi iyileştiriyor olabilirsiniz .

Bu yaklaşımın en uç örneği varlık sistem mimarisidir.

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.