การออกแบบที่ดีคืออะไรเพื่อหลีกเลี่ยงการขอประเภทรอง


11

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

abstract class Shape {
  ShapeType getType();
  bool collide(Shape other);
}

class Circle : Shape {
  getType() { return Type.Circle; }

  bool collide(Shape other) {
    if(other.getType() == Type.Rect) {
      collideCircleRect(this, (Rect) other);     
    } else if(other.getType() == Type.Polygon) {
      collideCirclePolygon(this, (Polygon) other);
    }
  }
}

นี่เป็นรูปแบบการออกแบบที่ไม่ดีเหรอ? ฉันจะแก้ปัญหานี้โดยไม่ต้องอนุมานประเภทย่อยของคลาสได้อย่างไร


1
คุณจะพบว่าทุกอินสแตนซ์เช่น Circle รู้รูปร่างทุกประเภท ดังนั้นพวกเขาทั้งหมดเชื่อมต่อที่มั่นคงอย่างใด และทันทีที่คุณเพิ่มรูปร่างใหม่เช่นสามเหลี่ยมคุณจะเพิ่มการสนับสนุนรูปสามเหลี่ยมทุกที่ มันขึ้นอยู่กับสิ่งที่คุณต้องการเปลี่ยนแปลงบ่อยขึ้นคุณจะเพิ่มรูปร่างใหม่หรือไม่การออกแบบนี้ไม่ดี เนื่องจากคุณมีวิธีแก้ไขปัญหาอย่างกว้างขวาง - การสนับสนุนรูปสามเหลี่ยมของคุณต้องเพิ่มทุกที่ คุณควรแยก Collisiondetection ของคุณออกเป็น Class แยกต่างหากซึ่งสามารถทำงานกับทุกประเภทและผู้รับมอบสิทธิ์
thepacker


IMO จะมีข้อกำหนดเกี่ยวกับประสิทธิภาพ ยิ่งรหัสเฉพาะเจาะจงมากเท่าไรก็ยิ่งสามารถทำงานได้มากขึ้นเท่านั้น ในกรณีนี้โดยเฉพาะ (ดำเนินการมันเกินไป) การตรวจสอบสำหรับประเภทก็โอเคเพราะการปรับแต่งการตรวจสอบการชนกันสามารถenourmouslyเร็วกว่าวิธีการแก้ปัญหาทั่วไป แต่เมื่อประสิทธิภาพของรันไทม์ไม่สำคัญฉันจะใช้วิธีการทั่วไป / polymorphic เสมอ
marstato

ขอบคุณทุกคนในกรณีที่ประสิทธิภาพของฉันมีความสำคัญและฉันจะไม่เพิ่มรูปร่างใหม่บางทีฉันอาจจะใช้วิธีการ CollisionDetection ทำรูปร่างหรือทำอินสแตนซ์ "ของ" กับ Shape ในคลาส CollisionDetection แทนหรือไม่
Alejandro

1
ไม่มีขั้นตอนการชนที่มีประสิทธิภาพระหว่างShapeวัตถุนามธรรม ตรรกะของคุณขึ้นอยู่กับ internals ของวัตถุอื่นเว้นแต่ว่าคุณกำลังตรวจสอบการชนกันของจุดขอบเขตbool collide(x, y)(ส่วนย่อยของจุดควบคุมอาจเป็นการแลกเปลี่ยนที่ดี) มิฉะนั้นคุณต้องตรวจสอบประเภทอย่างใด - หากมีความจำเป็นต้องใช้ abstractions จริงๆแล้วการสร้างCollisionประเภท (สำหรับวัตถุในพื้นที่ของนักแสดงในปัจจุบัน) ควรเป็นแนวทางที่ถูกต้อง
ตัวสั่น

คำตอบ:


13

ความแตกต่าง

ตราบใดที่คุณใช้งานgetType()หรืออะไรทำนองนั้นคุณไม่ได้ใช้โพลีมอร์ฟิซึม

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

รหัสขั้นตอนรับข้อมูลแล้วทำการตัดสินใจ รหัสเชิงวัตถุบอกให้วัตถุทำสิ่งต่าง ๆ
- อเล็กซ์ชาร์ป

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

encapsulation

คุณสามารถบอกฉันว่าไม่จำเป็นต้องมีรูปทรงอื่น แต่ฉันไม่เชื่อคุณและไม่ควรทำ

เอฟเฟกต์ที่ดีของการห่อหุ้มต่อไปนี้คือมันง่ายที่จะเพิ่มประเภทใหม่เพราะรายละเอียดของมันจะไม่กระจายไปในโค้ดที่แสดงifและswitchลอจิก รหัสสำหรับประเภทใหม่ทั้งหมดควรอยู่ในที่เดียว

ระบบตรวจจับการชนชนิดไม่รู้งมงาย

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

ป้อนคำอธิบายรูปภาพที่นี่

สมมติว่าคุณควรจะวาดมัน ดูเหมือนง่าย มันคือวงกลมทั้งหมด มันเป็นการสร้างคลาสวงกลมที่เข้าใจการชน ปัญหาคือสิ่งนี้จะส่งแนวความคิดที่แยกออกจากกันเมื่อเราต้องการ 1,000 วงกลม

เราไม่ควรคิดถึงวงกลม เราควรคิดถึงพิกเซล

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

ป้อนคำอธิบายรูปภาพที่นี่

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

ภาพนี้คุณไม่ต้องแสดงต่อผู้ใช้ คุณสร้างมันด้วยรหัสเดียวกันกับที่วาดขึ้นมาก่อน เพียงแค่มีสีที่ต่างกัน

เมื่อผู้ใช้คลิกที่วงกลมฉันรู้ว่าวงกลมใดเพราะสีเพียงวงกลมเดียว

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

ชนิดใหม่: สี่เหลี่ยม

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

ไม่มีความรู้ในแวดวงรั่วไหลเข้าสู่ระบบตรวจจับ ไม่สนใจรัศมีเส้นรอบวงหรือจุดศูนย์กลาง มันใส่ใจเกี่ยวกับพิกเซลและสี

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

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

ตัวเลือกการนำไปใช้

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

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

การจัดส่งสองครั้ง

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

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

อย่างไรก็ตามมีการชนกันหลายประเภท การเก็งกำไรเล็กน้อย (สิ่งที่อันตราย) ประดิษฐ์การชนแบบยืดหยุ่น (เด้ง), ไม่ยืดหยุ่น (เหนียว), มีพลัง (อธิบาย) และทำลาย (damagy) อาจมีมากกว่านี้ แต่ถ้านี่น้อยกว่า n 2 จะช่วยให้ไม่สามารถออกแบบการชนของเรา

ซึ่งหมายความว่าเมื่อตอร์ปิโดของฉันปะทะกับสิ่งที่ยอมรับความเสียหายมันไม่จำเป็นต้องรู้ว่ามันโจมตียานอวกาศ มันต้องบอกว่า "ฮ่าฮ่า! คุณได้รับความเสียหาย 5 คะแนน"

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

ยานอวกาศสามารถส่งกลับไปที่ torp "Ha ha! คุณได้รับความเสียหาย 100 คะแนน" เช่นเดียวกับ "ตอนนี้คุณติดลำเรือของฉัน" และ torp สามารถส่งกลับ "ดีฉันทำเพื่อลืมเกี่ยวกับฉัน"

ไม่มีประเด็นใดที่จะรู้ว่าแต่ละสิ่งนั้นคืออะไร พวกเขารู้วิธีพูดคุยกันผ่านส่วนต่อประสานการชน

ตอนนี้แน่ใจว่าการจัดส่งสองครั้งช่วยให้คุณสามารถควบคุมสิ่งต่าง ๆ ได้มากกว่านี้ แต่คุณต้องการสิ่งนั้นจริงหรือ

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

ประสิทธิภาพ

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


ขอให้เรายังคงอภิปรายนี้ในการแชท
candied_orange

+1 สำหรับ"คุณสามารถบอกฉันได้ว่าจะไม่ต้องการรูปทรงอื่น ๆ แต่ฉันไม่เชื่อคุณและไม่ควรทำเช่นนั้น"
Tulains Córdova

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

7

คำอธิบายของปัญหาดูเหมือนว่าคุณควรใช้Multimethods (aka การจัดส่งหลาย) ในกรณีนี้โดยเฉพาะ - การจัดส่งสองครั้ง คำตอบแรกมีความยาวเกี่ยวกับวิธีจัดการกับรูปร่างที่ชนกันในการเรนเดอร์แบบแรนด์ แต่ฉันเชื่อว่า OP ต้องการคำตอบ "เวกเตอร์" หรืออาจเป็นปัญหาทั้งหมดที่ได้รับการปรับรูปแบบในรูปทรง

แม้บทความ wikipedia อ้างว่าใช้คำเปรียบเทียบการชนกันให้ฉันแค่อ้างอิง (Python ไม่ได้มีหลายวิธีในตัวเช่นภาษาอื่น ๆ ):

@multimethod(Asteroid, Asteroid)
def collide(a, b):
    """Behavior when asteroid hits asteroid"""
    # ...define new behavior...
@multimethod(Asteroid, Spaceship)
def collide(a, b):
    """Behavior when asteroid hits spaceship"""
    # ...define new behavior...
# ... define other multimethod rules ...

ดังนั้นคำถามต่อไปคือวิธีการรับการสนับสนุนหลายวิธีในภาษาการเขียนโปรแกรมของคุณ



ใช่กรณีพิเศษของ Multipethod aka หรือ Multimethods ได้ถูกเพิ่มเข้าไปในคำตอบ
Roman Susi

5

ปัญหานี้ต้องมีการออกแบบใหม่ในสองระดับ

ก่อนอื่นคุณต้องลบตรรกะเพื่อตรวจจับการชนกันของรูปร่างต่าง ๆ นี่คือดังนั้นคุณจะไม่ละเมิดOCPทุกครั้งที่คุณต้องการเพิ่มรูปร่างใหม่ให้กับโมเดล ลองนึกภาพคุณได้กำหนด Circle, Square และสี่เหลี่ยมผืนผ้าแล้ว จากนั้นคุณสามารถทำสิ่งนี้:

class ShapeCollisionDetector
{
    public void DetectCollisionCircleCircle(Circle firstCircle, Circle secondCircle)
    { 
        //Code that detects collision between two circles
    }

    public void DetectCollisionCircleSquare(Circle circle, Square square)
    {
        //Code that detects collision between circle and square
    }

    public void DetectCollisionCircleRectangle(Circle circle, Rectangle rectangle)
    {
        //Code that detects collision between circle and rectangle
    }

    public void DetectCollisionSquareSquare(Square firstSquare, Square secondSquare)
    {
        //Code that detects collision between two squares
    }

    public void DetectCollisionSquareRectangle(Square square, Rectangle rectangle)
    {
        //Code that detects collision between square and rectangle
    }

    public void DetectCollisionRectangleRectangle(Rectangle firstRectangle, Rectangle secondRectangle)
    { 
        //Code that detects collision between two rectangles
    }
}

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

    interface IShape
{
    void DetectCollision(IShape shape);
    void Accept (ShapeVisitor visitor);
}

ต่อไปเราต้องมีคลาสผู้เยี่ยมชมหลัก:

    abstract class ShapeVisitor
{
    protected ShapeCollisionDetector collisionDetector = new ShapeCollisionDetector();

    abstract public void VisitCircle (Circle circle);

    abstract public void VisitSquare(Square square);

    abstract public void VisitRectangle(Rectangle rectangle);

}

ฉันกำลังใช้คลาสที่นี่แทนอินเทอร์เฟซเนื่องจากฉันต้องการให้ผู้เยี่ยมชมแต่ละออบเจ็กต์มีคุณลักษณะเป็นShapeCollisionDetectorประเภท

การใช้IShapeอินเทอร์เฟซทุกครั้งจะยกตัวอย่างผู้เยี่ยมชมที่เหมาะสมและเรียกใช้Acceptวิธีการที่เหมาะสมของวัตถุที่วัตถุที่เรียกใช้โต้ตอบเช่นนี้

    class Circle : IShape
{
    public void DetectCollision(IShape shape)
    {
        CircleVisitor visitor = new CircleVisitor(this);
        shape.Accept(visitor);
    }

    public void Accept(ShapeVisitor visitor)
    {
        visitor.VisitCircle(this);
    }
}

    class Rectangle : IShape
{
    public void DetectCollision(IShape shape)
    {
        RectangleVisitor visitor = new RectangleVisitor(this);
        shape.Accept(visitor);
    }

    public void Accept(ShapeVisitor visitor)
    {
        visitor.VisitRectangle(this);
    }
}

และผู้เข้าชมที่เฉพาะเจาะจงจะมีลักษณะเช่นนี้:

    class CircleVisitor : ShapeVisitor
{
    private Circle Circle { get; set; }

    public CircleVisitor(Circle circle)
    {
        this.Circle = circle;
    }

    public override void VisitCircle(Circle circle)
    {
        collisionDetector.DetectCollisionCircleCircle(Circle, circle);
    }

    public override void VisitSquare(Square square)
    {
        collisionDetector.DetectCollisionCircleSquare(Circle, square);
    }

    public override void VisitRectangle(Rectangle rectangle)
    {
        collisionDetector.DetectCollisionCircleRectangle(Circle, rectangle);
    }
}

    class RectangleVisitor : ShapeVisitor
{
    private Rectangle Rectangle { get; set; }

    public RectangleVisitor(Rectangle rectangle)
    {
        this.Rectangle = rectangle;
    }

    public override void VisitCircle(Circle circle)
    {
        collisionDetector.DetectCollisionCircleRectangle(circle, Rectangle);
    }

    public override void VisitSquare(Square square)
    {
        collisionDetector.DetectCollisionSquareRectangle(square, Rectangle);
    }

    public override void VisitRectangle(Rectangle rectangle)
    {
        collisionDetector.DetectCollisionRectangleRectangle(Rectangle, rectangle);
    }
}

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

ข้อเสียเปรียบของโซลูชันนี้คือถ้าคุณเพิ่มรูปร่างใหม่คุณต้องขยายคลาส ShapeVisitor ด้วยวิธีสำหรับรูปร่างนั้น (เช่นVisitTriangle(Triangle triangle)) และดังนั้นคุณจะต้องใช้วิธีนั้นกับผู้เข้าชมรายอื่นทั้งหมด อย่างไรก็ตามเนื่องจากนี่เป็นส่วนขยายในแง่ที่ว่าไม่มีการเปลี่ยนแปลงวิธีการที่มีอยู่ แต่มีการเพิ่มวิธีการใหม่เท่านั้นจึงไม่เป็นการละเมิดOCPและรหัสค่าใช้จ่ายมีน้อย นอกจากนี้โดยใช้คลาสShapeCollisionDetectorคุณหลีกเลี่ยงการละเมิดSRPและคุณหลีกเลี่ยงรหัสซ้ำซ้อน


5

ปัญหาพื้นฐานของคุณคือในภาษาการเขียนโปรแกรม OO ที่ทันสมัยที่สุดฟังก์ชั่นการทำงานหนักเกินพิกัดไม่ทำงานกับการเชื่อมโยงแบบไดนามิก สิ่งที่คุณต้องการคือการเรียกใช้เมธอดเสมือนที่เสมือนบนวัตถุสองชิ้นแทนที่จะเป็นเพียงวัตถุเดียว วิธีการดังกล่าวจะเรียกว่าหลายวิธี อย่างไรก็ตามมีวิธีที่จะเลียนแบบพฤติกรรมนี้ในภาษาเช่น Java, C ++ เป็นต้นนี่คือจุดที่การกระจายสองครั้งมีประโยชน์มาก

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

abstract class Shape {
  bool collide(Shape other);
  bool collide(Rect other);
  bool collide(Circle other);
}

class Circle : Shape {

  bool collide(Shape other) {
    return other.collide(this);
  }

  bool collide(Rect other) {
    // algorithm to detect collision between Circle and Rect
  }

  // ...
}

class Rect : Shape {

  bool collide(Shape other) {
    return other.collide(this);
  }

  bool collide(Circle other) {
    // algorithm to detect collision between Circle and Rect
  }

  // ...
}

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


2

บางทีนี่อาจไม่ใช่วิธีที่ดีที่สุดในการแก้ไขปัญหานี้

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

กลยุทธ์ผู้ประกอบการเกินพิกัด

หากคุณไม่สามารถทำให้ปัญหาพื้นฐานทางคณิตศาสตร์ได้ง่ายขึ้นฉันจะแนะนำวิธีการใช้ตัวดำเนินการโอเวอร์โหลดอีกครั้ง สิ่งที่ต้องการ:

 public final class ShapeOp 
 {
     static { ... }

     public static boolean collision( Shape s1, Shape s2 )  { ... }
     public static boolean collision( Point p1, Point p2 ) { ... }
     public static boolean collision( Point p1, Square s1 ) { ... }
     public static boolean collision( Point p1, Circle c1 ) { ... }
     public static boolean collision( Point p1, Line l1 ) { ... }
     public static boolean collision( Square s1, Point p2 ) { ... }
     public static boolean collision( Square s1, Square s2 ) { ... }
     public static boolean collision( Square s1, Circle c1 ) { ... }
     public static boolean collision( Square s1, Line l1 ) { ... }
     (...)

ในเครื่องกำจัดไฟฟ้าสถิตแบบคงที่ฉันจะใช้การสะท้อนเพื่อสร้างแผนที่ของวิธีการที่จะใช้การกระจายแบบไดนามิคในการชนทั่วไป (Shape s1, Shape s2) เครื่องมือ Intializer แบบสแตติกสามารถมีตรรกะในการตรวจสอบฟังก์ชันการชนที่หายไปและรายงานพวกเขาปฏิเสธที่จะโหลดคลาส

นี่เป็นชนิดที่คล้ายกับโอเวอร์โหลดของตัวดำเนินการ C ++ ในการโอเวอร์โหลดของผู้ให้บริการ C ++ นั้นสับสนมากเพราะคุณมีชุดสัญลักษณ์ที่แน่นอนซึ่งคุณสามารถโอเวอร์โหลดได้ อย่างไรก็ตามแนวคิดนั้นน่าสนใจมากและสามารถทำซ้ำได้ด้วยฟังก์ชั่นคงที่

เหตุผลที่ฉันจะใช้วิธีนี้คือการชนนั้นไม่ใช่การดำเนินการกับวัตถุ การชนกันคือการดำเนินการจากภายนอกที่กล่าวถึงความสัมพันธ์บางอย่างเกี่ยวกับวัตถุโดยพลการสองอย่าง นอกจากนี้ initializer คงที่จะสามารถตรวจสอบได้ว่าฉันพลาดฟังก์ชั่นการชนกันหรือไม่

ลดความซับซ้อนของปัญหาคณิตศาสตร์ของคุณถ้าเป็นไปได้

ดังที่ฉันได้กล่าวถึงจำนวนฟังก์ชั่นการชนกันคือกำลังสองของจำนวนชนิดรูปร่าง ซึ่งหมายความว่าในระบบที่มีรูปทรงเพียง 20 รูปคุณจะต้องมี 400 รูทีนโดยมี 21 รูปร่าง 441 เป็นต้น สิ่งนี้ไม่สามารถขยายได้อย่างง่ายดาย

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

ดัก

คุณสังเกตเห็นแพคเกจ 3 มิติและเกมส่วนใหญ่วิเคราะห์ทุกสิ่งหรือไม่ นั่นคือรูปแบบหนึ่งของการทำให้คณิตศาสตร์ง่ายขึ้น สิ่งนี้นำไปใช้กับรูปร่าง 2d เช่นกัน โพลีสสามารถวิเคราะห์ได้ วงกลมและเส้นโค้งสามารถประมาณให้กับโพลิกอน

อีกครั้ง ... คุณจะมีฟังก์ชั่นการชนกันครั้งเดียว ชั้นเรียนของคุณกลายเป็น:

public class Shape 
{
    public Triangle[] triangulate();
}

และการดำเนินงานของคุณ:

public final class ShapeOp
{
    public static boolean collision( Triangle[] shape1, Triangle[] shape2 )
}

เรียบง่ายใช่ไหม

Rasterize

คุณสามารถ rasterize รูปร่างของคุณเพื่อให้มีฟังก์ชันการชนกันครั้งเดียว

Rasterization อาจเป็นวิธีการแก้ปัญหาที่รุนแรง แต่อาจมีราคาไม่แพงและรวดเร็วขึ้นอยู่กับความแม่นยำของรูปร่างของคุณ หากไม่จำเป็นต้องแม่นยำ (เหมือนในเกม) คุณอาจมีบิตแมปความละเอียดต่ำ แอปพลิเคชั่นส่วนใหญ่ไม่ต้องการความแม่นยำแน่นอนในวิชาคณิตศาสตร์

การประมาณอาจดีพอ ตัวอย่างซูเปอร์คอมพิวเตอร์ของ ANTON สำหรับการจำลองทางชีววิทยา คณิตศาสตร์ของมันทิ้งผลกระทบเชิงควอนตัมจำนวนมากซึ่งยากต่อการคำนวณและจนถึงตอนนี้การจำลองที่เกิดขึ้นนั้นสอดคล้องกับการทดลองที่เกิดขึ้นในโลกแห่งความจริง โมเดลคอมพิวเตอร์กราฟิก PBR ที่ใช้ในเกมเอ็นจิ้นและแพ็คเกจการเรนเดอร์ทำให้การลดความซับซ้อนนั้นง่ายขึ้นซึ่งจะช่วยลดพลังงานของคอมพิวเตอร์ที่จำเป็นในการแสดงผลแต่ละเฟรม จริง ๆ แล้วไม่แม่นยำ phisically แต่อยู่ใกล้พอที่จะเชื่อด้วยตาเปล่า

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.