zoukankan      html  css  js  c++  java
  • 开放封闭原则 (OCP)

    英文:Open-Closed Principle

    参考/翻译: https://www.dotnetcurry.com/software-gardening/1176/solid-open-closed-principle

    把软件开发比作建筑,我们可以看出软件是坚固的,而且很难改变。相反,我们应该把软件开发比作园艺,因为花园总是在变化的。软件园艺包含实践和工具,可以帮助您为您的软件创建最好的可能的花园,允许它增长和改变较少的努力。了解更多关于什么是软件园艺。

    今天我们来看看字母O,SOLID的第二个原理。O 是开放封闭原则。开放-关闭原则(OCP)指出“一个软件实体应该对扩展开放,但对修改关闭”。创造“开放-封闭”这一术语的功劳通常归于Bertrand Meyer在他1998年的著作《Object Oriented Software Construction》中。

    当你第一次听到“开闭”这个词的时候,你可能会想,“怎么可能有东西同时开着又关着呢?”,我曾听人开玩笑地把这个叫做Shroedinger(薛定谔)的OOP原则。然而,薛定谔谈论的是猫,而不是面向对象编程。

     Robert C.“Uncle Bob  ”Martin在他2003年的著作《敏捷软件开发:原则、模式和实践》中进一步扩展了OCP的定义:

    “对扩展开放。这意味着模块的行为可以扩展。随着应用程序需求的变化,我们能够用新的行为扩展模块来满足这些变化。换句话说,我们可以改变模块的功能。

    “对修改关闭。扩展一个模块的行为不会导致模块的源代码或二进制代码的改变。模块的二进制可执行版本,无论是在可链接库、DLL或Java .jar中,都保持不变。

    emmm。听起来鲍勃叔叔想让我们去做不可能的事。我们不仅不能改变现有的代码,而且我们也不能改变exe或dll文件。坚持下去。我将解释这是如何做到的。

    对修改关闭

    让我们深入了解这个规则,首先查看对修改的关闭,因为这个更容易讨论。简而言之,对修改关闭意味着你不应该改变现有代码的行为。这与我在上一期专栏中解释过的单一职责紧密相关。如果一个类或方法只做一件事,那么修改它的可能性就很小。

    但是,有三种方法可以改变现有代码的行为。

    第一个是修复一个错误。毕竟,代码不能正常运行,应该被修复。在这里您需要小心,因为该类的客户端可能知道这个错误,并已经采取了措施来解决这个问题。

    举个例子,惠普的一些打印机驱动程序多年来都有一个bug。这个错误导致了Windows下的打印错误,惠普拒绝更改它。微软对Word和其他应用程序进行了修改,以解决这个问题。

    修改现有代码的第二个原因是重构代码,使其遵循其他可靠的原则。例如,代码可能工作得很好,但是做的事情太多了,因此您希望重构它以遵循单一的职责。

    最后,第三种方法(这有点争议)是,如果代码不能改变客户端更改的需求,则允许您更改代码。在这里,您必须小心,以免引入影响客户机的bug。良好的单元测试至关重要。

    对扩展开放

     在封闭了修改的道路之后,我们转向对扩展的开放。最初,Meyers从实现继承的角度对此进行了讨论。使用此解决方案,您继承一个类和它的所有行为,然后覆盖您想要更改的方法。这避免了更改原始代码,并允许类做一些不同的事情。

    任何尝试过大量实现继承的人都知道它有许多缺陷。正因为如此,许多人采用了实现接口继承。使用这种形式,只定义方法签名,每次实现接口时都必须创建每个方法的代码。这是缺点。然而,好处更大,允许您轻松地用一种行为替换另一种行为。常见的例子是基于ILogger接口的日志类,然后实现该类以将日志记录到Windows事件日志或文本文件中。客户端不知道也不关心它正在使用的是哪个实现。

    另一种打开方法进行扩展的方法是通过抽象方法。当继承时,抽象方法必须被重写。

    与抽象方法比较像的是虚方法。区别在于你必须覆盖抽象方法,而不必须重写虚拟方法。这给作为开发人员的您带来了更多的灵活性。

    还有一种提供额外功能的方法,但经常被忽视。这就是扩展方法虽然它不改变方法的行为,但它允许您在不改变原始类代码的情况下扩展类的功能。

    现在,让我们回到Uncle Bob’s对封闭定义的扩展进行修改。我们需要在不改变原始代码或二进制(exe或dll)文件的情况下扩展类的功能。好消息是,我们在这里看到的每一种扩展行为的方法都符合Uncle Bob的定义。在.net中没有规定关于类的所有内容必须在同一个源文件中,甚至在同一个程序集中。因此,通过将扩展代码放在不同的程序集中,您可以遵守Uncle Bob定义的OCP。

    一个开闭原则的例子

    解释很好,但是让我们看一个例子。首先,代码违反了OCP。

    public class ErrorLogger
    {
        private readonly string _whereToLog;
        public ErrorLogger(string whereToLog)
        {
            this._whereToLog = whereToLog.ToUpper();
        }
     
        public void LogError(string message)
        {
            switch (_whereToLog)
            {
                case "TEXTFILE":
                    WriteTextFile(message);
                    break;
                case "EVENTLOG":
                    WriteEventLog(message);
                    break;
                default:
                    throw new Exception("Unable to log error");
            }
        }
     
        private void WriteTextFile(string message)
        {
            System.IO.File.WriteAllText(@"C:UsersPublicLogFolderErrors.txt", message);
        }
     
        private void WriteEventLog(string message)
        {
            string source = "DNC Magazine";
            string log = "Application";
             
            if (!EventLog.SourceExists(source))
            {
                EventLog.CreateEventSource(source, log);
            }
            EventLog.WriteEntry(source, message, EventLogEntryType.Error, 1);
        }
    }

    如果您需要添加一个新的日志位置,比如数据库或web服务,会发生什么?您需要在几个地方修改此代码。您还需要添加新的代码来将消息写入新位置。最后,您必须修改单元测试以适应新功能。所有这些类型的更改都有可能引入bug。

    您可能忽略了这段代码的另一个问题。它违反了单一责任原则。

    这里有一个更好的方法。虽然功能还没有完全完成,但这只是一个起点。

    public interface IErrorLogger
    {
        void LogError(string message);
    }
     
    public class TextFileErrorLogger : IErrorLogger
    {
        public void LogError(string message)
        {
            System.IO.File.WriteAllText(@"C:UsersPublicLogFolderErrors.txt", message);
        }
    }
     
    public class EventLogErrorLogger : IErrorLogger
    {
        public void LogError(string message)
        {
            string source = "DNC Magazine";
            string log = "Application";
     
            if (!EventLog.SourceExists(source))
            {
                EventLog.CreateEventSource(source, log);
            }
     
            EventLog.WriteEntry(source, message, EventLogEntryType.Error, 1);
        }
    }

    当您需要实现其他类型的记录器时,这很简单……只需添加新的类,而不是修改现有的类。

    public class DatabaseErrorLogger : IErrorLogger
    {
        public void LogError(string message)
        {
            // Code to write error message to a database
        }
    }
     
    public class WebServiceErrorLogger : IErrorLogger
    {
        public void LogError(string message)
        {
            // Code to write error message to a web service
        }
    }

     哈哈...一个更好的构建日志功能的方法。

    现在,您的软件园又有了一个种子。通过遵循开闭原则,你的代码会更好,更不容易出错。您正在使您的软件变得丰富、绿色和充满活力的道路上。

  • 相关阅读:
    阿里云公网IP不能使用
    Python2 socket TCPServer 多线程并发 超时关闭
    Python2 socket 多线程并发 ThreadingTCPServer Demo
    Python2 socket 多线程并发 TCPServer Demo
    Python socket TCPServer Demo
    Python socket server demo
    jsp注释方式
    面试小结(java基础)
    java 多线程sleep和wait的区别
    Java中Runnable和Thread的区别
  • 原文地址:https://www.cnblogs.com/xieweikang/p/14161803.html
Copyright © 2011-2022 走看看