ควรหยุดการสืบทอดเมื่อใด


9

กาลครั้งหนึ่งนานมานี้ฉันถามคำถามเกี่ยวกับการโอเวอร์โฟลว์เกี่ยวกับการสืบทอด

ฉันได้กล่าวว่าฉันออกแบบเครื่องมือหมากรุกในแบบ OOP ดังนั้นฉันจึงรับช่วงต่อของฉันทั้งหมดจากระดับนามธรรมของ Piece แต่การสืบทอดยังคงดำเนินต่อไป ให้ฉันแสดงด้วยรหัส

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

public abstract class Pawn: Piece {}

public class WhitePawn :Pawn {}

public class BlackPawn:Pawn {}

โปรแกรมเมอร์พบว่าการออกแบบของฉันมีความเหนือกว่าทางวิศวกรรมเล็กน้อยและแนะนำให้เอาคลาสสีที่มีสีออกมา

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

ด้วยวิธีนี้ Piece สามารถรู้สีของเขาได้ หลังจากการนำไปใช้ฉันได้เห็นการดำเนินการของฉันเป็นไปดังนี้

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
        {

        }
    }
}

ตอนนี้ฉันเห็นแล้วว่าให้คุณสมบัติของสีทำให้เกิดข้อความสั่งในการนำไปใช้งาน มันทำให้ฉันรู้สึกว่าเราต้องการชิ้นสีเฉพาะในลำดับชั้นการสืบทอด

ในกรณีเช่นนี้คุณจะมีคลาสเช่น WhitePawn, BlackPawn หรือคุณจะไปที่การออกแบบโดยการรักษาคุณสมบัติของสี?

หากไม่เห็นปัญหาดังกล่าวคุณจะเริ่มออกแบบได้อย่างไร รักษาคุณสมบัติสีหรือมีวิธีแก้ปัญหาการสืบทอดอยู่หรือไม่

แก้ไข:ฉันต้องการระบุว่าตัวอย่างของฉันอาจไม่ตรงกับตัวอย่างชีวิตจริงทั้งหมด ดังนั้นก่อนที่จะพยายามเดารายละเอียดการใช้งานก็แค่ตั้งคำถาม

ฉันแค่ถามว่าการใช้คุณสมบัติสีจะทำให้เกิดถ้างบดีกว่าที่จะใช้การสืบทอด?


3
ฉันไม่เข้าใจจริง ๆ ว่าทำไมคุณถึงสร้างแบบจำลองเช่นนี้ขึ้นมา เมื่อใดที่คุณเรียก MakeMove () มันจะย้ายไปไหน? ฉันจะบอกว่าสิ่งที่คุณต้องการเริ่มต้นด้วยคือการสร้างแบบจำลองสถานะของกระดานและดูว่าคุณต้องการเรียนวิชาอะไร
เจเค

11
ฉันคิดว่าความเข้าใจผิดขั้นพื้นฐานของคุณคือการไม่ตระหนักว่าในหมากรุก "สี" เป็นคุณลักษณะของผู้เล่นมากกว่าชิ้น นั่นเป็นเหตุผลว่าทำไมเราถึงพูดว่า "คนผิวดำเคลื่อนไหว" ฯลฯ การระบายสีมีไว้เพื่อระบุผู้เล่นคนไหน ผู้จำนำปฏิบัติตามกฎและข้อ จำกัด ในการเคลื่อนไหวเดียวกันไม่ว่าสีจะเป็นเพียงรูปแบบใดทิศทางเดียวคือ "ไปข้างหน้า"
James Anderson

5
เมื่อคุณไม่สามารถหาการสืบทอด "เมื่อใดที่จะหยุด" ให้ปล่อยมันให้ดีกว่า คุณสามารถแนะนำการสืบทอดในขั้นตอนการออกแบบ / การนำไปใช้ / บำรุงรักษาในภายหลังได้เมื่อลำดับชั้นจะชัดเจนสำหรับคุณ ฉันใช้วิธีการนี้ใช้งานได้อย่างมีเสน่ห์
ริ้น

1
ปัญหาที่ท้าทายมากขึ้นคือการสร้างคลาสย่อยสำหรับแต่ละชิ้นส่วนหรือไม่ ประเภทชิ้นกำหนดลักษณะพฤติกรรมมากกว่าสีชิ้น
NoChance

3
หากคุณถูกล่อลวงให้เริ่มออกแบบด้วย ABCs (Abstract Base Classes) พวกเราทุกคนชอบและไม่ ... กรณีที่ ABCs นั้นมีประโยชน์จริง ๆ นั้นหายากมากและถึงอย่างนั้นก็มักจะมีประโยชน์มากกว่าที่จะใช้อินเทอร์เฟซ แทน.
Evan Plaice

คำตอบ:


12

ลองนึกภาพคุณออกแบบโปรแกรมที่มียานพาหนะ คุณสร้างบางสิ่งเช่นนี้หรือไม่?

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

ในชีวิตจริงสีเป็นสมบัติของวัตถุ (ไม่ว่าจะเป็นรถยนต์หรือชิ้นหมากรุก) และจะต้องเป็นตัวแทนของทรัพย์สิน

มีMakeMoveและTakeBackMoveแตกต่างกันสำหรับคนผิวดำและคนผิวขาว? ไม่ใช่เลย: กฎเหมือนกันสำหรับผู้เล่นทั้งสองซึ่งหมายความว่าทั้งสองวิธีจะเหมือนกันสำหรับคนผิวดำและคนผิวขาวซึ่งหมายความว่าคุณกำลังเพิ่มเงื่อนไขที่คุณไม่ต้องการ

ในทางกลับกันถ้าคุณมีWhitePawnและBlackPawnฉันจะไม่แปลกใจถ้าไม่ช้าก็เร็วคุณจะเขียน:

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

หรือแม้กระทั่งสิ่งที่ชอบ:

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 ฉันคิดว่ามันจะดีกว่าถ้ามีdirectionคุณสมบัติและใช้มันเพื่อย้ายไปกว่าการใช้คุณสมบัติสี สีควรใช้เพื่อการเรนเดอร์ไม่ใช่สำหรับตรรกะ
Gyan aka Gary Buyn

7
นอกจากนี้ชิ้นส่วนจะเคลื่อนที่ไปในทิศทางเดียวกันไปข้างหน้าไปข้างหลังถอยหลังซ้ายขวา ความแตกต่างมาจากด้านใดของบอร์ดที่เริ่ม บนถนนรถยนต์ที่อยู่ฝั่งตรงข้ามก็เดินไปข้างหน้า
slipset

1
@Blrfl คุณอาจพูดถูกว่าdirectionทรัพย์สินเป็นบูลีน ฉันไม่เห็นว่ามีอะไรผิดปกติ สิ่งที่ทำให้ฉันสับสนเกี่ยวกับคำถามจนถึงความคิดเห็นของ Freshblood ด้านบนคือสาเหตุที่เขาต้องการเปลี่ยนตรรกะการเคลื่อนไหวตามสี ฉันจะบอกว่ามันยากที่จะเข้าใจเพราะมันไม่มีความหมายต่อพฤติกรรมของสีต่อเอฟเฟกต์ หากสิ่งที่คุณต้องการทำคือแยกแยะว่าผู้เล่นคนใดเป็นเจ้าของชิ้นส่วนใดที่คุณสามารถใส่ไว้ในคอลเล็กชั่นสองชุดแยกกันและหลีกเลี่ยงความต้องการทรัพย์สินหรือมรดก ชิ้นส่วนของตัวเองอาจไม่รู้สุขสันต์ซึ่งผู้เล่นที่พวกเขาอยู่
Gyan aka Gary Buyn

1
ฉันคิดว่าเรากำลังมองสิ่งเดียวกันจากสองมุมมองที่แตกต่างกัน หากทั้งสองฝ่ายเป็นสีแดงและสีน้ำเงินพฤติกรรมของพวกเขาจะแตกต่างกันเมื่อพวกเขาเป็นสีดำและสีขาว? ฉันจะบอกว่าไม่ซึ่งเป็นเหตุผลที่ฉันบอกว่าสีไม่ใช่สาเหตุของความแตกต่างด้านพฤติกรรม มันเป็นเพียงความแตกต่างที่ละเอียดอ่อน แต่นั่นคือเหตุผลที่ฉันจะใช้directionหรืออาจเป็นplayerทรัพย์สินแทนที่จะเป็นcolorหนึ่งในตรรกะของเกม
Gyan aka Gary Buyn

1
@Freshblood white castle's moves differ from black's- อย่างไร คุณหมายถึง castling หรือเปล่า ทั้งแบบหล่อแบบ King-side และ Queen-castling นั้นมีความสมมาตร 100% สำหรับภาพขาวดำ
Konrad Morawski

4

สิ่งที่คุณควรถามตัวเองคือเหตุผลที่คุณต้องการข้อความเหล่านี้ในชั้นเรียนจำนำของคุณ:

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

    }
    else
    {
    }

ฉันค่อนข้างแน่ใจว่ามันจะเพียงพอที่จะมีฟังก์ชั่นชุดเล็ก ๆ

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

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

ในคลาส Piece ของคุณและใช้ฟังก์ชั่นทั้งหมดในPawn(และการสืบทอดอื่น ๆ ของPiece) โดยใช้ฟังก์ชั่นเหล่านั้นโดยไม่ต้องมีข้อความใด ๆifข้างต้น ข้อยกเว้นเท่านั้นอาจเป็นฟังก์ชั่นในการกำหนดทิศทางการย้ายสำหรับการจำนำด้วยสีของมันเนื่องจากเป็นแบบเฉพาะสำหรับการจำนำ แต่ในกรณีอื่นเกือบทั้งหมดคุณไม่จำเป็นต้องใช้รหัสเฉพาะสีใด ๆ

คำถามที่น่าสนใจอื่น ๆ คือถ้าคุณควรมีคลาสย่อยที่แตกต่างกันสำหรับชิ้นส่วนที่แตกต่างกันเลย มันดูสมเหตุสมผลและเป็นธรรมชาติสำหรับฉันเนื่องจากกฎสำหรับการเคลื่อนไหวของแต่ละชิ้นนั้นแตกต่างกันและคุณสามารถใช้มันได้อย่างแน่นอนโดยใช้ฟังก์ชั่นโอเวอร์โหลดที่แตกต่างกันสำหรับชิ้นส่วนแต่ละประเภท

แน่นอนหนึ่งได้พยายามที่จะจำลองนี้ในทางทั่วไปมากขึ้นโดยการแยกคุณสมบัติของชิ้นส่วนจากกฎการเคลื่อนย้ายของพวกเขา แต่ตอนนี้อาจจะจบในระดับนามธรรมMovingRuleและลำดับชั้นประเภทรองMovingRulePawn, MovingRuleQueen... ฉันจะไม่เริ่มต้นที่ วิธีเนื่องจากมันสามารถนำไปสู่รูปแบบอื่นของวิศวกรรมที่เกินได้


+1 สำหรับการชี้ให้เห็นว่ารหัสเฉพาะสีน่าจะไม่มี การจำนำสีขาวและสีดำนั้นมีพฤติกรรมเหมือนกันเหมือนกับชิ้นอื่น ๆ ทั้งหมด
Konrad Morawski

2

มรดกไม่เป็นประโยชน์เมื่อขยายไม่ได้ส่งผลกระทบต่อวัตถุความสามารถภายนอก

สถานที่ให้บริการสีไม่ได้ส่งผลกระทบต่อวิธีการที่วัตถุชิ้นจะถูกนำมาใช้ ตัวอย่างเช่นชิ้นส่วนทั้งหมดสามารถเรียกใช้เมธอด moveForward หรือ moveBackwards (แม้ว่าการย้ายจะไม่ถูกกฎหมาย) แต่ตรรกะที่ห่อหุ้มของ Piece ใช้คุณสมบัติ Color เพื่อกำหนดค่าสัมบูรณ์ที่แสดงถึงการเคลื่อนที่ไปข้างหน้าหรือข้างหลัง (-1 หรือ + 1 บน "แกน y") ตราบใดที่ API ของคุณเกี่ยวข้องซูเปอร์คลาสและคลาสย่อยของคุณเป็นวัตถุที่เหมือนกัน (เนื่องจากพวกเขาทั้งหมดเปิดเผยวิธีการเดียวกัน)

โดยส่วนตัวแล้วฉันจะกำหนดค่าคงที่บางอย่างที่เป็นตัวแทนของแต่ละประเภทของชิ้นส่วนจากนั้นใช้คำสั่งสลับเพื่อแยกเป็นวิธีส่วนตัวที่คืนค่าการย้ายที่เป็นไปได้สำหรับอินสแตนซ์ "นี้"


การเคลื่อนไหวถอยหลังเป็นสิ่งผิดกฎหมายในกรณีที่จำนำ และพวกเขาไม่จำเป็นต้องรู้ว่าพวกเขาเป็นสีขาวหรือดำ - มันเพียงพอ (และตรรกะ) ที่จะทำให้พวกเขาแยกแยะความแตกต่างไปข้างหน้าและข้างหลัง Boardระดับ (ตัวอย่าง) อาจจะอยู่ในความดูแลของการแปลงแนวคิดเหล่านี้เป็น -1 หรือ +1 ขึ้นอยู่กับสีของชิ้น
Konrad Morawski

0

โดยส่วนตัวแล้วฉันจะไม่รับสิ่งใดเลยPieceเพราะฉันไม่คิดว่าคุณจะได้อะไรจากการทำเช่นนั้น สมมุติว่าชิ้นส่วนหนึ่งไม่สนใจว่ามันเคลื่อนไหวเพียงใดสี่เหลี่ยมเพียงชิ้นเดียวคือปลายทางที่ถูกต้องสำหรับการเคลื่อนที่ครั้งต่อไป

บางทีคุณสามารถสร้างเครื่องคิดเลขการย้ายที่ถูกต้องซึ่งรู้รูปแบบการเคลื่อนไหวที่มีอยู่สำหรับชิ้นส่วนและสามารถดึงรายการของสแควร์ปลายทางที่ถูกต้องตัวอย่างเช่น

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) {}
}

แต่ใครเป็นผู้ตัดสินว่าชิ้นส่วนใดที่ทำให้เครื่องคิดเลขเคลื่อนที่ได้? หากคุณมีคลาสเบสเป็นชิ้น ๆ และมี PawnPiece คุณสามารถสันนิษฐานได้ แต่ด้วยอัตราดังกล่าวชิ้นงานเองก็สามารถนำไปใช้งานได้ตามที่ได้รับการออกแบบในคำถามเดิม
Steven Striga

@WeekendWarrior - "โดยส่วนตัวแล้วฉันจะไม่รับมรดกเลยPiece" ดูเหมือนว่า Piece จะรับผิดชอบมากกว่าเพียงแค่การคำนวณการเคลื่อนไหวซึ่งเป็นเหตุผลในการแยกการเคลื่อนไหวออกเป็นข้อกังวลแยกต่างหาก การพิจารณาว่าชิ้นส่วนใดที่ได้รับเครื่องคิดเลขซึ่งจะเป็นเรื่องของการฉีดพึ่งพาเช่นvar my_knight = new Piece(new KnightMoveCalculator())
Ben Cottrell

นี่จะเป็นตัวเลือกที่ดีสำหรับรูปแบบฟลายเวท มี 16 เบี้ยบนกระดานหมากรุกและพวกเขาร่วมกันทั้งหมดเดียวกันพฤติกรรม พฤติกรรมนี้สามารถดึงออกมาเป็นฟลายเวทเดียวได้อย่างง่ายดาย เช่นเดียวกันกับชิ้นส่วนอื่น ๆ ทั้งหมด
MattDavey

-2

ในคำตอบนี้ฉันสมมติว่าโดย "เครื่องมือหมากรุก" ผู้เขียนคำถามหมายถึง "หมากรุก AI" ไม่ใช่เครื่องมือตรวจสอบความเคลื่อนไหว

ฉันคิดว่าการใช้ OOP เป็นชิ้น ๆ และการเคลื่อนไหวที่ถูกต้องเป็นความคิดที่ไม่ดีด้วยเหตุผลสองประการ:

  1. ความแรงของเครื่องยนต์หมากรุกสูงขึ้นอยู่กับวิธีการที่รวดเร็วที่จะสามารถจัดการกับผ้าดูเช่นหมากรุกเป็นตัวแทนคณะกรรมการโครงการ เมื่อพิจารณาถึงระดับของการปรับให้เหมาะสมที่บทความนี้อธิบายไว้ฉันไม่คิดว่าการเรียกใช้ฟังก์ชั่นทั่วไปนั้นมีราคาไม่แพง การเรียกใช้เมธอดเสมือนจะเพิ่มระดับของการอ้อมและทำให้เกิดการลงโทษประสิทธิภาพที่สูงขึ้น ค่าใช้จ่ายของ OOP นั้นไม่แพง
  2. ประโยชน์ของ OOP เช่นความสามารถในการขยายนั้นไม่มีประโยชน์สำหรับชิ้นส่วนเนื่องจากหมากรุกไม่น่าจะมีชิ้นส่วนใหม่ ๆ ในเวลาไม่นาน ทางเลือกที่ทำงานได้กับลำดับชั้นของชนิดอิงตามการสืบทอดรวมถึงการใช้ enums และสวิตช์ เทคโนโลยีต่ำมากและเหมือน C แต่ยากที่จะเอาชนะประสิทธิภาพฉลาด

การย้ายออกจากการพิจารณาเกี่ยวกับประสิทธิภาพการทำงานรวมถึงสีของชิ้นส่วนในลำดับชั้นของประเภทเป็นความคิดที่ไม่ดีของฉัน คุณจะพบว่าตัวเองอยู่ในสถานการณ์ที่คุณต้องตรวจสอบว่ามีสองชิ้นที่มีสีแตกต่างกันหรือไม่ตัวอย่างเช่นการจับภาพ การตรวจสอบสภาพนี้ทำได้ง่าย ๆ โดยใช้คุณสมบัติ การทำโดยใช้การis WhitePieceทดสอบจะเป็นสิ่งที่ไม่ดีในการเปรียบเทียบ

นอกจากนี้ยังมีเหตุผลในทางปฏิบัติอีกประการหนึ่งสำหรับการหลีกเลี่ยงการเคลื่อนย้ายการสร้างการเคลื่อนไหวไปยังวิธีการเฉพาะสำหรับชิ้นงานนั่นคือชิ้นส่วนที่แตกต่างกันมีการเคลื่อนไหวร่วมกันเช่น rooks และ queens เพื่อหลีกเลี่ยงรหัสที่ซ้ำกันคุณจะต้องแยกแยะรหัสทั่วไปให้เป็นวิธีการพื้นฐานและยังมีการโทรที่มีค่าใช้จ่ายสูง

ถ้าหากมันเป็นเอ็นจิ้นหมากรุกตัวแรกที่คุณเขียนฉันจะแนะนำให้ใช้หลักการเขียนโปรแกรมที่สะอาดและหลีกเลี่ยงเทคนิคการปรับให้เหมาะสมที่อธิบายโดยผู้เขียน Crafty การจัดการกับกฎเฉพาะของหมากรุก (การขว้างการส่งเสริมการเล่นการพนัน) และเทคนิค AI ที่ซับซ้อน (กระดานแฮชบอร์ดการตัดอัลฟ่า - เบต้า) นั้นท้าทายพอ


1
มันอาจไม่ได้มีวัตถุประสงค์เพื่อเขียนโปรแกรมหมากรุกเร็วเกินไป
Freshblood

5
-1 การตัดสินเช่น "สมมุติฐานว่ามันอาจกลายเป็นปัญหาด้านประสิทธิภาพได้ดังนั้นมันจึงไม่ดี" เป็น IMHO ที่มีความเชื่อโชคลางล้วนๆ และการรับมรดกไม่ได้เกี่ยวกับ "ความสามารถในการขยาย" ในแง่ที่คุณพูดถึง กฎหมากรุกที่แตกต่างกันนำไปใช้กับชิ้นส่วนที่แตกต่างกันและแต่ละประเภทมีการใช้งานที่แตกต่างกันของวิธีการที่ชอบWhichMovesArePossibleเป็นวิธีการที่เหมาะสม
หมอสีน้ำตาล

2
หากคุณไม่ต้องการความสามารถในการขยายความสามารถวิธีการที่ใช้สวิตช์ / enum จะดีกว่าเพราะเร็วกว่าการส่งเมธอดเสมือน เอ็นจิ้นหมากรุกเป็นซีพียูที่หนักและการดำเนินการที่กระทำบ่อยที่สุด (และดังนั้นประสิทธิภาพที่สำคัญ) กำลังทำการเคลื่อนย้ายและเลิกทำ เพียงหนึ่งระดับด้านบนที่มาพร้อมกับรายการการเคลื่อนไหวที่เป็นไปได้ทั้งหมด ฉันคาดหวังว่านี่จะเป็นช่วงเวลาที่สำคัญเช่นกัน ความจริงก็คือฉันได้ตั้งโปรแกรมหมากรุกในซีถึงแม้ว่าจะไม่มี OOP การเพิ่มประสิทธิภาพในระดับต่ำในการเป็นตัวแทนของบอร์ดก็มีความสำคัญ คุณมีประสบการณ์แบบไหนในการเลิกทำเหมือง?
Joh

2
@Joh: ฉันจะไม่ดูถูกประสบการณ์ของคุณ แต่ฉันค่อนข้างแน่ใจว่านั่นไม่ใช่สิ่งที่ OP ทำหลังจากนั้น และถึงแม้ว่าการเพิ่มประสิทธิภาพในระดับต่ำอาจมีความสำคัญสำหรับปัญหาต่าง ๆ แต่ฉันคิดว่ามันเป็นความผิดพลาดที่จะทำให้พวกเขาเป็นพื้นฐานสำหรับการตัดสินใจในการออกแบบระดับสูงโดยเฉพาะเมื่อไม่มีปัญหาด้านประสิทธิภาพเลย ประสบการณ์ของฉันคือ Knuth นั้นถูกต้องมากเมื่อพูดว่า "การปรับให้เหมาะสมก่อนกำหนดเป็นรากฐานของความชั่วร้ายทั้งหมด"
Doc Brown

1
-1 ฉันต้องเห็นด้วยกับหมอ ความจริงก็คือ "เอ็นจิ้น" นี้ OP กำลังพูดถึงอาจเป็นเครื่องมือตรวจสอบสำหรับเกมหมากรุกผู้เล่น 2 คน ในกรณีนี้การคิดเกี่ยวกับประสิทธิภาพของ OOP เทียบกับรูปแบบอื่นใดน่าหัวเราะอย่างสมบูรณ์การตรวจสอบความถูกต้องง่าย ๆ ของเวลาจะใช้เวลาเป็นมิลลิวินาทีแม้บนอุปกรณ์ ARM ที่ต่ำสุด อย่าอ่านข้อกำหนดเพิ่มเติมมากกว่าที่ระบุไว้จริง
Timothy Baldridge
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.