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

Strategy

Strategy นิยามตระกูลของอัลกอริทึม วางแต่ละตัวไว้หลัง interface ร่วมเดียวกัน และทำให้สับเปลี่ยนกันได้ object ที่ต้องการให้ทำงานจะมอบหมายให้ strategy ที่ถูกเลือกในขณะรัน ดังนั้นอัลกอริทึมจึงผันแปรได้โดยไม่ต้องแตะ code ที่เรียกใช้

ลองนึกถึงหน้า checkout ที่คำนวณราคาสุดท้าย ตอนแรกมีกฎเพียงข้อเดียว จากนั้นฝ่ายการตลาดเพิ่มส่วนลดเป็นเปอร์เซ็นต์สำหรับสมาชิก คูปองลดราคาแบบคงที่ ข้อเสนอซื้อหนึ่งแถมหนึ่ง และโปรลดราคาตามฤดูกาล หากทั้งหมดนี้อยู่ใน method เดียว จะเติบโตเป็นห่วงโซ่ของการแตกแขนงยาวเหยียดที่คุณต้องเปิดและทดสอบใหม่ทุกครั้งที่มีกฎใหม่ปรากฏขึ้น และตรรกะการตั้งราคาก็จะพันกันยุ่งกับ code ของออเดอร์ที่อยู่รอบ ๆ

ความผันแปรในที่นี้คือ ตัวอัลกอริทึมการตั้งราคาเอง Strategy แยกแกนนั้นออกมา แต่ละกฎกลายเป็น object เล็ก ๆ ของตัวเองที่ implement interface ร่วมกัน และออเดอร์ถือ reference ไปยังตัวที่ใช้บังคับอยู่ การเพิ่มกฎหมายถึงการเพิ่ม class ไม่ใช่การแก้เงื่อนไข การสลับกฎในขณะรัน ระหว่างสมาชิกกับแขก หรือระหว่างช่วงลดราคากับช่วงปกติ ก็เป็นเพียงการกำหนด strategy คนละตัว

classDiagram
  class Context {
    -strategy: PricingStrategy
    +setStrategy(s)
    +priceFor(amount) number
  }
  class PricingStrategy {
    <<interface>>
    +apply(amount) number
  }
  class RegularPricing {
    +apply(amount) number
  }
  class MemberPricing {
    +apply(amount) number
  }
  class CouponPricing {
    +apply(amount) number
  }
  Context o--> PricingStrategy
  PricingStrategy <|.. RegularPricing
  PricingStrategy <|.. MemberPricing
  PricingStrategy <|.. CouponPricing
Context มอบหมายงานให้หนึ่งในหลาย ๆ การ implement ของ PricingStrategy ที่สับเปลี่ยนกันได้
  • Strategy — interface ร่วมที่ทุกอัลกอริทึม implement context พึ่งพาเพียงตัวนี้เท่านั้น
  • Concrete Strategy — การ implement หนึ่งตัวของ interface ในที่นี้คือกฎการตั้งราคาแต่ละข้อ
  • Context — ถือ reference ไปยัง strategy แล้วมอบหมายงานให้ strategy นั้น สามารถถูกตั้งค่าใหม่ด้วย strategy คนละตัวได้ในขณะรัน
  • Client — เลือก concrete strategy แล้วส่งให้ context

context การตั้งราคาที่คำนวณยอดสุดท้ายโดยมอบหมายให้ pricing strategy ที่สับเปลี่ยนกันได้ แต่ละภาษาแสดง strategy ด้วยสิ่งที่เป็นสำนวนเฉพาะของตัวเอง ไม่ว่าจะเป็น interface, protocol, function type หรือ trait object

interface PricingStrategy {
apply(amount: number): number;
}
class RegularPricing implements PricingStrategy {
apply(amount: number): number {
return amount;
}
}
class MemberPricing implements PricingStrategy {
apply(amount: number): number {
return amount * 0.9; // 10% off
}
}
class CouponPricing implements PricingStrategy {
constructor(private readonly off: number) {}
apply(amount: number): number {
return Math.max(0, amount - this.off);
}
}
class Checkout {
constructor(private strategy: PricingStrategy) {}
setStrategy(strategy: PricingStrategy): void {
this.strategy = strategy;
}
priceFor(amount: number): number {
return this.strategy.apply(amount);
}
}
const checkout = new Checkout(new RegularPricing());
console.log(checkout.priceFor(100)); // 100
checkout.setStrategy(new MemberPricing());
console.log(checkout.priceFor(100)); // 90
checkout.setStrategy(new CouponPricing(15));
console.log(checkout.priceFor(100)); // 85
  • ข้อดี: สลับอัลกอริทึมได้ในขณะรันและเพิ่มตัวใหม่ได้โดยไม่ต้องแก้ context หรือ strategy ที่มีอยู่
  • ข้อดี: แทนที่เงื่อนไขที่บานปลายด้วยหน่วยเล็ก ๆ ที่ทดสอบได้ทีละตัว
  • ข้อดี: แต่ละ strategy ทดสอบหน่วยได้อย่างโดดเดี่ยว ปลอดจาก context ที่ห้อมล้อม
  • ข้อเสีย: เพิ่ม object และ interface ขึ้นมา สำหรับการแตกแขนงเล็กน้อยสองทาง if ธรรมดาก็ง่ายกว่า
  • ข้อเสีย: client ต้องรู้มากพอที่จะเลือก strategy ที่ถูกต้อง ที่เป็นการผลักการตัดสินใจออกไปข้างนอก
  • State มีโครงสร้างเดียวกันแต่ intent ต่างกัน object ของตัวเองสลับ ตัวเอง เมื่อสถานะเปลี่ยน แทนที่จะถูก client เลือก
  • Template Method ทำให้อัลกอริทึมผันแปรผ่านการสืบทอดและขั้นตอนที่ถูก override ขณะที่ Strategy ทำให้อัลกอริทึมผันแปรผ่านการประกอบ (composition)
StrategyStateTemplate Method
ใครเลือก behaviorclient (inject จากนอก)object เลือกเองinheritance กำหนด
เปลี่ยนได้ขณะ runtimeขณะ runtimeขณะ compile
structurecompositioncompositioninheritance
เมื่อใช้algorithm หลายตัวที่สลับได้behavior ผันแปรตาม state ภายในalgorithm ที่มี step คงที่
Strategy pattern ทำให้สิ่งใดสับเปลี่ยนกันได้?
Context ใช้ Strategy อย่างไร?
ข้อได้เปรียบหลักเหนือเงื่อนไขขนาดใหญ่คืออะไร?
Strategy ต่างจาก State อย่างไร?