RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 900455
Accepted
Andrew_STOP_RU_AGRESSION_IN_UA
Andrew_STOP_RU_AGRESSION_IN_UA
Asked:2020-10-31 22:53:45 +0000 UTC2020-10-31 22:53:45 +0000 UTC 2020-10-31 22:53:45 +0000 UTC

带有简单 C# 代码示例的简单语言的 SOLID 原则 [关闭]

  • 772
关闭。这个问题需要具体说明。目前不接受回复。

你想改进这个问题吗? 重新构建问题,使其只关注一个问题。

3年前关闭。

改进问题

我所到之处,一切都写得太难理解了。我决定自己写一个易于理解的关于 SOLID 原理的描述,并附上示例。同时,更详细地处理它们。

如果有人在阅读时有任何澄清/批评/改进建议,请写在评论或单独的答案中。好吧,或者,直接编辑我的答案。

我的问题/答案的主要目标正是为任何级别的人编写尽可能容易理解的内容:

  • 用最简单和简短的例子。(总是打破一切来理解一堆别人的代码)
  • 用最少的术语。最简单的语言。

很明显,有关该主题的文章是大海。但是易于理解并且代码列表很短。

就个人而言,我还没有找到任何东西。

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    Andrew_STOP_RU_AGRESSION_IN_UA
    2020-10-31T22:53:45Z2020-10-31T22:53:45Z

    SOLID是OOP原则。重要的是要理解这些是建议,而不是教条。不需要申请。您还需要了解,有时遵循其中一项原则可能会违反另一项原则。

    应用这些原则的后果:

    • 更多代码
    • 但另一方面,这段代码会更容易修改和测试(如果应用正确的话)

    还有YAGNI的原则——你不需要它(你不需要它)。这个想法是,如果你现在没有客观的理由在这里应用一个原则或模式,那么你根本不需要应用它。以 SOLID 为例:如果您以后不需要修改和开发代码(例如 1 晚的项目)- 以更简单的方式编写代码,而不使用 SOLID。试图在开发的早期阶段创建一个灵活的解决方案通常是一个过于复杂的解决方案。

    让我们回到我们的羊:

    SOLID是以下的缩写:

    • SRP:单一责任原则——“一个类应该只有一个改变的理由”
    • OCP:开放/封闭原则——软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
    • LSP:L iskov 替换原则 - 替换其任何子类型而不是基本类型的能力。
    • ISP:接口隔离原则
    • DIP:依赖倒置原理——依赖倒置(需要依赖抽象)

    让我们尝试使用 c# 语言的示例找出并理解它们。 我会出事的。




    单一责任。“一个班级应该只有一个改变的理由。” 听起来很模糊,对吧?

    PS:“单一职责/责任原则”听起来完全不直观,所以我建议你不要这样背。

    更改类逻辑的原因:

    • 改变类之间的关系
    • 引入新要求或废除旧要求。

    这个原则的本质是类的功能必须是完整的,具有高度的逻辑连贯性。如果一个类有很多职责(许多“来自不同领域”的功能),那么它会经常变化。因此,如果一个类有多个职责,那么当代码更改时,这会导致设计脆弱并在意想不到的地方出现错误。任何复杂的类都必须分解成几个简单的组件,这些组件负责行为的某个方面。

    让我们以伯德为例。有会飞的鸟,也有不会飞的鸟。如果我们创建一个会飞的鸵鸟或猕猴桃的实例,那会很奇怪。也就是说,而不是类

    public class Bird
    {
        public void Fly() { }
    }
    

    我们应该有 3 个类:

    public class Bird
    {
         public void Jump() { }
    }
    
    public class BirdFlying: Bird
    {
         public void Fly() { }
    }
    
    public class BirdNonFlying: Bird
    {
        public void Run() {}
    }
    

    应用细微差别:

    • 责任合并是一种常见的做法,没有什么不好,只要它易于维护。也就是说,没有必要狂热地拆分课程——这会使项目的工作变得复杂,就像不应用这个原则一样。
    • 如果你在做一个游戏,而你只有飞鸟,那么,当然,像例子中那样做这样的粉碎是没有意义的。
    • 如果你的班级人数开始膨胀,那么值得考虑是否需要将这个班级分成几个班级。
    • 该类解决了不同抽象级别的问题(例如,它解析一个 json 对象并分析其内容) - 一个明显的迹象表明这不符合原则。
    • 类接口过于异构并且包含负责松散耦合逻辑操作的方法。
    • 一个类或接口包含多个由不同客户端使用的具有相似语义的方法



    接口隔离是为每个客户端提供最小(理想情况下是独立的!)接口。

    考虑代码:

    interface IMachine
    {
        public bool print(List<Item> item);
        public bool staple(List<Item> item);
        public bool fax(List<Item> item);
        public bool scan(List<Item> item);
        public bool photoCopy(List<Item> item);
    }  
    
    class Machine : IMachine
    {
       public bool print(List<Item> item){} //Печатает все документы
    
       public bool staple(List<Item> item){} //Скрепляет степлером
    
       public bool fax(List<Item> item) {} //Отправляет факсом документы
    
       public bool scan(List<Item> item) {} //Сканирует документы
    
       public bool photoCopy(List<Item> item) //Делает копии документов
    } 
    

    代码问题:

    1. 重新编译所有代码,即使是很小的改动。
    2. 客户将可以访问许多“左”方法,即使他只对打印感兴趣(例如)

    根据ISP原理,我们需要将一个接口分成若干个:IPrinter, IStaple, IFax, IScan,IPhotoCopy

    而且,由于我们有一台实现上述所有功能的通用机器,因此为它创建一个将从它们继承的接口。

    interface IMachine : IPrinter, IFax, IScan, IPhotoCopy,IStaple
    {
        public bool print(List<Item> item);
        public bool staple(List<Item> item);
        public bool fax(List<Item> item);
        public bool scan(List<Item> item);
        public bool photoCopy(List<Item> item);
    }
    

    并添加一个类构造函数Machine

    public  Machine(IPrinter printer, IFax fax, IScan scan, IPhotoCopy photoCopy, IStaple staple)
    {
        this.Printer = printer;
        this.fax = fax;    
        this.scan = scan;
        this.photoCopy = photocopy;
        this.staple = staple;
    }
    

    那么这样一台万能机的创建过程如下:

    var allOneClient  = new Machine(
                            new Printer(), 
                            new Fax(), 
                            new Scanner(), 
                            new PhotoCopy(), 
                            new Staple() );
    

    通过这样做,我们可以获得以下好处:

    1. 客户只能访问客户需要的那些方法。他不知道其他人。
    2. 重新编译项目时,只会重新编译那些已更改的块。



    打开/关闭- 程序实体必须对扩展开放,但对修改关闭。

    这是什么意思?

    Closed for modify意味着:你可以改变一个类\函数\模块的代码的唯一原因是直接改变其中嵌入的函数或者修复这个函数运行中的错误。不可能有其他原因。

    开放扩展意味着:如果您需要您的类\功能\模块能够在新环境中执行其功能,他们必须在不更改代码的情况下支持它。

    假设我们有一个类来对数组进行排序

    public class BubbleSorter
    {
        public void Sort(int[] data)
        {
            int n = data.Length;
            for (int i = 0; i < n - 1; i++)
                for (int j = 0; j < n - i - 1; j++)
                    if (data[j] > data[j + 1])
                    {
                        int temp = data[j];
                        data[j] = data[j + 1];
                        data[j + 1] = temp;
                    }
        }
    }
    

    此方法只能对整数进行排序。

    如果我们的程序已经需要相同的方法,但不仅是整数(或者将来肯定会需要它)——这就是我们的扩展点。

    我们使用 dotnet 中已有的接口,稍微改写一下代码:

    public class BubbleSorter<T>
    {
        IComparer<T> _comparer;
    
        public BubbleSorter(IComparer<T> comparer)
        {
            _comparer = comparer;
        }
    
        public void Sort(T[] data)
        {
            int n = data.Length;
            for (int i = 0; i < n - 1; i++)
                for (int j = 0; j < n - i - 1; j++)
                    if (_comparer.Compare(data[j], data[j + 1]) > 0)
                    {
                        var temp = data[j];
                        data[j] = data[j + 1];
                        data[j + 1] = temp;
                    }
        }
    }
    

    我们通过这些变化取得了很多成就,即:

    • 我们可以对所有可以通过我们的方法排序的东西进行排序:整数、浮点数、字符串。
    • 如果我们想按降序而不是升序排序,我们不需要更改排序器代码或创建相同的反向排序函数。

    应用细微差别:

    • 过多地应用原则与不应用原则一样对结果不利。过于抽象和通用的代码对于您或其他人来说将难以解析,并且会创建太多难以理解的抽象层。
    • 这个例子是假设在这个项目中除了数组之外不需要对其他数据进行排序。
    • 这个例子是为了改变原始数据数组而特意编写的。也就是说,所有更改都在原始数组对象中进行。我们不需要实现我们可能只是理论上需要的东西。

    应用原理的第二个例子:通过继承。

    我们有一个处理 CSV(逗号分隔值)的类。也就是说,它将表格写入文本文件,其中逗号是一行中的单元格分隔符,而新行是表格中的新行[通常,那里的一切都更复杂,但这是为了便于理解]

    如果突然我们需要同一个类,但它也写列标题,那么我们只需要从这个类继承,而不是改变基类。

    public class Csv
    { 
        //Some methods
    }
    
    public class CsvWithHeader: Csv
    {
        //header realization
    }
    

    这篇文章是根据答案写的什么是开放和封闭的原则?

    如果有兴趣,可以通过使用装饰器来应用此原则的第三个示例。




    Liskov 替换——实现“CORRECT”多态的想法。也就是说,有必要以这样一种方式实现子类型继承,即可以替换它的任何子类型而不是基类型。

    有时这很难做到。用一个例子来考虑实现它的复杂性:

    public interface IDuck
    {
       void Swim();
       // contract says that IsSwimming should be true if Swim has been called.
       bool IsSwimming { get; }
    }
    public class OrganicDuck : IDuck
    {
       public void Swim() { }
       bool IsSwimming { get{}}
    }
    public class ElectricDuck : IDuck
    {
       bool _isSwimming;
    
       public void Swim()
       {
          if (!IsTurnedOn)
            return;
    
          _isSwimming = true;
          //swim logic
       }
    
       bool IsSwimming { get { return _isSwimming; } }
    }
    

    假设我们有一些方法应该对鸭子做一些事情。无论。public void DoSomethingWithDuck(IDuck: duck) {}.

    这里应用原理的难点在于,电鸭只有在开机的情况下才能游泳。也就是说,如果我们在这个方法中放入一个OFF电鸭子并调用duck.Swim();它,它就不会游泳了。也就是说,在这种情况下,LSP 只是乍一看,但实际上并没有遵守这一原则。

    当然,这可以通过以下形式在此方法中使用拐杖来解决:

    if (duck is ElectricDuck)
    {
        ((ElectricDuck)duck).TurnOn();
    }
    duck.Swim();
    

    但这只是一根拐杖。

    遵循原理的正确解决方案是更改电子鸭子,使其在调用该方法时自动打开.Swim();。

    第二种方法是根本不让电鸭成为有机鸭的“亲戚”。




    依赖转换。这个原则到处都被解释为“上层模块不应该依赖于下层模块。两者都应该依赖于抽象。” 对我来说,这句话很难理解。这在实践中意味着什么?

    让我们转向该原则的作者(Bob Martin),他简短地说“你需要依赖抽象,而不是具体的东西。” 使用这个原理,一个模块可以很容易地被另一个模块替换,只需改变依赖模块,然后低级模块的任何变化都不会影响高级模块。

    • 不应该有存储对具体类的引用的变量。
    • 不应该有从具体类派生的类。
    • 不应该有任何方法覆盖在基类之一中实现的方法。
    • 但与此同时,对语言的“本地”类的依赖是很正常的。也就是说,对String的依赖是很正常的。如果我们自己编写类,那么它们可以是可变的。我们不想直接依赖这些类。

    例如,它可能看起来像这样:

    public class MySQLConnection
    {
       public bool Connect() 
       {
             //Коннектимся к MYSQL бд
       }
    }
    
    public class PasswordReminder
    { 
        private MySQLConnection _dbConnection;
    
        public void PasswordReminder (MySQLConnection dbConnection) 
        {
            _dbConnection = dbConnection;
        }
    }
    

    PasswordReminder 类依赖于 MySQLConnection。但是更高级别的 PasswordReminder 不应该依赖于更低级别的 MySQLConnection 模块。

    如果我们需要将连接从 MySQLConnection 更改为 MongoDBConnection,那么我们必须将代码中编写的构造函数注入更改为 PasswordReminder 类。

    PasswordReminder 类应该依赖于抽象,而不是任何特定的东西。但是怎么做呢?

    public interface IDbConnection
    {
        public Connect();
    }
    
    public class DbConnection: IDbConnection
    {
        public bool Connect()
        {
            //Коннектимся к MYSQL бд
        }
    }
    
    public class PasswordReminder
    {
        private IDbConnection _dbConnection;
    
        public void PasswordReminder (IDbConnection dbConnection) 
        {
            _dbConnection = dbConnection;
        }
    }
    

    由于这些变化PasswordReminder()只依赖于抽象。我们可以通过改变一种方法来连接 MongoDB 而不是Connect()MYSQL——或者创建一个 MongoDBConnection 并从 IDbConnection 继承;

    • 4

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    是否可以在 C++ 中继承类 <---> 结构?

    • 2 个回答
  • Marko Smith

    这种神经网络架构适合文本分类吗?

    • 1 个回答
  • Marko Smith

    为什么分配的工作方式不同?

    • 3 个回答
  • Marko Smith

    控制台中的光标坐标

    • 1 个回答
  • Marko Smith

    如何在 C++ 中删除类的实例?

    • 4 个回答
  • Marko Smith

    点是否属于线段的问题

    • 2 个回答
  • Marko Smith

    json结构错误

    • 1 个回答
  • Marko Smith

    ServiceWorker 中的“获取”事件

    • 1 个回答
  • Marko Smith

    c ++控制台应用程序exe文件[重复]

    • 1 个回答
  • Marko Smith

    按多列从sql表中选择

    • 1 个回答
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Suvitruf - Andrei Apanasik 什么是空? 2020-08-21 01:48:09 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5