物件導向系列
單一職責原則 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/--菜雞與物件導向
元哥的筆記