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

Factories — แยก "การ​ใช้" ออก​จาก "การ​สร้าง"

เรา​จะ สร้าง บันทึก และ​ดึง domain object กลับ​มา​ได้​อย่างไร โดยที่ domain modelDomain ModelModel ของ Domain ที่​ฝัง​กฎ​และ​กระบวนการ​ทาง​ธุรกิจ​ไว้​ใน code จริง หัวใจ​ของ DDD คือ​การ​พัฒนา domain model ที่ “เข้าใจ​ธุรกิจ​อย่าง​ลึกซึ้ง”Strategic Design ยัง​ไม่​ถูก​รบกวน​ด้วย​เรื่อง​เชิง​เทคนิค? วงจร​ชีวิต​ของ object ที่​ซับซ้อน​มัก​ต้อง​อาศัย​การ “ประสาน​งาน” ทั้ง​ตอน​ถือ​กำเนิด​และ​ตอน​ถูก​บันทึก และ​การ​รับประกัน​ว่า invariantInvariantกฎ​ทาง​ธุรกิจ​ที่ “ต้อง​เป็น​จริง​เสมอ” ภายใน Aggregate เช่น ยอด​รวม​คำ​สั่ง​ซื้อ​ต้อง​เท่ากับ​ผล​รวม​ของ​รายการ​สินค้า — ต้อง​ถูกต้อง​ภายใน transaction เดียวTactical Design ถูกต้อง​ตั้งแต่​ตอน​สร้าง​นั้น​ทำได้​ด้วย FactoryFactoryตัว​ที่​ห่อ​หุ้ม​ตรรกะ​การ “สร้าง” Aggregate ที่​ซับซ้อน และ​รับประกัน​ว่าอ็อบเจ็กต์ใหม่​ถูกต้อง​ตาม invariant ตั้งแต่​แรก​เกิด — “สร้าง​ของ​ใหม่” (repository หา “ของ​เก่า”)Tactical Design — หนึ่ง​ใน creational pattern ของ Gang of Four ที่ Eric Evans หยิบ​มา​ปรับ​ใช้​ใน DDD

บท​นี้​จะ​พา​ดู​ว่า Factory แก้​ปัญหา​อะไร ใช้​ตอน​ไหน และ​ทำไม​การ “แยก​การ​ใช้​ออก​จาก​การ​สร้าง” (separate use from construction) จึง​ทำให้ code ของ​เรา​สื่อ​ความหมาย​และ​ดูแล​รักษา​ง่าย​ขึ้น


เมื่อ AggregateAggregateกลุ่ม​ของ Entity และ Value Object ที่​ถูก​มอง​เป็น​หนึ่ง​หน่วย​เดียว​เพื่อ​รักษา​ความ​ถูกต้อง​ของ​ข้อมูล มี​ขอบเขต​ชัดเจน และ​เป็น​หน่วย​ของ transactionTactical Design, EntityEntityอ็อบเจ็กต์ที่​มี “ตัวตน” (identity) ต่อ​เนื่อง​ตาม​เวลา แม้​ค่า​ภายใน​เปลี่ยน ก็​ยัง​เป็น​สิ่ง​เดิม เช่น ลูกค้า, คำ​สั่ง​ซื้อ — แยกแยะ​ด้วย id ไม่ใช่​ด้วย​ค่าTactical Design และ Value ObjectValue Objectอ็อบเจ็กต์ที่​สำคัญ​ที่ “ค่า” ของ​มัน ไม่มี​ตัวตน​เฉพาะ สอง​ตัว​ที่​ค่า​เท่า​กัน​ถือว่า​เท่า​กัน ควร​เป็น immutable (เปลี่ยน​ค่า​ไม่​ได้) เช่น เงิน, วัน​ที่, พิกัดTactical Design เริ่ม​ซับซ้อน​ใน domain ที่​ใหญ่​และ​เต็ม​ไป​ด้วย​ตรรกะ การ​สร้าง instance ที่ “ถูกต้อง” อาจ​ต้อง​อาศัย​ความ​รู้เชิง​ลึก​เกี่ยว​กับ​กฎ​ภายใน​ของ object อื่น หาก​ความ​รู้​เช่น​นี้​รั่ว​ไป​อยู่​ผิด​ที่ จะ​ทำให้ domain อ่าน​ยาก​และ ละเมิด​หลัก Single Responsibility Principle (SRP) เพราะ class หนึ่ง​ต้อง​ไป​รับรู้ invariant ของ​อีก class หนึ่ง

Evans จึง​แนะนำ​ให้ แยก “การ​ใช้” ออก​จาก “การ​สร้าง” (separate use from construction) แล้ว​ห่อ​หุ้ม​ตรรกะ​การ​สร้าง​ไว้​ใน factory object เมื่อ​ตรรกะ​นั้น​ซับซ้อน​หรือ​เมื่อ​มัน​สื่อ​ความหมาย​ได้​ดี​กว่า​เดิม การ​สร้าง object ไม่ใช่ “เรื่อง​ของ domain” โดยตรง แต่​ก็​อาศัย​อยู่​ใน domain layer — คุณ​แทบ​ไม่​เคย​พูด​เรื่อง factory กับ domain expert แต่​มัน​ก็​มี​บทบาท​สำคัญ​อยู่​เบื้องหลัง

หัวใจ​ของ Factory

FactoryFactoryตัว​ที่​ห่อ​หุ้ม​ตรรกะ​การ “สร้าง” Aggregate ที่​ซับซ้อน และ​รับประกัน​ว่าอ็อบเจ็กต์ใหม่​ถูกต้อง​ตาม invariant ตั้งแต่​แรก​เกิด — “สร้าง​ของ​ใหม่” (repository หา “ของ​เก่า”)Tactical Design ทำ​หน้าที่ ซ่อน​ความ​ซับซ้อน​ของ​การ​สร้าง object — ตัดสิน​ใจ​ว่า​จะ​สร้าง class ไหน ตรวจสอบ invariant และ​สื่อ “เจตนา (intent)” ของ​การ​สร้าง​แต่ละ​แบบ​ให้​ชัดเจน ซึ่ง​เป็น​สิ่ง​ที่ constructor เปล่าๆ ทำได้​ยาก จำหลักสั้นๆ ไว้​ว่า Factory สร้าง “ของ​ใหม่” ส่วน RepositoryRepositoryตัว​ที่​ทำให้​เรา​เข้าถึง Aggregate ราวกับ​เป็น collection ใน​หน่วย​ความ​จำ ซ่อน​รายละเอียด​ฐาน​ข้อมูล มี1 repository ต่อ1 Aggregate RootTactical Design หา “ของ​เก่า”

วัตถุประสงค์​หลัก​ของ factory pattern คือ ซ่อน​ความ​ซับซ้อน​ของ​การ​สร้าง object โดย​ความ​ซับซ้อน​นั้น​อาจ​เป็นการ​ตัดสิน​ใจ​ว่า​จะ instantiate class ไหน (เมื่อ client ทำงาน​กับ abstraction) หรือ​อาจ​เป็นการ​ตรวจสอบ invariant ส่วน​วัตถุประสงค์​รอง​คือ สื่อ​เจตนา​เบื้องหลัง​การ​สร้าง​ที่​มี​หลาย​รูปแบบ ให้​ชัดเจน

รูปแบบ​มาตรฐาน​คือ​มี static method ที่​คืน​ค่า​เป็น abstract class หรือ interface โดย client ส่ง​ข้อมูล​บาง​อย่าง​เข้าไป แล้ว factory ใช้​ข้อมูล​นั้น​ตัดสิน​ใจ​ว่า​จะ​สร้าง subclass ตัว​ไหน​คืน​กลับ ผล​คือ client ไม่​จำเป็น​ต้อง​รู้​เลย​ว่า dependent class ถูก​สร้าง​ขึ้น​อย่างไร — เป็น​ไป​ตาม​หลัก Dependency Inversion Principle และ​ยัง​ได้​ประโยชน์​เพิ่มเติม​คือ รวม0 code การ​สร้าง ไว้​ที่​เดียว หาก​วิธี​สร้าง​เปลี่ยน ก็​แก้​เพียง​จุด​เดียว​โดย​ไม่​กระทบ code ที่​เรียก​ใช้


เมื่อ​ต้อง​เพิ่ม element เข้าไป​ใน Aggregate สิ่ง​สำคัญ​คือ อย่า​เปิดเผย​โครงสร้าง​ภายใน​ของ Aggregate ลอง​ดู​ปัญหา​ก่อน: สมมติ application service ต้อง​รู้​รายละเอียด​การ​ประกอบ BasketItem เอง​ทั้งหมด

AddProductToBasket.cs (ก่อน​แก้ — service รู้มาก​เกิน​ไป)
public class AddProductToBasket
{
public void Add(Product product, Guid basketId)
{
var basket = _basketRepository.FindBy(basketId);
// service ต้องรู้กฎการประกอบ BasketItem เอง — ไม่ใช่หน้าที่มัน
var rate = TaxRateService.ObtainTaxRateFor(product.Id, country.Id);
var item = new BasketItem(rate, product.Id, product.price);
basket.Add(item);
}
}

Application serviceApplication Serviceบริการ​ที่ “ประสาน​งาน” use case — จัดการ transaction, ความ​ปลอดภัย, การ​บันทึก, แปลง DTO — แต่​ไม่มี​กฎ​ธุรกิจ​อยู่​ใน​ตัว​มัน​เองTactical Design ควร​มีหน้าที่ ประสาน​งาน (coordination) เท่านั้น ไม่ใช่​ไป​รับรู้​ว่า BasketItem ประกอบ​ขึ้น​อย่างไร เรา​เลี่ยง​การ​รั่วไหล​ของ​โครงสร้าง​ภายใน​ได้​ด้วย​การ​ย้าย​ความ​รับผิดชอบ​กลับ​เข้าไป​ใน domain layer — ให้ Basket เอง​เป็น​ผู้รับ product แล้ว​จัดการ​เพิ่ม​รายการ​ภายใน

Basket.cs (ย้าย​ความ​รับผิดชอบ​เข้า domain layer)
public class AddProductToBasket
{
public void Add(Product product, Guid basketId)
{
var basket = _basketRepository.FindBy(basketId);
basket.Add(product); // client เรียบง่ายลงมาก ไม่รู้โครงสร้างภายใน
}
}
public class Basket // domain layer
{
public void Add(Product product)
{
if (Contains(product))
GetItemFor(product).IncreaseItemQuantityBy(1);
else
{
var rate = TaxRateService.ObtainTaxRateFor(product.Id, country.Id);
_items.Add(new BasketItem(rate, product.Id, product.price));
}
}
}

ตอน​นี้ Basket ใน​ฐานะ Aggregate รักษา​ความ​ถูกต้อง​ของ collection ภายใน​ได้​เอง เพราะ​มัน​บังคับ invariant ของ​ตัวเอง​ได้ แต่​ยัง​เหลือ​ปัญหา​อยู่: Basket ต้อง​รับผิดชอบ​การ​สร้าง dependency ที่​มัน​ไม่​ได้​เป็น​เจ้าของ (อย่าง tax rate) เพราะ​มัน​ต้อง​รู้​ทั้ง​วิธี​ประกอบ item ที่​ถูกต้อง​และ​รู้​ว่า​จะ​หา tax rate ที่​ถูกต้อง​ได้​จาก​ที่​ใด — กลาย​เป็น​ความ​รับผิดชอบ​ส่วนเกิน

ทาง​แก้​คือ แทรก factory object เข้า​มา​ห่อ​หุ้ม​การ​สร้าง BasketItem รวม​ถึง​การ​ไป​ดึง tax rate ที่​ถูกต้อง

BasketItemFactory.cs (delegate การ​สร้าง​ให้ factory)
public class Basket
{
public void Add(Product product)
{
if (Contains(product))
GetItemFor(product).IncreaseItemQuantityBy(1);
else
_items.Add(BasketItemFactory.CreateItemFor(product, deliveryAddress));
}
}
public class BasketItemFactory
{
public static BasketItem CreateItemFor(Product product, Country country)
{
var rate = TaxRateService.ObtainTaxRateFor(product.Id, country.Id);
return new BasketItem(rate, product.Id, product.price);
}
}

ถ้า​วิธี​คำนวณ tax rate เปลี่ยน หรือ BasketItem ต้องการ​ข้อมูล​ชนิด​อื่น​เพิ่ม class Basket จะ​ไม่​ได้​รับ​ผลกระทบ​เลย

อย่า​ยัด​ตรรกะหนักๆ ลงใน constructor

คุณ “อาจ” เรียก service ภายใน constructor ของ BasketItem ได้ แต่​นั่น​เป็น​แนวทาง​ที่​ไม่​ดี — constructor ควร​เรียบ​ง่าย​เข้า​ไว้ หาก​คุณ​พบ​ว่า​มี code ซับซ้อน​แฝง​อยู่​ใน constructor นั่น​มัก​เป็น​สัญญาณ​ว่า​คุณ​ต้องการ Factory

🛒 ตะกร้า​สินค้า: Factory ปกป้อง Aggregate

ใน​ระบบ e-commerce Basket คือ Aggregate ส่วน​การ​ประกอบ BasketItem ที่​ถูกต้อง (ต้อง​มี tax rate ตาม​ประเทศ​ปลายทาง) คือ​ตรรกะ​ที่​ซับซ้อน เมื่อ​ย้าย​มัน​ไป​ไว้​ที่ BasketItemFactory ทั้ง application service และ​ตัว Basket เอง​ก็​ไม่​ต้อง​แบก​ความ​รู้เรื่อง​ภาษี​อีก​ต่อ​ไป — แต่ละ​ส่วน​รับผิดชอบ​หน้าที่​เดียว​อย่าง​ชัดเจน


Factory ใน domain layer ยัง​ใช้ ซ่อน​การ​ตัดสิน​ใจ​เลือก​ชนิด (type) ได้ เมื่อ​มี​ตัว​เลือก​หลาย​แบบ​และ​การ​เลือก​นั้น​ไม่ใช่​หน้าที่​ของ client โดย client เขียน code อิง​กับ interface หรือ abstract class แล้ว​ปล่อย​ให้ factory เป็น​ผู้​สร้าง concrete type ที่​เหมาะสม

ลอง​ดู​ตัวอย่าง​จาก​โลก​การ​ขนส่ง: Order สามารถ​สร้าง Consignment (พัสดุ​ที่​จะ​ส่ง) ได้ — ซึ่ง​ตัว method นี้​ก็​คือ factory method ใน​ตัวเอง ที่​น่า​สนใจ​คือ​การ​จะ​สร้าง consignment ที่​ถูกต้อง​นั้น​ต้อง​เลือก ผู้​ขนส่ง (Courier) แต่ Order ไม่รู้​ว่า​ควร​ใช้ courier ราย​ใด จึง delegate งาน​นี้​ให้ CourierFactory แล้ว​ทำงาน​กับ abstract class Courier

CourierFactory.cs (เลือก subclass ที่​ถูกต้อง)
public class Order
{
public Consignment CreateFor(IEnumerable<Item> items, DeliveryAddress destination)
{
var courier = CourierFactory.GetCourierFor(items, destination.Country);
var consignment = new Consignment(items, destination, courier);
SetAsDispatched(items, consignment);
return consignment;
}
}
public class CourierFactory
{
public static Courier GetCourierFor(
IEnumerable<Item> consignmentItems, DeliveryAddress destination)
{
if (AirMail.CanDeliver(consignmentItems, destination))
return new AirMail(consignmentItems, destination);
if (TrackedService.CanDeliver(consignmentItems, destination))
return new TrackedService(consignmentItems, destination);
return new StandardMail(consignmentItems, destination);
}
}

CourierFactory ถาม implementation แต่ละ​ตัว​ว่า​รับ​งาน​นี้​ได้​ไหม (CanDeliver) แล้ว​คืน concrete type ที่​เหมาะสม — Order ไม่​ต้อง​รู้​เลย​ว่า​มี courier กี่​ชนิด​หรือ​เลือก​อย่างไร

🚢 เลือก​ผู้​ขนส่ง​ตาม​เส้นทาง

ใน​ระบบ​ขนส่ง​สินค้า การ​เลือก​ว่า​จะ​ส่ง​พัสดุ​ด้วย AirMail, TrackedService หรือ StandardMail ขึ้น​อยู่​กับ​น้ำหนัก ปลายทาง และ​ข้อจำกัดอื่นๆ — เป็นการ​ตัดสิน​ใจ​ที่ Order ไม่​ควร​ต้อง​แบก​ไว้ ปล่อย​ให้ CourierFactory เป็น​ผู้​ตัดสิน​ใจ แล้ว Order ทำงาน​กับ abstraction Courier เท่านั้น ทำให้​เพิ่ม​ผู้​ขนส่ง​ราย​ใหม่​ได้​โดย​ไม่​ต้อง​แตะ Order


Factory ไม่​จำเป็น​ต้อง​เป็น static class แยก​ต่างหาก​เสมอ​ไป — factory method สามารถ​อยู่​บน Aggregate เอง​ได้ เพื่อ​ซ่อน​ความ​ซับซ้อน​ของ​การ​สร้าง​จาก client

ตัวอย่าง: Basket เปิด​ความ​สามารถ “ย้าย​สินค้า​ไป Wish List” โดย​แปลง BasketItem ให้​เป็น WishListItem (ซึ่ง​เป็น Entity ของ Aggregate WishList) ตัว Basket ไม่​ได้​ข้อง​เกี่ยว​กับ WishListItem หลัง​สร้าง​เสร็จ แต่​มัน​ถือ “ข้อมูล” ที่​จำเป็นต่อ​การ​สร้าง​อยู่ — การ​วาง factory method ไว้​ตรง​นี้​จึง​เป็น​ธรรมชาติ และ client ก็​ไม่​ต้อง​รู้​วิธี​ดึง​ข้อมูล​ออก​จาก BasketItem มา​ประกอบ​เป็น WishListItem เอง

Basket.cs (factory method แปลง​ข้าม Aggregate)
public class Basket
{
public WishListItem MoveToWishList(Product product)
{
if (BasketContainsAnItemFor(product))
{
var wishListItem = WishListItemFactory.CreateFrom(GetItemFor(product));
RemoveItemFor(product);
return wishListItem;
}
}
}

factory method ยัง​สามารถ สร้าง Aggregate ตัว​ใหม่ ได้​ด้วย เช่น ใช้ Account สร้าง Order โดย​ตรวจสอบ​ก่อน​ว่า​มี credit เพียงพอ​หรือ​ไม่ — นี่​คือ​จุด​ที่ factory method ช่วย “บังคับ invariant ตั้งแต่​แรก​เกิด” ได้​อย่าง​สวยงาม

Account.cs (factory method สร้าง Aggregate พร้อม​บังคับ invariant)
public class Account
{
public Order CreateOrder()
{
if (HasEnoughCreditToOrder())
return new Order(this.Id, this.PaymentMethod, this.Address);
else
throw new InsufficientCreditToCreateAnOrder();
}
// factory method อีกตัวที่สื่อ "เจตนา" ต่างออกไป — ข้ามการเช็ก credit
public Order CreateAnOrderIgnoringCreditRating()
{
return new Order(this.Id, this.PaymentMethod,
this.Address, PaymentType.PayBeforeShipping);
}
}
ชื่อ method สื่อ​เจตนา​ดี​กว่า constructor หลาย​ตัว

หาก Order มี constructor หลาย​ตัว​เพื่อ​จุด​ประสงค์​ต่าง​กัน ย่อม​เข้าใจ​ยากว่า​ตัว​ไหน​ทำ​อะไร แต่​เมื่อ​ใช้ factory method ที่​ตั้ง​ชื่อ​สื่อ​ความหมาย — CreateOrder() กับ CreateAnOrderIgnoringCreditRating()เจตนา​ก็​ชัดเจน​ใน​ทันที นี่​คือ​คุณค่า​หลัก​อย่าง​หนึ่ง​ของ Factory


หาก​คุณ​ไม่​ได้​ใช้ ORM ที่ map data model เข้า​กับ domain model ได้​โดยตรง​ด้วย reflection หรือ​คุณ​ดึง domain object มา​จาก​ระบบ​เก่า (legacy) ผ่าน web service หรือ file flat คุณ​จำเป็น​ต้อง ปั้น (reconstitute) domain object กลับ​มา​พร้อม​รับประกัน​ว่า invariant ครบถ้วน ซึ่ง​การ reconstitute ด้วย factory จะ​ซับซ้อน​กว่า​การ​สร้าง​ของ​ใหม่​อยู่​เล็กน้อย

ใน​ตัวอย่าง​นี้ RepositoryRepositoryตัว​ที่​ทำให้​เรา​เข้าถึง Aggregate ราวกับ​เป็น collection ใน​หน่วย​ความ​จำ ซ่อน​รายละเอียด​ฐาน​ข้อมูล มี1 repository ต่อ1 Aggregate RootTactical Design ดึง persistence model ของ basket มา​ใน​รูป raw data ล้วนๆ แล้ว delegate ให้ BasketFactory (ซึ่ง​อยู่​ใน domain layer) ปั้น Basket ขึ้น​มา​จาก raw data นั้น

BasketFactory.cs (Factory ใช้​ปั้น object จาก raw data)
public class BasketRepository : IBasketRepository // infrastructure
{
public Basket FindBy(Guid id)
{
BasketDTO rawData = ExternalService.ObtainBasket(id.ToString());
return BasketFactory.ReconstituteBasketFrom(rawData);
}
}
public class BasketFactory // domain layer
{
public static Basket ReconstituteBasketFrom(BasketDTO rawData)
{
Basket basket;
// ...
var deliveryOption = DeliveryFactory.Create(rawData.DeliveryOptionId);
if (deliveryOption.CanBeUsedFor(rawData.Items))
basket.Set(deliveryOption);
else
// ใช้ Null Object pattern แทนการ throw เพื่อให้ผู้ใช้เลือกใหม่ได้
basket.Set(DeliveryFactory.CreateNonChosen());
return basket;
}
}

BasketFactory สร้าง DeliveryOption จาก raw data แล้ว​ตรวจสอบ​ว่า​ใช้​กับ item ใน​ตะกร้า​นี้​ได้​หรือ​ไม่ หาก ใช้​ไม่​ได้ มัน​ก็​จะ​ไม่​ใช้​ตัว​นั้น แต่​หัน​ไป​ใช้ Null Object pattern เพื่อ​บังคับ​ให้​มี​การ​เลือก DeliveryOption ใหม่​แทน

reconstitution กับ​ความ​เป็น​จริง​ของ​ข้อมูล​ที่ persist แล้ว

เมื่อ​ปั้น Aggregate กลับ​มา การ​รักษา invariant ถือ​เป็น​เรื่อง​สำคัญ​มาก — แต่​ก็​ต้อง​ยอมรับ​ความ​จริง​ว่า object นั้น “มี​ตัวตน​อยู่​ใน​สถานะ​ที่​ถูก​บันทึก​ไว้​แล้ว” ทาง​เลือก​หนึ่ง​คือ throw exception แล้ว​เปิด workflow ซ่อมแซม แต่​ใน​กรณี​ข้าง​ต้น การ​ปั้น Aggregate ขึ้น​มา​แล้ว​แทนที่ delivery option ด้วย placeholder (Null Object) ให้​ผู้​ใช้​เลือก​ใหม่ ง่าย​และ​สมจริง​กว่า


Factory ช่วย ลด​ความ​รก​ของ domain model ให้​ยัง​คง​สื่อ​ความหมาย​ได้​ดี แต่ อย่า​ใช้​อย่าง​พร่ำเพรื่อ​ทุก​ครั้ง​ที่​ต้อง instantiate object ควร​ใช้ factory เมื่อ:

  • มัน​สื่อ​ความหมาย​ได้​ดี​กว่า constructor เปล่าๆ
  • มัน​ให้​ความ​สะดวก​เมื่อ​มี constructor หลาย​ตัว​จน​ชวน​สับสน
  • element ที่​ต้อง​ใช้​ใน​ตรรกะ​การ​สร้าง ไม่ใช่​หน้าที่ ของ dependent class
flowchart TD
    Q1{ตรรกะการสร้างซับซ้อน<br/>หรือต้องเช็ก invariant?}
    Q2{มี constructor หลายตัว<br/>จนเจตนาไม่ชัด?}
    Q3{client ไม่ควรรู้ว่าจะ<br/>สร้าง subclass ไหน?}
    USE[ใช้ Factory]
    CTOR[constructor ธรรมดาก็พอ]

    Q1 -->|ใช่| USE
    Q1 -->|ไม่| Q2
    Q2 -->|ใช่| USE
    Q2 -->|ไม่| Q3
    Q3 -->|ใช่| USE
    Q3 -->|ไม่| CTOR
สรุป​ประเด็น​แก่น​ของ​บท​นี้
  • Factory แยก “การ​ใช้” ออก​จาก “การ​สร้าง” (separate use from construction) ภายใน domain
  • ความ​รู้​ใน​การ​สร้าง domain object ที่​ซับซ้อน​ถูก ห่อ​หุ้ม​ไว้​จาก client และ domain object อื่น ได้​ด้วย factory
  • Factory เลือก​สร้าง instance ที่​เหมาะสม​ตาม​ความ​ต้องการ​ของ​ผู้​เรียก​ได้ โดย​ซ่อน​การ​ตัดสิน​ใจ​เลือก subclass ไว้​เบื้องหลัง
  • factory method ห่อ​หุ้ม​สถานะ​ภายใน​ของ Aggregate ได้​ใน​ตัว
  • เมื่อ​มี constructor หลาย​ตัว​เพื่อ​จุด​ประสงค์​ต่าง​กัน ให้​ใช้ factory ที่​ตั้ง​ชื่อ​สื่อ​ความหมาย​เพื่อ​ให้​เจตนา​ชัดเจน​ยิ่ง​ขึ้น
🔗 อ้างอิง​เพิ่มเติม​ใน DevIQ

เจาะ​ลึก​แนวคิด​ใน​บท​นี้​ต่อ​ได้ที่​คลัง​อ้างอิง DevIQ:

  • Factory Method — GoF pattern ต้นทาง​ของ​แนวคิด factory method ที่​บท​นี้​ใช้​ห่อ​หุ้ม​การ​สร้าง object และ​บังคับ invariant ตั้งแต่​แรก​เกิด
  • Abstract Factory — ใกล้​เคียง​กับ​กรณี CourierFactory ที่​ต้อง​เลือก​สร้าง concrete subclass (AirMail, TrackedService, StandardMail) โดยที่ client ทำงาน​กับ abstraction เท่านั้น
  • Aggregate — Factory มีหน้าที่​สร้าง​และ reconstitute Aggregate พร้อม​รับประกัน​ว่า invariant ของ​มัน​ถูกต้อง​ตั้งแต่​ถือ​กำเนิด
  • Repository — คู่หู​ของ Factory ตาม​หลัก “Factory สร้าง​ของ​ใหม่ ส่วน Repository หา​ของ​เก่า” ที่​บท​นี้​ใช้​เป็น​หลัก​จำ
  • Null Object — เทคนิค​ที่ BasketFactory ใช้​แทน delivery option ที่​ใช้​ไม่​ได้​ตอน reconstitute แทน​การ throw exception

เช็กความเข้าใจ — บทที่ 20

ข้อ 1 / 4

วัตถุประสงค์ 'หลัก' ของ Factory pattern ตามหนังสือคืออะไร?