物件導向系列
單一職責原則 Single Responsibility Principle (SRP)
最早的解釋是:一個模組應該只有一個改變的理由。
到整潔架構的定義是:一個模組只對一個角色負責。
來看看以下程式碼的設計
public interface IUserService
{
   bool Login(string userName, string password);
   bool Register(string email, string userName, string password, string confirmPasword);
   void LogError(Exception exception, string errorMessage);
   void SendEmail(string emailAddress, string content);
}以上IUserService介面包含三個不同職責,LogError和SendEmail不應該屬於IUserService職責。
上述會有若是類別集中會有以下的問題
- 程式和類別複雜度大幅提升
 - 當一個程式碼有重複利用需求,可以考慮SRP原則,要了解需求、來定義一個物件責任
 
改成如下
public interface IUserService
{
    bool Login(string userName, string password);
    bool Register(string email, string userName, string password, string confirmPasword);
}
public interface IErrorLogger
{
    void Log(Exception exception, string errorMessage);
}
public interface IEmailSender
{
    void Send(string emailAddress, string content);
}開放封閉原則 Open Closed Principle (OCP)
解釋:一個軟體應該對於擴展是開放,對修改是關閉。
這段白話解釋是應該藉由新增功能來擴充功能,而不是修改程式碼。
筆者在看大話重構有看到關於OCP的原則,詳細看這一篇筆者筆記https://www.dotblogs.com.tw/bda605/2022/03/08/223126
實現OCP的手段可以透過
- 透過繼承來擴充
 - 使用擴充方法來擴充既有類別
 - 利用抽象型別,來多載物件
 
以下例子
public class ReportGeneration
{
    private string _reportType;
    public ReportGeneration(string reportType) 
    {
       _reportType = reportType;
    }
    
    public void GenerateReport(Employee employee)
    {
        if (ReportType == "CRS")
        {
            // Report generation with employee data in Crystal Report.
        }
        if (ReportType == "PDF")
        {
            // Report generation with employee data in PDF.
        }
    }
}上面會遇到問題
- 如果要追加CSV的產出,會必須動到原本的 ReportGeneration 類別。
 - 必須測試ReportGeneration類別所有功能,確保最新修改有無破壞現有功能。
 
// Interface
public interface IReportGeneration
{
    void GenerateReport(Employee employee);
}
// Clients
public class PdfReportGeneration : IReportGeneration
{
    public void GenerateReport(Employee employee)
    {
        Console.WriteLine("Generating pdf report.");
    }
}
public class CRSReportGeneration : IReportGeneration
{
    public void GenerateReport(Employee employee)
    {
        Console.WriteLine("Generating CRS report.");
    }
}
public class CsvReportGeneration : IReportGeneration
{
    public void GenerateReport(Employee employee)
    {
        Console.WriteLine("Generating csv report.");
    }
    
}里氏替換原則 Liskov Substitution Principle (LSP)
定義:子類別可以替換其父類別。
目前C#裡面可以用抽象類別方式搭配virtual和override,程式看到老爸的類別有virtaul才會執行子類別有override的方法。
筆者比較常用LSP搭配Interface來實現,因為用抽象類別或類別繼承要寫好。
// Interface
internal interface IBird
{
   void MakeSound();
   void Fly();
   void Run();
}
// Implementations
public class Duck : IBird
{
   public void MakeSound()
   {
       Console.WriteLine("Making sound.");
   }
   public void Fly()
   {
       Console.WriteLine("Flying...");
   }
   public void Run()
   {
       Console.WriteLine("Running...");
   }
}
// This breaks the Liskov substituion principle because if we follow polymorphism and call the Fly() method from IBird reference variable
// then it will throw NotImplementedException.
public class Ostrich : IBird
{
   public void MakeSound()
   {
       Console.WriteLine("Making sound.");
   }
   // Ostrich cannot fly.
   public void Fly()
   {
       throw new NotImplementedException();
   }
   public void Run()
   {
       Console.WriteLine("Running...");
   }
}上述有個問題是若呼叫Fly(),它會拋出異常。
改成以下
// Interfaces
internal interface IBird
{
    void MakeSound();
    void Run();
}
internal interface IFlyingBird : IBird
{
    void Fly();
}
public class Duck : IFlyingBird
{
    public void MakeSound()
    {
        Console.WriteLine("Making sound.");
    }
    public void Fly()
    {
        Console.WriteLine("Flying...");
    }
    public void Run()
    {
        Console.WriteLine("Running...");
    }
}
public class Ostrich : IBird
{
    public void MakeSound()
    {
        Console.WriteLine("Making sound.");
    }
    public void Run()
    {
        Console.WriteLine("Running...");
    }
}介面隔離原則 Interface Segregation Principle (ISP)
定義:客戶不應該強迫相依沒有使用的方法。
Interface有多個方法,應該拆成多個介面,而介面設計特定需求來定義規格,用多個介面來取代單一大型介面,將特定功能規格定義專屬小介面上。
public interface IEmployeeTasks
{
   void CreateTask();
   void AssginTask();
   void WorkOnTask();
}
public class TeamLead : IEmployeeTasks
{
   public void CreateTask()
   {
       Console.WriteLine("Task created.");
   }
   public void AssginTask()
   {
       Console.WriteLine("Task assigned.");
   }
   public void WorkOnTask()
   {
       Console.WriteLine("Started working on the task.");
   }
}
public class Manager : IEmployeeTasks
{
   public void CreateTask()
   {
       Console.WriteLine("Task created.");
   }
   public void AssginTask()
   {
       Console.WriteLine("Task assigned.");
   }
   // The Manager client has been forced to implement `WorkOnTask()` method although Manager does not work on task.
   public void WorkOnTask()
   {
       throw new NotImplementedException();
   }
}
public class Programmer : IEmployeeTasks
{
   // The Programmer client has been forced to implement `CreateTask()` method although Programmer cannot create task.
   public void CreateTask()
   {
       throw new NotImplementedException();
   }
   // The Programmer client has been forced to implement `AssginTask()` method although Programmer cannot assign task.
   public void AssginTask()
   {
       throw new NotImplementedException();
   }
   public void WorkOnTask()
   {
       Console.WriteLine("Started working on the task.");
   }
}上述問題造成
- 管理用戶端已經強制實現方法,管理者不處理WorkOnTask()
 - 程式設計師被迫實現AssignTask(),程式設計師無法建立任務和指派任務。CreateTask(),AssignTask()
 
以下程式碼解決上述問題
// Interfaces
public interface ILead
{
    void CreateTask();
    void AssginTask();
}
public interface IProgrammer
{
    void WorkOnTask();
}
// Clients
public class Manager : ILead
{
    public void CreateTask()
    {
        Console.WriteLine("Task created.");
    }
    public void AssginTask()
    {
        Console.WriteLine("Task assigned.");
    }
}
public class TeamLead : ILead, IProgrammer
{
    public void CreateTask()
    {
        Console.WriteLine("Task created.");
    }
    public void AssginTask()
    {
        Console.WriteLine("Task assigned.");
    }
    public void WorkOnTask()
    {
        Console.WriteLine("Started working on the task.");
    }
}
public class Programmer : IProgrammer
{
    public void WorkOnTask()
    {
        Console.WriteLine("Started working on the task.");
    }
}
相依反轉原則 Dependency Inversion Principle (DIP)
定義:高層次模組不應該依賴低層次模組、兩者依賴於介面
高階模組呼叫
public class EmailSender
{
    public void Send(string email, string content)
    {
        Console.WriteLine($"Sending email to {email}");
    }
}
// High level module
public class OrderService
{
    private readonly EmailSender _emailSender;
    public OrderService(EmailSender emailSender)
    {
        _emailSender = emailSender;
    }
    public void CreateOrder()
    {
        Console.WriteLine("Creating order");
        //Sending order details
        _emailSender.Send("tanvirarjel2@gmail.com", "This is order detail.");
    }
}上述程式碼:高級模組緊OrderService密依賴於低級模組EmailSender
如果我們反轉依賴並重新設計程式如下,它將符合依賴反轉原則
// Low level module
public class EmailSender : IEmailSender
{
    public void Send(string email, string content)
    {
        Console.WriteLine($"Sending email to {email}");
    }
}
// High level module
// Abstraction
public interface IEmailSender
{
    void Send(string email, string content);
}
public class OrderService
{
    private readonly IEmailSender _emailSender;
    public OrderService(IEmailSender emailSender)
    {
        _emailSender = emailSender;
    }
    public void CreateOrder()
    {
        Console.WriteLine("Creating order");
        //Sending order details
        _emailSender.Send("tanvirarjel2@gmail.com", "This is order detail.");
    }
}參考資料
https://igouist.github.io/series/%E8%8F%9C%E9%9B%9E%E8%88%87%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91/--菜雞與物件導向
元哥的筆記