บทนำ: จะให้ความรับผิดชอบแก่ class ไหน?
SOLID เป็นหลักการสูง ที่บอกว่า “ควรเป็นแบบนี้” แต่ GRASP เป็นคำแนะนำที่ปฏิบัติได้จริง – มันตอบคำถาม “ในสถานการณ์นี้ ควรให้ class ไหนรับผิดชอบสิ่งนี้?”
GRASP มี 9 หลักการ แต่ในที่นี้เราจะศึกษา 5 หลักการหลัก ที่ใช้บ่อยที่สุด
GRASP: 5 หลักการหลัก
text1. Creator - ใครควรสร้าง object?
2. Information Expert - ใครควรเก็บข้อมูล?
3. Low Coupling - ลดการขึ้นต่อกัน
4. High Cohesion - เพิ่มความสัมพันธ์ภายใน
5. Controller - ใครควรควบคุมการไหลของการทำงาน?
1. Creator: ใครควรสร้าง Object?
หลักการ
Creator = ถ้า class A มี method ที่ใช้หรือสร้าง class B หลายครั้ง และ A “สำเร็จการศึกษา” ด้วยการใช้ B แล้ว A ควรจะสร้าง B
❌ ไม่ดี: สร้างผิดที่
java// ❌ ไม่ดี: Client สร้าง object
public class Order {
private List<OrderItem> items;
public Order() {
this.items = new ArrayList<>();
}
public void addItem(String productID, int quantity) {
items.add(new OrderItem(productID, quantity));
}
}
public class Client {
public static void main(String[] args) {
// Client เป็นคนสร้าง Order
Order order = new Order();
order.addItem("P001", 2);
// Client เองต้องรู้ว่า OrderItem เป็นอะไร
// ถ้า OrderItem เปลี่ยน constructor Client ต้องแก้ด้วย
}
}
✓ ดี: สร้างในที่เหมาะสม
java// ✓ ดี: Order เป็นคนสร้าง OrderItem
public class OrderItem {
private String productID;
private int quantity;
public OrderItem(String productID, int quantity) {
this.productID = productID;
this.quantity = quantity;
}
}
public class Order {
private List<OrderItem> items;
public Order() {
this.items = new ArrayList<>();
}
// Order เป็นคนสร้าง OrderItem
public void addItem(String productID, int quantity) {
OrderItem item = new OrderItem(productID, quantity);
items.add(item);
}
public int getItemCount() {
return items.size();
}
}
public class Client {
public static void main(String[] args) {
// Client เพียง "ใช้" Order ไม่ต้องรู้ OrderItem
Order order = new Order();
order.addItem("P001", 2);
order.addItem("P002", 1);
System.out.println("จำนวนสินค้า: " + order.getItemCount());
}
}
Output:
textจำนวนสินค้า: 2
ข้อดี:
- Client ไม่ต้องรู้รายละเอียด OrderItem
- Order เป็นผู้เชี่ยวชาญในการสร้าง OrderItem
- เปลี่ยน OrderItem ไม่ต้องแก้ Client
2. Information Expert: ใครควรเก็บข้อมูล?
หลักการ
Information Expert = class ที่เก็บข้อมูล (data) ควรเป็นคนทำการดำเนินการกับข้อมูลนั้นๆ
❌ ไม่ดี: ถามข้อมูล แล้วไปคำนวณที่อื่น
java// ❌ ไม่ดี: Order ให้ข้อมูลออก คนอื่นไปคำนวณ
public class OrderItem {
private double price;
private int quantity;
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
}
public class Order {
private List<OrderItem> items;
// ให้ข้อมูลออก
public List<OrderItem> getItems() { return items; }
}
public class InvoiceService {
// ต้องรู้รายละเอียด OrderItem เพื่อคำนวณ
public double calculateTotal(Order order) {
double total = 0;
for (OrderItem item : order.getItems()) {
total += item.getPrice() * item.getQuantity();
}
return total;
}
}
✓ ดี: ให้ class เก็บข้อมูลทำการคำนวณเอง
java// ✓ ดี: OrderItem รู้วิธีคำนวณของตัวเอง
public class OrderItem {
private double price;
private int quantity;
public OrderItem(double price, int quantity) {
this.price = price;
this.quantity = quantity;
}
// OrderItem เป็นผู้เชี่ยวชาญ คำนวณราคาของตัวเอง
public double getSubtotal() {
return price * quantity;
}
}
public class Order {
private List<OrderItem> items;
public Order() {
this.items = new ArrayList<>();
}
public void addItem(OrderItem item) {
items.add(item);
}
// Order รู้วิธีคำนวณ total ของตัวเอง
public double getTotal() {
double total = 0;
for (OrderItem item : items) {
total += item.getSubtotal();
}
return total;
}
}
public class InvoiceService {
// ง่ายมาก เพียงขออ total จาก Order
public void printInvoice(Order order) {
System.out.println("รวมทั้งสิ้น: " + order.getTotal() + " บาท");
}
}
public class Main {
public static void main(String[] args) {
Order order = new Order();
order.addItem(new OrderItem(100, 2));
order.addItem(new OrderItem(50, 3));
InvoiceService invoice = new InvoiceService();
invoice.printInvoice(order);
}
}
Output:
textรวมทั้งสิ้น: 350.0 บาท
ข้อดี:
- OrderItem รู้ข้อมูลของตัวเอง จึงควรคำนวณเอง
- InvoiceService ไม่ต้องรู้รายละเอียด
- เปลี่ยนวิธีคำนวณ ทำได้ใน OrderItem เท่านั้น
3. Low Coupling: ลดการขึ้นต่อกัน
หลักการ
Low Coupling = ลดจำนวน “ความเชื่อมโยง” ระหว่าง classes ให้น้อยที่สุด
❌ ไม่ดี: High Coupling (ผูกกันแน่น)
java// ❌ ไม่ดี: PersonService ผูกตรงกับ DatabaseConnection
public class DatabaseConnection {
public void connect() {
System.out.println("เชื่อมต่อฐานข้อมูล");
}
public void executeSql(String sql) {
System.out.println("รัน SQL: " + sql);
}
}
public class PersonService {
private DatabaseConnection db = new DatabaseConnection(); // ← ผูกตรงๆ
public void savePerson(String name) {
db.connect();
db.executeSql("INSERT INTO persons VALUES ('" + name + "')");
}
}
// ปัญหา: ถ้าต้องเปลี่ยนจาก DatabaseConnection เป็น OtherDatabase
// ต้องแก้ PersonService
✓ ดี: Low Coupling (ผ่าน interface)
java// ✓ ดี: PersonService ขึ้นอยู่กับ interface
public interface DataStore {
void save(String entity, String data);
}
public class DatabaseConnection implements DataStore {
@Override
public void save(String entity, String data) {
System.out.println("บันทึก " + entity + " ลงฐานข้อมูล");
}
}
public class FileDataStore implements DataStore {
@Override
public void save(String entity, String data) {
System.out.println("บันทึก " + entity + " ลงไฟล์");
}
}
public class PersonService {
private DataStore dataStore; // ← ใช้ interface
public PersonService(DataStore dataStore) {
this.dataStore = dataStore;
}
public void savePerson(String name) {
dataStore.save("Person", name);
}
}
public class Main {
public static void main(String[] args) {
// ใช้ฐานข้อมูล
PersonService service1 = new PersonService(new DatabaseConnection());
service1.savePerson("สมชาย");
// เปลี่ยนเป็นไฟล์ ไม่ต้องแก้ PersonService
PersonService service2 = new PersonService(new FileDataStore());
service2.savePerson("สมชาย");
}
}
Output:
textบันทึก Person ลงฐานข้อมูล
บันทึก Person ลงไฟล์
ข้อดี:
- PersonService ไม่ผูกกับ DatabaseConnection เพียงสิ่งเดียว
- เปลี่ยน DataStore ได้ง่าย
- ง่ายต่อการทดสอบ (test ด้วย mock DataStore)
4. High Cohesion: เพิ่มความสัมพันธ์ภายใน
หลักการ
High Cohesion = elements ที่อยู่ใน class ควร “เกี่ยวข้องกันมาก” ทำให้ class มี focus ชัด
❌ ไม่ดี: Low Cohesion
java// ❌ ไม่ดี: Utility class ที่ไม่มี focus
public class Utilities {
// ความรับผิดชอบที่ไม่เกี่ยวข้องกัน
public void sendEmail(String to, String message) {
System.out.println("ส่ง email");
}
public void calculateTax(double amount) {
System.out.println("คำนวณ tax");
}
public void generateReport(String data) {
System.out.println("สร้าง report");
}
public void connectDatabase() {
System.out.println("เชื่อมต่อฐานข้อมูล");
}
}
✓ ดี: High Cohesion
java// ✓ ดี: แยกความรับผิดชอบ แต่ละ class มี focus
public class EmailService {
public void send(String to, String message) {
System.out.println("ส่ง email ไปยัง: " + to);
}
}
public class TaxCalculator {
public double calculate(double amount) {
System.out.println("คำนวณ tax");
return amount * 0.07;
}
}
public class ReportGenerator {
public void generate(String data) {
System.out.println("สร้าง report: " + data);
}
}
public class DatabaseConnection {
public void connect() {
System.out.println("เชื่อมต่อฐานข้อมูล");
}
}
public class Main {
public static void main(String[] args) {
EmailService email = new EmailService();
email.send("[email protected]", "Hello");
TaxCalculator tax = new TaxCalculator();
System.out.println("Tax: " + tax.calculate(1000));
ReportGenerator report = new ReportGenerator();
report.generate("Sales Report");
}
}
Output:
textส่ง email ไปยัง: [email protected]
คำนวณ tax
Tax: 70.0
สร้าง report: Sales Report
ข้อดี:
- แต่ละ class มีความรับผิดชอบชัดเจน
- เข้าใจง่าย ดูแลรักษาง่าย
- ง่ายต่อการเปลี่ยน/เพิ่ม
5. Controller: ใครควบคุม Flow?
หลักการ
Controller = มีคนคนหนึ่ง (หรือ class หนึ่ง) ควบคุมการไหลของการทำงานทั้งระบบ
❌ ไม่ดี: Flow ระเลาะระลวง
java// ❌ ไม่ดี: การไหลของงาน กระจายไปทั่ว
public class Main {
public static void main(String[] args) {
// การไหลของงานใน Main - ยุ่งยากมาก
// Step 1: สร้าง order
Order order = new Order();
order.addItem("P001", 2);
// Step 2: ตรวจสอบ inventory
InventoryService inv = new InventoryService();
inv.check("P001", 2);
// Step 3: คำนวณ tax
TaxService tax = new TaxService();
double total = 1000;
double taxAmount = tax.calculate(total);
// Step 4: ประมวลผลการชำระเงิน
PaymentService payment = new PaymentService();
payment.process(total + taxAmount);
// Step 5: บันทึกการสั่งซื้อ
OrderRepository repo = new OrderRepository();
repo.save(order);
// ถ้า flow เปลี่ยน ต้องแก้ Main
}
}
✓ ดี: มี Controller รวบรวม
java// ✓ ดี: มี OrderController เป็นตัวควบคุม
public class OrderController {
private OrderService orderService;
private InventoryService inventoryService;
private PaymentService paymentService;
public OrderController(OrderService os, InventoryService is, PaymentService ps) {
this.orderService = os;
this.inventoryService = is;
this.paymentService = ps;
}
// Controller เป็นตัวควบคุม flow ทั้งหมด
public void checkout(Order order) {
// Step 1: ตรวจสอบ inventory
if (!inventoryService.isAvailable(order)) {
System.out.println("❌ สินค้าไม่เพียงพอ");
return;
}
// Step 2: ประมวลผลการชำระเงิน
double total = orderService.calculateTotal(order);
if (!paymentService.process(total)) {
System.out.println("❌ ชำระเงินล้มเหลว");
return;
}
// Step 3: บันทึกการสั่งซื้อ
orderService.saveOrder(order);
System.out.println("✅ สั่งซื้อสำเร็จ");
}
}
public class OrderService {
public double calculateTotal(Order order) {
return order.getTotal();
}
public void saveOrder(Order order) {
System.out.println("บันทึกการสั่งซื้อ");
}
}
public class InventoryService {
public boolean isAvailable(Order order) {
System.out.println("ตรวจสอบ inventory");
return true;
}
}
public class PaymentService {
public boolean process(double amount) {
System.out.println("ประมวลผลการชำระเงิน: " + amount);
return true;
}
}
public class Main {
public static void main(String[] args) {
Order order = new Order();
order.addItem("P001", 2);
OrderController controller = new OrderController(
new OrderService(),
new InventoryService(),
new PaymentService()
);
controller.checkout(order);
}
}
Output:
textตรวจสอบ inventory
ประมวลผลการชำระเงิน: 200.0
บันทึกการสั่งซื้อ
✅ สั่งซื้อสำเร็จ
ข้อดี:
- Flow ของการทำงานอยู่ใน Controller เพียงที่เดียว
- ง่ายต่อการเปลี่ยนแปลง
- เห็นความไหลของระบบชัดเจน
ตัวอย่างรวม: Application ที่ใช้ GRASP
java// Entity
public class Product {
private String id;
private String name;
private double price;
public Product(String id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
public String getId() { return id; }
public String getName() { return name; }
public double getPrice() { return price; }
}
// Creator Pattern
public class ShoppingCart {
private List<CartItem> items = new ArrayList<>();
public void addItem(Product product, int quantity) {
// ShoppingCart เป็นคนสร้าง CartItem
CartItem item = new CartItem(product, quantity);
items.add(item);
}
// Information Expert
public double getTotal() {
double total = 0;
for (CartItem item : items) {
total += item.getSubtotal();
}
return total;
}
}
// CartItem
public class CartItem {
private Product product;
private int quantity;
public CartItem(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
}
public double getSubtotal() {
return product.getPrice() * quantity;
}
}
// Data Store Interface - Low Coupling
public interface CartRepository {
void save(ShoppingCart cart);
}
public class DatabaseCartRepository implements CartRepository {
@Override
public void save(ShoppingCart cart) {
System.out.println("บันทึก shopping cart ลงฐานข้อมูล");
}
}
// Controller - High Cohesion
public class CheckoutController {
private CartRepository cartRepository;
public CheckoutController(CartRepository repo) {
this.cartRepository = repo;
}
public void processCheckout(ShoppingCart cart) {
System.out.println("=== Checkout ===");
System.out.println("ราคารวม: " + cart.getTotal() + " บาท");
cartRepository.save(cart);
System.out.println("✅ ชำระเงินสำเร็จ");
}
}
// Client - ง่ายต่อการใช้งาน
public class Main {
public static void main(String[] args) {
Product p1 = new Product("P001", "Notebook", 50);
Product p2 = new Product("P002", "Pen", 10);
ShoppingCart cart = new ShoppingCart();
cart.addItem(p1, 2);
cart.addItem(p2, 3);
CheckoutController checkout = new CheckoutController(
new DatabaseCartRepository()
);
checkout.processCheckout(cart);
}
}
Output:
text=== Checkout ===
ราคารวม: 130.0 บาท
บันทึก shopping cart ลงฐานข้อมูล
✅ ชำระเงินสำเร็จ
สรุป
GRASP เป็นชุดของ “คำแนะนำในการออกแบบ” ที่ช่วยให้เราตัดสินใจได้ว่า “ความรับผิดชอบของสิ่งนี้ควรให้ใคร?”
- Creator: ใครควรสร้าง object → class ที่ใช้มากที่สุด
- Information Expert: ใครควรทำการดำเนินการ → class ที่เก็บข้อมูล
- Low Coupling: ลดการขึ้นต่อกัน → ใช้ interface แทน concrete class
- High Cohesion: เพิ่มความสัมพันธ์ → class ควร focus กับสิ่งเดียว
- Controller: ใครควบคุม flow → มี class เดียวควบคุม flow โหลก
เมื่อคุณปฏิบัติตาม GRASP อย่างสม่ำเสมอ code ของคุณจะเป็นระบบที่ “ทำความหน้าที่ของตัวเอง” ได้อย่างสิ้นสุด ไม่พึ่งพา class อื่น มากเกินไป และง่ายต่อการแก้ไขหรือเพิ่มเติมในอนาคต นี่คือสถาปัตยกรรมที่ดีของ object-oriented program
