Kalıtım ve Satranç Taşları Kompozisyonu


9

Bu yığın değişiminin hızlı bir araştırması, genel kompozisyonda genellikle mirastan daha esnek olarak kabul edildiğini, ancak her zamanki gibi projeye vb. Bağlıdır ve mirasın daha iyi bir seçim olduğu zamanlar vardır. Her parçanın bir ağ, muhtemelen farklı animasyonlar ve benzeri bir 3D satranç oyunu yapmak istiyorum. Bu somut örnekte, her iki yaklaşım için de dava açabileceğiniz anlaşılıyor muyum?

Kalıtım böyle bir şeye benzeyecektir (uygun kurucu vb. İle)

class BasePiece
{
    virtual Squares GetValidMoveSquares() = 0;
    Mesh* mesh;
    // Other fields
}

class Pawn : public BasePiece
{
   Squares GetValidMoveSquares() override;
}

"is-a" prensibine kesinlikle uyurken kompozisyon böyle bir şeye benzeyecektir.

class MovementComponent
{
    virtual Squares GetValidMoveSquares() = 0;
}

class PawnMovementComponent
{
     Squares GetValidMoveSquares() override;
}

enum class Type
{
     PAWN,
     BISHOP, //etc
}


class Piece
{
    MovementComponent* movementComponent;
    MeshComponent* mesh;
    Type type;
    // Other fields
 }

Bu daha çok kişisel bir tercih midir yoksa bir yaklaşım buradakinden daha akıllıca bir seçim midir?

EDIT: Ben her cevaptan bir şey öğrendim düşünüyorum, bu yüzden sadece bir tane seçmek için kötü hissediyorum. Son çözümüm buradaki birkaç gönderiden ilham alacak (hala üzerinde çalışıyor). Cevap vermek için zaman ayıran herkese teşekkürler.


Her ikisinin de yan yana olabileceğine inanıyorum, bu ikisi farklı amaçlar için, ama bunlar birbirlerine özel değil. Satranç, farklı parça ve tahtalardan oluşuyor ve parçalar farklı tiplerde. Bu parçalar bazı temel özellik ve davranışları paylaşır ve ayrıca belirli özellik ve davranışlara sahiptir. Bu yüzden bana göre kompozisyon tahta ve parçalar üzerine uygulanmalı, kalıtım ise parça türleri üzerinden izlenmelidir.
Hitesh Gaur

Bazı insanların tüm kalıtımın kötü olduğunu iddia etmelerine rağmen, genel fikir, arayüz kalıtımının iyi / iyi olduğunu ve uygulama mirasının yararlı olduğunu ancak sorunlu olabileceğini düşünüyorum. Deneyimime göre, tek bir miras seviyesinin ötesindeki her şey sorgulanabilir. Bunun ötesinde sorun yok ama kıvrımlı bir karışıklık yaratmadan çıkmak kolay değil.
JimmyJames

Strateji modeli, bir nedenden dolayı oyun programlamasında yaygındır. var Pawn = new Piece(howIMove, howITake, whatILookLike)kalıtım hiyerarşisinden çok daha basit, daha yönetilebilir ve daha sürdürülebilir görünüyor.
Ant P

@AntP Bu iyi bir nokta, teşekkürler!
CanISleepYet

@AntP Bir cevap verin! ... Bunun bir çok değeri var!
svidgen

Yanıtlar:


4

İlk bakışta, cevabınız "kompozisyon" çözümünün miras kullanmadığını iddia ediyor. Ama sanırım bunu eklemeyi unuttun:

class PawnMovementComponent : MovementComponent
{
     Squares GetValidMoveSquares() override;
}

(ve bu türevlerden daha fazlası, altı parça tipinin her biri için bir tane). Şimdi bu daha çok klasik “Strateji” modeline benziyor ve kalıtımdan da faydalanıyor.

Ne yazık ki, bu çözümün bir kusuru var: her biri Pieceşimdi tür bilgilerini yedekli olarak iki kez tutar:

  • üye değişkenin içinde type

  • içeride movementComponent(alt tip ile temsil edilir)

Bu artıklık sizi gerçekten belaya sokacak şeydir - a gibi basit bir sınıf Piece, iki kaynak değil, "tek bir hakikat kaynağı" sağlamalıdır.

Tabii ki, tür bilgisini sadece içinde depolamaya çalışabilir typeve alt sınıflar da oluşturamazsınız MovementComponent. Ancak bu tasarım büyük olasılıkla büyük bir " switch(type)" ifadesinin uygulanmasına yol açacaktır GetValidMoveSquares. Ve bu kesinlikle mirasın burada daha iyi bir seçim olması için güçlü bir göstergedir .

"Miras" tasarımda Not olmayan bir yedekli bir şekilde "türü" alanını temin etmek oldukça kolaydır: sanal bir yöntemi eklemek GetType()için BasePieceve her temel sınıfındaki buna göre uygulamak.

"Terfi" konusunda: @svidgen ile buradayım, @ TheCatWhisperer'in cevabı tartışmalı olarak sunulan argümanları buluyorum .

"Tanıtımı", aynı parçanın türünün değişmesi olarak yorumlamak yerine, parçaların fiziksel değişimi olarak yorumlamak bana çok daha doğal geliyor. Yani bunu benzer bir şekilde uygulamak - bir parçayı farklı tipte bir başkasıyla değiştirmek - büyük olasılıkla büyük sorunlara neden olmaz - en azından belirli satranç durumu için değil.


Desenin adını söylediğin için teşekkürler, bunun Strateji olarak adlandırıldığını fark etmedim. Ayrıca haklısın sorumun içindeki hareket bileşeninin mirasını kaçırdım. Yine de bu tür gerçekten gereksiz mi? Tahtadaki tüm kraliçeleri aydınlatmak istersem, hangi hareket bileşenine sahip olduklarını kontrol etmek garip görünebilirdi, ayrıca ne tür bir süper çirkin olacağını görmek için hareket bileşenini dökmem gerekirdi?
CanISleepYet

@CanISleepYet: Önerilen "tür" alanınızla ilgili sorun, alt türünden farklı olabilir movementComponent. "Miras" tasarımında bir yazım alanının nasıl güvenli bir şekilde sağlanacağına ilişkin düzenlememi görün.
Doc Brown

4

Açık, iyi ifade edilmiş nedenleriniz veya güçlü genişletilebilirlik ve bakım endişeleriniz yoksa muhtemelen daha basit seçeneği tercih ederim .

Kalıtım seçeneği daha az kod satırı ve daha az karmaşıklıktır. Her bir parça tipi, hareket özellikleri ile "değişmez" bire bir ilişkiye sahiptir. (1'e 1 olan, ancak mutlaka değişmez olmayan temsilin aksine - çeşitli "kaplamalar" sunmak isteyebilirsiniz.)

Parçalar ve davranışları / hareketleri arasındaki bu değişmez bire bir ilişkiyi birden fazla sınıfa ayırırsanız, muhtemelen sadece karmaşıklık eklersiniz - başka bir şey değil. Yani, eğer bu kodu gözden geçiriyor veya miras alıyorsam, karmaşıklığın iyi, belgelenmiş nedenlerini görmeyi beklerdim.

Tüm bunlar, daha da basit bir seçenek olan IMO, bireysel Parçalarınızın uyguladığı bir Piece arayüz oluşturmaktır . Çoğu dilde, bu aslında kalıtımdan çok farklıdır , çünkü bir arayüz sizi başka bir arayüz uygulamaktan kısıtlamaz . ... bu durumda herhangi bir temel sınıf davranışını ücretsiz olarak alamazsınız: Paylaşılan davranışı başka bir yere koymak istersiniz.


Kalıtım çözümünün 'daha az kod satırı' olduğunu satın almıyorum. Belki bu bir sınıfta ama genel olarak değil.
JimmyJames

@JimmyJames Gerçekten mi? ... Sadece OP kod satırları saymak ... itiraf tüm çözüm değil, ama çözüm daha fazla LOC ve karmaşıklığı korumak için kötü bir gösterge değil.
svidgen

Bu konuda çok iyi bir noktaya değinmek istemiyorum çünkü hepsi bu noktada kavramsal ama evet, gerçekten öyle düşünüyorum. Benim görüşüm tüm kod ile karşılaştırıldığında bu kod biraz ihmal edilebilir olmasıdır. Bu kuralların uygulanmasını kolaylaştırırsa (tartışmalı), düzinelerce hatta yüzlerce kod satırı kaydedebilirsiniz.
JimmyJames

Teşekkürler, arayüzü düşünmedim ama bunun en iyi yol olduğu doğru olabilir. Daha basit neredeyse her zaman daha iyidir.
CanISleepYet

2

Satranç ile ilgili olan şey, oyun ve parça kurallarının önceden belirlenmiş ve sabitlenmiş olmasıdır. Çalışan herhangi bir tasarım gayet iyi - istediğinizi kullanın! Deneyin ve hepsini deneyin.

Bununla birlikte, iş dünyasında hiçbir şey bu kadar kesin bir şekilde reçete edilmez - iş kuralları ve gereksinimleri zamanla değişir ve programlar uyum sağlamak için zamanla değişmelidir. İs-a ve has-a ve verinin fark yarattığı yer burasıdır. Burada sadelik, karmaşık çözümleri zaman içinde ve değişen gereksinimleri korumak için kolaylaştırır. Genel olarak, iş dünyasında, bir veritabanını da içerebilecek kalıcılık ile uğraşmak zorundayız. Yani, tek bir sınıf işi yapacağında birden fazla sınıf kullanmama ve kompozisyon yeterli olduğunda miras kullanma gibi kurallarımız var. Ancak bu kuralların tümü, değişen kurallar ve gereksinimler karşısında kodun uzun vadede korunmasını sağlamaya yöneliktir - ki bu satrançta geçerli değildir.

Satranç ile, en uzun vadeli bakım yolu, programınızın daha akıllı ve daha akıllı olması gerektiğidir, bu da sonunda hız ve depolama optimizasyonlarının baskın olacağı anlamına gelir. Bunun için, genellikle performans için okunabilirliği feda eden takaslar yapmanız gerekecek ve böylece en iyi OO tasarımı bile sonunda yol kenarına gidecek.


Bir kavram alan ve daha sonra karışıma yeni şeyler atan birçok başarılı oyun olduğunu unutmayın. Gelecekte bunu satranç oyununuzla yapmak istiyorsanız, gelecekteki olası değişiklikleri dikkate almalısınız.
Flater

2

Problem alanı (yani satranç kuralları) hakkında bazı gözlemler yaparak başlardım:

  • Bir parça için geçerli hamle seti sadece parçanın tipine değil, tahtanın durumuna da bağlıdır (kare boşsa piyon ileri hareket edebilir / bir parça yakalanırsa çapraz olarak hareket edebilir).
  • Bazı eylemler, mevcut bir parçanın oyundan kaldırılmasını (bir parçanın tanıtım / yakalama) veya birden fazla parçanın taşınmasını (döküm) içerir.

Bunlar parçanın kendisinin bir sorumluluğu olarak modellenmek gariptir ve burada ne kalıtım ne de kompozisyon harika hissettirmez. Bu kuralları birinci sınıf vatandaşlar olarak modellemek daha doğal olurdu:

// pseudo-code, not pure C++

interface MovementRule {
  set<Square> getValidMoves(Board board, Square from); // what moves can I make from the given square?
  void makeMove(Board board, Square from, Square to); // update board state to reflect a specific move
}

class Game {
  Board board;
  map<PieceType, MovementRule> rules;
}

MovementRuleuygulamaların, döküm ve tanıtım gibi karmaşık hareketleri desteklemek için gereken herhangi bir şekilde pano durumunu güncellemesini sağlayan basit ama esnek bir arabirimdir. Standart satranç için, her bir parça türü için bir uygulamanız olur, ancak Gamefarklı satranç çeşitlerini desteklemek için bir örnekte farklı kurallar da ekleyebilirsiniz .


İlginç yaklaşım - Düşünce için iyi yemek
CanISleepYet

1

Bu durumda mirasın daha temiz bir seçim olacağını düşünürdüm. Kompozisyon biraz daha zarif olabilir, ancak bana biraz daha zorlanmış gibi geliyor.

Farklı davranan hareketli parçalar kullanan başka oyunlar geliştirmeyi planlıyorsanız, özellikle de her oyun için gereken parçaları üretmek için bir fabrika deseni kullandıysanız kompozisyon daha iyi bir seçim olabilir.


2
Kalıtım kullanarak terfi ile nasıl başa çıkardınız?
TheCatWhisperer

4
@TheCatWhisperer Oyunu fiziksel olarak oynarken nasıl halledersiniz ? (Umarım parçayı değiştirirsiniz - piyonu yeniden
oymuyorsunuz

0

"is-a" prensibine kesinlikle uyan

Doğru, yani miras kullanıyorsun. Parça dersinizde yine de beste yapabilirsiniz ama en azından bir omurgaya sahip olacaksınız. Kompozisyon daha esnektir, ancak esneklik genel olarak abartılır ve kesinlikle bu durumda, çünkü katı modelinizi terk etmenizi gerektiren yeni bir parça türünü tanıma şansı sıfırdır. Satranç oyunu yakında değişmeyecek. Kalıtım size yol gösterici bir model, ilişkilendirilecek bir şey, öğretim kodu, yön verir. Kompozisyon çok değil, en önemli alan varlıkları göze çarpmıyor, spineless, kodu anlamak için oyunu anlamanız gerekiyor.


kompozisyon ile kod hakkında mantık zor olduğu noktasını eklediğiniz için teşekkür ederiz
CanISleepYet 17:19

0

Lütfen burada kalıtım kullanmayın. Diğer cevaplar kesinlikle birçok bilgelik taşına sahip olsa da, burada miras kullanmak kesinlikle bir hata olacaktır. Kompozisyon kullanmak sadece beklemediğiniz sorunlarla başa çıkmanıza yardımcı olmakla kalmaz, aynı zamanda mirasın zarif bir şekilde ele alamayacağı bir sorun görüyorum.

Bir piyon daha değerli bir parçaya dönüştürüldüğünde terfi, kalıtımla ilgili bir sorun olabilir. Teknik olarak, bir parçayı bir başkasıyla değiştirerek bununla başa çıkabilirsiniz, ancak bu yaklaşımın sınırlamaları vardır. Bu sınırlamalardan biri, parça bilgilerini promosyon üzerine sıfırlamak istemeyebileceğiniz parça istatistiklerini izlemek olabilir (veya kopyalamak için ekstra kodu yazabilirsiniz).

Şimdi, sorunuzdaki kompozisyon tasarımınız kadarıyla, bence gereksiz bir şekilde karmaşık. Her eylem için ayrı bir bileşene sahip olmanız gerektiğinden şüphelenmiyorum ve parça türü başına bir sınıfa yapışabiliyordum. Enum türü de gereksiz olabilir.

Bir Piecesınıfı (zaten sahip olduğunuz gibi) ve bir sınıfı tanımlarım PieceType. PieceTypeSınıf tanımlar, örneğin, bir piyon, kraliçe, vb tanımlamalıdır tüm yöntemler, CanMoveTo, GetPieceTypeName, vb. Sonra Pawnve Queen, vb sınıfları neredeyse sınıftan miras alır PieceType.


3
Tanıtım ve değiştirme ile ilgili gördüğünüz sorunlar önemsizdir, IMO. Endişeleri ayrılması örneğin "birim başına izleme" (ya da herneyse) bağımsız oldukça fazla görev ele alınması zaten . ... Çözümünüzün sunduğu herhangi bir potansiyel avantaj, ele almaya çalıştığınız hayali endişeler IMO ile örtüşüyor.
svidgen

5
Açıklığa kavuşturmak için: Şüphesiz önerilen çözümünüzün bazı avantajları vardır. Ancak cevabınızda bulmak zor, bu da öncelikle “miras çözümü” nde çözülmesi gerçekten çok kolay olan sorunlara odaklanıyor . ... Fikriniz hakkında iletişim kurmaya çalıştığınız her neyse (bu benim için zaten) bu türden düşmeler.
svidgen

1
PieceSanal değilse , bu çözümü kabul ediyorum. Sınıf tasarımı yüzeyde biraz daha karmaşık görünse de, uygulamanın genel olarak daha basit olacağını düşünüyorum. İlişkili olan Pieceve olmayan önemli şeyler, PieceTypekimliği ve potansiyel olarak hareket geçmişidir.
JimmyJames

1
@svidgen Oluşturulan bir Parçayı kullanan ve kalıtım tabanlı bir parçayı kullanan bir uygulama, temelde bu çözümde açıklanan ayrıntıların dışında aynı görünecektir. Bu kompozisyon çözeltisi, tek bir dolaylama seviyesi ekler. Bunu kaçınmaya değer bir şey olarak görmüyorum.
JimmyJames

2
@JimmyJames Doğru. Ancak, birden fazla parçanın hareket geçmişi izlenmeli ve ilişkilendirilmelidir - herhangi bir parçanın sorumlu olması gereken bir şey değil, IMO. SOLID gibi şeylerle ilgileniyorsanız bu "ayrı bir endişe" dir.
svidgen

0

Benim bakış açımdan, sağlanan bilgilerle, mirasın veya kompozisyonun gidilecek yol olup olmadığı şeklinde cevap vermek akıllıca değil.

  • Kalıtım ve kompozisyon, nesne yönelimli tasarımcının alet kemerindeki araçlardır.
  • Sorunun etki alanı için birçok farklı model tasarlamak için bu araçları kullanabilirsiniz.
  • Bir alanı temsil edebilecek bu tür birçok model olduğundan, tasarımcının gereksinimlere bakış açısına bağlı olarak, işlevsel olarak eşdeğer modeller bulmak için miras ve kompozisyonu çeşitli şekillerde birleştirebilirsiniz.

Modelleriniz tamamen farklıdır, birincisinde satranç taşları kavramını, ikincisinde ise parçaların hareket kavramını. İşlevsel olarak eşdeğer olabilirler ve etki alanını daha iyi temsil eden ve bu konuda daha kolay bir şekilde bana neden olanı seçerdim.

Ayrıca, ikinci tasarımınızda, Piecesınıfınızın bir typealanı olduğu gerçeği , parçanın kendisinin birden fazla türde olabileceği için burada bir tasarım problemi olduğunu açıkça ortaya koymaktadır. Bu, parçanın kendisinin tür olduğu bir çeşit miras kullanması gibi görünmüyor mu?

Yani, çözümünüz hakkında tartışmak çok zor. Önemli olan miras veya kompozisyon kullanıp kullanmadığınız değil, modelinizin sorunun etki alanını doğru bir şekilde yansıtıp yansıtmadığı ve bununla ilgili akıl yürütmenin ve bir uygulamanın sağlanmasının faydalı olup olmadığıdır.

Kalıtım ve kompozisyonu doğru kullanmak biraz deneyim gerektirir, ancak bunlar tamamen farklı araçlardır ve bir sistemin tamamen kompozisyon kullanarak tasarlanabileceğini kabul edebilememe rağmen birinin diğerini "değiştirebileceğine" inanmıyorum.


Aracın değil, modelin önemli olduğuna dikkat çekiyorsunuz. Gereksiz tip ile ilgili olarak, miras gerçekten yardımcı olur mu? Örneğin, tüm piyonları vurgulamak veya bir parçanın kral olup olmadığını kontrol etmek istersem, döküm yapmaya gerek kalmaz mıydı?
CanISleepYet

-1

Ben de önermiyorum.

Nesne yönelimi güzel bir araç olmakla birlikte, çoğu zaman işleri basitleştirmeye yardımcı olurken, araçtaki tek araç bu değildir.

Bunun yerine, basit bir bytearray içeren bir tahta sınıfı uygulamayı düşünün.
Bunu yorumlamak ve üzerinde işlem yapmak için bit işlevini ve anahtarlamayı kullanarak üye işlevlerinde yapılır.

Boş için sıfır rezerv olsa da, parça için 7 bit bırakarak sahibi için MSB kullanın.
Alternatif olarak, boş için sıfır, sahip için imza, parça için mutlak değer.

İsterseniz, manuel maskelemeyi önlemek için bit alanlarını kullanabilirsiniz, ancak bunlar kaydedilemez:

class field {
    signed char data = 0;
public:
    constexpr field() = default;
    constexpr field(bool black, piece x) noexcept
    : data(x < piece::pawn || x > piece::king ? 0 : (black << 7) | x))
    {}
    constexpr bool is_black() noexcept { return data < 0; }
    constexpr bool is_white() noexcept { return data > 0; }
    constexpr bool empty() noexcept { return data == 0; }
    constexpr piece piece() noexcept { return piece(data & 0x7f); }
};

İlginç ... ve bir C ++ sorusu olduğunu düşünerek uygun . Ancak, bir C ++ programcısı olmamak, bu benim ilk (veya ikinci veya üçüncü ) içgüdüm olmazdı ! ... Bu belki de burada en C ++ yanıtı olabilir!
svidgen

7
Bu, gerçek bir uygulamanın takip etmemesi gereken bir mikrooptimizasyondur.
Lie Ryan

@LieRyan Eğer sahip olduğunuz tek şey OOP ise, her şey bir nesne gibi kokar. Ve gerçekten "mikro" nun da ilginç bir soru olup olmadığı. Ne bağlı do onunla, oldukça önemli olabilir.
Tekilleştirici

Heh ... söyledi , C ++ ise Nesne yönelimli. Ben OP sadece C yerine C ++ kullanıyor bir nedeni olduğunu varsayalım .
svidgen

3
Burada OOP eksikliğinden daha az endişeliyim, ancak kodu biraz twiddling ile şaşırtmak. 8-bit Nintendo için kod yazmıyorsanız veya kart durumunun milyonlarca kopyasıyla uğraşmayı gerektiren algoritmalar yazmıyorsanız, bu erken mikrooptimizasyon ve tavsiye edilemez. Normal senaryolarda, muhtemelen daha hızlı olmayacaktır çünkü hizalanmamış bit erişimi ekstra bit işlemleri gerektirir.
Yalan Ryan
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.