# 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** ไปฉีดเข้าเส้นไปซ๊าาา 🥴


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.saladpuk.com/basic/solid/ocp.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
