草庐IT

C# 设计模式——设计原则

魏杨杨 2023-03-28 原文

1、前言

最近在搭建项目的的时候才会想设计原则问题,之前也看过设计模式,没有写博客很快就忘了也没有起到什么作用。现在在项目上遇到了你才会发现它的美。博客园也有很多前辈写的很好,对于我来说好记性不如烂笔头嘛。别人写的在好你看了之后终究是别人的。只有自己写下来会用了才是自己的。

2、定义

个人理解设计原则其实就是一个规范一样,为啥要用设计原则?就是为了写出适应变化、提高复用率、可维护性、可扩展性的代码。在进行设计的时候,我们需要遵循单一职责原则、开闭原则、里氏替代原则、依赖倒置原则、接口隔离原则、合成复用原则和迪米特法则。

3、单一职责原则

自己的事情自己干,一个类只弄它单一职责的模块。比如说Login类就只负责登录相关的业务,User类只负责用户相关的业务。如果说Login里面又夹杂着User的功能这样User牵连着其他的类是不是Login类也牵连进去了,这样下来耦合度就很好了。单一职责原则优点就是降低耦合,提高代码的复用率使得模块看起来有目的性,结构简单,修改当前模块对于其他模块的影响很低。缺点就是如果过度的单一,过度的细分,就会产生出很多模块,无形之中增加了系统的复杂程度。

比如Login能登录 但是里面又写了个User吃饭的方法,登录跟吃饭八杆子打不到一块去。生物分类学是研究生物分类的方法和原理的生物学分支。分类就是遵循分类学原理和方法,对生物的各种类群进行命名和等级划分:界门纲目科属种一样。程序里面类也是一样的。

Login login=new();
login.Sign("登录");

public class Login
{
    /// <summary>
    /// 登录
    /// </summary>
    public void Sign(string name)
    {
        Console.WriteLine($"账号密码{name}");
    }
}

现在在弄进去一个Eat(吃饭)login.Sign("吃饭");输出 账号密码吃饭。感觉就不合适了。如果要兼顾两个职责这时候要把程序做一点改变

第一种:这样就让Sign兼顾了两个职责:登录跟吃饭

 public void Sign2(string name)
    {
        if(name=="登录")
        Console.WriteLine($"账号密码{name}");
        else if(name=="吃饭")
            Console.WriteLine($"用户{name}");
    }

 

第二种:此时Sign跟Eat职责都是单一的,但是我们设计之初就是Login是用来登录的

public class Login
{
    /// <summary>
    /// 登录
    /// </summary>
    public void Sign(string name)
    {
        Console.WriteLine($"账号密码{name}");
    }
    /// <summary>
    /// 吃饭
    /// </summary>
    /// <param name="name"></param>
    public void Eat(string name)
    {
        Console.WriteLine($"用户{name}");
    }
    public void Sign2(string name)
    {
        if(name=="登录")
        Console.WriteLine($"账号密码{name}");
        else if(name=="吃饭")
            Console.WriteLine($"用户{name}");
    }
}

第三种:此时Login 的Sign 跟User的Eat职责都是单一的,只做一件事。

public class Login
{
    /// <summary>
    /// 登录
    /// </summary>
    public void Sign(string name)
    {
        Console.WriteLine($"账号密码{name}");
    }
   
}
public class User
{
    /// <summary>
    /// 吃饭
    /// </summary>
    /// <param name="name"></param>
    public void Eat(string name)
    {
        Console.WriteLine($"用户{name}");
    }
}

4、开闭原则(OCP)

强调的是:一个软件实体(指的类、函数、模块等)应该对扩展开放,对修改关闭。即每次发生变化时,要通过添加新的代码来增强现有类型的行为,而不是修改原有的代码。修改本省的代码破坏现有的程序可能稍微不注意就引起很大的连锁反应。通过扩展来实现就是本可能出现的结果抽象出来。

栗子:狗的叫声 Dog(狗)、Show(实现)、Voice(声音)。根据实现传入动物的声音

public class Dog
{
    public  void Call(DogVoice dogVoice)
    {
        Console.WriteLine($"狗的叫声是{dogVoice.Value}");
    }
}
public class DogVoice
{
    public  string Value
    {
        get { return "汪汪汪"; }
    }
}
public class Show
{
    public  void ShowVoice(Dog dog,DogVoice dogVoice)
    {
        dog.Call(dogVoice);
    }
}

调用

Dog d = new Dog();
DogVoice dv = new DogVoice();
Show s = new();
s.ShowVoice(d,dv);

 如果有一天我们引入了新的类小猫类Cat跟声音CatVoice,这个时候就要修改Show 显示类了,这就违反了开闭原则。如果要符合开闭原则 我们就要对Dog跟Voice类做一个抽象,抽象出Call跟Voice的类或者接口。这里我们抽象两个接口ICall 跟IVoice

public interface ICall
{
    void Call(IVoice voice);
}
public interface IVoice
{
    string Value { get; }
}
public class Gog2 : ICall
{
    public void Call(IVoice voice)
    {
        Console.WriteLine($"狗的叫声是{voice.Value}");
    }
}
public class DogVoice2 : IVoice
{
    public string Value => "汪汪汪";
}
public class Show2
{
    public void ShowVoice(ICall dog, IVoice dogVoice)
    {
        dog.Call(dogVoice);
    }
}

调用

ICall d = new Dog2();
IVoice dv = new DogVoice2();
Show2 s = new();
s.ShowVoice(d, dv);

 这样你要新加锚的叫声不是就不用修改show了;新添加两个类就可以了Cat跟CatVoice。

public class Cat : ICall
{
    public void Call(IVoice voice)
    {
        Console.WriteLine($"猫的叫声是{voice.Value}");
    }
}
public class CatVoice : IVoice
{
    public string Value => "喵喵喵";
}

 调用

ICall d = new Dog2();
IVoice dv = new DogVoice2();
Show2 s = new();
s.ShowVoice(d, dv);

ICall c = new Cat();
IVoice v = new CatVoice();
s.ShowVoice(c, v);

5、里氏替换原则(LSP)

一个程序中的子类如果继承了父类那么他将满足父类所有的方法与属性。也就是说在程序中,把父类都替换成它的子类,程序的行为没有任何变化。子类可以有自己的特点也可以重写父类的方法,但是父类有的方法子类一定要实现,这里举一个简单的列子,父类动物(Adimal),子类 母鸡(Hen)

/// <summary>
/// 抽象类父类 动物
/// </summary>
public abstract class Animal
{
    /// <summary>
    /// 抽象方法 下蛋量
    /// </summary>
    /// <returns></returns>
    public abstract double LayEgg();
    /// <summary>
    /// 一年平均每天下蛋
    /// </summary>
    /// <returns></returns>
    public abstract double LayEggAvg(Animal s);
}
public class Hen : Animal
{
    public override double LayEgg()
    {
        return 100;
    }

    public override double LayEggAvg(Animal s)
    {
        return 365 / s.LayEgg();
    }
}

这时候如果在加一个类 羊  Sheep  羊也属于动物类 但是它不会下蛋呀,这个时候计算平均下蛋量的时候程序就要出错;0作为被除数程序编译都通过不了。

public class Sheep : Animal
{
    public override double LayEgg()
    {
        return 0;
    }

    public override double LayEggAvg(Animal s)
    {
        return 365 / s.LayEgg();//0
    }
}

 要运行也可以直接判断是不是绵羊类。是的话返回0,这样又不符合开闭原则了呀。因为Sheep就完全没有继承Animal类,它实现不了LayEggAve的方法。所以现实中绵羊属于动物类,但是程序中不属于,不要强行继承,如果继承就要完全实现。

public class Sheep2 : Animal
{
    public override double LayEgg()
    {
        return 0;
    }

    public override double LayEggAvg(Animal s)
    {
        //传入的类型如果是绵羊类 返回0
        if (s.GetType().Equals(typeof(Sheep2)))
        {
            return 0;
        }
        else
        {
            return 365 / s.LayEgg();
        }
    }
}

6、合成复用原则

如果程序一个对象A包含了另一个对象B,那么A就可以委托B来使用B的功能。 这里学生表李有班级 我们就可以通过学生直接看到班级的信息。

public class Student
{
    public Class Class { get; set; }
}
public class Class
{
    public string GetName
    {
        get { return "计科一班"; }
、    }
}

 

调用

Student student = new();
var name=student.Class.GetName;

7、接口隔离原则

原则上使用多个接口,应用程序端不依赖不用多余的接口,这样也可以保证程序的安全性。比如说系统内部用的接口我们都要实现增删查改,而对于第三方我们只提供查询的接口就可以了。

public interface ICardServiceOut
{
    void Query();//
}
/// <summary>
/// 系统内部就继承两个接口
/// </summary>
public class OurCard : ICardService, ICardServiceOut
{
    public void Add()
    {
        throw new NotImplementedException();
    }

    public void Query()
    {
        throw new NotImplementedException();
    }

    public void Remove()
    {
        throw new NotImplementedException();
    }

    public void Update()
    {
        throw new NotImplementedException();
    }
}
/// <summary>
/// 第三方就只能继承外部接口 只能查询操作
/// </summary>
public class ThirdCard : ICardServiceOut
{
    public void Query()
    {
        throw new NotImplementedException();
    }
}

8、依赖倒置原则(DIP)

抽象不应该依赖细节,而细节应该依赖抽象,高层模块不依赖于低层模块的实现,而低层模块依赖于高层模块定义的接口。一般来讲,就是高层模块定义接口,低层模块负责具体的实现。针对接口编程而不是针对细节编程。详情查看Asp.Net Core 3.1学习-依赖注入、服务生命周期(6) 

9、迪米特法则(LoD)(最少知识原则(LKP))

指的是一个对象应当对其他对象有尽可能少的了解。也就是说,一个模块或对象应尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立,这样当一个模块修改时,影响的模块就会越少,扩展起来更加容易。如果想要满足迪米特法则,就要尽可能少的写public方法和变量,不需要让别的对象知道的方法或者字段就不要公开,好朋友之间都有自己的秘密一个的意思。

其实用不用设计原则程序都能运行出来,没有多大影响,但是后面要增加模块或者修改功能的时候就看你的程序能不能经得起折腾咯?

PS:学习向日葵,做一个积极吸收正能量的人。人生多数时候都是自寻烦恼。就是吸收的负能量太多。要学习向日葵,哪里有阳光就朝向哪里。多接触优秀的人,多谈论健康向上的话题,多想想有利于人生发展的问题。心里若是充满阳光,人生即便下雨,也会变成春雨。

有关C# 设计模式——设计原则的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  5. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  6. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  7. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  8. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  9. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  10. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

随机推荐