> For the complete documentation index, see [llms.txt](https://www.saladpuk.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.saladpuk.com/basic/solid/ocp.md).

# Open/Closed Principle

## 👑 หัวใจหลักของ Open/Closed Principle (OCP)

> Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

**"การออกแบบซอฟต์แวร์ จะต้องเพิ่มความสามารถใหม่ๆให้ซอฟต์แวร์เราได้ โดยที่ห้ามไปยุ่งกับโค้ดเดิมนะ!"** นี่หัวคือใจหลักในเรื่อง **OCP** ในรอบนี้ แค่อ่านก็กาวแล้ว ถ้าเราจะเพิ่มความสามารถหรือพฤติกรรมใหม่ๆ ปรกติเราก็ต้องแก้โค้ดเดิมนิ แต่นี่เล่นไม่ให้แก้โค้ดเดิมเลย แล้วมันจะทำยังไงกันล่ะ?

## ❓ ทำไมต้องห้ามแก้โค้ดล่ะ ?

ในการแก้โค้ดเพียง 1 ครั้ง นั่นหมายถึงอาจะเกิด bug ตามมาในโปรแกรมได้ (บางทีแก้ 1 ครั้ง bug อาจะเกิดเป็นสิบๆตัวเลยก็ได้) ดังนั้นเขาเลยบอกว่าโค้ดที่มันเคยใช้งานได้อยู่แล้ว จงอย่าไปยุ่งกับมันปล่อยให้มันทำงานไป ส่วนถ้าอยากได้ความสามารถใหม่ๆจงเขียนโค้ดตัวใหม่เข้าไปเพิ่มซะ

## 😕 แล้วจะเพิ่มความสามารถใหม่ๆยังไง โดยไม่แก้โค้ดเดิม ?

โดยปรกติเวลาเราเขียนโค้ด เราจะชอบเขียนให้โค้ดมัน**ดิ้นไม่ได้โดยไม่รู้ตัว** เช่น กำหนดว่า method นี้จะต้องรับ parameter เป็น **Concrete class** นี้นะ ตามตัวอย่างด้านล่าง

{% hint style="info" %}
**Concrete class**\
คือคลาสธรรมดาที่เราสร้างขึ้นมานี่แหละ และเป็นคลาสที่สามารถเอาไปใช้สร้าง object ได้เลย (ในคำนิยามจริงๆมันคือคลาสที่ตายตัวอ่ะนะ อ่านแล้วอาจจะ งงๆ)
{% endhint %}

```csharp
// อ่านไฟล์ทั้งหมดแล้วแปลงเป็น string
public string ReadAllTextFromFile(PdfFile pdf)
{
   // อ่านไฟล์ที่ส่งเข้ามาแล้วทำการแปลงเป็น string
}
```

**ปัญหาจากโค้ดตัวอย่าง** เราจะไม่สามารถส่ง object อื่นๆเข้าไปทำงานกับ method **ReadAllTextFromFile()** ได้เลยนอกเสียจาก object ที่มาจาก **PdfFile** และลูกๆของมันเท่านั้น ดังนั้นทุกครั้งที่เราอยากให้มันอ่านไฟล์แบบใหม่ได้ เราก็ต้องมาแก้โค้ดตรงนี้เสมอๆ **แบบนี้ถือว่าละเมิดกฎ OCP**

## 💡 การแก้ให้โค้ดของเราดิ้นได้

แทนที่เราจะไปผูกติด (coupling) โค้ดของเราเข้ากับ Concrete class เราก็แค่เปลี่ยนมาใช้ **Abstraction** งุยล่ะ

{% hint style="info" %}
**Abstraction**\
คือคลาสที่ไม่ได้ระบุเฉพาะเจาะจงลงไปว่ามันต้องเป็นตัวไหน เช่น abstract class หรือ interface และรวมถึงการทำ Polymorphism ด้วย
{% endhint %}

ดังนั้นเราก็แค่เปลี่ยน method ด้านบนให้รับเป็น abstraction ตามโค้ดด้านล่างก็พอแล้ว

```csharp
public interface IDocument 
{
   int TotalPages();
   string ReadAllText(int pageIndex);
}

public string ReadAllTextFromFile(IDocument doc)
{
   var sbd = new StringBuilder();
   for (int i = 0; i < doc.TotalPages(); i++)
   {
      sbd.Append(doc.ReadAllText(i));
   }
   return sbd.ToString();
}
```

จากโค้ดด้านบนจะเห็นว่า เราสามารถส่งคลาสอะไรก็ได้ที่ **implement IDocument** เข้าไป มันก็จะสามารถทำงานด้วยได้หมดแล้วโดยที่เราไม่ต้องไปแก้ไขโค้ด ReadAllTextFromFile() แม้แต่บรรทัดเดียวเลย **นี่แหละพลังแห่งการออกแบบและไม่ผิดกฎ OCP**

## 😒 **ควรออกแบบยังไงดี**

สมมุติว่าเราต้องเขียนโปรแกรมจัดการไฟล์ **PDF** โดยที่มีคลาสจัดการไฟล์อยู่ 1 ตัว (**DocumentManager**) เราจะออกแบบยังไง ?

### 😟 การออกแบบที่ไม่ดี

ถ้าเราออกแบบให้ตัวจัดการไฟล์มันไปผูกติดอยู่กับ Concrete class เลยนั่นหมายถึง เวลาที่เราจะแก้ไขอะไร เราจะต้องไปแก้ไขเจ้า concrete class ตัวนั่นเท่านั้น **นี่คือตัวอย่างการละเมิดกฎ OCP** ทำให้ทุกครั้งที่จะเพิ่มไฟล์ประเภทใหม่ๆเข้าไป เช่น Word, Csv, Json ต่างๆ เราจะต้องแก้ไขโค้ดเดิมเสมอ ตามรูปด้านล่าง

![](/files/-M1VQQkyYqu12v9eU6Hm)

### 😄 การออกแบบที่ควรเป็น

จากที่เราเคยผูกติดกับ Concrete class เราแค่เปลี่ยนมาเป็น **Abstraction** ซะ เพียงเท่านี้เราก็สามารถเปิดรับการทำงานร่วมกับไฟล์ใหม่ๆในอนาคตได้แล้ว เช่น Word, Csv, Json โดยที่เราไม่ต้องไปแก้ไขโค้ดเดิมเลย

![](/files/-LshbQNF7szqx5oD690t)

หรือเราจะใช้ Design Pattern 2 ตัวนี้มาช่วยได้นะครับ

{% content-ref url="/pages/-LmQYyzsOLkTezAyQGBs" %}
[Strategy](/software-design/designpatterns/behavioral-patterns/strategy.md)
{% endcontent-ref %}

{% content-ref url="/pages/-LmQZDRdTFk9jdGsKd18" %}
[Template Method](/software-design/designpatterns/behavioral-patterns/template-method.md)
{% endcontent-ref %}

## 🎯 บทสรุป

Open/Closed Principle คือหนึ่งในหัวใจหลักในการออกแบบโค้ดแบบ **OOP** ซึ่งจะช่วยให้โค้ดเรายืดหยุ่นมากขึ้น reuse ได้และที่สำคัญคือบำรุงรักษาแก้ไขได้ง่ายขึ้นอีกจมเบย ดังนั้นอย่างนิ่งอยู่เฉย จงน้อมรับเอา **OCP** ไปฉีดเข้าเส้นไปซ๊าาา 🥴
