คลาสขนาดเล็กจำนวนมากเทียบกับการสืบทอดแบบลอจิคัล (แต่) ที่ซับซ้อน


24

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

Class A
{
  String name;
  string description;
}

Class B
{
  String name;
  String count;
  String description;
}

Class C
{
  String name;
  String count;
  String description;
  String imageUrl;
}

Class D
{
  String name;
  String count;
}

Class E
{
  String name;
  String count;
  String imageUrl;
  String age;
}

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

โดยใช้การสืบทอดคุณจะต้องจบลงด้วยสิ่งนี้ แต่ชื่อคลาสจะสูญเสียความหมายตามบริบท (เนื่องจาก is-a, not-a-a):

Class A
{
  String name;
  String description;
}

Class B : A
{
  String count;
}

Class C : B
{
  String imageUrl;
}

Class D : C
{
  String age;
}

ฉันรู้ว่าการสืบทอดไม่ได้และไม่ควรใช้สำหรับการนำโค้ดกลับมาใช้ใหม่ แต่ฉันไม่เห็นวิธีที่เป็นไปได้อื่น ๆ ในการลดการทำซ้ำรหัสในกรณีเช่นนี้

คำตอบ:


58

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

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


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

20
@ user969153: เมื่อชั้นเรียนจะคงที่อย่างแท้จริงมันไม่สำคัญ วิศวกรรมซอฟต์แวร์ที่ดีทั้งหมดมีไว้สำหรับผู้ดูแลในอนาคตที่ต้องการเปลี่ยนแปลง หากจะไม่มีการเปลี่ยนแปลงในอนาคตทุกอย่างดำเนินไป แต่เชื่อฉันคุณจะมีการเปลี่ยนแปลงเสมอเว้นแต่คุณจะตั้งโปรแกรมสำหรับถังขยะ
thiton

@ user969153: นั่นถูกกล่าวว่า "เหตุผลของการเปลี่ยนแปลง" คือการแก้ปัญหา ช่วยให้คุณตัดสินใจไม่มีอะไรมาก
thiton

2
คำตอบที่ดี. เหตุผลในการเปลี่ยนแปลงคือ S ในตัวย่อ SOLID ของลุงบ็อบซึ่งจะช่วยในการออกแบบซอฟต์แวร์ที่ดี
Klee

4
@ user969153 จากประสบการณ์ของฉัน "ที่เรียนเหล่านี้อยู่ที่ไหนและจะคงที่เสมอ?" ไม่ใช่กรณีการใช้งานที่ถูกต้อง :) ฉันยังไม่ได้ทำงานกับแอปพลิเคชันขนาดที่มีความหมายซึ่งไม่พบการเปลี่ยนแปลงที่ไม่คาดคิดทั้งหมด
cdk เลิก

18

โดยใช้การสืบทอดคุณจะต้องจบลงด้วยสิ่งนี้ แต่ชื่อคลาสจะสูญเสียความหมายตามบริบท (เนื่องจาก is-a, not-a-a):

เผง ดูที่นี่จากบริบท:

Class D : C
{
    String age;
}

D มีคุณสมบัติใด คุณจำได้ไหม? เกิดอะไรขึ้นถ้าคุณมีความซับซ้อนลำดับชั้นเพิ่มเติม:

Class E : B
{
    int numberOfWheels;
}

Class F : E
{
    String favouriteColour;
}

แล้วถ้าหากสยองขวัญกับความน่ากลัวคุณต้องการเพิ่มฟิลด์ imageUrl ลงใน F แต่ไม่ใช่ E? คุณไม่สามารถหาได้จาก C และ E

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

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

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

ฉันไม่สามารถพูดสิ่งนี้ได้มากพอ: นี่ไม่ใช่ความคิดที่ดี

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

มันเกิดขึ้นในที่สุดเราก็มีส่วนประกอบโดยไม่มีคำอธิบาย ดังนั้นเราจึงเริ่มมีคำอธิบายที่ว่างในวัตถุเหล่านั้น ไม่เป็นโมฆะ โมฆะ เสมอ. สำหรับผลิตภัณฑ์ประเภท X ... หรือใหม่กว่า Y ... และ N

นี่เป็นการละเมิดหลักการแทนที่ Liskov อย่างมหันต์

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

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


4

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

หากคุณต้องการที่จะใส่โครงสร้างที่แตกต่างกันในส่วนต่าง ๆ ของพฤติกรรมของคุณแล้วพิจารณาการเชื่อมต่อกับวิธีการรับ / การตั้งค่าหรือคุณสมบัติเช่น IHasName, IHasAge ฯลฯ ซึ่งจะทำให้การออกแบบที่สะอาดมากขึ้นและช่วยให้องค์ประกอบเหล่านี้ดีขึ้น ประเภทของ hiearchies


3

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

IName = Interface(IInterface)
  function Getname : String;
  property Name : String read Getname
end;

Class Dog(InterfacedObject, IName)
private
  FName : String;
  Function GetName : String;
Public
  Name : Read Getname;
end;

Class Movie(InterfacedObject, IName)
private
  FCount;
  FName : String;
  Function GetName : String;
Public
  Name : String Read Getname;
  Count : read FCount; 
end;

1

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

นี่คือตัวอย่างของวิธีการนี้สามารถแก้ไขได้โดยใช้การสืบทอดหลายแบบ

trait Nameable { String name; }
trait Describable { String description; }
trait Countable { Integer count; }
trait HasImageUrl { String imageUrl; }
trait Living { String age; }

class A : Nameable, Describable;
class B : Nameable, Describable, Countable;
class C : Nameable, Describable, Countable, HasImageUrl;
class D : Nameable, Countable;
class E : Nameable, Countable, HasImageUrl, Living;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.