RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 888860
Accepted
Vladimr Vladimirovoch
Vladimr Vladimirovoch
Asked:2020-10-04 14:37:23 +0000 UTC2020-10-04 14:37:23 +0000 UTC 2020-10-04 14:37:23 +0000 UTC

开放与封闭的原则是什么?

  • 772

我学习 SOLID 原理。告诉我一个清楚地说明这个原则的例子,我心里明白,这个类应该是封闭的,而不是扩展的,这里有扩展,告诉我。

我读了书,但我不明白这样的时刻:我只是不明白如果一个类因更改而关闭,那么它如何扩展

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    tym32167
    2020-10-05T04:21:14Z2020-10-05T04:21:14Z

    所以原理就是

    编程实体(类、模块、函数等)必须对扩展开放,对修改关闭

    所以,很明显我们在谈论:

    1. 班级
    2. 模块
    3. 功能
    4. 等等

    它们都必须对扩展开放,对修改关闭。听起来不错,但要通过它的名字来理解这个原理是相当困难的。

    让我们从“关闭修改”开始。这意味着您可以更改类\函数\模块代码的唯一原因是直接更改嵌入其中的函数。全部。应该没有更多理由更改此代码。正是在这一点上,责任唯一性原则有了交集。

    此外,开放扩展是什么意思?这意味着如果您需要您的类\功能\模块能够在新环境中执行其预期功能,它们必须支持它而不更改其代码。

    让我们看一个例子来说明。

    使用委托扩展类

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

    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;
                    }
        }
    }
    

    让我们看看这堂课。让我们试着了解它有哪些扩展点。扩展点是您的应用程序在代码的不同用例过程中可能遇到(或已经遇到)的那些需求。

    例如,我们看到这个方法只对数字进行排序。但在 99% 的概率下,我们需要对除数字之外的其他内容进行排序。此外,排序只是按升序排序,但很可能我们需要按降序排序。事实上,我们再次与责任唯一性原则相交——我们确定类现在有哪些责任并尝试分担这些责任。让我们使用 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;
                    }
        }
    }
    

    现在我们的分拣机更加灵活。现在,如果我们需要对字符串而不是数字进行排序,我们不必对排序器进行更改。如果我们需要按降序而不是升序排序,我们不必更改排序器代码。因此,我们可以使用新功能扩展我们的类,而无需实际更改类本身。

    函数也是如此。例如,

    var sorted = data.OrderBy(x=> /* ваше направление сортировки */ );
    

    程序模块也是如此(例如,当您可以在不同的场景中重用相同的模块时)。

    但是,以上内容并不意味着您应该立即赶赴课堂并开始将此类技巧注入其中。与往常一样,在这样做之前,您需要用头脑思考。例如,您可以看到上面的代码只对数组进行排序。并就地排序(更改原始数组)。我故意省略了它,因为我想表明一个人不应该走极端。没有必要盲目地遵循原则,写东西之前要仔细考虑。因为如果你漫不经心地遵循这个原则,那么你最终可能会得到太抽象的代码,没有人会理解任何东西。

    就我而言,我决定不需要对数组以外的其他数据类型进行排序。我假设我在当前的项目中不需要这个(将其视为综合示例的一部分)。

    但是我们只考虑了扩展类的一种选择——将其部分责任转移到另一个可以动态替换的对象。我们还能用课堂做什么?

    使用继承扩展类

    假设我们在 CSV 文件中有一个典型的 writer 类

    public class CsvWriter<T>
    {   
        protected void WriteBody(IEnumerable<T> obj, TextWriter stream){
            foreach(var ob in obj) stream.WriteLine(ob);
        }
    
        protected virtual void WriteInternal(IEnumerable<T> obj, TextWriter stream)
        {
            WriteBody(obj, stream);
        }
    
        public void Write(IEnumerable<T> obj, TextWriter stream)
        {
            WriteInternal(obj, stream);
        }
    }
    

    通过训练有素的眼睛,您可以看到在这种情况下,类本身决定了文件中对象条目的外观。当然,这可以作为单独的责任单独列出,但这与我们现在无关。但担心的是我们的类没有写文件头。也就是说,他可以以某种方式写下这些列,但每列都没有标题。该怎么办?从类函数的签名可以看出,它有一个虚方法,完全决定了数据写入文件的顺序。我们可以很容易地编写一个子类并向其添加文件头,而无需更改基类:

    public class CsvWriterWithHeader<T> : CsvWriter<T>
    {
        protected virtual void WriteHeader(TextWriter stream){
            stream.WriteLine("I AM HEADER!");
        }
    
        protected override void WriteInternal(IEnumerable<T> obj, TextWriter stream)
        {
            WriteHeader(stream);
            WriteBody(obj, stream);
        }   
    }
    

    结果,功能得到了扩展,原来的类没有被触及。

    使用聚合扩展类

    因此,例如,我们有一个用于存储库的接口和一个类。您甚至可能拥有此类存储库的多个实现。

    public interface IRepository
    {
        void SaveStuff();
    }
    
    public class Repository : IRepository
    {
        public void SaveStuff()
        {
            // save stuff   
        }
    }
    

    当然,这一切都有一些客户。

    class RepoClient
    {
        public void DoSomethig(IRepository repo)
        {
            //...
            repo.SaveStuff();
        }
    }
    

    有一天,您的老板告诉您,您需要记录对存储库的每个实现的每次调用。但正因为如此,我不想改变所有的实现(你已经知道为什么了)。该怎么办?当然,推出一个新的包装器实现

    public class RepositoryLogDecorator  : IRepository
    {
        public IRepository _inner;
    
        public RepositoryLogDecorator(IRepository inner)
        {
            _inner = inner;
        }
    
        public void SaveStuff()
        {
            // log enter to method
            try
            {
                _inner.SaveStuff();
            }
            catch(Exception ex)
            {
                // log exception
            }       
            // log exit to method
        }
    }
    

    她在干嘛?事实上,它接受装饰对象并通过日志记录对其进行代理调用。如果在您的代码看起来像这样之前:

    var client = new RepoClient();
    client.DoSomethig(new Repository());
    

    现在它看起来像这样:

    var client = new RepoClient();
    client.DoSomethig(new RepositoryLogDecorator(new Repository ()));
    

    我们是否更改了存储库实现?不。您是否为它们添加了功能?是的。

    有关类的类似技巧的更多信息here


    因此,我想指出,我们只涉及了几个选项,用于在不影响实现的情况下扩展功能。其实还有更多的选择,理解这里的原理很重要——你写一个类\函数\模块,它们只做它们被创建的东西并扩展它们的功能而不影响它们的源代码。并且不要忘记责任唯一性原则——正如你应该注意到的,它与开放/封闭原则密切相关。

    同样,您需要具有数据结构的灵活性以满足项目的要求,并且不要产生过度的灵活性,因为过度的灵活性会使您的抽象变得毫无意义。

    如果你已经读到这里,那么我会告诉你一个很大的秘密——上面的每一种方法都使用一种或另一种模式。试着想想哪种模式在哪里,从最著名的模式书(来自帮派)中回顾主要模式,想想哪些模式扩展了类的功能,然后,希望马赛克将开始形成。

    • 12

相关问题

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