Introduce Parameter Object
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”มองหากลุ่ม argument ที่โผล่มาด้วยกันซ้ำ ๆ ในหลาย function แล้วแทนด้วย object ตัวเดียวที่เก็บค่าทั้งชุดไว้ กลุ่มนั้นจะได้ชื่อ ความสัมพันธ์ระหว่างค่าจะชัดขึ้น และ function ที่เคยรับ argument กระจัดกระจาย ก็เหลือรับ parameter เรียบร้อยตัวเดียว
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”ท่านี้รักษา Long Parameter List กับญาติคือ Data Clump ซึ่งก็คือค่าสองสามตัวเดิม ๆ ที่ถูกส่งเรียงติดกันตามลำดับเดิม ไล่ไปทีละ function เช่น start กับ end ที่จริง ๆ แล้วคือ ช่วงวันที่ หรือ latitude กับ longitude ที่จริง ๆ แล้วคือ ตำแหน่ง พอค่าหลายตัวเดินทางกันเป็นฝูง ฝูงนั้นคือแนวคิดที่ code ยังไม่ได้ตั้งชื่อ ที่แย่กว่านั้นคือรายการยาว ๆ ที่ไม่มีชื่อกำกับ ยังเชิญชวนให้เกิดความผิดพลาดคลาสสิกอย่างการส่ง argument สลับลำดับ
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”การจองเที่ยวบินที่ลาก argument สี่ตัวซึ่งเกี่ยวข้องกันหลวม ๆ ไปไหนมาไหน หลัง refactor ทั้งหมดยุบเหลือ object Trip ตัวเดียว
// Beforefunction bookFlight( from: string, to: string, startDate: string, endDate: string,): string { return `Flight ${from}->${to} from ${startDate} to ${endDate}`;}
const summary = bookFlight("BKK", "NRT", "2026-07-01", "2026-07-09");
// Afterinterface Trip { from: string; to: string; startDate: string; endDate: string;}
function bookFlight(trip: Trip): string { return `Flight ${trip.from}->${trip.to} from ${trip.startDate} to ${trip.endDate}`;}
const summary = bookFlight({ from: "BKK", to: "NRT", startDate: "2026-07-01", endDate: "2026-07-09",});# Beforedef book_flight(origin, destination, start_date, end_date): return f"Flight {origin}->{destination} from {start_date} to {end_date}"
summary = book_flight("BKK", "NRT", "2026-07-01", "2026-07-09")
# Afterfrom dataclasses import dataclass
@dataclassclass Trip: origin: str destination: str start_date: str end_date: str
def book_flight(trip): return ( f"Flight {trip.origin}->{trip.destination} " f"from {trip.start_date} to {trip.end_date}" )
summary = book_flight(Trip("BKK", "NRT", "2026-07-01", "2026-07-09"))// Beforefunc BookFlight(from, to, startDate, endDate string) string { return fmt.Sprintf("Flight %s->%s from %s to %s", from, to, startDate, endDate)}
summary := BookFlight("BKK", "NRT", "2026-07-01", "2026-07-09")
// Aftertype Trip struct { From string To string StartDate string EndDate string}
func BookFlight(t Trip) string { return fmt.Sprintf("Flight %s->%s from %s to %s", t.From, t.To, t.StartDate, t.EndDate)}
summary := BookFlight(Trip{ From: "BKK", To: "NRT", StartDate: "2026-07-01", EndDate: "2026-07-09",})// Beforefn book_flight(from: &str, to: &str, start_date: &str, end_date: &str) -> String { format!("Flight {from}->{to} from {start_date} to {end_date}")}
let summary = book_flight("BKK", "NRT", "2026-07-01", "2026-07-09");
// Afterstruct Trip { from: String, to: String, start_date: String, end_date: String,}
fn book_flight(trip: &Trip) -> String { format!( "Flight {}->{} from {} to {}", trip.from, trip.to, trip.start_date, trip.end_date )}
let trip = Trip { from: "BKK".to_string(), to: "NRT".to_string(), start_date: "2026-07-01".to_string(), end_date: "2026-07-09".to_string(),};let summary = book_flight(&trip);flowchart LR
subgraph Before["Before"]
A["bookFlight(<br/>from, to,<br/>startDate, endDate)"]
end
subgraph After["After"]
B["bookFlight(trip)"]
B --> C["Trip<br/>{ from, to,<br/>startDate, endDate }"]
end
Before -.->|"Introduce Parameter Object"| After กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- ระบุกลุ่มให้ได้ก่อน คือ argument ที่โผล่มาด้วยกันเสมอ แล้วตั้งชื่อให้แนวคิดที่กลุ่มนั้นแทน
- สร้างโครงสร้างใหม่ (class, struct, record หรือ interface) ที่มี field สำหรับสมาชิกแต่ละตัวของกลุ่ม
- เพิ่ม object ใหม่เป็น parameter ให้ function เป้าหมายตัวหนึ่ง โดยตอนแรกวางไว้ข้าง ๆ argument เดิม รัน test
- ภายในเนื้อใน ให้เริ่มอ่านจาก field ของ object แทน parameter ที่กระจัดกระจาย ทีละ field โดย test ไปเรื่อย ๆ ขณะทำ
- เมื่อเนื้อในอ่านจาก object เพียงอย่างเดียวแล้ว ให้ลบ parameter ที่กระจัดกระจายซึ่งไม่ได้ใช้แล้วออกจากลายเซ็น จากนั้นอัปเดต caller ของ function นั้นให้สร้างและส่ง object
- ทำซ้ำกับ function อื่น ๆ ที่ใช้กลุ่มเดียวกัน เพื่อให้ทั้งหมดรับ object
- รัน test หลังแก้แต่ละ function และคอยสังเกต behavior ใหม่ที่ผุดขึ้นมา เพราะพอข้อมูลมารวมกันแล้ว logic ที่ทำงานกับข้อมูลชุดนั้นมักอยากย้ายเข้าไปอยู่บน object ตามไปด้วย
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”แนะนำให้ใช้ parameter object เมื่อค่าชุดเดิมถูกส่งด้วยกันซ้ำ ๆ เมื่อรายการ parameter ยาวจน caller ส่งลำดับผิด หรือเมื่อคุณรู้สึกได้ว่ามีแนวคิดที่ยังไม่ได้ตั้งชื่อซ่อนอยู่ในรายการ argument object บันทึกความสัมพันธ์ไว้และให้ที่อยู่ตามธรรมชาติแก่ behavior ที่เป็นของกลุ่มนั้น
ข้อแลกเปลี่ยนคือมี type เพิ่มมาให้นิยาม และต้องเสียพิธีเล็กน้อยตอนสร้าง object ที่จุดเรียกแต่ละจุด ถ้าเป็นการเรียกสอง argument ครั้งเดียวที่ไม่เคยเกิดซ้ำ พิธีนั้นก็อาจไม่คุ้ม ผลตอบแทนจะโตขึ้นตามจำนวนการใช้ซ้ำ ยิ่งมี function ใช้กลุ่มนี้ร่วมกันมากเท่าไร object ก็ยิ่งคุ้ม และยิ่งกลายเป็นแม่เหล็กดูด method ที่เกี่ยวข้องเข้ามา
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Introduce Parameter Object เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| parameter กลุ่มเดียวกันปรากฏในหลาย function | parameter ที่จะรวมไม่มี cohesion จริง ๆ |
| function มี parameter เกิน 3-4 ตัว | object ใหม่จะมีแค่ data ไม่มี behavior |
| parameter แต่ละตัวมีความสัมพันธ์กัน (date range, point) | มีแค่ 2 parameter และมักเปลี่ยนแยกกัน |
⚠️ ไม่ควร Introduce Parameter Object เมื่อ:
- parameter ที่รวมกันไม่ได้ปรากฏพร้อมกันเสมอ
- object ใหม่จะไม่มี behavior ที่เกี่ยวข้อง — แค่ data bag
- function นั้นเป็น one-off ที่ไม่ถูกเรียกจากหลายที่