Düşük bağlantı ve sıkı kohezyon


11

Tabii ki duruma bağlı. Ancak bir alt kaldıraç nesnesi veya sistemi bir üst düzey sistemle iletişim kurduğunda, bir göstergeyi daha üst düzey bir nesneye tutmak için geri aramalar veya olaylar tercih edilmeli midir?

Örneğin, worldüye değişkeni olan bir sınıfımız var vector<monster> monsters. Ne zaman monstersınıf iletişim kurduğu world, o zaman bir geri arama işlevini kullanarak tercih etmeliyiz yoksa bir işaretçi olmalıdır worldiçinden sınıfın monstersınıfa?


Soru başlığındaki yazımın yanı sıra, aslında bir soru şeklinde ifade edilmez. Bence yeniden ifade etmek burada sorduğunuz şeyi sağlamlaştırmaya yardımcı olabilir, çünkü bu soruya şu anki haliyle yararlı bir cevap alabileceğinizi sanmıyorum. Ve aynı zamanda bir oyun tasarımı sorusu değil, programlama yapısı ile ilgili bir soru (bunun tasarım etiketinin uygun olup olmadığından emin değilim, 'yazılım tasarımı' ve etiketlerine nereden geldiğimizi hatırlayamıyorum)
MrCranky

Yanıtlar:


10

Bir sınıfın başkalarıyla sıkı sıkıya bağlı kalmadan konuşabilmesinin üç ana yolu vardır:

  1. Geri arama fonksiyonu ile.
  2. Bir olay sistemi aracılığıyla.
  3. Bir arayüz aracılığıyla.

Üçü birbiriyle yakından ilişkilidir. Bir olay sistemi birçok yönden sadece geri arama listesidir. Geri arama, aşağı yukarı tek bir yöntemle kullanılan bir arabirimdir.

C ++, nadiren geri aramalar kullanın:

  1. C ++, thisişaretçilerini koruyan geri aramalar için iyi bir desteğe sahip olmadığından , nesne yönelimli kodda geri arama kullanmak zordur.

  2. Geri arama temel olarak genişletilemeyen tek yöntemli bir arabirimdir. Zamanla, neredeyse her zaman bu arayüzü tanımlamak için birden fazla yönteme ihtiyaç duyduğumu ve tek bir geri aramanın nadiren yeterli olduğunu fark ettim.

Bu durumda, muhtemelen bir arayüz yapardım. Sorunuzda, aslında neyle monsteriletişim kurmanız gerektiğini bilmiyorsunuz world. Bir tahmin yaparak, şöyle bir şey yapardım:

class IWorld {
public:
  virtual Monster* getNearbyMonster(const Position & position) = 0;
  virtual Item*    getItemAt(const Position & position) = 0;
};

class Monster {
public:
  void update(IWorld * world) {
    // Do stuff...
  }
}

class World : public IWorld {
public:
  virtual Monster* getNearbyMonster(const Position & position) {
    // ...
  }

  virtual Item*    getItemAt(const Position & position) {
    // ...
  }

  // Lots of other stuff that Monster should not have access to...
}

Buradaki fikir, sadece erişilmesi gereken IWorldçıplak asgari (ki bu da berbat bir isim) koymanızdır Monster. Onun dünya görüşü mümkün olduğunca dar olmalıdır.


1
+1 Delegeler (geri aramalar) zaman geçtikçe genellikle daha fazla hale gelir. Canavarlara bir şey vermek, böylece eşyalara ulaşabilirler, bence gitmek için iyi bir yoldur.
Michael Coleman

12

Aradığınız işlevi gizlemek için geri arama işlevi kullanmayın. Bir geri çağırma işlevi koyacaksanız ve bu geri çağırma işlevinde kendisine bir ve yalnızca bir işlev atanmışsa, bağlantıyı gerçekten bozmazsınız. Sadece başka bir soyutlama katmanıyla maskeliyorsun. Hiçbir şey kazanmıyorsunuz (belki derleme zamanı dışında), ama netliğini kaybediyorsunuz.

Tam olarak en iyi uygulama olarak adlandırmam, ancak ebeveynlerinin işaretçisi olan bir şeyin içerdiği varlıklara sahip olmak yaygın bir örüntüdür.

Bununla birlikte, canavarlarınıza dünya üzerinde arayabilecekleri sınırlı bir işlevsellik alt kümesi vermek için arayüz desenini kullanmak zaman ayırmaya değer olabilir.


+1 Canavara ebeveyni aramak için sınırlı bir yol vermek bence güzel bir orta yol.
Michael Coleman

7

Genellikle iki yönlü bağlantıları dener ve kaçınırım, ama eğer onlara sahip olmalıyım, onları yapmak için bir yöntem ve onları kırmak için bir yöntem olduğundan kesinlikle emin olurum, böylece asla tutarsızlıklar elde edemezsiniz.

Genellikle çift yönlü bağlantıyı gerektiği gibi veri ileterek tamamen önleyebilirsiniz. Önemsiz bir yeniden düzenleme, bunu yapmak için canavarın dünyayla bağlantı kurmasını sağlamak yerine, ona ihtiyaç duyan canavar yöntemlerine referansla dünyayı geçersiniz. Daha da iyisi, sadece canavarın kesinlikle ihtiyaç duyduğu dünyanın bitleri için bir arayüze geçmektir, yani canavar dünyanın somut uygulamasına güvenmez. Bu, Arayüz Segregasyon Prensibi ve Bağımlılık Ters Çevirme Prensibine karşılık gelir , ancak bazen olaylar, sinyaller + yuvalar vb. İle elde edebileceğiniz aşırı soyutlamayı tanıtmaya başlamaz.

Bir bakıma, geri arama kullanmanın çok özel bir mini arayüz olduğunu ve bu iyi olduğunu iddia edebilirsiniz. Bir arabirim nesnesindeki bir yöntem koleksiyonu veya farklı geri aramalardaki çeşitli çeşitli yöntemler aracılığıyla hedeflerinize daha anlamlı bir şekilde ulaşıp ulaşamayacağınıza karar vermelisiniz.


3

İçerdiği nesneleri kapsayıcı olarak adlandırmaktan kaçınmaya çalışıyorum çünkü kafa karışıklığına yol açıyor, haklı göstermek çok kolay oluyor, aşırı kullanılacak ve yönetilemeyen bağımlılıklar yaratıyor.

Bence ideal çözüm, üst düzey sınıfların, alt düzey sınıfları yönetecek kadar akıllı olmalarıdır. Örneğin, bir canavar ve bir şövalye arasındaki çarpışmanın, diğerini bilmeden meydana gelip gelmediğini belirlemeyi bilen dünya, benim için bir şövalye ile çarpışıp çarpışmadığını soran canavardan daha iyi.

Sizin durumunuzdaki başka bir seçenek, muhtemelen canavar sınıfının neden dünya sınıfı hakkında bilmesi gerektiğini anlıyorum ve büyük olasılıkla dünya sınıfında kendi sınıfına ayrılabilecek bir şey olduğunu göreceksiniz. canavar sınıfının bilmesi için bir anlam.


2

Olaylar olmadan çok ileri gitmeyeceksin, ama bir olay sistemi yazmaya (ve tasarlamaya) başlamadan önce, sana gerçek soruyu sormalısın: canavar neden dünya sınıfıyla iletişim kursun? Gerçekten mi?

"Klasik" bir durumu ele alalım, bir oyuncuya saldıran bir canavar.

Canavar saldırıyor: Dünya, bir kahramanın bir canavarın yanında olduğu durumu çok iyi tanımlayabilir ve canavara saldırmasını söyleyebilir. Canavardaki işlev şöyle olur:

void Monster::attack(LivingCreature l)
{
  // Call to combat system
}

Ancak (zaten canavarı bilen) dünyanın canavar tarafından bilinmesine gerek yoktur. Aslında, canavar Dünya sınıfının varlığını görmezden gelebilir, ki bu muhtemelen daha iyidir.

Canavar hareket ederken de aynı şey var (alt sistemlerin yaratığı almasına ve hareket hesaplamasını / niyetini ele almasına izin veriyorum, canavar sadece bir veri çantasıdır, ancak birçok insan bunun gerçek OOP olmadığını söylerdi).

Demek istediğim şu: etkinlikler (veya geri arama) elbette harika, ancak karşılaşacağınız her sorunun tek cevabı değiller.


1

Mümkün olduğunda, nesneler arasındaki iletişimi bir istek-yanıt modeliyle kısıtlamaya çalışıyorum. Programımdaki nesneler üzerinde herhangi bir A ve B nesnesi arasında, A'nın B yöntemini doğrudan veya dolaylı olarak çağırmasının veya B'nin A yöntemini doğrudan veya dolaylı olarak çağırmasının bir yolu olabileceği ima edilen kısmi bir sıralama vardır. ancak A ve B'nin birbirlerinin yöntemlerini karşılıklı olarak çağırması asla mümkün değildir. Bazen, elbette, bir yöntemin arayanıyla geriye doğru iletişim kurmak istersiniz. Bunu yapmaktan hoşlandığım birkaç yol var ve ikisi de geri aramıyor.

Bunun bir yolu, yöntem çağrısının dönüş değerine daha fazla bilgi eklemektir, yani istemci kodu yordam denetimi geri döndürdükten sonra ne yapacağına karar verir .

Diğer yol, karşılıklı bir alt nesneyi çağırmaktır. Yani, A B'de bir yöntemi çağırırsa ve B'nin A'ya bazı bilgileri iletmesi gerekiyorsa, B C'de bir yöntemi çağırır, burada A ve B her ikisi de C'yi arayabilir, ancak C A veya B'yi arayamaz. B kontrolü A'ya döndürdükten sonra C'den bilgi almakla sorumludur. Bunun, ilk önerdiğimden gerçekten farklı olmadığını unutmayın. A Nesnesi yine de yalnızca bir dönüş değerinden bilgi alabilir; A nesnesinin yöntemlerinden hiçbiri B veya C tarafından çağrılmaz. Bu hilenin bir varyasyonu, yönteme parametre olarak C'yi iletmektir, ancak C'nin A ve B ile ilişkisi üzerindeki kısıtlamalar hala geçerlidir.

Şimdi, önemli soru neden işleri bu şekilde yapmakta ısrarcı olduğum. Üç ana neden vardır:

  • Nesnelerimi daha gevşek bağlı tutar. Nesnelerim başka nesneleri de kapsayabilir, ancak hiçbir zaman arayanın bağlamına bağlı olmayacak ve bağlam hiçbir zaman kapsüllenmiş nesnelere bağlı olmayacaktır.
  • Kontrol akışımın akıl yürütmesini kolaylaştırır. selfBir yöntem yürütülürken dahili durumunu değiştirebilecek tek kodun bir yöntem olduğunu ve başka bir yöntem olmadığını varsayabiliriz . Bu, eşzamanlı nesnelere muteks koymaya neden olabilecek aynı akıl yürütmedir.
  • Nesnelerimin kapsüllenmiş verileri üzerindeki değişmezleri korur. Genel yöntemlerin değişmezlere bağlı olmasına izin verilir ve bir yöntem zaten yürütülürken bir yöntem harici olarak çağrılabilirse bu değişmezler ihlal edilebilir.

Geri aramaların tüm kullanımlarına karşı değilim. A nesnesinin hiçbir zaman "arayanı arama" ilkesine uygun olarak, bir nesne B üzerinde bir yöntem çağırır ve ona bir geri çağrı iletirse, geri arama A'nın dahili durumunu değiştirmeyebilir ve bu A ve the A bağlamındaki nesneler. Başka bir deyişle, geri arama yalnızca B tarafından verilen nesneler üzerinde yöntemleri çağırabilir. Geri arama, aslında, B ile aynı kısıtlamalar altındadır.

Bağlanmak için son bir gevşek son, bahsettiğim bu kısmi sıralamaya bakılmaksızın herhangi bir saf işlevin çağrılmasına izin vereceğim. Saf işlevler, değişebilir durumu veya yan etkileri değiştiremedikleri veya güvenemedikleri için yöntemlerden biraz farklıdır, bu yüzden kafa karıştırıcı konular hakkında endişeleri yoktur.


0

Şahsen? Sadece bir singleton kullanıyorum.

Evet, tamam, kötü tasarım, nesneye yönelik değil, vb. Biliyor musunuz? Umurumda değil . Bir teknoloji vitrini değil, bir oyun yazıyorum. Kimse beni kodda derecelendirmeyecek. Amaç eğlenceli bir oyun yapmak, ve yoluma çıkan her şey daha az eğlenceli bir oyunla sonuçlanacak.

Aynı anda iki dünyamız olacak mı? Olabilir! Belki yapacaksın. Ama şu anda bu durumu düşünemezseniz, muhtemelen yapmazsınız.

Benim çözümüm: Dünya single'ı yapmak. Üzerindeki çağrı işlevleri. Bütün karmaşa ile yapılır. Sen olabilir nerede bu potansiyel müşteriler var hata ve yapmak - her işleve ek parametresinde geçmektedir. Veya sadece çalışan bir kod yazabilirsiniz.

Bu şekilde yapmak, dağınık hale geldiğinde işleri temizlemek için biraz disiplin gerektirir (bu "ne zaman", "if" değil), ancak kodun dağınık olmasını önleyemezsiniz - ya spagetti probleminiz var ya da binlerce - soyutlama katmanları sorunu. En azından bu şekilde büyük miktarda gereksiz kod yazmıyorsunuz.

Ve artık bir singleton istemediğinize karar verirseniz, ondan kurtulmak genellikle oldukça basittir. Biraz iş alır, milyarlarca parametre geçirir, ama bunlar yine de geçmeniz gereken parametrelerdir.


2
Muhtemelen biraz daha özlü bir şekilde ifade ederdim: "Refactor'dan korkma".
Tetrad

Singletons kötüdür! Globaller çok daha iyi. Listelediğiniz tüm tekil erdemler bir küresel için aynıdır. Çeşitli alt sistemime global işaretçiler (aslında küresel işlevler referansları döndürerek) kullanıyorum ve bunları ana işlevimde ilklendiriyorum / imha ediyorum. Küresel işaretçiler başlatma sırası sorunlarını önler, yıkım sırasında tekilleri sarkar, önemsiz olmayan tekil yapıları vb.
deft_code

@ Tetrad, kabul etti. Sahip olabileceğiniz en iyi becerilerden biridir.
ZorbaTHut
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.