รูปแบบของตัวสร้างไม่สามารถแก้ไข "ปัญหา" ของข้อโต้แย้งมากมาย แต่ทำไมข้อโต้แย้งมากมายจึงเป็นปัญหา?
- พวกเขาแสดงให้เห็นระดับของคุณอาจจะทำมากเกินไป อย่างไรก็ตามมีหลายประเภทที่ประกอบด้วยสมาชิกจำนวนมากที่ไม่สามารถจัดกลุ่มอย่างสมเหตุสมผลได้
- การทดสอบและทำความเข้าใจฟังก์ชั่นที่มีอินพุตจำนวนมากจะมีความซับซ้อนเพิ่มขึ้นแบบทวีคูณ - ตามตัวอักษร!
- เมื่อภาษาที่ไม่ได้มีชื่อพารามิเตอร์การเรียกฟังก์ชั่นเป็นตัวเองไม่ได้จัดเก็บเอกสาร การอ่านการเรียกใช้ฟังก์ชันที่มีอาร์กิวเมนต์หลายตัวนั้นค่อนข้างยากเพราะคุณไม่รู้ว่าควรจะทำอย่างไรกับพารามิเตอร์ที่ 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);
}
}
ขึ้นอยู่กับแอพพลิเคชั่นนี้อาจเป็นตัวเปลี่ยนเกมซึ่งวิธีการจากโรงงานสิ้นสุดลงโดยแทบจะไม่มีข้อโต้แย้งใด ๆ เพราะผู้จัดการการอ้างอิงทั้งหมดสามารถจัดเตรียมได้ทั้งหมดหรืออาจเป็นรหัสจำนวนมากที่ทำให้การสร้างอินสแตนซ์ซับซ้อน โรงงานดังกล่าวมีประโยชน์มากกว่าสำหรับการทำแผนที่อินเตอร์เฟสกับชนิดที่เป็นรูปธรรมมากกว่าที่ใช้สำหรับการจัดการพารามิเตอร์ อย่างไรก็ตามวิธีนี้พยายามแก้ไขปัญหารูทของพารามิเตอร์มากเกินไปแทนที่จะซ่อนไว้ด้วยอินเทอร์เฟซที่ใช้งานได้อย่างคล่องแคล่ว