理论跟实践的关系,说远不远,说近不近。能不能把理论用到实践上,还真不好说。

通常讲到设计模式,一个最通用的原则是SOLID:

  1. S - Single Responsibility Principle,单一责任原则
  2. O - Open Closed Principle,开闭原则
  3. L - Liskov Substitution Principle,里氏替换原则
  4. I - Interface Segregation Principle,接口隔离原则
  5. D - Dependency Inversion Principle,依赖倒置原则

嗯,这就是五大原则。

后来又加入了一个:Law of Demeter,迪米特法则。于是,就变成了六大原则。

原则好理解。怎么用在实践中?

    为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/13525841.html

一、单一责任原则

单一责任原则,简单来说就是一个类或一个模块,只负责一种或一类职责。

看代码:

public interface IUser
{
    void AddUser();
    void RemoveUser();
    void UpdateUser();     void Logger();
    void Message();
}

根据原则,我们会发现,对于IUser来说,前三个方法:AddUserRemoveUserUpdateUser是有意义的,而后两个LoggerMessage作为IUser的一部分功能,是没有意义的并不符合单一责任原则的。

所以,我们可以把它分解成不同的接口:

public interface IUser
{
    void AddUser();
    void RemoveUser();
    void UpdateUser();
}
public interface ILog
{
    void Logger();
}
public interface IMessage
{
    void Message();
}

拆分后,我们看到,三个接口各自完成自己的责任,可读性和可维护性都很好。

下面是使用的例子,采用依赖注入来做:

public class Log : ILog
{
    public void Logger()
    {
        Console.WriteLine("Logged Error");
    }
}
public class Msg : IMessage
{
    public void Message()
    {
        Console.WriteLine("Messaged Sent");
    }
}
class Class_DI
{
    private readonly IUser _user;
    private readonly ILog _log;
    private readonly IMessage _msg;
    public Class_DI(IUser user, ILog log, IMessage msg)
    {
        this._user = user;
        this._log = log;
        this._msg = msg;
    }
    public void User()
    {
        this._user.AddUser();
        this._user.RemoveUser();
        this._user.UpdateUser();
    }
    public void Log()
    {
        this._log.Logger();
    }
    public void Msg()
    {
        this._msg.Message();
    }
}
public static void Main()
{
    Class_DI di = new Class_DI(new User(), new Log(), new Msg());
    di.User();
    di.Log();
    di.Msg();
}

这样的代码,看着就漂亮多了。

二、开闭原则

开闭原则要求类、模块、函数等实体应该对扩展开放,对修改关闭。

我们先来看一段代码,计算员工的奖金:

public class Employee
{
    public int Employee_ID;
    public string Name;
    public Employee(int id, string name)
    {
        this.Employee_ID = id;
        this.Name = name;
    }
    public decimal Bonus(decimal salary)
    {
        return salary * .2M;
    }
}
class Program
{
    static void Main(string[] args)
    {
        Employee emp = new Employee(101, "WangPlus");
        Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", emp.Employee_ID, emp.Name, emp.Bonus(10000));
    }
}

现在假设,计算奖金的公式做了改动。

要实现这个,我们可能需要对代码进行修改:

public class Employee
{
    public int Employee_ID;
    public string Name;
    public string Employee_Type;     public Employee(int id, string name, string type)
    {
        this.Employee_ID = id;
        this.Name = name;
        this.Employee_Type = type;
    }
    public decimal Bonus(decimal salary)
    {
        if (Employee_Type == "manager")
            return salary * .2M;
        else
            return
                salary * .1M;
    }
}

显然,为了实现改动,我们修改了类和方法。

这违背了开闭原则。

那我们该怎么做?

我们可以用抽象类来实现 - 当然,实际有很多实现方式,选择最习惯或自然的方式就成:

public abstract class Employee
{
    public int Employee_ID;
    public string Name;
    public Employee(int id, string name)
    {
        this.Employee_ID = id;
        this.Name = name;
    }
    public abstract decimal Bonus(decimal salary);
}

然后,我们再实现最初的功能:

public class GeneralEmployee : Employee
{
    public GeneralEmployee(int id, string name) : base(id, name)
    {
    }
    public override decimal Bonus(decimal salary)
    {
        return salary * .2M;
    }
}
class Program
{
    public static void Main()
    {
        Employee emp = new GeneralEmployee(101, "WangPlus");
        Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", emp.Employee_ID, emp.Name, emp.Bonus(10000));
    }
}

在这儿使用抽象类的好处是:如果未来需要修改奖金规则,则不需要像前边例子一样,修改整个类和方法,因为现在的扩展是开放的。

代码写完整了是这样:

public abstract class Employee
{
    public int Employee_ID;
    public string Name;
    public Employee(int id, string name)
    {
        this.Employee_ID = id;
        this.Name = name;
    }
    public abstract decimal Bonus(decimal salary);
} public class GeneralEmployee : Employee
{
    public GeneralEmployee(int id, string name) : base(id, name)
    {
    }
    public override decimal Bonus(decimal salary)
    {
        return salary * .1M;
    }
}
public class ManagerEmployee : Employee
{
    public ManagerEmployee(int id, string name) : base(id, name)
    {
    }
    public override decimal Bonus(decimal salary)
    {
        return salary * .2M;
    }
}
class Program
{
    public static void Main()
    {
        Employee emp = new GeneralEmployee(101, "WangPlus");
        Employee emp1 = new ManagerEmployee(102, "WangPlus1");
        Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", emp.Employee_ID, emp.Name, emp.Bonus(10000));
        Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", emp1.Employee_ID, emp1.Name, emp1.Bonus(10000));
    }
}

三、里氏替换原则

里氏替换原则,讲的是:子类可以扩展父类的功能,但不能改变基类原有的功能。它有四层含义:

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
  2. 子类中可以增加自己的特有方法;
  3. 当子类重载父类的方法时,方法的前置条件(形参)要比父类的输入参数更宽松;
  4. 当子类实现父类的抽象方法时,方法的后置条件(返回值)要比父类更严格。

在前边开闭原则中,我们的例子里,实际上也遵循了部分里氏替换原则,我们用GeneralEmployeeManagerEmployee替换了父类Employee

还是拿代码来说。

假设需求又改了,这回加了一个临时工,是没有奖金的。

public class TempEmployee : Employee
{
    public TempEmployee(int id, string name) : base(id, name)
    {
    }
    public override decimal Bonus(decimal salary)
    {
        throw new NotImplementedException();
    }
}
class Program
{
    public static void Main()
    {
        Employee emp = new GeneralEmployee(101, "WangPlus");
        Employee emp1 = new ManagerEmployee(101, "WangPlus1");
        Employee emp2 = new TempEmployee(102, "WangPlus2");
        Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", emp.Employee_ID, emp.Name, emp.Bonus(10000));
        Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", emp1.Employee_ID, emp1.Name, emp1.Bonus(10000));
        Console.WriteLine("Employee ID: {0} Name: {1} Bonus: {2}", emp2.Employee_ID, emp2.Name, emp2.Bonus(10000));
        Console.ReadLine();
    }
}

显然,这个方式不符合里氏替原则的第四条,它抛出了一个错误。

所以,我们需要继续修改代码,并增加两个接口:

interface IBonus
{
    decimal Bonus(decimal salary);
}
interface IEmployee
{
    int Employee_ID { get; set; }
    string Name { get; set; }
    decimal GetSalary();
}
public abstract class Employee : IEmployee, IBonus
{
    public int Employee_ID { get; set; }
    public string Name { get; set; }
    public Employee(int id, string name)
    {
        this.Employee_ID = id;
        this.Name = name;
    }
    public abstract decimal GetSalary();
    public abstract decimal Bonus(decimal salary);
}
public class GeneralEmployee : Employee
{
    public GeneralEmployee(int id, string name) : base(id, name)
    {
    }
    public override decimal GetSalary()
    {
        return 10000;
    }
    public override decimal Bonus(decimal salary)
    {
        return salary * .1M;
    }
}
public class ManagerEmployee : Employee
{
    public ManagerEmployee(int id, string name) : base(id, name)
    {
    }
    public override decimal GetSalary()
    {
        return 10000;
    }
    public override decimal Bonus(decimal salary)
    {
        return salary * .1M;
    }
}
public class TempEmployee : IEmployee
{
    public int Employee_ID { get; set; }
    public string Name { get; set; }
    public TempEmployee(int id, string name)
    {
        this.Employee_ID = id;
        this.Name = name;
    }
    public decimal GetSalary()
    {
        return 5000;
    }
}
class Program
{
    public static void Main()
    {
        Employee emp = new GeneralEmployee(101, "WangPlus");
        Employee emp1 = new ManagerEmployee(102, "WangPlus1");
        Console.WriteLine("Employee ID: {0} Name: {1} Salary: {2} Bonus:{3}", emp.Employee_ID, emp.Name, emp.GetSalary(), emp.Bonus(emp.GetSalary()));
        Console.WriteLine("Employee ID: {0} Name: {1} Salary: {2} Bonus:{3}", emp1.Employee_ID, emp1.Name, emp1.GetSalary(), emp1.Bonus(emp1.GetSalary()));         List<IEmployee> emp_list = new List<IEmployee>();
        emp_list.Add(new GeneralEmployee(101, "WangPlus"));
        emp_list.Add(new ManagerEmployee(102, "WangPlus1"));
        emp_list.Add(new TempEmployee(103, "WangPlus2"));
        foreach (var obj in emp_list)
        {
            Console.WriteLine("Employee ID: {0} Name: {1} Salary: {2} ", obj.EmpId, obj.Name, obj.GetSalary());
        }
    }
}

四、接口隔离原则

接口隔离原则要求客户不依赖于它不使用的接口和方法;一个类对另一个类的依赖应该建立在最小的接口上。

通常的做法,是把一个臃肿的接口拆分成多个更小的接口,以保证客户只需要知道与它相关的方法。

这个部分不做代码演示了,可以去看看上边单一责任原则里的代码,也遵循了这个原则。

五、依赖倒置原则

依赖倒置原则要求高层模块不能依赖于低层模块,而是两者都依赖于抽象。另外,抽象不应该依赖于细节,而细节应该依赖于抽象。

看代码:

public class Message
{
    public void SendMessage()
    {
        Console.WriteLine("Message Sent");
    }
}
public class Notification
{
    private Message _msg;     public Notification()
    {
        _msg = new Message();
    }
    public void PromotionalNotification()
    {
        _msg.SendMessage();
    }
}
class Program
{
    public static void Main()
    {
        Notification notify = new Notification();
        notify.PromotionalNotification();
    }
}

这个代码中,通知完全依赖Message类,而Message类只能发送一种通知。如果我们需要引入别的类型,例如邮件和SMS,则需要修改Message类。

下面,我们使用依赖倒置原则来完成这段代码:

public interface IMessage
{
    void SendMessage();
}
public class Email : IMessage
{
    public void SendMessage()
    {
        Console.WriteLine("Send Email");
    }
}
public class SMS : IMessage
{
    public void SendMessage()
    {
        Console.WriteLine("Send Sms");
    }
}
public class Notification
{
    private IMessage _msg;
    public Notification(IMessage msg)
    {
        this._msg = msg;
    }
    public void Notify()
    {
        _msg.SendMessage();
    }
}
class Program
{
    public static void Main()
    {
        Email email = new Email();
        Notification notify = new Notification(email);
        notify.Notify();         SMS sms = new SMS();
        notify = new Notification(sms);
        notify.Notify();
    }
}

通过这种方式,我们把代码之间的耦合降到了最小。

六、迪米特法则

迪米特法则也叫最少知道法则。从称呼就可以知道,意思是:一个对象应该对其它对象有最少的了解。

在写代码的时候,尽可能少暴露自己的接口或方法。写类的时候,能不public就不public,所有暴露的属性、接口、方法,都是不得不暴露的,这样能确保其它类对这个类有最小的了解。

这个原则没什么需要多讲的,调用者只需要知道被调用者公开的方法就好了,至于它内部是怎么实现的或是有其他别的方法,调用者并不关心,调用者只关心它需要用的。反而,如果被调用者暴露太多不需要暴露的属性或方法,那么就可能导致调用者滥用其中的方法,或是引起一些其他不必要的麻烦。

最后说两句:所谓原则,不是规则,不是硬性的规定。在代码中,能灵活应用就好,不需要非拘泥于形式,但是,用好了,会让代码写得很顺手,很漂亮。

(全文完)


微信公众号:老王Plus

扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

本文版权归作者所有,转载请保留此声明和原文链接

C#实践设计模式原则SOLID的更多相关文章

  1. 实践GoF的23种设计模式:SOLID原则(上)

    摘要:本文以我们日常开发中经常碰到的一些技术/问题/场景作为切入点,示范如何运用设计模式来完成相关的实现. 本文分享自华为云社区<实践GoF的23种设计模式:SOLID原则(上)>,作者: ...

  2. C#软件设计——小话设计模式原则之:依赖倒置原则DIP

    前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身.群里面有朋友问博主是否改行做前端了,呵呵,其实博主是想做“全战”, ...

  3. C#软件设计——小话设计模式原则之:单一职责原则SRP

    前言:上篇C#软件设计——小话设计模式原则之:依赖倒置原则DIP简单介绍了下依赖倒置的由来以及使用,中间插了两篇WebApi的文章,这篇还是回归正题,继续来写写设计模式另一个重要的原则:单一职责原则. ...

  4. C#软件设计——小话设计模式原则之:接口隔离原则ISP

    前言:有朋友问我,设计模式原则这些东西在园子里都讨论烂了,一搜一大把的资料,还花这么大力气去整这个干嘛.博主不得不承认,园子里确实很多这方面的文章,并且不乏出色的博文.博主的想法是,既然要完善知识体系 ...

  5. C#软件设计——小话设计模式原则之:开闭原则OCP

    前言:这篇继续来看看开闭原则.废话少说,直接入正题. 软件设计原则系列文章索引 C#软件设计——小话设计模式原则之:依赖倒置原则DIP C#软件设计——小话设计模式原则之:单一职责原则SRP C#软件 ...

  6. 设计模式原则——依赖倒转&里氏代换原则

    设计模式一共有六大原则: 单一原则.开放封闭原则.接口分离原则.里氏替换原则.最少知识原则.依赖倒置原则. 这篇博客是自己对依赖倒转&里氏代换原则的一些拙见,有何不对欢迎大家指出. 依赖倒转原 ...

  7. Java设计模式(二)设计模式原则

    学习Java设计模式之前,有必要先了解设计模式原则. 开闭原则 定义 一个软件实体如类.模块和函数应该对扩展开放,对修改关闭 用抽象构建框架,用实现扩展细节 优点:提高软件系统的可复用性及可维护性 C ...

  8. 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则

    目录 前言 聚合 聚合和聚合根原则 包含业务原则 单个单元原则 事务边界原则 可序列化原则 聚合和聚合根最佳实践 只通过ID引用其他聚合 用于 EF Core 和 关系型数据库 保持聚合根足够小 聚合 ...

  9. 基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则

    目录 系列文章 仓储 仓储的通用原则 仓储中不包含领域逻辑 规约 在实体中使用规约 在仓储中使用规约 组合规约 学习帮助 围绕DDD和ABP Framework两个核心技术,后面还会陆续发布核心构件实 ...

随机推荐

  1. 部署SpringBoot到阿里云

    目录 安装Mysql 1. 下载命令 2. 进行repo的安装: 3. 安装mysql 部署SpringBoot到阿里云服务器 1.IDEA下载插件 2.进入 Preference 配置一个 Acce ...

  2. Fortify Audit Workbench 笔记 Path Manipulation

    Path Manipulation Abstract 通过用户输入控制 file system 操作所用的路径,借此攻击者可以访问或修改其他受保护的系统资源. Explanation 当满足以下两个条 ...

  3. 一分钟玩转 Spring IoC!

    前言 「上一篇文章」我们对 Spring 有了初步的认识,而 Spring 全家桶中几乎所有组件都是依赖于 IoC 的. 刚开始听到 IoC,会觉得特别高大上,但其实掰开了很简单. 跟着我的脚步,一文 ...

  4. 求100以内所有奇数的和,存于字变量X中。

    问题 求100以内所有奇数的和,存于字变量X中. 代码 data segment x dw ? data ends stack segment stack db 100 dup(?) stack en ...

  5. 从键盘输入一个字符串(长度不超过30),统计字符串中非数字的个数,并将统计的结果显示在屏幕上,用EXE格式实现。

    问题 从键盘输入一个字符串(长度不超过30),统计字符串中非数字的个数,并将统计的结果显示在屏幕上,用EXE格式实现. 源程序 data segment hintinput db "plea ...

  6. PHP MySQL Delete删除数据库中的数据

    PHP MySQL Delete DELETE 语句用于从数据库表中删除行. 删除数据库中的数据 DELETE FROM 语句用于从数据库表中删除记录. 语法 DELETE FROM table_na ...

  7. PHP curl_unescape函数

    (PHP 5 >= 5.5.0) curl_unescape — 解码经过URL编码的字符串. 说明 string curl_unescape ( resource $ch , string $ ...

  8. PHP log1p() 函数

    实例 返回不同数的 log(1+number): <?phpecho(log1p(2.7183) . "<br>");echo(log1p(2) . " ...

  9. PHP strcasecmp() 函数

    实例 比较两个字符串(不区分大小写): <?php高佣联盟 www.cgewang.comecho strcasecmp("Hello world!","HELLO ...

  10. PHP setlocale() 函数

    实例 设置地区为 US English,然后再设置回系统默认: <?php高佣联盟 www.cgewang.comecho setlocale(LC_ALL,"US");ec ...