Kendisini veya diğer iki şeyi temsil eden bir şey için veri türü nasıl yapılır


16

Arka fon

İşte üzerinde çalıştığım asıl sorun: Kart oyunu Magic: The Gathering'de kartları temsil etmenin bir yolunu istiyorum . Oyundaki çoğu kart normal görünümlü kartlardır, ancak bazıları her biri kendi adına sahip iki bölüme ayrılmıştır. Bu iki parçalı kartların her bir yarısı kartın kendisi olarak kabul edilir. Netleştirmek için, Cardsadece normal bir kart veya iki parçalı bir kartın yarısını (başka bir deyişle, sadece bir isimle) ifade etmek için kullanacağım .

kartları

Yani baz tipimiz var, Card. Bu nesnelerin amacı gerçekten sadece kartın özelliklerini tutmaktır. Gerçekten kendi başlarına hiçbir şey yapmıyorlar.

interface Card {
    String name();
    String text();
    // etc
}

CardAradığım iki alt sınıf var PartialCard(iki parçalı kartın yarısı) ve WholeCard(normal kart). PartialCardiki ek yöntemi daha vardır: PartialCard otherPart()ve boolean isFirstPart().

Temsilcileri

Eğer bir destem varsa, WholeCard değil, s Cardbir şekilde, s Cardbir olabileceğini PartialCardve bu anlamlı olmaz. Bu yüzden bir "fiziksel kartı" temsil eden bir nesne, yani bir WholeCardveya iki PartialCardsaniyeyi temsil edebilecek bir şey istiyorum . Geçici olarak bu tip arıyorum Representativeve Cardyöntemi olurdu getRepresentative(). A Representative, temsil ettiği kart (lar) hakkında neredeyse hiçbir doğrudan bilgi sağlamaz, sadece / kartlara işaret eder. Şimdi, benim parlak / çılgın / aptalca fikrim (siz karar verin) WholeCard'ın hem Card ve hem deRepresentative . Sonuçta, kendilerini temsil eden kartlar! WholeCards getRepresentativeolarak uygulayabilir return this;.

Gelince PartialCards, kendilerini temsil etmiyorlar, ancak bir Representativeolmayan Card, ancak iki PartialCards'ye erişmek için yöntemler sağlayan bir dışa sahipler .

Bu tür hiyerarşinin mantıklı olduğunu düşünüyorum, ancak karmaşık. Card"Kavramsal kartlar" ve Representative"fiziksel kartlar " olarak düşünürsek , çoğu kart her ikisi de! Sanırım fiziksel kartların aslında kavramsal kartlar içerdiğini ve aynı şey olmadığını iddia edebilirsiniz, ama öyle olduklarını iddia ediyorum.

Tip döküm gerekliliği

Çünkü PartialCards veWholeCards ikisi de Cards'dir ve onları ayırmak için genellikle iyi bir neden yoktur, normalde sadece birlikte çalışıyordum Collection<Card>. Bu yüzden bazen PartialCardek yöntemlerine erişebilmek için oyuncular kullanmam gerekirdi . Şu anda, burada açıklanan sistemi kullanıyorum çünkü açık seçimleri gerçekten sevmiyorum. Ve benzeri Card, Representativeya bir dönüştürülmeyecektir gerek olacağını WholeCardveya Compositefiili erişmek için, Cardtemsil ettikleri s.

Özet olarak:

  • Baz tipi Representative
  • Baz tipi Card
  • Tür WholeCard extends Card, Representative(erişim gerekmez, kendini temsil eder)
  • Tür PartialCard extends Card(diğer bölüme erişim sağlar)
  • Tür Composite extends Representative(her iki parçaya da erişim sağlar)

Bu delilik mi? Aslında çok mantıklı olduğunu düşünüyorum, ama dürüstçe emin değilim.


1
PartialCards, fiziksel kart başına iki karttan başka etkin kartlar mıdır? Oynandıkları emir önemli mi? Sadece bir "Güverte Yuvası" ve "WholeCard" sınıfı yapmak ve DeckSlots birden fazla WholeCards sahip izin vermek değil, ve sonra sadece DeckSlot.WholeCards.Play gibi bir şey yapmak ve 1 veya 2 varsa her ikisi de ayrı gibi oynamak kartlar? Çok daha karmaşık tasarımınız göz önüne alındığında eksik olduğum bir şey olmalı :)
enderland

Bu sorunu çözmek için tüm kartlar ve kısmi kartlar arasında polimorfizm kullanabileceğimi gerçekten hissetmiyorum. Evet, eğer bir "play" işlevi istersem, bunu kolayca uygulayabilirdim, ama sorun kısmi kartlar ve tüm kartların farklı öznitelik kümeleri var. Bu nesneleri sadece temel sınıfları olarak değil, oldukları gibi görebilmem gerekiyor. Bu soruna daha iyi bir çözüm olmadığı sürece. Ancak, polimorfizmin ortak bir temel sınıfı paylaşan türlerle iyi bir şekilde karışmadığını, ancak mantıklıysa, aralarında farklı yöntemlere sahip olduğunu buldum.
kod kırıcı

1
Dürüst olmak gerekirse, bunun gülünç derecede karmaşık olduğunu düşünüyorum. Sıkı bağlantı ile çok katı bir tasarıma yol açan sadece "model" ilişkilere benziyorsunuz. Daha ilginç olan, bu kartların gerçekte ne yaptığıdır?
Daniel Jour

2
Eğer "sadece veri nesneleri" iseler, o zaman içgüdüm ikinci sınıfın bir dizi nesnesini içeren bir sınıfa sahip olmak ve onu orada bırakmak olurdu; kalıtım veya diğer anlamsız komplikasyonlar yok. Bu iki sınıfın PlayableCard ve DrawableCard veya WholeCard ve CardPart olması gerektiği konusunda hiçbir fikrim yok, çünkü oyununuzun nasıl çalıştığı hakkında neredeyse bilmiyorum, ama eminim onları aramak için bir şeyler düşünebilirsiniz.
Ixrec

1
Ne tür özelliklere sahipler? Bu özellikleri kullanan eylemlere örnekler verebilir misiniz?
Daniel Jour

Yanıtlar:


14

Bana öyle geliyor ki,

class PhysicalCard {
    List<LogicalCard> getLogicalCards();
}

Fiziksel kartla ilgili kod, fiziksel kart sınıfıyla ilgilenebilir ve mantıksal kartla ilgili kod bununla başa çıkabilir.

Sanırım fiziksel kartların aslında kavramsal kartlar içerdiğini ve aynı şey olmadığını iddia edebilirsiniz, ama öyle olduklarını iddia ediyorum.

Fiziksel ve mantık kartının aynı şey olduğunu düşünüp düşünmemeniz önemli değildir. Sadece aynı fiziksel nesne olduklarından, kodunuzda aynı nesne olduklarını varsaymayın. Önemli olan, bu modeli benimsemenin kodlayıcının okunmasını ve yazılmasını kolaylaştırıp kolaylaştırmamasıdır. Gerçek şu ki, her fiziksel kartın tutarlı bir şekilde,% 100 zamanın bir mantıksal kart koleksiyonu olarak kabul edildiği daha basit bir model benimsenmesi daha basit bir kodla sonuçlanacaktır.


2
Verilen neden için olmasa da, bu en iyi cevap olduğu için seçildi. Bu en iyi cevap, basit olduğu için değil, fiziksel kartlar ve kavramsal kartlar aynı şey olmadığı ve bu çözümün ilişkilerini doğru bir şekilde modellediği için. İyi bir OOP modelinin her zaman fiziksel gerçekliği yansıtmadığı, ancak her zaman kavramsal gerçekliği yansıttığı doğrudur. Basitlik elbette iyidir, ancak kavramsal doğruluğa arka koltuk alır.
Kevin Krumwiede

2
@KevinKrumwiede, gördüğüm gibi tek bir kavramsal gerçeklik yok. Farklı insanlar aynı şeyi farklı şekillerde düşünür ve insanlar bu düşünceyi değiştirebilir. Fiziksel ve mantıksal kartları ayrı varlıklar olarak düşünebilirsiniz. Veya bölünmüş kartları bir kartın genel kavramına bir tür istisna olarak düşünebilirsiniz. İkisi de yanlıştır, ancak biri daha basit modellemeye uygundur.
Winston Ewert

8

Kör olmak gerekirse, önerilen çözümün çok kısıtlayıcı ve çok bükülmüş ve fiziksel gerçeklikten kopuk olduğunu düşünüyorum, çok az avantajlı modeller.

İki alternatiften birini öneririm:

Seçenek 1. MTG sitesi Aşınma // Gözyaşı listelediği gibi Yarım A // Yarım B olarak tanımlanan tek bir kart gibi davranın . Ama, senin izin içerecek şekilde varlık N Her bir özelliğin: oynanabilir-name, mana, türü, nadir, metin, efektler, vb ..Card

interface Card {
  List<String> Names();
  List<ManaCost> Costs();
  List<CardTypes> Types();
  /* etc. */
}

Seçenek 2. Seçenek 1'den farklı olarak, fiziksel gerçeklikten sonra modelleyin. Fiziksel kartıCard temsil eden bir varlığınız var . Ve o zaman amacı N şeyi tutmak . Bunların her birinin ayrı bir adı, mana bedeli, efekt listesi, yetenek listesi vb. Olabilir . Ve "fiziksel" değeriniz, her birinin adının bir bileşiği olan kendi tanımlayıcısına (veya adına) sahip olabilir. MTG veritabanı gibi görünüyor. PlayablePlayableCardPlayable

interface Card {
  String Name();
  List<Playable> Playables();
}

interface Playable {
  String Name();
  ManaCost Cost();
  CardType Type();
  /* etc. */
}

Bu seçeneklerden birinin fiziksel gerçekliğe oldukça yakın olduğunu düşünüyorum. Ve bence bu kodunuza bakan herkes için faydalı olacak. (6 ay içinde kendin gibi.)


5

Bu nesnelerin amacı gerçekten sadece kartın özelliklerini tutmaktır. Gerçekten kendi başlarına hiçbir şey yapmıyorlar.

Bu cümle tasarımınızda yanlış bir şey olduğunu gösteren bir işarettir: OOP'ta her sınıfın tam olarak bir rolü olmalı ve davranış eksikliği kodda kötü bir koku olan potansiyel bir Veri Sınıfını ortaya çıkarır .

Sonuçta, kendilerini temsil eden kartlar!

IMHO, biraz garip ve hatta biraz garip geliyor. "Kart" türünde bir nesne bir kartı temsil etmelidir. Dönemi.

Magic: Toplama hakkında hiçbir şey bilmiyorum , ancak sanırım gerçek yapıları ne olursa olsun kartlarınızı benzer bir şekilde kullanmak istiyorsunuz: bir dize temsili görüntülemek istiyorsunuz, bir saldırı değeri hesaplamak istiyorsunuz, vb.

Açıkladığınız sorun için, bu DP'nin genellikle daha genel bir sorunu çözmek için sunulmuş olmasına rağmen, bir Kompozit Tasarım Deseni öneriyorum :

  1. Oluşturmak Card önce yaptığınız gibi arayüz .
  2. Bir oluşturun ConcreteCard, o uygular Cardve tanımlar basit yüz kartı. Normal bir davranış sergilemekte tereddüt etmeyinBu sınıfa kartın .
  3. Bir oluşturun CompositeCard, o uygular Cardve sahip iki ek (ve önsel özel) Cards. Onları arayalım leftCardve rightCard.

Yaklaşımın şıklığı, a'nın CompositeCardkendisinin ConcreteCard veya CompositeCard olabileceği iki kart içermesidir. Oyunda leftCardve rightCardmuhtemelen sistematik olarak olacak ConcreteCard, ancak Tasarım Deseni, isterseniz daha yüksek seviyeli kompozisyonlar tasarlamanıza izin verir. Kart manipülasyonunuz, kartlarınızın gerçek türünü dikkate almayacaktır ve bu nedenle alt sınıfa döküm gibi şeylere ihtiyacınız yoktur.

CompositeCardCardelbette belirtilen yöntemleri uygulamalıdır ve böyle bir kartın 2 karttan oluştuğunu göz önünde bulundurarak yapmalıdır (artı, isterseniz CompositeCardkartın kendisine özgü bir şey . Örneğin, aşağıdaki uygulama:

public class CompositeCard implements Card
{ 
   private final Card leftCard, rightCard;
   private final double factor;

   @Override // Defined in Card
   public double attack(Player p){
      return factor * (leftCard.attack(p) + rightCard.attack(p));
   }

   @Override // idem
   public String name()
   {
       return leftCard.name() + " combined with " + rightCard.name();
   }

   ...
}

Bunu yaparak, CompositeCardtam olarak herhangi biri için yaptığınız gibi kullanabilirsiniz Cardve polimorfizm sayesinde spesifik davranış gizlidir.

Bir CompositeCardi'nin her zaman iki normal Cards içereceğinden eminseniz , fikri saklayabilir ve sadece ve ConcreateCardiçin bir tür olarak kullanabilirsiniz . leftCardrightCard


Sen neden bahsediyorsun kompozit desen , ama aslında sen iki başvuruları tutuyorsun beri Cardiçinde CompositeCardsize uyguladıklarını dekoratör deseni . Ben de bu çözümü kullanmak için OP tavsiye, dekoratör gitmek için yol!
Benekli

İki Card örneğine sahip olmanın neden dersimi bir dekoratör yaptığını anlamıyorum. Kendi bağlantınıza göre, bir dekoratör tek bir nesneye özellikler ekler ve bu nesnenin kendisiyle aynı sınıfın / arayüzün bir örneğidir. Diğer bağlantınıza göre, bir bileşik aynı sınıf / arabirimin birden çok nesnesinin kullanılmasına izin verir. Ama sonuçta kelimeler önemli değil ve sadece fikir harika.
mgoeminne

@Spotted Terim Java'da kullanıldığı için bu kesinlikle dekoratör modeli değildir. Dekoratör deseni, tek bir nesne üzerindeki yöntemlerin geçersiz kılınmasıyla ve bu nesneye özgü anonim bir sınıfın oluşturulmasıyla uygulanır.
Kevin Krumwiede

@KevinKrumwiede sürece CompositeCardek yöntemler ortaya çıkarmaz CompositeCard, sadece bir dekoratör olduğunu.
Benekli

"... Tasarım Deseni, daha üst düzey kompozisyonları ücretsiz olarak tasarlamanızı sağlar" - hayır, ücretsiz değil , tam tersi - gereksinimler için gerekenden daha karmaşık bir çözüme sahip olmanın bedeli.
Doc Brown

3

Belki güvertede veya mezarlıkta olduğunda her şey bir Karttır ve oynadığınızda, hepsi Oynatılabilir olan veya genişleten bir veya daha fazla Kart nesnesinden bir Yaratık, Arazi, Efsun, vb. Daha sonra, bir kompozit, kurucusu iki kısmi Kart alan tek bir Oynanabilir olur ve bir kicker içeren bir kart, kurucusu mana argümanı alan bir Oynanabilir olur. Tür, onunla neler yapabileceğinizi (çiz, engelle, gider, dokun) ve neyi etkileyebileceğini yansıtır. Ya da bir Oynanabilir, bir kartı çağırmak ve ne yaptığını tahmin etmek için aynı arayüzü kullanmak gerçekten yararlıysa, oyundan çıkarıldığında dikkatli bir şekilde geri döndürülmesi gereken bir Karttır (bonusları ve sayaçları kaybetmek, bölünmek).

Belki de Kart ve Oynanabilir bir etkisi vardır.


Ne yazık ki, bu kartları oynamıyorum. Bunlar gerçekten sadece sorgulanabilecek veri nesneleri. Bir güverte veri yapısı istiyorsanız, bir Liste <WholeCard> veya başka bir şey istersiniz. Yeşil Anında Arama kartları için arama yapmak istiyorsanız bir Liste <Kart> istersiniz.
kod kırıcı

Ah tamam. O zaman probleminizle pek alakalı değil. Silmeli miyim?
Davislor

3

ziyaretçi desen gizli tür bilgileri kurtarmak için klasik bir tekniktir. Burada, yüksek soyutlama değişkenlerinde depolandıklarında bile iki tür arasında ayrım yapmak için (burada küçük bir varyasyon) kullanabiliriz.

Bu yüksek soyutlama ile başlayalım, bir Cardarayüz:

public interface Card {
    public void accept(CardVisitor visitor);
}

CardArayüzde biraz daha fazla davranış olabilir , ancak özellik alıcılarının çoğu yeni bir sınıfa taşınır CardProperties:

public class CardProperties {
    // property methods, constructors, etc.

    String name();
    String text();
    // ...
}

Artık SimpleCardtek bir özellik kümesiyle tüm bir kartı temsil edebiliriz :

public class SimpleCard implements Card {
    private CardProperties properties;

    // Constructors, ...

    @Override
    public void accept(CardVisitor visitor) {
        visitor.visit(properties);
    }
}

CardPropertiesHenüz yazılmamış ve henüz yazılmaya CardVisitorbaşladıklarını görüyoruz . CompoundCardİki yüzü olan bir kartı temsil etmek için bir yapalım :

public class CompoundCard implements Card {
    private CardProperties firstFaceProperties;
    private CardProperties secondFaceProperties;

    // Constructors, ...

    public void accept(CardVisitor visitor) {
        visitor.visit(firstFaceProperties, secondFaceProperties);
    }
}

Ortaya CardVisitorçıkmaya başlar. Şimdi bu arayüzü yazmaya çalışalım:

public interface CardVisitor {
    public void visit(CardProperties properties);
    public void visit(CardProperties firstFaceProperties, CardProperties secondFaceProperties);
}

(Bu, şimdilik arayüzün ilk sürümüdür. Daha sonra ele alınacak iyileştirmeler yapabiliriz.)

Şimdi tüm parçaları etledik. Şimdi onları bir araya getirmemiz gerekiyor:

List<Card> cards = new LinkedList<>();
cards.add(new SimpleCard(new CardProperties(/* ... */)));
cards.add(new CompoundCard(new CardProperties(/* ... */), new CardProperties(/* ... */)));

 for(Card card : cards) {
     card.accept(new CardVisitor() {
         @Override
         public void visit(CardProperties properties) {
             // Do something for simple cards with a single face
         }

         public void visit(CardProperties firstFaceProperties, CardProperties secondFaceProperties) {
             // Do something else for compound cards with two faces
         }
     });
 }

Çalışma zamanı, polimorfizm yoluyla#visit metodu doğru sürümüne göndermeyi, onu kırmaya çalışmak yerine ele alacaktır .

Anonim bir sınıf kullanmak yerine CardVisitor, davranış yeniden kullanılabilirse veya çalışma zamanında davranış değiştirme yeteneği istiyorsanız, bir iç sınıfa hatta bir tam sınıfa bile yükseltebilirsiniz.


Sınıfları şimdi oldukları gibi kullanabiliriz, ancak CardVisitorarayüzde yapabileceğimiz bazı iyileştirmeler var . Örneğin, Cards'nin üç veya dört veya beş yüze sahip olabileceği bir zaman gelebilir . Uygulamak için yeni yöntemler eklemek yerine, iki parametrenin yerine ikinci yöntemin alınmasını ve dizinin kullanılmasını sağlayabiliriz. Bu, çok yüzlü kartlara farklı muamele edilirse mantıklıdır, ancak birinin üzerindeki yüzlerin sayısı benzer şekilde ele alınır.

Ayrıca CardVisitorarayüz yerine soyut bir sınıfa dönüşebilir ve tüm yöntemler için boş uygulamalar yapabiliriz. Bu, yalnızca ilgilendiğimiz davranışları uygulamamıza izin verir (belki sadece tek yüzlülerle ilgileniyoruz Card). Ayrıca, varolan her sınıfı bu yöntemleri uygulamaya zorlamadan veya derleyemeden yeni yöntemler ekleyebiliriz.


1
Bu kolayca diğer iki yüzlü kart (yan yana yerine ön ve arka) genişletilebilir düşünüyorum. ++
RubberDuck

Daha fazla keşif için, bir Ziyaretçinin bir alt sınıfa özgü yöntemlerden yararlanma amacıyla kullanımına bazen Çoklu Gönderme denir. Double Dispatch bu soruna ilginç bir çözüm olabilir.
mgoeminne

1
Oy kullanıyorum çünkü ziyaretçi kalıbının koda gereksiz karmaşıklık ve bağlantı eklediğini düşünüyorum. Bir alternatif mevcut olduğundan (bkz. Mgoeminne'nin cevabı), onu kullanmam.
Benekli

@Spotted ziyaretçi olan karmaşık bir desen ve cevap yazarken ben de kompozit desen düşündü. Ziyaretçiyle gitmemin nedeni OP'nin benzer şeyleri benzer şekilde değil, farklı şeylere farklı davranmak istemesidir. Kartlar daha fazla davranışa sahipse ve oyun oynamak için kullanılıyorsa, bileşik desen birleştirilmiş bir istatistik oluşturmak için verilerin birleştirilmesine izin verir. Bunlar sadece veri torbalarıdır, ancak muhtemelen kullanılan bir kart ekranı oluşturur, bu durumda ayrı bilgi elde etmek basit bir bileşik toplayıcıdan daha yararlı görünüyordu.
cbojar
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.