ข้ามไปยังเนื้อหา

Preserve Whole Object

caller ถือ object อยู่ในมืออยู่แล้ว แต่กลับขุด field สองสามตัวออกมาเพื่อส่งเข้า function ทีละตัว ให้ส่ง ทั้ง object ไปเลยดีกว่า รายการ argument จะสั้นลง function เอื้อมไปหยิบ field ไหนก็ได้ที่ต้องใช้ และถ้าวันหลัง object งอก field ใหม่ที่เกี่ยวข้อง signature ก็ไม่ต้องเปลี่ยน

ที่จุดเรียกใช้คุณจะเห็นชุดของการเรียก accessor ที่ป้อนตรงเข้าไปในอีกการเรียกหนึ่ง: plot(point.x, point.y), fits(range.low, range.high) ค่าที่ถูกแกะออกมาทั้งหมดมาจาก object เดียวกันและเดินทางไปด้วยกัน รายการ parameter ของ function สะท้อนเนื้อในของ object ทั้งสองจึงถูกผูกติดกันอย่างเงียบ ๆ ทั้งที่ดูเหมือนเป็นอิสระต่อกัน

ค่าที่อ่านได้ถูกตรวจเทียบกับ range ของอุณหภูมิ โดย caller ดึง low กับ high ออกจาก range มาส่งเข้าไป แทนที่จะทำแบบนั้น ให้ส่งตัว range ไปเลย แล้วปล่อยให้ฝั่งตรวจถาม range เอาเอง

// Before
function fitsRange(reading: number, low: number, high: number): boolean {
return reading >= low && reading <= high;
}
const ok = fitsRange(today, range.low, range.high);
// After
interface Range { low: number; high: number; }
function fitsRange(reading: number, range: Range): boolean {
return reading >= range.low && reading <= range.high;
}
const ok = fitsRange(today, range);
flowchart LR
  subgraph Before["Before"]
    A["caller pulls low + high<br/>from range"] --> B["fits(low, high)"]
  end
  subgraph After["After"]
    C["caller passes range"] --> D["fits(range)"]
  end
  Before -.->|"Preserve Whole Object"| After
หลาย field ที่ถูกแกะออกมายุบรวมเป็น argument ทั้ง object เดียว
  1. เพิ่ม parameter ใหม่สำหรับทั้ง object โดยปล่อย parameterfield ที่มีอยู่ไว้ก่อน
  2. ภายใน function แทนที่การใช้ field ที่ถูกแกะแต่ละจุดด้วยการอ่านจากทั้ง object
  3. อัปเดต caller แต่ละจุดให้ส่ง object และเลิกส่ง field แต่ละตัว รัน test หลังจากแต่ละจุด
  4. เมื่อแปลง caller ทุกจุดแล้ว ให้ลบ parameterfield ที่ตอนนี้ไม่ถูกใช้แล้วทิ้ง
  5. มองหา logic ใน caller ที่ยุ่งอยู่กับ field เหล่านั้นอย่างเดียว เพราะ logic นั้นอาจควรย้ายเข้าไปใน function หรือกลายเป็น method บนตัว object เลย

หยิบท่านี้มาใช้เมื่อ field ที่แกะออกมาทั้งหมดเป็นของ object เดียวกัน เมื่อหลาย function ดึง field กลุ่มเดียวกันออกมาใช้ หรือเมื่อ function น่าจะได้ประโยชน์จากการเข้าถึง object มากกว่าที่ได้อยู่ตอนนี้ ท่านี้มักเผย Feature Envy ออกมาด้วย คือ code ที่เอาแต่ยุ่งกับข้อมูลของ object อื่น ซึ่งคุณย้ายไปเป็น method บน object นั้นได้เลย

ข้อแลกเปลี่ยนคือ dependency แน่นขึ้น เพราะตอนนี้ function ต้องรู้จัก type ของ object ไม่ใช่แค่รับตัวเลขสองตัว ถ้าปลายทางไม่ควรรู้จัก type นั้น ไม่ว่าจะเพราะอยากเอาไปใช้ซ้ำในบริบทที่ไม่เกี่ยวกัน หรือเพราะต้องเลี่ยง circular dependency ก็ให้คง parameter แบบหลวม ๆ ไว้ และถ้า caller ยังไม่มี object อยู่ในมือ การประกอบขึ้นมาใหม่เพียงเพื่อจะเรียกก็ถือว่าถอยหลัง

ใช้ Preserve Whole Object เมื่อหลีกเลี่ยงเมื่อ
หลาย field ที่แกะมาล้วนมาจาก object เดียวกันต้องการแยก function จาก type นั้นเพื่อ reuse
function น่าจะต้องการ field เพิ่มในภายหลังมี circular dependency ถ้าส่ง whole object
รายการ parameter ยาวเพราะ accessor chaincaller ยังไม่มี whole object ต้องสร้างขึ้นมาใหม่

⚠️ ไม่ควร Preserve Whole Object เมื่อ:

  • function ที่ถูกเรียกอยู่คนละ layer และไม่ควรรู้จัก type นั้น
  • ส่ง whole object สร้าง tight coupling ที่ทำให้ test ยากขึ้น
  • fields ที่ส่งมาไม่ได้อยู่ใน object เดียวกันจริง ๆ
Preserve Whole Object แทนที่อะไรในรายการ argument?
กลิ่นที่จุดเรียกใช้แบบใดที่บ่งบอกถึงการรีแฟกเตอร์นี้?
Preserve Whole Object มักเผยกลิ่นที่ลึกกว่าแบบใด?
เมื่อไรที่คุณควรคงการส่ง field แบบหลวมไว้แทนที่จะส่งทั้ง object?