Kalıtım ne zaman durdurulur?


9

Bir zamanlar Stack Overflow ile ilgili miras hakkında bir soru sordum .

Satranç motorunu OOP tarzında tasarladığımı söyledim. Bu yüzden bütün parçalarımı Parça soyut sınıfından miras alıyorum ama kalıtım hala devam ediyor. Kodla göstereyim

public abstract class Piece
{
    public void MakeMove();
    public void TakeBackMove();
}

public abstract class Pawn: Piece {}

public class WhitePawn :Pawn {}

public class BlackPawn:Pawn {}

Programcılar tasarımımı mühendislik üzerine biraz buldular ve renkli parça sınıflarını kaldırmayı ve parça rengini aşağıdaki gibi bir özellik üyesi olarak tutmayı önerdiler.

public abstract class Piece
{
    public Color Color { get; set; }
    public abstract void MakeMove();
    public abstract void TakeBackMove();
}

Bu şekilde Piece rengini bilebilir. Uygulamadan sonra uygulamamın aşağıdaki gibi gittiğini gördüm.

public abstract class Pawn: Piece
{
    public override void MakeMove()
    {
        if (this.Color == Color.White)
        {

        }
        else
        {

        }
    }

    public override void TakeBackMove()
    {
        if (this.Color == Color.White)
        {

        }
        else
        {

        }
    }
}

Şimdi görüyorum ki renk özelliğini uygulamalarda if ifadelerine neden oluyor. Kalıtım hiyerarşisinde belirli renk parçalarına ihtiyacımız olduğunu hissettiriyor.

Böyle bir durumda WhitePawn, BlackPawn gibi sınıflarınız olur mu veya Color özelliğini tutarak tasarıma gider misiniz?

Böyle bir problem görmeden tasarıma nasıl başlamak istersiniz? Color özelliğini mi yoksa devralma çözümünü mü kullanıyorsunuz?

Düzenleme: Örneğimin gerçek hayat örneğiyle tamamen eşleşmeyebileceğini belirtmek istiyorum. Bu yüzden uygulama detaylarını tahmin etmeden önce sadece soruyu konsantre edin.

Aslında sadece renk özelliği kullanarak ifadeler miras kullanmak daha iyi neden olur diye soruyor?


3
Neden böyle parçaları modelleyeceğinizi gerçekten anlamıyorum. MakeMove () öğesini çağırdığınızda hangi hareketi yapacak? Gerçekten başlamak için neye ihtiyacınız olduğunu söyleyebilirim, yönetim kurulu durumunu modellemek ve bunun için hangi sınıflara ihtiyacınız olduğunu görmek
jk.

11
Sanırım temel yanılgınız satrançta "renk" in oyuncunun parçadan ziyade gerçekten bir özelliği olduğunu anlamak değildir. Bu yüzden "siyahlar hareket ediyor" diyoruz, renk hangi oyuncunun parçaya sahip olduğunu tanımlamak için var. Bir piyon, hangi renk olursa olsun, hangi yönün "ileri" olduğu aynı hareket kurallarını ve kısıtlamalarını takip eder.
James Anderson

5
"ne zaman durulacağını" kalıtım olarak anlayamadığınızda, tamamen bırakmanız daha iyi olur. Hiyerarşinin sizin için aşikâr olacağı daha sonraki tasarım / uygulama / bakım aşamasında mirası yeniden sunabilirsiniz. Bu yaklaşımı kullanıyorum, bir cazibe gibi çalışıyor
gnat

1
Daha zor olan konu, her bir parça türü için bir alt sınıf oluşturulup oluşturulmayacağıdır. Parça tipi parça renginden daha fazla davranışsal yön belirler.
NoChance

3
ABC'lerle (Abstract Base Classes) bir tasarıma başlamak istiyorsanız, hepinize bir iyilik yapın ve etmeyin ... ABC'lerin gerçekten kullanışlı olduğu durumlar çok nadirdir ve o zaman bile, bir arayüz kullanmak genellikle daha yararlıdır yerine.
Evan Plaice

Yanıtlar:


12

Araç içeren bir uygulama tasarladığınızı düşünün. Böyle bir şey yaratıyor musunuz?

class BlackVehicle : Vehicle { }
class DarkBlueVehicle : Vehicle { }
class DarkGreenVehicle : Vehicle { }
// hundreds of other classes...

Gerçek hayatta renk, bir nesnenin (araba ya da satranç taşı) bir özelliğidir ve bir özellik tarafından temsil edilmelidir.

Are MakeMoveve TakeBackMovesiyahlar ve beyazlar için farklı? Hiç de değil: kurallar her iki oyuncu için de aynıdır, bu da bu iki yöntemin siyahlar ve beyazlar için tamamen aynı olacağı anlamına gelir, bu da ihtiyacınız olmayan bir koşul eklediğiniz anlamına gelir.

Öte yandan, eğer varsa WhitePawnve BlackPawnyakında veya daha sonra yazacak olursanız şaşırmayacağım:

var piece = (Pawn)this.CurrentPiece;
if (piece is WhitePawn)
{
}
else // The pice is of type BlackPawn
{
}

hatta şöyle bir şey:

public abstract class Pawn
{
    public Color Player
    {
        get
        {
            return this is WhitePawn ? Color.White : Color.Black;
        }
    }
}

// Now imagine doing the same thing for every other piece.

4
@Freshblood Bir özelliğe sahip olmak directionve bunu taşımak için color özelliğini kullanmaktan daha iyi olacağını düşünüyorum . Renk ideal olarak mantık için değil, yalnızca oluşturma için kullanılmalıdır.
Gyan aka Gary Buyn

7
Ayrıca, parçalar aynı yönde ilerler, ileri, geri, sola, sağa. Fark, tahtanın hangi tarafından başladığıdır. Bir yolda, karşı şeritteki arabalar ileri doğru hareket eder.
slipset

1
@Blrfl Tesisin directionbir boolean olması doğru olabilir . Bunda neyin yanlış olduğunu göremiyorum. Freshblood'un yukarıdaki yorumuna kadar soru hakkında beni şaşırtan şey, hareket mantığını neden renge göre değiştirmek isteyeceği. Bunun anlaşılması daha zor olduğunu söyleyebilirim çünkü rengin davranışı etkilemesi mantıklı değil. Yapmak istediğiniz tek şey, hangi oyuncunun hangi parçanın sahibi olduğunu ayırt etmek, onları iki ayrı koleksiyona koyabileceğiniz ve bir mülk veya miras ihtiyacından kaçınabilmeniz. Taşların kendileri hangi oyuncuya ait olduklarından habersiz olabilirler.
Gyan aka Gary Buyn

1
Aynı şeye iki farklı açıdan baktığımızı düşünüyorum. İki taraf kırmızı ve mavi olsaydı davranışları siyah beyaz olduğundakinden farklı olur mu? Hayır diyebilirim, bu yüzden rengin herhangi bir davranışsal farklılığın nedeni olmadığını söylüyorum. Bu sadece ince bir ayrım ama bu yüzden oyun mantığında bir yerine bir directionveya belki bir playerözellik kullanardım color.
Gyan aka Gary Buyn

1
@Freshblood white castle's moves differ from black's- nasıl? Şunu mu demek istediniz: castling Hem Kral tarafı döküm hem de Kraliçe tarafı döküm, Siyah Beyaz için% 100 simetriktir.
Konrad Morawski

4

Kendinize sormanız gereken şey, neden Pawn sınıfınızda bu ifadelere gerçekten ihtiyacınız olduğu:

    if (this.Color == Color.White)
    {

    }
    else
    {
    }

Eminim küçük bir fonksiyon setine sahip olmanın yeterli olacağından eminim

public abstract class Piece
{
    bool DoesPieceHaveSameColor(Piece otherPiece) { /*...*/   }

    Color OtherColor{get{/*...*/}}
    // ...
}

ve bu ifadeleri kullanarak, yukarıdaki ifadelerden hiçbirine sahip olmadan tüm işlevleri Pawn(ve diğer türevlerini Piece) uygulayabilirsiniz if. Tek istisna, bir Piyonun hareket yönünü rengine göre belirleme işlevi olabilir, çünkü bu piyonlara özgüdür, ancak hemen hemen tüm diğer durumlarda renge özgü bir koda ihtiyacınız yoktur.

Diğer ilginç soru, farklı parçalar için gerçekten farklı alt sınıflara sahip olmanız gerektiğidir. Bu benim için makul ve doğal görünüyor, çünkü her parça için hareket kuralları farklı ve her parça için farklı aşırı yüklenmiş fonksiyonları kullanarak bunu kesinlikle uygulayabilirsiniz.

Tabii ki, kimse onların hareket kurallarından adet özelliklerini ayırarak daha genel bir şekilde bu modellemek için deneyebilirsiniz, ancak bu soyut sınıfta sona erebilir MovingRuleve bir alt sınıf hiyerarşisi MovingRulePawn, MovingRuleQueen... Ben başlatmadım ki kolayca başka bir aşırı mühendislik şekline yol açabilir.


Renge özgü kodun büyük olasılıkla hayır olduğunu belirtmek için +1. Beyaz ve siyah piyonlar, diğer tüm parçalar gibi, aslında aynı şekilde davranırlar.
Konrad Morawski

2

Uzantı, nesnenin dış özelliklerini etkilemediğinde kalıtım o kadar yararlı olmaz .

Color özelliği, bir Piece nesnesinin nasıl kullanılacağını etkilemez . Örneğin, tüm parçalar akla uygun olarak bir moveForward veya moveBackwards yöntemini çağırabilir (taşıma yasal olmasa bile), ancak Parçanın kapsüllenmiş mantığı, ileri veya geri hareketi temsil eden mutlak değeri belirlemek için Color özelliğini kullanır (-1 veya + API'niz söz konusu olduğunda, üst sınıfınız ve alt sınıflarınız özdeş nesnelerdir (hepsi aynı yöntemleri açıkladığından).

Şahsen, her bir parça türünü temsil eden bazı sabitleri tanımlar, sonra "this" örneği için olası hareketleri döndüren özel yöntemlere dalmak için bir switch deyimi kullanırdım.


Geriye doğru hareket sadece piyonlar için yasadışıdır. Ve siyah veya beyaz olup olmadıklarını gerçekten bilmek zorunda değiller - ileri ve geri ayırt etmelerini sağlamak yeterli (ve mantıklı). Board(Örneğin) sınıfı parçasının rengine bağlı olarak -1 bu kavramların dönüştürme veya +1 sorumlu olabilir.
Konrad Morawski

0

Şahsen hiçbir şeyden miras kalmazdım Piece, çünkü bunu yapmaktan bir şey kazandığını sanmıyorum. Muhtemelen bir parça nasıl hareket ettiğini umursamaz , sadece hangi kareler bir sonraki hamlesi için geçerli bir varış noktasıdır.

Belki de bir parça türü için mevcut hareket modellerini bilen ve geçerli bir hedef kareler listesi alabilen Geçerli bir Hareket Hesap Makinesi oluşturabilirsiniz.

interface IMoveCalculator
{
    List<Square> GetValidDestinations();
}

class KnightMoveCalculator : IMoveCalculator;
class QueenMoveCalculator : IMoveCalculator;
class PawnMoveCalculator : IMoveCalculator; 
// etc.

class Piece
{
    IMoveCalculator move_calculator;
public:
    Piece(IMoveCalculator mc) : move_calculator(mc) {}
}

Ama hangi hamle hangi hareket hesap makinesi alır kim belirler? Eğer bir parça taban sınıfınız ve PawnPiece'niz varsa, bu varsayımı yapabilirsiniz. Ama sonra bu oranda, orijinal soruda tasarlandığı gibi, parçanın kendisi nasıl hareket ettiğini uygulayabilir
Steven Striga

@WeekendWarrior - "Şahsen ben hiçbir şey miras almazdım Piece". Parça, sadece hareketleri hesaplamaktan daha fazla sorumludur, bu da hareketi ayrı bir endişe olarak bölmenin sebebidir. Hangi parçanın hangi hesap makinesinin bağımlılık enjeksiyonu meselesi olacağını var my_knight = new Piece(new KnightMoveCalculator())
belirlediğini belirleme

Bu, sinek siklet modeli için iyi bir aday olacaktır. Bir satranç tahtasında 16 piyon vardır ve hepsi aynı davranışı paylaşır . Bu davranış kolayca tek bir sinek ağırlığına çıkarılabilir. Aynı şey diğer tüm parçalar için de geçerli.
MattDavey

-2

Bu cevapta, "satranç motoru" ile, sorunun yazarı "satranç AI" anlamına geldiğini varsayıyorum, sadece bir hareket doğrulama motoru değil.

Parçalar ve geçerli hareketleri için OOP kullanmanın iki nedenden dolayı kötü bir fikir olduğunu düşünüyorum:

  1. Bir satranç motorunun gücü o panoları işlemek mesela görebilirsiniz ne kadar hızlı son derece bağlıdır satranç programı kurulu temsillerini . Bu makalede anlatılan optimizasyon düzeyi göz önüne alındığında, düzenli bir işlev çağrısının uygun olduğunu düşünmüyorum. Sanal bir yönteme yapılan çağrı, bir miktar dolaylılık ekler ve daha da yüksek bir performans cezasına neden olur. OOP maliyeti orada uygun değil.
  2. Genişletilebilirlik gibi OOP'nin faydaları, satranç için yakın zamanda yeni parçalar elde edemeyeceği için parçalar için hiçbir faydası yoktur. Kalıtım tabanlı tür hiyerarşisine uygun bir alternatif, numaralandırma ve anahtar kullanmayı içerir. Çok düşük teknoloji ve C benzeri, ancak performans açısından yenilmesi zor.

Tip hiyerarşisindeki parçanın rengi de dahil olmak üzere performansla ilgili hususlardan uzaklaşmak bence kötü bir fikir. Kendinizi, örneğin çekim için iki parçanın farklı renklere sahip olup olmadığını kontrol etmeniz gereken durumlarda bulacaksınız. Bu durumun kontrolü bir özellik kullanılarak kolayca yapılabilir. is WhitePiece-Testler kullanarak bunu yapmak daha çirkin olurdu.

Ayrıca, hamle jenerasyonunun parçaya özgü metotlara geçmekten kaçınmanın başka bir pratik nedeni de vardır, yani farklı parçalar ortak hamleleri paylaşır, örneğin kayalar ve kraliçeler. Yinelenen kodlardan kaçınmak için, ortak kodu temel bir yöntemde çarpanlarına ayırmanız ve başka bir pahalı çağrı yapmanız gerekir.

Bununla birlikte, eğer yazdığınız ilk satranç motoru ise, kesinlikle temiz programlama ilkelerine gitmenizi ve Crafty'nin yazarı tarafından açıklanan optimizasyon numaralarından kaçınmanızı tavsiye ederim. Satranç kurallarının (döküm, promosyon, en-passant) ve karmaşık AI tekniklerinin (hash panoları, alfa-beta kesim) özellikleriyle uğraşmak yeterince zordur.


1
Satranç motorunu çok daha hızlı yazmak amaç olmayabilir.
Freshblood

5
-1. "Varsayımsal olarak bir performans sorunu haline gelebilir, bu yüzden kötü" gibi kararlar, IMHO neredeyse her zaman saf batıl inançtır. Ve kalıtım, bahsettiğiniz anlamda sadece "genişletilebilirlik" ile ilgili değildir. Farklı satranç kuralları farklı parçalar için geçerlidir ve böyle bir yöntemin farklı bir uygulaması olan her tür parça WhichMovesArePossiblemakul bir yaklaşımdır.
Doc Brown

2
Genişletilebilirliğe ihtiyacınız yoksa, sanal yöntem gönderme işleminden daha hızlı olduğu için switch / enum tabanlı bir yaklaşım tercih edilir. Bir satranç motoru CPU ağırdır ve en sık yapılan işlemler (ve dolayısıyla verimlilik açısından kritik önem taşır) hareketler gerçekleştirir ve bunları geri alır. Sadece bir seviye üstündeki tüm olası hareketleri listeler. Bunun da zaman açısından kritik olacağını umuyorum. Gerçek şu ki, C'de satranç motorları programladım. OOP olmasa bile, tahta sunumundaki düşük seviyeli optimizasyonlar önemliydi. Benimkini reddetmek için ne tür bir deneyiminiz var?
Joh

2
@Joh: Deneyiminize saygısızlık etmeyeceğim, ama OP'nin peşinde olmadığından eminim. Düşük düzeyli optimizasyonlar çeşitli sorunlar için önemli olsa da, bunları yüksek düzeyli tasarım kararları için bir temel haline getirmeyi bir hata olarak görüyorum - özellikle şu ana kadar herhangi bir performans sorunuyla karşılaşılmadığında. Deneyimlerim, Knuth'un "Erken optimizasyon tüm kötülüğün kökü olduğunu" söylerken çok haklı olduğudur.
Doc Brown

1
-1 Doc ile aynı fikirdeyim. Gerçek şu ki, OP'nin bahsettiği bu "motor" sadece 2 oyunculu bir satranç oyunu için doğrulama motoru olabilir. Bu durumda, OOP'nin diğer herhangi bir stile karşı performansını düşünmek tamamen gülünçtür, basit anların doğrulanması, düşük uçlu bir ARM cihazında bile milisaniye sürebilir. Gereksinimleri, aslında belirtilenden daha fazla okumayın.
Timothy Baldridge
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.