S.O.L.I.D 物件導向設計原則 - ISP (Interface Segregation Principle)

Posted by Ryan Tseng on 2017-11-09

解釋

The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.

意思就是說一個類別不應該被強迫實作一個它不需要的方法。

其實換句話說是限制你不要過度膨脹介面中定義的方法,盡量是能夠滿足使用的情境即可,別想要把全部的東西都塞進介面中,以下用個簡單的例子來描述。

Lab

我們想在系統裡面建立一個 Log 服務,裡面大概會提供以下的服務

  • 寫入 Log 至 文字檔/資料庫、或呼叫Log時透過Email寄送

ILogger.cs

我們開始著手先根據上面的需求建立一個基本的介面

1
2
3
4
5
6
7
8
9
10
11
public interface ILogger
{
string Subject { get; set; }

string Message { get; set; }

List<string> MailAddresses { get; set; }

void WriteLog(string message);

}

然後實作兩種不同類型的 Log 服務

MailLogger.cs, DatabaseLogger.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class MailLogger : ILogger
{
public string Subject { get; set; }

public string Message { get; set; }

public List<string> Addresses { get; set; }

public void WriteLog()
{
// 呼叫 Smtp 將 Mail 寄送給指定使用者
var mailMessage = new MailMessage();

mailMessage.Subject = Subject;
mailMessage.Body = Message;

foreach(var add in Addresses)
{
mailMessage.To.Add(add);
}

// ... 省略

var client = new SmtpClient();
client.Send(mailMessage);
}
}

public class DatabaseLogger : ILogger
{
public string Subject { get; set; }

public string Message { get; set; }

public List<string> Addresses { get; set; }

public void WriteLog()
{
// 連結 Db 寫入 Log
}
}

看完了兩個實作之後,是不是發現有一點怪怪的?

這個怪怪的地方就是 DatabaseLogger 裡面為什麼要有 List<string> Addresses {get;set;}

因為他完全用不到。

所以 ISP 原則所告訴我們的就是類別不要強迫實作他不需要的方法,我們要對原本的方法做一點小修正,把不需要用到的屬性抽出來放到另外一個介面當中

ILogger.cs, IMessageLogger.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface ILogger
{
string Subject { get; set; }

string Message { get; set; }

void WriteLog();

}

public interface IMailLogger
{
List<string> Addresses { get; set; }
}

DatabaseLogger.cs

把原本的 public List<string> Addresses { get; set; } 拿掉

1
2
3
4
5
6
7
8
9
10
11
public class DatabaseLogger : ILogger
{
public string Subject { get; set; }

public string Message { get; set; }

public void WriteLog()
{
// 連結 Db 寫入 Log
}
}

MailLogger.cs

MailLogger 多實作剛剛新增的 IMailLogger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MailLogger : ILogger, IMailLogger
{
public string Subject { get; set; }

public string Message { get; set; }

public List<string> Addresses { get; set; }

public void WriteLog()
{
// 呼叫 Smtp 將 Mail 寄送給指定使用者
var mailMessage = new MailMessage();

mailMessage.Subject = Subject;
mailMessage.Body = Message;

foreach(var add in Addresses)
{
mailMessage.To.Add(add);
}

// ... 省略

var client = new SmtpClient();
client.Send(mailMessage);
}
}

這個例子比較單純一點,目的是想表現出 ISP 的宗旨 不要讓類別去實作他沒有用到的方法

同時也告訴我們在設計界面時,要注意不要想塞什麼東西進去就塞什麼東西進去,讓整個介面定義的接口過多,要負責的事情如果太多,未來在修改程式時如果發現子類別實作太多沒有必要實作的方法,就會要改很多的程式碼,建議在實作時若能夠早點發現這樣的狀況及早邊做重構。