正反案例介绍SOLID原则
一.概述
SOLID五大原則使我們能夠管理解決大多數(shù)軟件設(shè)計(jì)問題。由Robert C. Martin在20世紀(jì)90年代編寫了這些原則。這些原則為我們提供了從緊耦合的代碼和少量封裝轉(zhuǎn)變?yōu)檫m當(dāng)松耦合和封裝業(yè)務(wù)實(shí)際需求的結(jié)果方法。使用這些原則,我們可以構(gòu)建一個(gè)具有整潔,可讀且易于維護(hù)的代碼應(yīng)用程序。
SOLID縮寫如下:
SRP? 單一責(zé)任原則
OCP 開放/封閉原則
LSP? 里氏替換原則
ISP? ?接口分離原則
DIP? ?依賴反轉(zhuǎn)原則
1.單一責(zé)任原則SRP?
? ?一個(gè)類承擔(dān)的責(zé)任在理想情況下應(yīng)該是多少個(gè)呢?答案是一個(gè)。這個(gè)責(zé)任是圍繞一個(gè)核心任務(wù)構(gòu)建,不是簡(jiǎn)化的意思。通過暴露非常有限的責(zé)任使這個(gè)類與系統(tǒng)的交集更小。
(1) 演示:違反了單一責(zé)任原則,原因是:顧客類中承擔(dān)了太多無關(guān)的責(zé)任。 ?
/// <summary>/// 顧客類所有實(shí)現(xiàn)
/// </summary>
public class Cliente
{
public int ClienteId { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get; set; }
public string AdicionarCliente()
{
//顧客信息驗(yàn)證
if (!Email.Contains("@"))
return "Cliente com e-mail inválido";
if (CPF.Length != 11)
return "Cliente com CPF inválido";
//保存顧客信息
using (var cn = new SqlConnection())
{
var cmd = new SqlCommand();
cn.ConnectionString = "MinhaConnectionString";
cmd.Connection = cn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO CLIENTE (NOME, EMAIL CPF, DATACADASTRO) VALUES (@nome, @email, @cpf, @dataCad))";
cmd.Parameters.AddWithValue("nome", Nome);
cmd.Parameters.AddWithValue("email", Email);
cmd.Parameters.AddWithValue("cpf", CPF);
cmd.Parameters.AddWithValue("dataCad", DataCadastro);
cn.Open();
cmd.ExecuteNonQuery();
}
//發(fā)布郵件
var mail = new MailMessage("empresa@empresa.com", Email);
var client = new SmtpClient
{
Port = 25,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Host = "smtp.google.com"
};
mail.Subject = "Bem Vindo.";
mail.Body = "Parabéns! Você está cadastrado.";
client.Send(mail);
return "Cliente cadastrado com sucesso!";
}
}
?
(2) 解決方案,使用單一責(zé)任原則,每個(gè)類只負(fù)責(zé)自己的業(yè)務(wù)。
/// 顧客實(shí)體
/// </summary>
public class Cliente
{
public int ClienteId { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get; set; }
/// <summary>
/// 顧客信息驗(yàn)證
/// </summary>
/// <returns></returns>
public bool IsValid()
{
return EmailServices.IsValid(Email) && CPFServices.IsValid(CPF);
}
}
/// <summary>
/// 保存顧客信息
/// </summary>
public class ClienteRepository
{
/// <summary>
/// 保存
/// </summary>
/// <param name="cliente">要保存的顧客實(shí)體</param>
public void AdicionarCliente(Cliente cliente)
{
using (var cn = new SqlConnection())
{
var cmd = new SqlCommand();
cn.ConnectionString = "MinhaConnectionString";
cmd.Connection = cn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO CLIENTE (NOME, EMAIL CPF, DATACADASTRO) VALUES (@nome, @email, @cpf, @dataCad))";
cmd.Parameters.AddWithValue("nome", cliente.Nome);
cmd.Parameters.AddWithValue("email", cliente.Email);
cmd.Parameters.AddWithValue("cpf", cliente.CPF);
cmd.Parameters.AddWithValue("dataCad", cliente.DataCadastro);
cn.Open();
cmd.ExecuteNonQuery();
}
}
}
/// <summary>
/// CPF服務(wù)
/// </summary>
public static class CPFServices
{
public static bool IsValid(string cpf)
{
return cpf.Length == 11;
}
}
/// <summary>
/// 郵件服務(wù)
/// </summary>
public static class EmailServices
{
public static bool IsValid(string email)
{
return email.Contains("@");
}
public static void Enviar(string de, string para, string assunto, string mensagem)
{
var mail = new MailMessage(de, para);
var client = new SmtpClient
{
Port = 25,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Host = "smtp.google.com"
};
mail.Subject = assunto;
mail.Body = mensagem;
client.Send(mail);
}
}
/// <summary>
/// 客戶服務(wù),程序調(diào)用入口
/// </summary>
public class ClienteService
{
public string AdicionarCliente(Cliente cliente)
{
//先驗(yàn)證
if (!cliente.IsValid())
return "Dados inválidos";
//保存顧客
var repo = new ClienteRepository();
repo.AdicionarCliente(cliente);
//郵件發(fā)送
EmailServices.Enviar("empresa@empresa.com", cliente.Email, "Bem Vindo", "Parabéns está Cadastrado");
return "Cliente cadastrado com sucesso";
}
}
2. 開放/封閉原則OCP
類應(yīng)該是可以可擴(kuò)展的,可以用作構(gòu)建其他相關(guān)新功能,這叫開放。但在實(shí)現(xiàn)相關(guān)功能時(shí),不應(yīng)該修改現(xiàn)有代碼(因?yàn)橐呀?jīng)過單元測(cè)試運(yùn)行正常)這叫封閉。
(1) 演示:違反了開放/封閉原則,原因是每次增加新形狀時(shí),需要改變AreaCalculator 類的TotalArea方法,例如開發(fā)后期又增加了圓形形狀。
/// <summary>/// 長(zhǎng)方形實(shí)體
/// </summary>
public class Rectangle
{
public double Height { get; set; }
public double Width { get; set; }
}
/// <summary>
/// 圓形
/// </summary>
public class Circle
{
/// <summary>
/// 半徑
/// </summary>
public double Radius { get; set; }
}
/// <summary>
/// 面積計(jì)算
/// </summary>
public class AreaCalculator
{
public double TotalArea(object[] arrObjects)
{
double area = 0;
Rectangle objRectangle;
Circle objCircle;
foreach (var obj in arrObjects)
{
if (obj is Rectangle)
{
objRectangle = (Rectangle)obj;
area += objRectangle.Height * objRectangle.Width;
}
else
{
objCircle = (Circle)obj;
area += objCircle.Radius * objCircle.Radius * Math.PI;
}
}
return area;
}
}
? (2) 解決方案,使用開放/封閉原則,每次增加新形狀時(shí)(開放),不需要修改TotalArea方法(封閉)
/// <summary>/// 形狀抽象類
/// </summary>
public abstract class Shape
{
/// <summary>
/// 面積計(jì)算
/// </summary>
/// <returns></returns>
public abstract double Area();
}
/// <summary>
/// 長(zhǎng)方形
/// </summary>
public class Rectangle : Shape
{
public double Height { get; set; }
public double Width { get; set; }
public override double Area()
{
return Height * Width;
}
}
/// <summary>
/// 圓形
/// </summary>
public class Circle : Shape
{
public double Radius { get; set; }
public override double Area()
{
return Radius * Radius * Math.PI;
}
}
/// <summary>
/// 面積計(jì)算
/// </summary>
public class AreaCalculator
{
public double TotalArea(Shape[] arrShapes)
{
double area = 0;
foreach (var objShape in arrShapes)
{
area += objShape.Area();
}
return area;
}
}
3.里氏替換原則LSP
這里也涉及到了類的繼承,也適用于接口。子類可以替換它們的父類。里氏替換原則常見的代碼問題是使用虛方法,在父類定義虛方法時(shí),要確保該方法里沒有任何私有成員。
(1) 演示:違反了里氏替換原則, 原因是不能使用ReadOnlySqlFile子類替代SqlFile父類。
/// <summary>/// sql文件類 讀取、保存
/// </summary>
public class SqlFile
{
public string FilePath { get; set; }
public string FileText { get; set; }
public virtual string LoadText()
{
/* Code to read text from sql file */
return "..";
}
public virtual void SaveText()
{
/* Code to save text into sql file */
}
}
/// <summary>
/// 開發(fā)途中增加了sql文件只讀類
/// </summary>
public class ReadOnlySqlFile : SqlFile
{
public override string LoadText()
{
/* Code to read text from sql file */
return "..";
}
public override void SaveText()
{
/* Throw an exception when app flow tries to do save. */
throw new IOException("Can't Save");
}
}
public class SqlFileManager
{
/// <summary>
/// 集合中存在兩種類:SqlFile和ReadOnlySqlFile
/// </summary>
public List<SqlFile> lstSqlFiles { get; set; }
/// <summary>
/// 讀取
/// </summary>
/// <returns></returns>
public string GetTextFromFiles()
{
StringBuilder objStrBuilder = new StringBuilder();
foreach (var objFile in lstSqlFiles)
{
objStrBuilder.Append(objFile.LoadText());
}
return objStrBuilder.ToString();
}
/// <summary>
/// 保存
/// </summary>
public void SaveTextIntoFiles()
{
foreach (var objFile in lstSqlFiles)
{
//檢查當(dāng)前對(duì)象是ReadOnlySqlFile類,跳過調(diào)用SaveText()方法
if (!(objFile is ReadOnlySqlFile))
{
objFile.SaveText();
}
}
}
}
? (2) 解決方案,使用里氏替換原則,子類可以完全代替父類
public interface IReadableSqlFile{
string LoadText();
}
public interface IWritableSqlFile
{
void SaveText();
}
public class ReadOnlySqlFile : IReadableSqlFile
{
public string FilePath { get; set; }
public string FileText { get; set; }
public string LoadText()
{
/* Code to read text from sql file */
return "";
}
}
public class SqlFile : IWritableSqlFile, IReadableSqlFile
{
public string FilePath { get; set; }
public string FileText { get; set; }
public string LoadText()
{
/* Code to read text from sql file */
return "";
}
public void SaveText()
{
/* Code to save text into sql file */
}
}
public class SqlFileManager
{
public string GetTextFromFiles(List<IReadableSqlFile> aLstReadableFiles)
{
StringBuilder objStrBuilder = new StringBuilder();
foreach (var objFile in aLstReadableFiles)
{
//ReadOnlySqlFile的LoadText實(shí)現(xiàn)
objStrBuilder.Append(objFile.LoadText());
}
return objStrBuilder.ToString();
}
public void SaveTextIntoFiles(List<IWritableSqlFile> aLstWritableFiles)
{
foreach (var objFile in aLstWritableFiles)
{
//SqlFile的SaveText實(shí)現(xiàn)
objFile.SaveText();
}
}
}
4.接口分離原則ISP
接口分離原則是解決接口臃腫的問題,建議接口保持最低限度的函數(shù)。永遠(yuǎn)不應(yīng)該強(qiáng)迫客戶端依賴于它們不用的接口。
?(1)? 演示:違反了接口分離原則。原因是Manager無法處理任務(wù),同時(shí)沒有人可以將任務(wù)分配給Manager,因此WorkOnTask方法不應(yīng)該在Manager類中。
/// <summary>/// 領(lǐng)導(dǎo)接口
/// </summary>
public interface ILead
{
//創(chuàng)建任務(wù)
void CreateSubTask();
//分配任務(wù)
void AssginTask();
//處理指定任務(wù)
void WorkOnTask();
}
/// <summary>
/// 團(tuán)隊(duì)領(lǐng)導(dǎo)
/// </summary>
public class TeamLead : ILead
{
public void AssginTask()
{
//Code to assign a task.
}
public void CreateSubTask()
{
//Code to create a sub task
}
public void WorkOnTask()
{
//Code to implement perform assigned task.
}
}
/// <summary>
/// 管理者
/// </summary>
public class Manager : ILead
{
public void AssginTask()
{
//Code to assign a task.
}
public void CreateSubTask()
{
//Code to create a sub task.
}
public void WorkOnTask()
{
throw new Exception("Manager can't work on Task");
}
}
? (2) 解決方案,使用接口分離原則
/// <summary>/// 程序員角色
/// </summary>
public interface IProgrammer
{
void WorkOnTask();
}
/// <summary>
/// 領(lǐng)導(dǎo)角色
/// </summary>
public interface ILead
{
void AssignTask();
void CreateSubTask();
}
/// <summary>
/// 程序員:執(zhí)行任務(wù)
/// </summary>
public class Programmer : IProgrammer
{
public void WorkOnTask()
{
//code to implement to work on the Task.
}
}
/// <summary>
/// 管理者:可以創(chuàng)建任務(wù)、分配任務(wù)
/// </summary>
public class Manager : ILead
{
public void AssignTask()
{
//Code to assign a Task
}
public void CreateSubTask()
{
//Code to create a sub taks from a task.
}
}
/// <summary>
/// 團(tuán)隊(duì)領(lǐng)域:可以創(chuàng)建任務(wù)、分配任務(wù)、執(zhí)行執(zhí)行
/// </summary>
public class TeamLead : IProgrammer, ILead
{
public void AssignTask()
{
//Code to assign a Task
}
public void CreateSubTask()
{
//Code to create a sub task from a task.
}
public void WorkOnTask()
{
//code to implement to work on the Task.
}
}
5. 依賴反轉(zhuǎn)原則DIP
依賴反轉(zhuǎn)原則是對(duì)程序的解耦。高級(jí)模塊/類不應(yīng)依賴于低級(jí)模塊/類,兩者都應(yīng)該依賴于抽象。意思是:當(dāng)某個(gè)類被外部依賴時(shí),就需要把該類抽象成一個(gè)接口。接口如何變成可調(diào)用的實(shí)例呢?實(shí)踐中多用依賴注入模式。這個(gè)依賴反轉(zhuǎn)原則在DDD中得到了很好的運(yùn)用實(shí)踐(參考前三篇)。
(1) 演示:違反了依賴反轉(zhuǎn)原則。原因是:每當(dāng)客戶想要引入新的Logger記錄形式時(shí),我們需要通過添加新方法來改變ExceptionLogger類。這里錯(cuò)誤的體現(xiàn)了:高級(jí)類 ExceptionLogger直接引用低級(jí)類FileLogger和DbLogger來記錄異常。
/// <summary>/// 數(shù)據(jù)庫(kù)日志類
/// </summary>
public class DbLogger
{
//寫入日志
public void LogMessage(string aMessage)
{
//Code to write message in database.
}
}
/// <summary>
/// 文件日志類
/// </summary>
public class FileLogger
{
//寫入日志
public void LogMessage(string aStackTrace)
{
//code to log stack trace into a file.
}
}
public class ExceptionLogger
{
public void LogIntoFile(Exception aException)
{
FileLogger objFileLogger = new FileLogger();
objFileLogger.LogMessage(GetUserReadableMessage(aException));
}
public void LogIntoDataBase(Exception aException)
{
DbLogger objDbLogger = new DbLogger();
objDbLogger.LogMessage(GetUserReadableMessage(aException));
}
private string GetUserReadableMessage(Exception ex)
{
string strMessage = string.Empty;
//code to convert Exception's stack trace and message to user readable format.
return strMessage;
}
}
public class DataExporter
{
public void ExportDataFromFile()
{
try
{
//code to export data from files to database.
}
catch (IOException ex)
{
new ExceptionLogger().LogIntoDataBase(ex);
}
catch (Exception ex)
{
new ExceptionLogger().LogIntoFile(ex);
}
}
}
? (2) 解決方案,使用依賴反轉(zhuǎn)原則,這里演示沒有用依賴注入。
public interface ILogger{
void LogMessage(string aString);
}
/// <summary>
/// 數(shù)據(jù)庫(kù)日志類
/// </summary>
public class DbLogger : ILogger
{
//寫入日志
public void LogMessage(string aMessage)
{
//Code to write message in database.
}
}
/// <summary>
/// 文件日志類
/// </summary>
public class FileLogger : ILogger
{
//寫入日志
public void LogMessage(string aStackTrace)
{
//code to log stack trace into a file.
}
}
public class ExceptionLogger
{
private ILogger _logger;
public ExceptionLogger(ILogger aLogger)
{
this._logger = aLogger;
}
//可以與這些日志類達(dá)到松散耦合
public void LogException(Exception aException)
{
string strMessage = GetUserReadableMessage(aException);
this._logger.LogMessage(strMessage);
}
private string GetUserReadableMessage(Exception aException)
{
string strMessage = string.Empty;
//code to convert Exception's stack trace and message to user readable format.
return strMessage;
}
}
public class DataExporter
{
public void ExportDataFromFile()
{
ExceptionLogger _exceptionLogger;
try
{
//code to export data from files to database.
}
catch (IOException ex)
{
_exceptionLogger = new ExceptionLogger(new DbLogger());
_exceptionLogger.LogException(ex);
}
catch (Exception ex)
{
_exceptionLogger = new ExceptionLogger(new FileLogger());
_exceptionLogger.LogException(ex);
}
}
}
參考文獻(xiàn)
SOLID原則簡(jiǎn)介
原文地址:https://www.cnblogs.com/MrHSR/p/10912615.html
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號(hào)文章匯總?http://www.csharpkit.com?
總結(jié)
以上是生活随笔為你收集整理的正反案例介绍SOLID原则的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 知乎个人精选 | 绝版的专业书到哪里找最
- 下一篇: RedLock 实现分布式锁