ตัวสร้างที่มีพารามิเตอร์เป็นจำนวนมากเทียบกับรูปแบบตัวสร้าง


21

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

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

คำตอบ:


25

รูปแบบของตัวสร้างไม่สามารถแก้ไข "ปัญหา" ของข้อโต้แย้งมากมาย แต่ทำไมข้อโต้แย้งมากมายจึงเป็นปัญหา?

  • พวกเขาแสดงให้เห็นระดับของคุณอาจจะทำมากเกินไป อย่างไรก็ตามมีหลายประเภทที่ประกอบด้วยสมาชิกจำนวนมากที่ไม่สามารถจัดกลุ่มอย่างสมเหตุสมผลได้
  • การทดสอบและทำความเข้าใจฟังก์ชั่นที่มีอินพุตจำนวนมากจะมีความซับซ้อนเพิ่มขึ้นแบบทวีคูณ - ตามตัวอักษร!
  • เมื่อภาษาที่ไม่ได้มีชื่อพารามิเตอร์การเรียกฟังก์ชั่นเป็นตัวเองไม่ได้จัดเก็บเอกสาร การอ่านการเรียกใช้ฟังก์ชันที่มีอาร์กิวเมนต์หลายตัวนั้นค่อนข้างยากเพราะคุณไม่รู้ว่าควรจะทำอย่างไรกับพารามิเตอร์ที่ 7 คุณจะไม่สังเกตด้วยซ้ำว่าอาร์กิวเมนต์ที่ 5 และ 6 ถูกสลับโดยไม่ตั้งใจโดยเฉพาะอย่างยิ่งถ้าคุณอยู่ในภาษาที่พิมพ์แบบไดนามิกหรือทุกอย่างเป็นสตริงหรือเมื่อพารามิเตอร์สุดท้ายเป็นtrueเหตุผลบางประการ

แกล้งทำพารามิเตอร์ที่มีชื่อ

สร้างรูปแบบที่อยู่เพียงหนึ่งในปัญหาเหล่านี้คือความกังวลการบำรุงรักษาของสายฟังก์ชั่นที่มีการขัดแย้งหลาย* ดังนั้นฟังก์ชั่นการโทรเช่น

MyClass o = new MyClass(a, b, c, d, e, f, g);

อาจกลายเป็น

MyClass o = MyClass.builder()
  .a(a).b(b).c(c).d(d).e(e).f(f).g(g)
  .build();

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

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

ในภาษาที่ง่ายต่อการกำหนดประเภทของค่าใหม่ฉันพบว่าเป็นวิธีที่ดีกว่าในการใช้microtyping / ประเภทเล็ก ๆเพื่อจำลองการตั้งชื่ออาร์กิวเมนต์ มันมีชื่อว่าดังนั้นเพราะประเภทมีขนาดเล็กมาก แต่คุณจบลงด้วยการพิมพ์มากขึ้น ;-)

MyClass o = new MyClass(
  new MyClass.A(a), new MyClass.B(b), new MyClass.C(c),
  new MyClass.D(d), new MyClass.E(e), new MyClass.F(f),
  new MyClass.G(g));

เห็นได้ชัดว่าชื่อชนิดA, B, C... ควรจะเป็นชื่อตนเอง documenting ที่แสดงให้เห็นความหมายของพารามิเตอร์ที่มักจะชื่อเดียวกับที่คุณต้องการให้ตัวแปรพารามิเตอร์ เมื่อเปรียบเทียบกับสำนวนตัวสร้างชื่อ - อาร์กิวเมนต์ - การใช้งานที่จำเป็นนั้นง่ายกว่ามากและมีโอกาสน้อยที่จะมีข้อบกพร่อง ตัวอย่างเช่น (ที่มีไวยากรณ์ Java-ish):

class MyClass {
  ...
  public static class A {
    public final int value;
    public A(int a) { value = a; }
  }
  ...
}

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

มีวิธีการทั่วไปอีกอย่างในการจำลองอาร์กิวเมนต์ที่มีชื่อ: วัตถุพารามิเตอร์นามธรรมเดียวที่ใช้ไวยากรณ์คลาสอินไลน์เพื่อเริ่มต้นฟิลด์ทั้งหมด ใน Java:

MyClass o = new MyClass(new MyClass.Arguments(){{ argA = a; argB = b; argC = c; ... }});

class MyClass {
  ...
  public static abstract class Arguments {
    public int argA;
    public String ArgB;
    ...
  }
}

อย่างไรก็ตามมันเป็นไปได้ที่จะลืมฟิลด์และนี่เป็นวิธีแก้ปัญหาเฉพาะภาษา (ฉันเคยเห็นการใช้งานใน JavaScript, C # และ C)

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

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

ใกล้ถึงปัญหารูต

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

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

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

Dependencies deps = new Dependencies(a, d);
...
MyClass o = deps.newMyClass(b, c, e, f, g);

class MyClass {
  MyClass(Dependencies deps, B b, C c, E e, F f, G g) {
    this.depA = deps.newDepA(b, c);
    this.depB = deps.newDepB(e, f);
    this.g = g;
  }
  ...
}

class Dependencies {
  private A a;
  private D d;
  public Dependencies(A a, D d) { this.a = a; this.d = d; }
  public DepA newDepA(B b, C c) { return new DepA(a, b, c); }
  public DepB newDepB(E e, F f) { return new DepB(d, e, f); }
  public MyClass newMyClass(B b, C c, E e, F f, G g) {
    return new MyClass(deps, b, c, e, f, g);
  }
}

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


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

ใน Android Studio 3.0 ชื่อพารามิเตอร์ในนวกรรมิกแสดงติดกับค่าที่ถูกส่งผ่านในการเรียกคอนสตรัค เช่น: ใหม่ A (operand1: 34, operand2: 56); operand1 และ operand2 เป็นชื่อพารามิเตอร์ในตัวสร้าง พวกมันถูกแสดงโดย IDE เพื่อให้โค้ดอ่านง่ายขึ้น ดังนั้นจึงไม่จำเป็นต้องไปที่คำนิยามเพื่อค้นหาว่าพารามิเตอร์คืออะไร
โกเมน

9

รูปแบบของตัวสร้างไม่ได้แก้ปัญหาอะไรให้คุณและไม่แก้ไขความล้มเหลวในการออกแบบ

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

ในทางกลับกันถ้าคุณมีคลาสบางที DTO แบบง่ายซึ่งคุณลักษณะบางอย่างของคลาสเป็นทางเลือกรูปแบบตัวสร้างอาจช่วยให้การสร้างวัตถุดังกล่าวง่ายขึ้น


1

แต่ละส่วนเช่นข้อมูลส่วนบุคคลข้อมูลการทำงาน (การจ้างงานแต่ละครั้ง "จำกัด ") เพื่อนแต่ละคน ฯลฯ ควรถูกแปลงเป็นวัตถุของตนเอง

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

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

รูปแบบของตัวสร้างเป็นเพียงวิธีการจับภาพรายการ (เรียงลำดับหรือไม่เรียงลำดับ) ของอาร์กิวเมนต์ที่พิมพ์ซึ่งสามารถส่งผ่านไปยังตัวสร้างจริง (หรือตัวสร้างสามารถอ่านอาร์กิวเมนต์ที่จับได้ของตัวสร้าง)

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

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

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