/ OOD

[365 วันแห่งโปรแกรม #day47] SOLID (ตอนที่ 4 Interface segregation)

วันที่สี่สิบเจ็ดของ ‪#‎365วันแห่งโปรแกรม วันนี้เป็นตอนที่ 4 ของ SOLID แล้วครับ ซึ่งมาในชื่อเรื่องว่า Interface segregation


Interface segregation principle

Interface segregation principle หรือเรียกย่อๆ ว่า ISP นั้นก็คือข้อกำหนดที่บอกว่าเราควรจะแบ่ง interface ออกเป็นส่วนย่อยๆ ตามที่ client ต้องการใช้จริงๆ แทนที่จะใช้ interface ขนาดใหญ่ตัวเดียว เพื่อลดการ couple และง่ายต่อการ refactor, change หรือ redeploy

ความเป็นมาของ ISP

บริษัท Xerox ต้องการสร้างระบบเครื่องพิมพ์แบบ all in one กล่าวคือเครื่องจักรเครื่องเดียว แต่สามารถทำได้ทั้ง พิมพ์ คัดลอก ส่งแฟกซ์ เป็นต้น ทาง Xerox จึงจัดจ้างนักพัฒนาเข้ามาทำระบบใหม่สำหรับเครื่องพิมพ์นี้ ไม่นานหลังจากนั้นก็สามารถ launch ระบบนี้ออกมาสู่ตลาดได้ และมันก็ทำงานได้ดีมาก แต่พอเวลาผ่านไประบบนี้ก็กลายเป็นฝันร้ายของนักพัฒนา เพราะมันรวมทุกอย่างอยู่ที่เดียว (ทุกอย่างอยู่ในคลาสเดียว)

interface IAllInOneMachine
{
    public void Print(List<IDocument> documents);
    public void Fax(List<IDocument> documents);
    public void Scan(List<IDocument> documents);
}  

class AllInOneMachine : IAllInOneMachine
{
    public void Print(List<IDocument> documents){...}
    public void Fax(List<IDocument> documents){...}
    public void Scan(List<IDocument> documents){...}
}  
Xerox แก้ปัญหานี้อย่างไร?

ง่ายๆ เลยครับ Xerox ก็แค่จ้างที่ปรึกษาที่เก่งๆ เข้ามาช่วย ซึ่งเขาก็คือ Uncle Bob นั่นเอง Uncle Bob เริ่มวิเคราะห์ปัญหาก่อนว่าจริงๆ แล้วปัญหาคืออะไร

  • การแก้ไขแค่ minor change ทำให้ต้อง recompile แล้วเทสใหม่ทั้งระบบ

  • ลูกค้าบางคนต้องการแค่ฟังก์ชันสำหรับพิมพ์ แต่เราก็ยังคงต้องส่งมอบซอฟต์แวร์ขนาดใหญ่ที่ทำได้ทุกอย่างซึ่งลูกค้าไม่ได้ใช้

หลังจากรับรู้ปัญหาจริงๆ แล้ว Martin ก็เริ่มให้คำแนะนำว่าควรจะแก้อย่างไร และนั่นก็คือจุดกำเนิดของ ISP โดยเค้าบอกว่า

  • ให้แยก interface ขนาดใหญ่นั้นออกเป็นส่วนย่อยๆ ซะ

  • implement แต่ละ interface แล้วใช้วิธีการ Dependency Injection ในการรวมเข้าด้วยกันใน Main class (หากต้องการใช้ซอฟต์แวร์เครื่องพิมพ์แบบ multi function)

เมื่อแก้แล้วจะได้ดังนี้

interface IPrinter
{
    public void Print(List<IDocument> documents);
}

interface IFax
{    
    public void Fax(List<IDocument> documents);
}

interface IScanner
{
    public void Scan(List<IDocument> documents);
}

class Printer : IPrinter
{
    public void Print(List<IDocument> documents);
}

class Fax : IFax
{    
    public void Fax(List<IDocument> documents);
}

class Scanner : IScanner
{
    public void Scan(List<IDocument> documents);
}

interface IAllInOneMachine : IPrinter, IFax, IScanner
{

}

class AllInOneMachine : IAllInOneMachine
{
    private IPrinter printer;
    private  IFax fax;
    private  IScanner scanner;

    public AllInOneMachine(IPrinter printer, IFax fax, IScanner scanner)
    public void Print(List<IDocument> documents){...}
    public void Fax(List<IDocument> documents){...}
    public void Scan(List<IDocument> documents){...}
}  

จากโค้ดข้างต้นจะเห็นว่า เมื่อเราแบ่ง interface ออกมาเป็น IPrinter, IFax, และ IScanner แล้ว เราก็สามารถส่งมอบเป็นส่วนๆ ตามที่ลูกค้าต้องการจริงๆ (ตาม physical hardware)

หรือจะรวมเข้าเป็นคลาสใหญ่สำหรับงายทุกประเภท แล้ว inject module ที่ทำงานนั้นๆ เข้าไปก็ได้

วิธีนี้ทำให้เวลาแก้ไข เราก็แค่ implement คลาสใหม่ หรือแก้ไขของเดิม แล้ว inject เข้าไปแค่นั้น แล้วก็เทศแค่ส่วนที่แก้อย่างเดียว ชีวิตก็ดี๊ดีครับ

ตัวอย่างเวลานำไปใช้

สำหรับ client ที่ต้องการฟังก์ชันพิมพ์อย่างเดียว

IPrinter printer = new Printer();
printer.Print(...);

สำหรับ client ที่ต้องการทุกฟังก์ชัน

IPrinter printer = new Printer();
IFax fax = new Fax();
IScanner scanner = new Scanner();

IAllInOneMachine machine = AllInOneMachine(printer, fax, scanner);
machine.Print(...);
machine.Fax(...);
machine.Scan(...);

เรื่องราวก็เป็นเช่นนี้แล...

สรุป

จริงๆ แล้วเรื่องนี้ก็ตอกน้ำครับว่าให้ทำตาม SRP เถอะ แต่สิ่ง ISP เพิ่มเติมคือ แนะนำให้สร้าง interface ครับ และเป็น interface ขนาดเล็กๆ ด้วย เพื่อที่จะได้ reuse concept ให้ได้มากที่สุด ซึ่งเป็นสิ่งที่สำคัญมากในซอฟต์แวร์ขนาดใหญ่

References

Interface segregation principle

Interface Segregation Principle (ISP of SOLID in C#)- Walk through History

#‎day47 #365วันแห่งโปรแกรม ‪#‎โครงการ365วันแห่ง‬...