Pewnego razu zadałem pytanie na temat przepełnienia stosu dotyczące dziedziczenia.
Powiedziałem, że projektuję silnik szachowy w stylu OOP. Więc odziedziczyłem wszystkie moje dzieła z klasy Abstract, ale dziedziczenie wciąż trwa. Pokaż mi kod
public abstract class Piece
{
public void MakeMove();
public void TakeBackMove();
}
public abstract class Pawn: Piece {}
public class WhitePawn :Pawn {}
public class BlackPawn:Pawn {}
Programiści odkryli, że mój projekt jest nieco przerobiony inżynieryjnie i zasugerowali usunięcie klas kolorowych elementów i zachowanie koloru elementu jako elementu właściwości, jak poniżej.
public abstract class Piece
{
public Color Color { get; set; }
public abstract void MakeMove();
public abstract void TakeBackMove();
}
W ten sposób Piece mogą poznać jego kolor. Po wdrożeniu zauważyłem, że moje wdrożenie przebiega jak poniżej.
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
{
}
}
}
Teraz widzę, że zachowują właściwość koloru powodującą, że instrukcje w implementacjach. Czuję, że potrzebujemy określonych kolorów w hierarchii dziedziczenia.
W takim przypadku czy miałbyś takie klasy jak WhitePawn, BlackPawn czy też zająłby się projektowaniem, zachowując właściwość Color?
Nie widząc takiego problemu, jak chciałbyś zacząć projektowanie? Zachować właściwość Color lub mieć rozwiązanie do dziedziczenia?
Edycja: Chcę określić, że mój przykład może nie pasować całkowicie do przykładu z życia. Więc zanim spróbujesz odgadnąć szczegóły implementacji, skoncentruj się na pytaniu.
Właściwie po prostu pytam, czy użycie właściwości Kolor spowoduje, że jeśli oświadczenia lepiej użyć dziedziczenia?
Odpowiedzi:
Wyobraź sobie, że projektujesz aplikację zawierającą pojazdy. Czy tworzysz coś takiego?
W rzeczywistości kolor jest własnością obiektu (samochodu lub figury szachowej) i musi być reprezentowany przez właściwość.
Są
MakeMove
iTakeBackMove
inne dla czarnych i białych? Wcale nie: zasady są takie same dla obu graczy, co oznacza, że te dwie metody będą dokładnie takie same dla czarnych i białych , co oznacza, że dodajesz warunek, w którym go nie potrzebujesz.Z drugiej strony, jeśli masz
WhitePawn
iBlackPawn
nie będę zaskoczony, jeśli prędzej czy później napiszesz:a nawet coś takiego:
źródło
direction
właściwość i użyć jej do przeniesienia, niż użyć właściwości color. Kolor najlepiej używać wyłącznie do renderowania, a nie do logiki.direction
właściwość jest wartością logiczną. Nie rozumiem, co jest z tym nie tak. To, co pomieszało mnie w tym pytaniu aż do powyższego komentarza Freshblood, to dlaczego chciałby zmienić logikę ruchu w oparciu o kolor. Powiedziałbym, że jest to trudniejsze do zrozumienia, ponieważ nie ma sensu, aby kolor wpływał na zachowanie. Jeśli wszystko, co chcesz zrobić, to rozróżnić, który gracz jest właścicielem, możesz umieścić je w dwóch osobnych kolekcjach i uniknąć potrzeby posiadania nieruchomości lub dziedziczenia. Same elementy mogą być błogo nieświadome, do którego gracza należą.direction
możeplayer
własnościcolor
.white castle's moves differ from black's
- jak? Masz na myśli roszadę? Zarówno roszada po stronie króla, jak i roszada po stronie królowej są w 100% symetryczne dla czerni i bieli.Powinieneś zadać sobie pytanie, dlaczego tak naprawdę potrzebujesz tych oświadczeń w swojej klasie pionków:
Jestem pewien, że wystarczy mieć mały zestaw funkcji takich jak
w swojej klasie Piece i zaimplementuj wszystkie funkcje w
Pawn
(i innych pochodnychPiece
) za pomocą tych funkcji, bez żadnej z powyższychif
instrukcji. Jedynym wyjątkiem może być funkcja określająca kierunek ruchu pionka na podstawie jego koloru, ponieważ jest on specyficzny dla pionków, ale w prawie wszystkich innych przypadkach nie będziesz potrzebować żadnego kodu specyficznego dla koloru.Innym interesującym pytaniem jest to, czy naprawdę powinieneś mieć różne podklasy dla różnych rodzajów elementów. Wydaje mi się to rozsądne i naturalne, ponieważ zasady ruchów dla każdego elementu są różne i na pewno możesz to zaimplementować, używając różnych przeciążonych funkcji dla każdego rodzaju elementu.
Oczywiście, można próbować modelować to w sposób bardziej ogólny, oddzielając właściwości kawałków z ich zasadami ruchu, ale może się to skończyć w abstrakcyjnej klasy
MovingRule
i podklasy hierarchiiMovingRulePawn
,MovingRuleQueen
... nie będę go uruchomić, że sposób, ponieważ może to łatwo doprowadzić do innej formy nadmiernej inżynierii.źródło
Dziedziczenie nie jest tak przydatne, gdy rozszerzenie nie wpływa na możliwości zewnętrzne obiektu .
Właściwość Kolor nie wpływa, jak jest obiektem Kawałek używany . Na przykład wszystkie elementy mogą wywoływać metodę moveForward lub moveBackwards (nawet jeśli ruch jest niezgodny z prawem), ale logika elementu Piece wykorzystuje właściwość Color do określenia wartości bezwzględnej reprezentującej ruch do przodu lub do tyłu (-1 lub + 1 na „osi y”), jeśli chodzi o interfejs API, twoja nadklasa i podklasy są identycznymi obiektami (ponieważ wszystkie ujawniają te same metody).
Osobiście zdefiniowałbym niektóre stałe reprezentujące każdy typ elementu, a następnie użyłem instrukcji switch do rozgałęzienia się na prywatne metody, które zwracają możliwe ruchy dla „tego” wystąpienia.
źródło
Board
Klasy (na przykład), może być odpowiedzialny za przekształcenie tych pojęć +1 lub -1 w zależności od koloru w całości.Osobiście w ogóle niczego nie odziedziczyłem
Piece
, ponieważ nie sądzę, byś coś z tego zyskał. Przypuszczalnie kawałek nie obchodzi, w jaki sposób się porusza, tylko te kwadraty są ważnym miejscem docelowym dla następnego ruchu.Być może możesz utworzyć prawidłowy kalkulator ruchu, który zna dostępne wzorce ruchu dla danego rodzaju elementu i może na przykład pobrać listę prawidłowych kwadratów docelowych
źródło
Piece
”. Wydaje się, że Piece są odpowiedzialne za coś więcej niż tylko obliczanie ruchów, co jest przyczyną rozdzielenia ruchu jako odrębnej troski. Ustalenie, który kawałek dostaje, który kalkulator byłby kwestią wstrzyknięcia zależności, np.var my_knight = new Piece(new KnightMoveCalculator())
W tej odpowiedzi zakładam, że przez „silnik szachowy” autor pytania oznacza „szachową AI”, a nie tylko silnik sprawdzania poprawności ruchu.
Myślę, że używanie OOP do elementów i ich prawidłowych ruchów jest złym pomysłem z dwóch powodów:
Odejście od rozważań dotyczących wydajności, w tym koloru elementu w hierarchii typów, jest moim zdaniem złym pomysłem. Znajdziesz się w sytuacjach, w których musisz sprawdzić, czy dwa elementy mają różne kolory, na przykład do robienia zdjęć. Sprawdzanie tego warunku można łatwo wykonać za pomocą właściwości. Robienie tego przy użyciu
is WhitePiece
-testów byłoby brzydsze w porównaniu.Istnieje również inny praktyczny powód, dla którego należy unikać generowania ruchów ruchomych do metod specyficznych dla kawałków, a mianowicie, że różne pionki dzielą wspólne ruchy, np. Wieże i królowe. Aby uniknąć powielania kodu, należy rozłożyć wspólny kod na metodę podstawową i uzyskać kolejne kosztowne wywołanie.
Biorąc to pod uwagę, jeśli jest to pierwszy silnik szachowy, który piszesz, zdecydowanie radzę stosować czyste zasady programowania i unikać sztuczek optymalizacyjnych opisanych przez autora Crafty'ego. Zajmowanie się specyfiką szachowych zasad (castling, promocja, en-passant) i skomplikowanymi technikami sztucznej inteligencji (tablice skrótów, cięcie alfa-beta) jest dość trudne.
źródło
WhichMovesArePossible
jest rozsądnym podejściem.