一.概述

  SOLID五大原则使我们能够管理解决大多数软件设计问题。由Robert C. Martin在20世纪90年代编写了这些原则。这些原则为我们提供了从紧耦合的代码和少量封装转变为适当松耦合和封装业务实际需求的结果方法。使用这些原则,我们可以构建一个具有整洁,可读且易于维护的代码应用程序。

  SOLID缩写如下:  

    SRP  单一责任原则

    OCP 开放/封闭原则

    LSP  里氏替换原则

    ISP   接口分离原则

    DIP   依赖反转原则

  1.单一责任原则SRP

      一个类承担的责任在理想情况下应该是多少个呢?答案是一个。这个责任是围绕一个核心任务构建,不是简化的意思。通过暴露非常有限的责任使这个类与系统的交集更小。

    (1) 演示:违反了单一责任原则,原因是:顾客类中承担了太多无关的责任。 

    /// <summary>
/// 顾客类所有实现
/// </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()
{
//顾客信息验证
if (!Email.Contains("@"))
return "Cliente com e-mail inválido"; if (CPF.Length != )
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();
} //发布邮件
var mail = new MailMessage("empresa@empresa.com", Email);
var client = new SmtpClient
{
Port = ,
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) 解决方案,使用单一责任原则,每个类只负责自己的业务。

    /// <summary>
/// 顾客实体
/// </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>
/// 顾客信息验证
/// </summary>
/// <returns></returns>
public bool IsValid()
{
return EmailServices.IsValid(Email) && CPFServices.IsValid(CPF);
}
} /// <summary>
/// 保存顾客信息
/// </summary>
public class ClienteRepository
{
/// <summary>
/// 保存
/// </summary>
/// <param name="cliente">要保存的顾客实体</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服务
/// </summary>
public static class CPFServices
{
public static bool IsValid(string cpf)
{
return cpf.Length == ;
}
} /// <summary>
/// 邮件服务
/// </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 = ,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Host = "smtp.google.com"
}; mail.Subject = assunto;
mail.Body = mensagem;
client.Send(mail);
}
} /// <summary>
/// 客户服务,程序调用入口
/// </summary>
public class ClienteService
{
public string AdicionarCliente(Cliente cliente)
{
//先验证
if (!cliente.IsValid())
return "Dados inválidos"; //保存顾客
var repo = new ClienteRepository();
repo.AdicionarCliente(cliente); //邮件发送
EmailServices.Enviar("empresa@empresa.com", cliente.Email, "Bem Vindo", "Parabéns está Cadastrado"); return "Cliente cadastrado com sucesso";
}
}
  2. 开放/封闭原则OCP

    类应该是可以可扩展的,可以用作构建其他相关新功能,这叫开放。但在实现相关功能时,不应该修改现有代码(因为已经过单元测试运行正常)这叫封闭。

    (1) 演示:违反了开放/封闭原则,原因是每次增加新形状时,需要改变AreaCalculator 类的TotalArea方法,例如开发后期又增加了圆形形状。

    /// <summary>
/// 长方形实体
/// </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>
/// 面积计算
/// </summary>
public class AreaCalculator
{
public double TotalArea(object[] arrObjects)
{
double area = ;
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) 解决方案,使用开放/封闭原则,每次增加新形状时(开放),不需要修改TotalArea方法(封闭)

   /// <summary>
/// 形状抽象类
/// </summary>
public abstract class Shape
{
/// <summary>
/// 面积计算
/// </summary>
/// <returns></returns>
public abstract double Area();
} /// <summary>
/// 长方形
/// </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>
/// 面积计算
/// </summary>
public class AreaCalculator
{
public double TotalArea(Shape[] arrShapes)
{
double area = ;
foreach (var objShape in arrShapes)
{
area += objShape.Area();
}
return area;
}
}

  

  3.里氏替换原则LSP

    这里也涉及到了类的继承,也适用于接口。子类可以替换它们的父类。里氏替换原则常见的代码问题是使用虚方法,在父类定义虚方法时,要确保该方法里没有任何私有成员。

    (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>
/// 开发途中增加了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)
{
//检查当前对象是ReadOnlySqlFile类,跳过调用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实现
objStrBuilder.Append(objFile.LoadText());
}
return objStrBuilder.ToString();
} public void SaveTextIntoFiles(List<IWritableSqlFile> aLstWritableFiles)
{
foreach (var objFile in aLstWritableFiles)
{
//SqlFile的SaveText实现
objFile.SaveText();
}
}
}
  4.接口分离原则ISP

    接口分离原则是解决接口臃肿的问题,建议接口保持最低限度的函数。永远不应该强迫客户端依赖于它们不用的接口。

     (1)  演示:违反了接口分离原则。原因是Manager无法处理任务,同时没有人可以将任务分配给Manager,因此WorkOnTask方法不应该在Manager类中。

   /// <summary>
/// 领导接口
/// </summary>
public interface ILead
{
//创建任务
void CreateSubTask();
//分配任务
void AssginTask();
//处理指定任务
void WorkOnTask();
} /// <summary>
/// 团队领导
/// </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>
/// 领导角色
/// </summary>
public interface ILead
{
void AssignTask();
void CreateSubTask();
} /// <summary>
/// 程序员:执行任务
/// </summary>
public class Programmer : IProgrammer
{
public void WorkOnTask()
{
//code to implement to work on the Task.
}
} /// <summary>
/// 管理者:可以创建任务、分配任务
/// </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>
/// 团队领域:可以创建任务、分配任务、执行执行
/// </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. 依赖反转原则DIP

    依赖反转原则是对程序的解耦。高级模块/类不应依赖于低级模块/类,两者都应该依赖于抽象。意思是:当某个类被外部依赖时,就需要把该类抽象成一个接口。接口如何变成可调用的实例呢?实践中多用依赖注入模式。这个依赖反转原则在DDD中得到了很好的运用实践(参考前三篇)。

    (1) 演示:违反了依赖反转原则。原因是:每当客户想要引入新的Logger记录形式时,我们需要通过添加新方法来改变ExceptionLogger类。这里错误的体现了:高级类 ExceptionLogger直接引用低级类FileLogger和DbLogger来记录异常。

   /// <summary>
/// 数据库日志类
/// </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) 解决方案,使用依赖反转原则,这里演示没有用依赖注入。

   public interface ILogger
{
void LogMessage(string aString);
} /// <summary>
/// 数据库日志类
/// </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;
}
//可以与这些日志类达到松散耦合
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);
}
}
}

  参考文献

    SOLID原则简介

asp.net core系列 65 正反案例介绍SOLID原则的更多相关文章

  1. asp.net core系列 53 IdentityServer4 (IS4)介绍

    一.概述 在物理层之间相互通信必须保护资源,需要实现身份验证和授权,通常针对同一个用户存储.对于资源安全设计包括二个部分,一个是认证,一个是API访问. 1 认证 认证是指:应用程序需要知道当前用户的 ...

  2. asp.net core系列 72 Exceptionless使用介绍

    一.Exceptionless介绍 Exceptionless专注于.net平台提供实时错误和日志报告.主要包括:错误通知.智能分组异常.详细错误报告堆栈跟踪.支持离线.UI查看重要错误和确定优先级. ...

  3. 【目录】asp.net core系列篇

    随笔分类 - asp.net core系列篇 asp.net core系列 68 Filter管道过滤器 摘要: 一.概述 本篇详细了解一下asp.net core filters,filter叫&q ...

  4. asp.net core 系列 18 web服务器实现

    一. ASP.NET Core Module 在介绍ASP.NET Core Web实现之前,先来了解下ASP.NET Core Module.该模块是插入 IIS 管道的本机 IIS 模块(本机是指 ...

  5. asp.net core系列 40 Web 应用MVC 介绍与详细示例

    一. MVC介绍 MVC架构模式有助于实现关注点分离.视图和控制器均依赖于模型. 但是,模型既不依赖于视图,也不依赖于控制器. 这是分离的一个关键优势. 这种分离允许模型独立于可视化展示进行构建和测试 ...

  6. asp.net core系列 39 Web 应用Razor 介绍与详细示例

    一. Razor介绍 在使用ASP.NET Core Web开发时, ASP.NET Core MVC 提供了一个新特性Razor. 这样开发Web包括了MVC框架和Razor框架.对于Razor来说 ...

  7. asp.net core系列 39 Razor 介绍与详细示例

    原文:asp.net core系列 39 Razor 介绍与详细示例 一. Razor介绍 在使用ASP.NET Core Web开发时, ASP.NET Core MVC 提供了一个新特性Razor ...

  8. Ajax跨域问题及解决方案 asp.net core 系列之允许跨越访问(Enable Cross-Origin Requests:CORS) c#中的Cache缓存技术 C#中的Cookie C#串口扫描枪的简单实现 c#Socket服务器与客户端的开发(2)

    Ajax跨域问题及解决方案   目录 复现Ajax跨域问题 Ajax跨域介绍 Ajax跨域解决方案 一. 在服务端添加响应头Access-Control-Allow-Origin 二. 使用JSONP ...

  9. asp.net core系列 38 WebAPI 返回类型与响应格式--必备

    一.返回类型 ASP.NET Core 提供以下 Web API Action方法返回类型选项,以及说明每种返回类型的最佳适用情况: (1) 固定类型 (2) IActionResult (3) Ac ...

随机推荐

  1. Jarvis OJ-Level3-x64

    linux64位ROP技术 #!/usr/bin/env python from pwn import * elf = ELF('level3_x64') Io = remote('pwn2.jarv ...

  2. tp5 -- 腾讯云cos简单使用

    因项目需要,本来是需要对接阿里云oss,但因客户错误将云存储买成腾讯云cos,因此简单做了个对象上传使用 首先下载cos的sdk: 三种方式在文档上面都有介绍 SDK 安装有三种方式:Composer ...

  3. qemu-img管理虚拟机

    qemu-img管理虚拟机 1. 查看正在运行的虚拟机 [root@idca-vm02 ~]# virsh list Id    名称                         状态 ----- ...

  4. PAT 乙级 1019

    题目 题目地址:PAT 乙级 1019 思路 本题没有考虑到小于1000的情况,当小于1000的时需要给vector的向量中推入0,直到向量中有四位数字,之后再进行排序并进行相关计算 代码 #incl ...

  5. mysql的字符串处理函数用法

    1.LOCATE函数 LOCATE(substr,str) 返回子串 substr 在字符串 str 中第一次出现的位置.如果子串 substr 在 str 中不存在,返回值为 0.如果substr或 ...

  6. 【IDE_PyCharm】PyCharm中配置当鼠标悬停时快速提示方法参数

    方法一:通过在settings里面设置当鼠标至于方法之上时给出快速提示 方法二:按住Ctrl键,光标放在任意变量或方法上都会弹出该变量或方法的详细信息,点击鼠标还能跳转到变量或方法的定义处

  7. 离线web-ApplicationCache

    https://www.html5rocks.com/en/tutorials/appcache/beginner/ http://diveintohtml5.info/offline.html#fa ...

  8. modelsim安装调试

    modelsim,debug:“unable to checkout a viewer license necessary for use of the modelsim graphical user ...

  9. PyQt5(2)、垃圾分类小程序(2)——初代窗口程序可执行文件

    又是一天时间(又没做大作业).今天的心路历程:(1)前端后端怎么连接?(2)后端数据库插数据(3)完全没用上之前的字典反查法(4)突然发现面向对象编程其实很好用,甚至越用越上瘾(5)QLineEdit ...

  10. bs4--官文--修改文档树

    修改文档树 Beautiful Soup的强项是文档树的搜索,但同时也可以方便的修改文档树 修改tag的名称和属性 在 Attributes 的章节中已经介绍过这个功能,但是再看一遍也无妨. 重命名一 ...