วิธีถกเถียงกับแนวคิด "สาธารณะอย่างสมบูรณ์" ของการออกแบบคลาสอ็อบเจ็กต์ทางธุรกิจ


9

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

ตัวอย่างคลาสที่ฉันไม่ใช่แฟนของ:

public class Foo
{
    private string field1;
    private string field2;
    private string field3;
    private string field4;
    private string field5;

    public Foo() { }

    public Foo(string in1, string in2)
    {
        field1 = in1;
        field2 = in2;
    }

    public Foo(string in1, string in2, string in3, string in4)
    {
        field1 = in1;
        field2 = in2;
        field3 = in3;
    }

    public Prop1
    { get { return field1; } }
    { set { field1 = value; } }

    public Prop2
    { get { return field2; } }
    { set { field2 = value; } }

    public Prop3
    { get { return field3; } }
    { set { field3 = value; } }

    public Prop4
    { get { return field4; } }
    { set { field4 = value; } }

    public Prop5
    { get { return field5; } }
    { set { field5 = value; } }

}

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

ฉันเกลียดชั้นเรียนนี้และฉันไม่รู้ว่าฉันแค่จู้จี้จุกจิก สิ่งที่ควรทราบ:

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

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

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

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



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

1
การพูดว่า "เราอาจไม่มีข้อมูลที่จำเป็นในการตั้งค่า Prop5 ในขณะที่สร้างวัตถุ" ทำให้ฉันถาม "คุณกำลังบอกว่าจะมีบางครั้งที่คุณจะมีข้อมูลนั้นในเวลาของการก่อสร้างและเวลาอื่นที่คุณไม่ต้องการ มีข้อมูลเหรอ? " มันทำให้ฉันสงสัยว่าจริง ๆ แล้วมีสองสิ่งที่แตกต่างกันที่คลาสนี้พยายามแสดงหรือบางทีคลาสนี้ควรแบ่งเป็นคลาสที่เล็กกว่า แต่ถ้ามันทำ "ในกรณี" มันก็แย่ IMO; ที่บอกกับฉันว่าไม่มีการออกแบบที่ชัดเจนสำหรับวิธีการสร้าง / บรรจุวัตถุ
ศิษย์ของ Dr. Wily

3
สำหรับเขตข้อมูลสำรองส่วนตัวที่ไม่มีตรรกะในคุณสมบัติมีเหตุผลที่คุณไม่ได้ใช้คุณสมบัติอัตโนมัติหรือไม่
Carson63000

2
@Kritner จุดรวมของคุณสมบัติอัตโนมัติคือถ้าคุณต้องการตรรกะจริง ๆ ในอนาคตคุณสามารถเพิ่มมันเข้าไปแทนที่รถยนต์getหรือset:-) ได้อย่าง
ราบรื่น

คำตอบ:


10

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

ทุกอย่างขึ้นอยู่กับประเภทของสิ่งที่เป็นนามธรรมที่คุณกำลังจะทำโมเดลกับพวกเขาเลเยอร์ที่คุณมีในระบบของคุณระดับของการห่อหุ้มที่คุณต้องการในเลเยอร์ที่แตกต่างกันและ (แน่นอน) สิ่งที่โรงเรียนแห่งความคิด จาก. คุณสามารถค้นหาทุกประเภทเหล่านี้ในรหัส SOLID

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

ในการพูดถึงประเด็นอื่น ๆ ของคุณ:

  • "เขตข้อมูลสำรองส่วนตัวที่ไม่มีตรรกะในคุณสมบัติ": ใช่คุณพูดถูกและไม่น่ารำคาญสำหรับเสียงเรียกเข้าเล็กน้อยและตัวตั้งค่านี่เป็นเพียง "เสียง" ที่ไม่จำเป็น เพื่อหลีกเลี่ยง "bloat" ชนิดนี้ C # มีไวยากรณ์ลัดสำหรับคุณสมบัติ get / set methods:

ดังนั้นแทนที่จะ

   private string field1;
   public string Prop1
   { get { return field1; } }
   { set { field1 = value; } }

เขียน

   public string Prop1 { get;set;}

หรือ

   public string Prop1 { get;private set;}
  • "ตัวสร้างหลายรายการ": นั่นไม่ใช่ปัญหาในตัวเอง มันจะมีปัญหาเมื่อมีการทำซ้ำรหัสที่ไม่จำเป็นในนั้นเช่นแสดงในตัวอย่างของคุณหรือลำดับชั้นการโทรมีความซับซ้อน สิ่งนี้สามารถแก้ไขได้อย่างง่ายดายโดยการ refactoring ชิ้นส่วนทั่วไปลงในฟังก์ชั่นที่แยกต่างหากและโดยการจัดระเบียบสายงานก่อสร้างในทิศทางเดียว

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

  • "มันมีคุณสมบัติมากเกินไป! (ในกรณี 30)": ใช่ถ้าคุณมีอิสระในการออกแบบชั้นเรียนในลักษณะที่เขียวขจี 30 มีมากเกินไปฉันเห็นด้วย อย่างไรก็ตามเราทุกคนไม่ได้มีความหรูหรานี้ (คุณไม่ได้เขียนความคิดเห็นด้านล่างนี้เป็นระบบเดิมหรือไม่) บางครั้งคุณต้องแมปบันทึกจากฐานข้อมูลที่มีอยู่หรือไฟล์หรือข้อมูลจาก API ของบุคคลที่สามกับระบบของคุณ ดังนั้นในกรณีเหล่านี้แอตทริบิวต์ 30 รายการอาจเป็นสิ่งที่เราต้องอยู่ด้วย


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

@Kritner: ดูการแก้ไขของฉัน
Doc Brown

โดยปกติเมื่อฉันสร้างคลาสใหม่เอี่ยมฉันไปตามเส้นทางของอสังหาริมทรัพย์อัตโนมัติ มีเขตข้อมูลสำรองส่วนตัว "เพียงเพราะ" เป็น (ในใจของฉัน) ไม่จำเป็นและยังทำให้เกิดปัญหา เมื่อคุณมีคุณสมบัติ ~ 30 คลาสและ 100+ คลาสไม่ไกลที่จะบอกว่าจะมีการตั้งค่าคุณสมบัติบางอย่างหรือได้รับจากเขตข้อมูลสำรองที่ไม่ถูกต้อง ... ฉันได้พบกับการรีแฟคเตอร์ของเราหลายครั้งแล้ว :)
Kritner

ขอบคุณค่าเริ่มต้นสำหรับสตริงnullมันคืออะไร? ดังนั้นหากหนึ่งในคุณสมบัติที่ใช้จริงใน a .ToUpper()แต่ไม่มีการตั้งค่าให้มันจะทำให้เกิดข้อยกเว้นรันไทม์ นี้เป็นอีกหนึ่งตัวอย่างที่ดีว่าทำไมต้องข้อมูลสำหรับการเรียนควรมีที่จะตั้งในระหว่างการก่อสร้างวัตถุ ไม่เพียงปล่อยให้ผู้ใช้เป็นผู้ใช้ ขอบคุณ
Kritner

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

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

  2. ดูเหมือนว่าจะไม่เกิดปัญหาหลายตัวสร้างที่นี่

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

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

  5. คุณมีคุณสมบัติที่ถูกต้องมากเกินไปคุณสามารถลองใช้การสืบทอด (ถ้าคุณสมบัติคล้ายกันมากพอ) แล้วมีรายการ / อาร์เรย์โดยใช้ข้อมูลทั่วไป? จากนั้นคุณสามารถเขียน getters / setters เพื่อเข้าถึงข้อมูลและทำให้วิธีการที่คุณโต้ตอบกับคุณสมบัติทั้งหมดนั้นง่าย


1
ขอบคุณ - # 1 และ # 4 ฉันรู้สึกสะดวกสบายที่สุดในการโต้แย้ง ไม่แน่ใจว่าคุณหมายถึงอะไรใน # 3 # 5 คุณสมบัติส่วนใหญ่ในคลาสประกอบด้วยคีย์หลักบน db เราใช้คีย์การแข่งขันบางครั้งถึง 8 คอลัมน์ - แต่นั่นเป็นปัญหาอื่น ฉันกำลังคิดเกี่ยวกับการพยายามวางส่วนต่าง ๆ ที่ประกอบขึ้นเป็นกุญแจลงในคลาส / โครงสร้างของตัวเองซึ่งจะกำจัดคุณสมบัติจำนวนมาก (อย่างน้อยในชั้นเรียนนี้ ) - แต่นี่เป็นแอปพลิเคชันรุ่นเก่าที่มีโค้ดหลายพันบรรทัด โทรติดต่อ ดังนั้นผมคิดว่าผมมีจำนวนมากที่จะคิดเกี่ยวกับ :)
Kritner

เอ๊ะ ถ้าคลาสนั้นไม่เปลี่ยนรูปก็ไม่มีเหตุผลที่จะเขียนรหัสการประสานใด ๆ ภายในชั้นเรียน
RubberDuck

@RubberDuck คลาสนี้ไม่เปลี่ยนรูปหรือไม่ คุณหมายถึงอะไร
นูป

3
มันไม่แน่นอนอย่างแน่นอน@StevieV คลาสใด ๆ ที่มี setters คุณสมบัติจะไม่แน่นอน
RubberDuck

1
@Kritner คุณสมบัติใช้เป็นคีย์หลักได้อย่างไร สันนิษฐานว่าต้องการตรรกะบางอย่าง (การตรวจสอบโมฆะ) หรือกฎ (เช่น NAME ให้ความสำคัญมากกว่าอายุ) ตาม OOP รหัสนี้อยู่ในชั้นนี้ คุณสามารถใช้สิ่งนี้เป็นข้อโต้แย้งได้หรือไม่?
user949300

2

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

ในตัวอย่างที่คุณแสดงFooมีลักษณะเหมือนDTOและขัดต่อหลักการ OOP ทั้งหมด (ดูรุ่น Anemic Domain Model )!

เป็นการปรับปรุงที่ฉันอยากจะแนะนำ:

  • ทำให้คลาสนี้ไม่เปลี่ยนรูปมากที่สุด อย่างที่คุณพูดคุณต้องการทำการทดสอบหน่วย หนึ่งความสามารถที่จำเป็นสำหรับการทดสอบหน่วยเป็นชะตาและโชคชะตาสามารถทำได้ผ่านทางเปลี่ยนไม่ได้เพราะมันแก้ผลข้างเคียงที่เกิดปัญหา
  • แบ่งคลาสเทพเจ้านี้ออกเป็นหลายคลาสที่ทำสิ่งเดียวเท่านั้น (ดูSRP ) หน่วยทดสอบคลาสแอตทริบิวต์ 30 ในPITAจริง ๆ
  • ลบ Constructor Redundancy โดยมีConstructor หลักเพียง 1 ตัวและตัวอื่น ๆ ที่เรียก Constructor หลัก
  • ลบผู้ได้รับที่ไม่จำเป็นออกไปฉันสงสัยอย่างจริงจังว่าผู้ได้รับทั้งหมดนั้นมีประโยชน์จริงๆ
  • นำตรรกะทางธุรกิจกลับมาในชั้นเรียนธุรกิจนี่คือที่ที่มันอยู่!

นี่อาจเป็นคลาสที่มีการรีแฟรกเมนต์ที่มีข้อสังเกตเหล่านี้:

public sealed class Foo
{
    private readonly string field1;
    private readonly string field2;
    private readonly string field3;
    private readonly string field4;

    public Foo() : this("default1", "default2")
    { }

    public Foo(string in1, string in2) : this(in1, in2, "default3", "default4")
    { }

    public Foo(string in1, string in2, string in3, string in4)
    {
        field1 = in1;
        field2 = in2;
        field3 = in3;
        field4 = in4;
    }

    //Methods with business logic

    //Really needed ?
    public Prop1
    { get; }

    //Really needed ?
    public Prop2
    { get; }

    //Really needed ?
    public Prop3
    { get; }

    //Really needed ?
    public Prop4
    { get; }

    //Really needed ?
    public Prop5
    { get; }
}

2

วิธีถกเถียงกับแนวคิด "สาธารณะอย่างสมบูรณ์" ของการออกแบบคลาสอ็อบเจ็กต์ทางธุรกิจ

  • "มีหลายวิธีที่กระจัดกระจายในคลาสลูกค้า (es) ทำมากกว่า 'เพียงหน้าที่ DTO' พวกเขาอยู่ด้วยกัน"
  • "อาจเป็น 'DTO' แต่มีตัวตนของธุรกิจ - เราต้องแทนที่ Equals"
  • "เราจำเป็นต้องจัดเรียงสิ่งเหล่านี้ - เราจำเป็นต้องใช้ IComparable"
  • "เรากำลังรวมคุณสมบัติหลายอย่างเข้าด้วยกันเรามาแทนที่ ToString เพื่อให้ลูกค้าทุกคนไม่ต้องเขียนโค้ดนี้"
  • "เรามีลูกค้าหลายรายที่ทำสิ่งเดียวกันกับคลาสนี้เราต้อง DRY โคด"
  • "ลูกค้ากำลังจัดการกับสิ่งต่าง ๆ เหล่านี้เห็นได้ชัดว่ามันควรจะเป็นคลาสคอลเลกชันที่กำหนดเองและหลาย ๆ ประเด็นก่อนหน้านี้จะทำให้คอลเลกชันที่กำหนดเองนั้นทำงานได้มากกว่าในปัจจุบัน"
  • "ดูที่การจัดการสายอักขระที่น่าเบื่อสัมผัสและเปิดเผยทั้งหมด! การห่อหุ้มในวิธีการที่เกี่ยวข้องกับธุรกิจจะเพิ่มประสิทธิภาพการเข้ารหัสของเรา"
  • "การดึงวิธีการเหล่านี้เข้าด้วยกันในชั้นเรียนจะทำให้สามารถทดสอบได้เพราะเราไม่ต้องจัดการกับคลาสไคลเอนต์ที่ซับซ้อนมากขึ้น"
  • "ขณะนี้เราสามารถทดสอบวิธีการปรับโครงสร้างเหล่านั้นใหม่ได้เนื่องจากเป็นเหตุผลที่ x, y, z ลูกค้าไม่สามารถทดสอบได้

  • เท่าที่มีข้อโต้แย้งใด ๆ ข้างต้นสามารถทำได้รวม:

    • ฟังก์ชันการทำงานที่จะกลายเป็นอีกครั้งที่ใช้งาน
    • มันแห้ง
    • มันแยกออกจากลูกค้า
    • การเข้ารหัสกับคลาสนั้นเร็วขึ้นและมีข้อผิดพลาดน้อยลง
  • "คลาสที่ทำได้ดีจะซ่อนสถานะและแสดงการทำงานนี่คือข้อเสนอเริ่มต้นสำหรับการออกแบบคลาส OO"
  • "ผลลัพธ์สุดท้ายทำหน้าที่ได้ดีกว่าในการแสดงโดเมนธุรกิจหน่วยงานที่แตกต่างและปฏิสัมพันธ์ที่ชัดเจน"
  • "ฟังก์ชั่น 'ค่าใช้จ่าย' นี้ (กล่าวคือไม่ใช่ข้อกำหนดด้านการใช้งานที่ชัดเจน แต่ต้องทำ) ต้องชัดเจนและปฏิบัติตามหลักการความรับผิดชอบเดี่ยว
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.