แนวทางปฏิบัติที่ดีที่สุดในการส่งผ่านข้อโต้แย้งไปยังวิธีการ?


95

ในบางครั้งเราต้องเขียนวิธีการที่ได้รับข้อโต้แย้งมากมายตัวอย่างเช่น:

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

เมื่อฉันพบปัญหาประเภทนี้ฉันมักจะรวมข้อโต้แย้งไว้ในแผนที่

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

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

คำตอบ:


140

ในภาษา Java ที่มีประสิทธิภาพบทที่ 7 (วิธีการ) รายการ 40 (วิธีการออกแบบลายเซ็นอย่างระมัดระวัง) Bloch เขียนว่า:

มีสามเทคนิคในการตัดทอนรายการพารามิเตอร์ที่ยาวเกินไป:

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

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


"พารามิเตอร์ที่ยาวเกินไป" คืออะไร? เมื่อใดที่เราสามารถบอกได้ว่าเมธอดมีพารามิเตอร์มากเกินไป มีจำนวนเฉพาะหรือช่วงหรือไม่?
แดงม

2
@RedM ฉันมักจะคิดว่าอะไรที่มากกว่า 3 หรือ 4 พารามิเตอร์นั้น "ยาวเกินไป"
jtate

2
@jtate เป็นทางเลือกส่วนตัวหรือคุณกำลังติดตามเอกสารอย่างเป็นทางการ?
แดง M

2
@RedM ความชอบส่วนตัว :)
jtate

2
ด้วยรุ่นที่สามของ Effective Java นี่คือบทที่ 8 (วิธีการ) ข้อ 51
GarethOwen

71

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

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

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


24

หากคุณมีพารามิเตอร์ที่เป็นทางเลือกมากมายคุณสามารถสร้าง API ได้อย่างคล่องแคล่ว: แทนที่ single method ด้วย chain of method

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

การใช้การนำเข้าแบบคงที่คุณสามารถสร้าง API ภายในที่คล่องแคล่ว:

... .datesBetween(from(date1).to(date2)) ...

3
จะเป็นอย่างไรหากต้องใช้ทุกพารามิเตอร์ไม่ใช่ทางเลือก
emeraldhieu

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

13

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

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

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

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


การเก็บขยะจะมีปัญหาหรือไม่?
rupinderjeet

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

Imo คุณควรมีXXXParameter param = new XXXParameter();ให้ใช้งานแล้วใช้XXXParameter.setObjA(objA); ฯลฯ ...
satibel

10

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


8

สิ่งนี้มักเป็นปัญหาเมื่อสร้างวัตถุ

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

คุณยังสามารถปรับให้เข้ากับการเรียกใช้เมธอดได้อีกด้วย

นอกจากนี้ยังเพิ่มความสามารถในการอ่านได้มาก

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

คุณยังสามารถใส่ตรรกะการตรวจสอบลงใน Builder set .. () และ build () วิธีการ


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

7

มีรูปแบบที่เรียกว่าเป็นวัตถุพารามิเตอร์

แนวคิดคือการใช้ออบเจ็กต์เดียวแทนพารามิเตอร์ทั้งหมด ตอนนี้แม้ว่าคุณจะต้องเพิ่มพารามิเตอร์ในภายหลังคุณก็ต้องเพิ่มลงในออบเจ็กต์ อินเทอร์เฟซวิธีการยังคงเหมือนเดิม


5

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


ฉันไม่คิดว่าจำเป็นต้องสร้างคลาสเพื่อเก็บพารามิเตอร์เมธอด
Sawyer

ฉันจะสร้างคลาสก็ต่อเมื่อมีการส่งผ่านพารามิเตอร์เดียวกันหลายอินสแตนซ์ สิ่งนี้จะส่งสัญญาณว่าพารามิเตอร์มีความสัมพันธ์กันและอาจอยู่ร่วมกันได้ หากคุณสร้างคลาสสำหรับวิธีการเดียวการรักษาอาจเลวร้ายยิ่งกว่าโรค
tvanfosson

ได้ - คุณสามารถย้ายพารามิเตอร์ที่เกี่ยวข้องไปยัง DTO หรือ Value Object ได้ พารามิเตอร์บางตัวเลือกได้หรือไม่เช่นเมธอดหลักมีพารามิเตอร์เพิ่มเติมเหล่านี้มากเกินไปหรือไม่ ในกรณีเช่นนี้ - ฉันรู้สึกว่าเป็นเรื่องที่ยอมรับได้
JoseK

นั่นคือสิ่งที่ฉันหมายถึงโดยการพูดต้องมีความหมายเพียงพอ
Johannes Rudolph

4

Code Complete * แนะนำสองสิ่ง:

  • "จำกัด จำนวนพารามิเตอร์ของกิจวัตรไว้ที่ประมาณเจ็ดตัวเซเว่นเป็นตัวเลขมหัศจรรย์สำหรับความเข้าใจของผู้คน" (น. 108)
  • "ใส่พารามิเตอร์ในลำดับอินพุต - แก้ไข - เอาต์พุต ... ถ้ารูทีนหลายตัวใช้พารามิเตอร์ที่คล้ายกัน
  • ใส่สถานะหรือตัวแปรข้อผิดพลาดล่าสุด
  • ในฐานะที่เป็นtvanfossonกล่าวผ่านเฉพาะส่วนของตัวแปรโครงสร้าง (วัตถุ) ที่ตอบสนองความต้องการประจำ ที่กล่าวว่าหากคุณกำลังใช้ตัวแปรที่มีโครงสร้างส่วนใหญ่ในฟังก์ชันให้ส่งผ่านโครงสร้างทั้งหมด แต่โปรดทราบว่าสิ่งนี้ส่งเสริมการมีเพศสัมพันธ์ในระดับหนึ่ง

* First Edition ฉันรู้ว่าควรอัปเดต นอกจากนี้ยังมีแนวโน้มว่าคำแนะนำบางส่วนอาจมีการเปลี่ยนแปลงเนื่องจากฉบับที่สองเขียนขึ้นเมื่อ OOP เริ่มเป็นที่นิยมมากขึ้น


2

แนวทางปฏิบัติที่ดีคือการ refactor แล้ววัตถุเหล่านี้หมายความว่าอย่างไรที่ควรส่งต่อไปยังวิธีนี้ ควรห่อหุ้มเป็นวัตถุชิ้นเดียวหรือไม่?


ใช่พวกเขาควร ตัวอย่างเช่นรูปแบบการค้นหาขนาดใหญ่มีข้อ จำกัด และความต้องการที่ไม่เกี่ยวข้องจำนวนมากสำหรับการแบ่งหน้า คุณต้องผ่าน currentPageNumber, searchCriteria, pageSize ...
Sawyer

2

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

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

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

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


1

สร้างคลาส bean และตั้งค่าพารามิเตอร์ทั้งหมด (วิธี setter) และส่งผ่านวัตถุ bean นี้ไปยังเมธอด


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

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

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


0

นี่มักเป็นข้อบ่งชี้ว่าชั้นเรียนของคุณมีความรับผิดชอบมากกว่าหนึ่งอย่าง (เช่นชั้นเรียนของคุณทำมากเกินไป)

ดูหลักการความรับผิดชอบเดียว

สำหรับรายละเอียดเพิ่มเติม


0

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


0

ฉันจะบอกว่ายึดติดกับวิธีที่คุณเคยทำมาก่อน จำนวนพารามิเตอร์ในตัวอย่างของคุณไม่มากนัก แต่ทางเลือกอื่นน่ากลัวกว่ามาก

  1. แผนที่ - มีประสิทธิภาพที่คุณพูดถึง แต่ปัญหาใหญ่กว่านี้คือ:

    • ผู้โทรไม่รู้ว่าจะส่งอะไรถึงคุณโดยไม่ได้อ้างถึงสิ่ง
      อื่น ... คุณมี javadocs ที่ระบุว่าใช้คีย์และ
      ค่าอะไรหรือไม่? หากคุณทำ (ซึ่งดีมาก) การมีพารามิเตอร์จำนวนมากก็ไม่ใช่ปัญหาเช่นกัน
    • กลายเป็นเรื่องยากมากที่จะยอมรับอาร์กิวเมนต์ประเภทต่างๆ คุณสามารถ จำกัด พารามิเตอร์อินพุตเป็นประเภทเดียวหรือใช้ Map <String, Object> และส่งค่าทั้งหมด ตัวเลือกทั้งสองนั้นน่ากลัวเกือบตลอดเวลา
  2. วัตถุ Wrapper - นี่เป็นเพียงการย้ายปัญหาเนื่องจากคุณต้องเติมวัตถุ Wrapper ตั้งแต่แรก - แทนที่จะเป็นวิธีการของคุณโดยตรงมันจะไปที่ตัวสร้างของวัตถุพารามิเตอร์ การพิจารณาว่าการย้ายปัญหานั้นเหมาะสมหรือไม่ขึ้นอยู่กับการนำวัตถุดังกล่าวกลับมาใช้ใหม่ ตัวอย่างเช่น:

จะไม่ใช้มัน: จะใช้เพียงครั้งเดียวในการโทรครั้งแรกดังนั้นรหัสเพิ่มเติมจำนวนมากเพื่อจัดการกับ 1 บรรทัด ... ?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

อาจใช้มัน: ที่นี่มันสามารถทำอะไรได้อีกเล็กน้อย ขั้นแรกสามารถแยกตัวประกอบพารามิเตอร์สำหรับการเรียก 3 วิธี มันยังสามารถทำอีก 2 บรรทัดในตัวมันเอง ... มันจึงกลายเป็นตัวแปรสถานะในความหมาย ...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. รูปแบบตัวสร้าง - นี่เป็นการต่อต้านรูปแบบในมุมมองของฉัน กลไกการจัดการข้อผิดพลาดที่พึงปรารถนาที่สุดคือการตรวจจับก่อนหน้านี้ไม่ใช่ในภายหลัง แต่ด้วยรูปแบบตัวสร้างการเรียกที่ขาดหายไป (โปรแกรมเมอร์ไม่คิดที่จะรวมไว้) พารามิเตอร์บังคับจะถูกย้ายจากเวลาคอมไพล์ไปเป็นรันไทม์ แน่นอนว่าถ้าโปรแกรมเมอร์ตั้งใจใส่ null หรือในสล็อตนั่นจะเป็นรันไทม์ แต่การตรวจจับข้อผิดพลาดบางอย่างก่อนหน้านี้ถือเป็นข้อได้เปรียบที่ใหญ่กว่ามากในการรองรับโปรแกรมเมอร์ที่ปฏิเสธที่จะดูชื่อพารามิเตอร์ของวิธีที่พวกเขากำลังเรียกใช้ ฉันคิดว่าเหมาะสมก็ต่อเมื่อต้องจัดการกับพารามิเตอร์ที่เป็นทางเลือกจำนวนมากและถึงอย่างนั้นประโยชน์ก็น้อยที่สุด ฉันต่อต้านผู้สร้าง "แบบแผน" เป็นอย่างมาก

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

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

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