วิธีใดที่ดีที่สุดในการปรับโครงสร้างเมธอดที่มีพารามิเตอร์ (6+) มากเกินไป


102

บางครั้งฉันเจอวิธีการที่มีพารามิเตอร์จำนวนไม่เพียงพอ บ่อยกว่านั้นพวกเขาดูเหมือนจะเป็นผู้สร้าง ดูเหมือนว่าน่าจะมีวิธีที่ดีกว่านี้ แต่ฉันมองไม่เห็นว่ามันคืออะไร

return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)

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

ShnizArgs args = new ShnizArgs(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
return new Shniz(args);

ดูเหมือนว่าจะไม่ดีขึ้น แล้วแนวทางที่ดีที่สุดคืออะไร?


คุณพูดว่า "struct" คำนั้นมีความหมายที่แตกต่างกันในภาษาโปรแกรมต่างๆ คุณตั้งใจให้มันหมายถึงอะไร?
Jay Bazuzi

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

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

คำตอบ:


94

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

ตัวอย่างเช่นหากคุณส่งผ่านข้อกำหนดสำหรับสี่เหลี่ยมผืนผ้าคุณสามารถส่ง x, y, ความกว้างและความสูงหรือคุณสามารถส่งผ่านวัตถุสี่เหลี่ยมผืนผ้าที่มี x, y, ความกว้างและความสูง

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


4
ความคิดที่ดี แต่เป็นตัวอย่างที่ไม่ดี ตัวสร้างสำหรับสี่เหลี่ยมผืนผ้าจะต้องมี 4 อาร์กิวเมนต์ สิ่งนี้จะสมเหตุสมผลกว่าหากวิธีนี้คาดหวังพิกัด / มิติสี่เหลี่ยมผืนผ้า 2 ชุด จากนั้นคุณสามารถส่งสี่เหลี่ยม 2 รูปแทน x1, x2, y1, y2 ...
Outlaw Programmer

2
พอใช้. อย่างที่ฉันพูดมันสมเหตุสมผลที่จะทำก็ต่อเมื่อคุณจบลงด้วยการจัดกลุ่มเชิงตรรกะหลาย ๆ กลุ่ม
Matthew Brubaker

23
+1: สำหรับความรับผิดชอบเดียวเป็นหนึ่งในความคิดเห็นไม่กี่คำตอบทั้งหมดที่กล่าวถึงปัญหาที่แท้จริง วัตถุใดต้องการค่านิยมอิสระ 7 ประการในการสร้างอัตลักษณ์
AnthonyWJones

6
@AnthonyWJones ฉันไม่เห็นด้วย ข้อมูลสำหรับสภาพอากาศปัจจุบันสามารถมีค่าที่เป็นอิสระได้อีกมากมายเพื่อสร้างเอกลักษณ์
funct7

107

ฉันจะถือว่าคุณหมายถึงC # สิ่งเหล่านี้บางอย่างใช้กับภาษาอื่นด้วย

คุณมีหลายทางเลือก:

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

class C
{
    public string S { get; set; }
    public int I { get; set; }
}

new C { S = "hi", I = 3 };

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

แบบสร้าง

คิดเกี่ยวกับความสัมพันธ์ระหว่างและstring StringBuilderคุณสามารถรับสิ่งนี้สำหรับชั้นเรียนของคุณเอง ผมชอบที่จะใช้มันเป็นชั้นซ้อนกันเพื่อให้ชั้นเรียนมีระดับที่เกี่ยวข้องC C.Builderฉันยังชอบอินเทอร์เฟซที่คล่องแคล่วบนตัวสร้าง ทำถูกแล้วคุณจะได้รับไวยากรณ์ดังนี้:

C c = new C.Builder()
    .SetX(4)    // SetX is the fluent equivalent to a property setter
    .SetY("hello")
    .ToC();     // ToC is the builder pattern analog to ToString()

// Modify without breaking immutability
c = c.ToBuilder().SetX(2).ToC();

// Still useful to have a traditional ctor:
c = new C(1, "...");

// And object initializer syntax is still available:
c = new C.Builder { X = 4, Y = "boing" }.ToC();

ฉันมีสคริปต์ PowerShell ที่ให้ฉันสร้างโค้ดตัวสร้างเพื่อทำสิ่งนี้โดยที่อินพุตดูเหมือน:

class C {
    field I X
    field string Y
}

ดังนั้นฉันสามารถสร้างในเวลาคอมไพล์ partialชั้นเรียนให้ฉันขยายทั้งคลาสหลักและตัวสร้างโดยไม่ต้องแก้ไขโค้ดที่สร้างขึ้น

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

new C(a, b, c, d);

กลายเป็น

new C(new D(a, b, c, d));

อย่างไรก็ตามแนวทางนี้มีศักยภาพมากที่สุดในการสร้างผลกระทบเชิงบวกต่อโค้ดของคุณ ดังนั้นดำเนินการต่อโดยทำตามขั้นตอนเหล่านี้:

  1. มองหาชุดย่อยของพารามิเตอร์ที่เข้ากันได้ดี การจัดกลุ่มพารามิเตอร์ทั้งหมดของฟังก์ชันเข้าด้วยกันโดยไม่ตั้งใจก็ไม่ได้ช่วยอะไรคุณมากนัก เป้าหมายคือการจัดกลุ่มที่เหมาะสม คุณจะรู้ว่าคุณเข้าใจถูกต้องเมื่อชื่อประเภทใหม่ชัดเจน

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

  3. มองหาฟังก์ชันที่อยู่ในโค้ดที่มีอยู่ แต่อยู่ในประเภทใหม่

ตัวอย่างเช่นคุณอาจเห็นรหัสบางอย่างที่ดูเหมือน:

bool SpeedIsAcceptable(int minSpeed, int maxSpeed, int currentSpeed)
{
    return currentSpeed >= minSpeed & currentSpeed < maxSpeed;
}

คุณสามารถใช้minSpeedand maxSpeedพารามิเตอร์และวางไว้ในประเภทใหม่:

class SpeedRange
{
   public int Min;
   public int Max;
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return currentSpeed >= sr.Min & currentSpeed < sr.Max;
}

สิ่งนี้ดีกว่า แต่หากต้องการใช้ประโยชน์จากประเภทใหม่ให้ย้ายการเปรียบเทียบไปยังประเภทใหม่:

class SpeedRange
{
   public int Min;
   public int Max;

   bool Contains(int speed)
   {
       return speed >= min & speed < Max;
   }
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return sr.Contains(currentSpeed);
}

และตอนนี้เรากำลังไปถึงที่ไหนสักแห่ง: การนำไปใช้SpeedIsAcceptable()ตอนนี้บอกว่าคุณหมายถึงอะไรและคุณมีคลาสที่มีประโยชน์และนำกลับมาใช้ใหม่ได้ (ขั้นตอนต่อไปที่ชัดเจนคือการSpeedRangeเข้าสู่Range<Speed>)

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


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

4
คำตอบที่ดีเยี่ยม หากคุณได้กล่าวถึงคำอธิบายการปรับโครงสร้างก่อนที่จะมีน้ำตาลไวยากรณ์ c # สิ่งนี้จะได้รับการโหวตให้ IMHO สูงขึ้น
rpattabi

10
โอ! +1 สำหรับ "คุณจะรู้ว่าคุณเข้าใจถูกต้องเมื่อชื่อประเภทใหม่ชัดเจน"
Sean McMillan

20

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

Foo foo = new Foo()
          .configBar(anything)
          .configBaz(something, somethingElse)
          // and so on

หากเป็นวิธีการปกติคุณควรคิดถึงความสัมพันธ์ระหว่างค่าที่กำลังส่งผ่านและอาจสร้าง Transfer Object


ตอบกลับดีเยี่ยม บางทีอาจจะเกี่ยวข้องมากกว่าคำตอบ "ใส่พารามิเตอร์ในคลาส" ที่ทุกคน (รวมถึงฉัน) ให้ด้วยซ้ำ
Wouter Lievens

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

@outlaw - หากมีข้อกังวลเกี่ยวกับความไม่แน่นอนคุณสามารถใช้ความหมาย "run once" ได้อย่างง่ายดาย อย่างไรก็ตามพารามิเตอร์ ctor จำนวนมากมักบ่งบอกถึงความจำเป็นในการกำหนดค่า (หรือตามที่คนอื่น ๆ สังเกตเห็นว่าคลาสที่พยายามทำหลายอย่างเกินไป) (ต่อ)
kdgregory

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

ฉันชอบรูปแบบตัวสร้าง แต่ฉันแยกประเภทตัวสร้างที่ไม่เปลี่ยนรูปและไม่สามารถเปลี่ยนแปลงได้เช่น string / StringBuilder แต่ฉันใช้คลาสซ้อน: Foo / Foo.Builder ฉันมีสคริปต์ PowerShell เพื่อสร้างรหัสเพื่อทำสิ่งนี้สำหรับคลาสข้อมูลอย่างง่าย
Jay Bazuzi

10

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

เช่นแทนที่จะเป็น:

driver.connect(host, user, pass)

คุณสามารถใช้

config = new Configuration()
config.setHost(host)
config.setUser(user)
config.setPass(pass)
driver.connect(config)

YMMV


5
ฉันต้องการโค้ดชิ้นแรกมากกว่านี้ ฉันยอมรับว่ามีข้อ จำกัด บางประการซึ่งเหนือกว่าที่พารามิเตอร์ numbe rof จะดูน่าเกลียด แต่สำหรับรสนิยมของฉันแล้ว 3 จะเป็นที่ยอมรับได้
blabla999

10

อ้างจากหนังสือ Fowler and Beck: "Refactoring"

รายการพารามิเตอร์แบบยาว

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


7

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

อย่าเข้าใจฉันผิด: วิธีการและตัวสร้างจะมีพารามิเตอร์มากมายในบางครั้ง แต่เมื่อพบแล้วให้พยายามพิจารณาการห่อหุ้มข้อมูลด้วยพฤติกรรมแทน

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


7

เมื่อฉันเห็นรายการพารามิเตอร์แบบยาวคำถามแรกของฉันคือฟังก์ชั่นหรือวัตถุนี้ทำงานมากเกินไปหรือไม่ พิจารณา:

EverythingInTheWorld earth=new EverythingInTheWorld(firstCustomerId,
  lastCustomerId,
  orderNumber, productCode, lastFileUpdateDate,
  employeeOfTheMonthWinnerForLastMarch,
  yearMyHometownWasIncorporated, greatGrandmothersBloodType,
  planetName, planetSize, percentWater, ... etc ...);

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

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

 Order order=new Order(customerName, customerAddress, customerCity,
   customerState, customerZip,
   orderNumber, orderType, orderDate, deliveryDate);

เราสามารถมี:

Customer customer=new Customer(customerName, customerAddress,
  customerCity, customerState, customerZip);
Order order=new Order(customer, orderNumber, orderType, orderDate, deliveryDate);

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

Employee employee=new Employee(employeeId, firstName, lastName,
  socialSecurityNumber,
  address, city, state, zip);

ใช่มันเป็นช่องต่างๆมากมาย แต่สิ่งที่เรากำลังจะทำก็คือบันทึกลงในบันทึกฐานข้อมูลหรือโยนลงบนหน้าจอหรือบางส่วน ที่นี่มีการประมวลผลไม่มากนัก

เมื่อรายการพารามิเตอร์ของฉันยาวฉันชอบมากถ้าฉันสามารถให้ฟิลด์ประเภทข้อมูลต่างๆ เช่นเดียวกับเมื่อฉันเห็นฟังก์ชันเช่น:

void updateCustomer(String type, String status,
  int lastOrderNumber, int pastDue, int deliveryCode, int birthYear,
  int addressCode,
  boolean newCustomer, boolean taxExempt, boolean creditWatch,
  boolean foo, boolean bar);

แล้วฉันเห็นมันถูกเรียกด้วย:

updateCustomer("A", "M", 42, 3, 1492, 1969, -7, true, false, false, true, false);

ฉันกังวล เมื่อมองไปที่การโทรยังไม่ชัดเจนว่าตัวเลขรหัสและธงที่เป็นความลับเหล่านี้หมายถึงอะไร นี่เป็นเพียงการขอข้อผิดพลาด โปรแกรมเมอร์อาจสับสนได้ง่ายเกี่ยวกับลำดับของพารามิเตอร์และสลับไปมาสองตัวโดยไม่ได้ตั้งใจและหากเป็นประเภทข้อมูลเดียวกันคอมไพเลอร์ก็ยอมรับ ฉันอยากจะมีลายเซ็นที่ทุกสิ่งเหล่านี้เป็น enums ดังนั้นการโทรจะส่งผ่านในสิ่งต่างๆเช่น Type.ACTIVE แทนที่จะเป็น "A" และ CreditWatch.NO แทนที่จะเป็น "false" เป็นต้น


5

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

return new Shniz.Builder(foo, bar).baz(baz).quux(quux).build();

รายละเอียดนี้มีอธิบายไว้ใน Effective Java, 2nd Ed., p. 11. สำหรับอาร์กิวเมนต์เมธอดหนังสือเล่มเดียวกัน (น. 189) อธิบายสามแนวทางในการย่อรายการพารามิเตอร์:

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

4

ฉันจะใช้ตัวสร้างเริ่มต้นและตัวตั้งค่าคุณสมบัติ C # 3.0 มีไวยากรณ์ที่ดีในการทำสิ่งนี้โดยอัตโนมัติ

return new Shniz { Foo = foo,
                   Bar = bar,
                   Baz = baz,
                   Quuz = quux,
                   Fred = fred,
                   Wilma = wilma,
                   Barney = barney,
                   Dino = dino,
                   Donkey = donkey
                 };

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


2
สิ่งนี้จะช่วยให้วัตถุ t ใหม่ Shniz () มีอยู่ การใช้งาน OO ที่ดีจะพยายามลดความเป็นไปได้ของวัตถุที่มีอยู่ในสถานะที่ไม่สมบูรณ์
AnthonyWJones

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

4

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

Shniz (ฟู, บาร์, บาส, ควอซ์, เฟรด, วิลมา, บาร์นีย์, ดิโน, ลา)

สามารถตีความได้ว่า:

void Shniz(int foo, int bar, int baz, int quux, int fred, 
           int wilma, int barney, int dino, int donkey) { ...

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

// old way
Shniz(1,2,3,2,3,2,1,2);
Shniz(1,2,2,3,3,2,1,2); 

//versus
ShnizParam p = new ShnizParam { Foo = 1, Bar = 2, Baz = 3 };
Shniz(p);

หรือหากคุณมี:

void Shniz(Foo foo, Bar bar, Baz baz, Quux quux, Fred fred, 
           Wilma wilma, Barney barney, Dino dino, Donkey donkey) { ...

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

นอกจากนี้พารามิเตอร์บางตัวเลือกได้หรือไม่? มีการแทนที่เมธอด (ชื่อเมธอดเดียวกัน แต่มีลายเซ็นของเมธอดต่างกันหรือไม่) รายละเอียดเหล่านี้ล้วนมีความสำคัญกับสิ่งที่คำตอบที่ดีที่สุดคืออะไร

* กระเป๋าทรัพย์สินก็มีประโยชน์เช่นกัน แต่ไม่ได้ดีไปกว่านั้นโดยเฉพาะเนื่องจากไม่มีพื้นหลังให้

อย่างที่คุณเห็นมีคำตอบที่ถูกต้องมากกว่า 1 ข้อสำหรับคำถามนี้ เลือกของคุณ


3

คุณสามารถลองจัดกลุ่มพารามิเตอร์ของคุณเป็นโครงสร้าง / คลาสที่มีความหมายแบบทวีคูณ (ถ้าเป็นไปได้)


2

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

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


2

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


2

หากภาษาของคุณรองรับให้ใช้พารามิเตอร์ที่มีชื่อและสร้างตัวเลือก (โดยมีค่าเริ่มต้นที่สมเหตุสมผล) ให้มากที่สุด


1

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


1

จะไม่ตั้งค่าทั้งหมดพร้อมกันในตัวสร้าง แต่ทำผ่านคุณสมบัติ / ตัวตั้งค่า ? ฉันเคยเห็นคลาส. NET บางคลาสที่ใช้แนวทางนี้เช่นProcessคลาส:

        Process p = new Process();

        p.StartInfo.UseShellExecute = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.FileName = "cmd";
        p.StartInfo.Arguments = "/c dir";
        p.Start();

3
C # 3 มีไวยากรณ์สำหรับการทำสิ่งนี้อย่างง่ายดาย: object initializers
Daren Thomas

1

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


1

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

ชอบคลาส / วิธีการขนาดเล็กมากกว่าขนาดใหญ่ จำหลักความรับผิดชอบเดียว


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

@recursive - ฉันไม่เห็นด้วยที่ฟิลด์ / คุณสมบัติต้องเขียนได้เสมอ สำหรับชั้นเรียนขนาดเล็กมีหลายครั้งที่สมาชิกอ่านอย่างเดียวมีเหตุผล
Brian Rasmussen

1

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

ตัวเลือกอื่นที่ฉันจะมองหาในการทำ refactor ประเภทนี้จะเป็นกลุ่มของพารามิเตอร์ที่เกี่ยวข้องซึ่งอาจจัดการได้ดีกว่าในฐานะวัตถุอิสระ ตัวอย่างการใช้คลาส Rectangle จากคำตอบก่อนหน้านี้คอนสตรัคเตอร์ที่รับพารามิเตอร์สำหรับ x, y, ความสูงและความกว้างสามารถแยกตัวประกอบ x และ y ออกเป็นอ็อบเจ็กต์ Point ได้ทำให้คุณสามารถส่งผ่านพารามิเตอร์สามตัวไปยังตัวสร้างของ Rectangle ได้ หรือไปอีกหน่อยและทำให้เป็นสองพารามิเตอร์ (UpperLeftPoint, LowerRightPoint) แต่นั่นจะเป็นการปรับโครงสร้างใหม่ที่รุนแรงกว่า


0

ขึ้นอยู่กับประเภทของอาร์กิวเมนต์ที่คุณมี แต่ถ้าเป็นค่าบูลีน / ตัวเลือกจำนวนมากคุณอาจใช้ Flag Enum?


0

ฉันคิดว่าปัญหานั้นเกี่ยวข้องอย่างลึกซึ้งกับขอบเขตของปัญหาที่คุณกำลังพยายามแก้ไขกับชั้นเรียน

ในบางกรณีตัวสร้างพารามิเตอร์ 7 ตัวอาจบ่งบอกถึงลำดับชั้นของคลาสที่ไม่ดี: ในกรณีนั้นโครงสร้างตัวช่วย / คลาสที่แนะนำข้างต้นมักจะเป็นแนวทางที่ดี แต่คุณก็มักจะลงเอยด้วยโครงสร้างจำนวนมากซึ่งเป็นเพียงถุงคุณสมบัติ และอย่าทำอะไรที่เป็นประโยชน์ คอนสตรัคเตอร์ 8 อาร์กิวเมนต์อาจบ่งชี้ว่าคลาสของคุณมีลักษณะทั่วไปเกินไป / เอนกประสงค์เกินไปดังนั้นจึงต้องการตัวเลือกมากมายเพื่อให้มีประโยชน์จริงๆ ในกรณีนี้คุณสามารถปรับโครงสร้างคลาสใหม่หรือใช้ตัวสร้างแบบคงที่ซึ่งซ่อนตัวสร้างที่ซับซ้อนจริง: เช่น Shniz.NewBaz (foo, bar) สามารถเรียกตัวสร้างจริงผ่านพารามิเตอร์ที่ถูกต้องได้


0

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

อาจมีการกำหนดคุณสมบัติที่เขียนต่อสาธารณะได้หลังการก่อสร้าง

ค่านิยมมาจากไหน? บางทีค่าบางค่าอาจเป็นค่าภายนอกอย่างแท้จริงในขณะที่ค่าอื่น ๆ มาจากการกำหนดค่าหรือข้อมูลส่วนกลางที่ดูแลโดยไลบรารี

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

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


0

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

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

ดูด้านล่าง:

public class Toto {
    private final String state0;
    private final String state1;
    private final String state2;
    private final String state3;

    public Toto(String arg0, String arg1, String arg2, String arg3) {
        this.state0 = arg0;
        this.state1 = arg1;
        this.state2 = arg2;
        this.state3 = arg3;
    }

    public static class TotoBuilder {
        private String arg0;
        private String arg1;
        private String arg2;
        private String arg3;

        public TotoBuilder addArg0(String arg) {
            this.arg0 = arg;
            return this;
        }
        public TotoBuilder addArg1(String arg) {
            this.arg1 = arg;
            return this;
        }
        public TotoBuilder addArg2(String arg) {
            this.arg2 = arg;
            return this;
        }
        public TotoBuilder addArg3(String arg) {
            this.arg3 = arg;
            return this;
        }

        public Toto newInstance() {
            // maybe add some validation ...
            return new Toto(this.arg0, this.arg1, this.arg2, this.arg3);
        }
    }

    public static void main(String[] args) {
        Toto toto = new TotoBuilder()
            .addArg0("0")
            .addArg1("1")
            .addArg2("2")
            .addArg3("3")
            .newInstance();
    }

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