【面向对象设计原则】之依赖倒置原则(DIP)

news/2024/7/3 1:18:22

依赖倒转原则(Dependency Inversion  Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对抽象(接口)编程,而不是针对实现细节编程。

开闭原则(OCP)是面向对象设计原则的基础也是整个设计的一个终极目标,而依赖倒置原则(DIP )则是实现OCP原则的一个基础,换句话说开闭原则(OCP)是你盖一栋大楼的设计蓝图,那么依赖倒置原则就是盖这栋大楼的一个钢构框架,没有钢构架构是很难顺利盖起一栋大楼的,同样的在面向对象软件设计的过程中不遵守依赖倒置原则是很难开发出符合开闭原则的软件的。更不用说开发出易于维护,易于升级的软件。 因此开闭原则是非常重要的一个原则,它有很强的实操性,并且能够直接指导我们写代码代码。

通常要符合这个原则的第一步就是针对抽象编程,类之间的依赖关系尽量去使用高层抽象不要使用底层的实现细节,从软件工程来说高层抽象是较稳定的,也就是说抽象具有一定的稳定性,而实现细节较不稳定,也就是说实现细节具有易变性,而我们期望软件具有更好的稳定性,显而易见我们在开发的时候自然而然的要走稳定路线(依赖抽象编程)。这个原则也是对软件工程中要求“高聚低偶”实践一个保障和指导。

我们来看一个例子假设我们在开发一个软件产品需要一个日志系统,要将系统产生的一些重要事情记录在记事本上。通常我们的实现如下:

    public class Logger
    {
        public void Info(string infoText)
        {
            Console.WriteLine($"[{DateTime.Now}][Info]:{infoText}");
        }

        public void Debug(string debugText)
        {
            Console.WriteLine($"[{DateTime.Now}][Debug]:{debugText}");
        }

        public void Warn(string warmText)
        {
            Console.WriteLine($"[{DateTime.Now}][Warm]:{warmText}");
        }

        public void Error(string errorText,Exception exception)
        {
            Console.WriteLine($"[{DateTime.Now}][Error]:{errorText} - Exception:{exception.Message}");
        }
    }

客户端调用如下:

    static void Main(string[] args)
    {
        Logger logger = new Logger();
        logger.Info("This is a info text.");
        logger.Debug("This is a debug text.");
        logger.Warn("This is a warn text.");
        logger.Error("This is a error text", new Exception("This is a exception."));

        Console.ReadKey();
    }

输出:

image

这看起来还不错,一切都是那么自然。但是随着时间的推移,产品做的好买了很多客户,产品变得越来越大,使用Logger 类的地方成千上万处,可怕的事情终于发生了:

A 客户提出来我想把日志存在数据库中便于做统计分析。

B 客户说我想把日志打印在一个控制台上便于我时时监测系统运行情况。

C 客户说我要把日志存到Windows Azure  Storage上。

。。。。

客户越来越多奇葩需求不断涌出。我们的产品变得很难修改,很难维护,很难去适合所有的客户。 怎么办呢? 回过头来看看我们的这个日志系统的设计才恍然大悟:没有遵守面向对象设计原则的依赖倒置原则和开闭原则了。知道就好,找到法门了, 我们将日志这一块的设计重构一下让其符合OCP和DIP应该就可以了。 那么我们就要首先抽象写日志的接口ILog, 让实际调用的地方调用高层抽象(ILog),具体的实现类TextLogger,ConsoleLogger,DatabaseLogger,AzureStorageLogger都继承自ILog接口,然后我们在利用反射加配置,不同的用户配置不同的具体实现类,这样问题就迎任而解了。 看代码:

    public interface ILog
    {
        void Info(string infoText);
        void Debug(string debugText);
        void Warn(string warmText);
        void Error(string errorText, Exception exception);
    }
    public class TextLogger:ILog
    {
        public void Info(string infoText)
        {
            Console.WriteLine($"[{DateTime.Now}][Info]:{infoText}");
        }

        public void Debug(string debugText)
        {
            Console.WriteLine($"[{DateTime.Now}][Debug]:{debugText}");
        }

        public void Warn(string warmText)
        {
            Console.WriteLine($"[{DateTime.Now}][Warm]:{warmText}");
        }

        public void Error(string errorText,Exception exception)
        {
            Console.WriteLine($"[{DateTime.Now}][Error]:{errorText} - Exception:{exception.Message}");
        }
    }

    public class DatabaseLogger:ILog
    {
        public void Info(string infoText)
        {
            Console.WriteLine($"[{DateTime.Now}][Info]:{infoText}");
        }

        public void Debug(string debugText)
        {
            Console.WriteLine($"[{DateTime.Now}][Debug]:{debugText}");
        }

        public void Warn(string warmText)
        {
            Console.WriteLine($"[{DateTime.Now}][Warm]:{warmText}");
        }

        public void Error(string errorText,Exception exception)
        {
            Console.WriteLine($"[{DateTime.Now}][Error]:{errorText} - Exception:{exception.Message}");
        }
    }
    public class ConsoleLogger:ILog
    {
        public void Info(string infoText)
        {
            Console.WriteLine($"[{DateTime.Now}][Info]:{infoText}");
        }

        public void Debug(string debugText)
        {
            Console.WriteLine($"[{DateTime.Now}][Debug]:{debugText}");
        }

        public void Warn(string warmText)
        {
            Console.WriteLine($"[{DateTime.Now}][Warm]:{warmText}");
        }

        public void Error(string errorText,Exception exception)
        {
            Console.WriteLine($"[{DateTime.Now}][Error]:{errorText} - Exception:{exception.Message}");
        }
    }

    public class AzureStorageLogger:ILog
    {
        public void Info(string infoText)
        {
            Console.WriteLine($"[{DateTime.Now}][Info]:{infoText}");
        }

        public void Debug(string debugText)
        {
            Console.WriteLine($"[{DateTime.Now}][Debug]:{debugText}");
        }

        public void Warn(string warmText)
        {
            Console.WriteLine($"[{DateTime.Now}][Warm]:{warmText}");
        }

        public void Error(string errorText,Exception exception)
        {
            Console.WriteLine($"[{DateTime.Now}][Error]:{errorText} - Exception:{exception.Message}");
        }
    }

添加一个配置在Config中:

    <appSettings>
        <add key="Logger" value="ConsoleApp1.TextLogger"/>
    </appSettings>

客户端的调用改成调用ILog:

    static void Main(string[] args)
    {
        string key = ConfigurationManager.AppSettings["Logger"];
        ILog logger = ObjectBuildFactory<ILog>.Instance(key);
        logger.Info("This is a info text.");
        logger.Debug("This is a debug text.");
        logger.Warn("This is a warn text.");
        logger.Error("This is a error text", new Exception("This is a exception."));

        Console.ReadKey();
    }

输出:

image

A客户期望将日志写在数据库中只需要将配置改成下面这样就可以了:

    <appSettings>
        <add key="Logger" value="ConsoleApp1.DatabaseLogger"/>
    </appSettings>

根据不同的客户需求只需要改这个配置的value值就可以了。

要使上面的代码顺利运行我们要加一个辅助类用于反射:

public class ObjectBuildFactory<T>
{
    public static T Instance(string key)
    {
        Type obj = Type.GetType(key);
        if (obj == null) return default(T);

        T factory = (T)obj.Assembly.CreateInstance(obj.FullName);

        return factory;
    }
}

那么有一天E客户说他们公司有自己的日志系统并开发了一套日志分析工具,他们可以开放API让我们把日志直接存到他们的日志系统中去。 这次好办了啊,只需要定义一个具体类继承自ILog接口并实现所有的方法,在每一个实现的方法中调用客户的API, 最后将实现的类配置到配置文件中就可以很好的满足客户的要求了, 这样是不是很完美呢?我们完全遵守了DIP和OCP原则,也很好的使用了LSP,使得我们软件变得稳定,应对需求的变化变得简单了,也易于升级和易于维护了。

在使用DIP是需要注意一下几点

1. 继承自高层接口的类要实现所有接口中的方法。

2.子类中除了接口的方法,在用接口声明的对象调用的地方是无法被调用到的。除非直接调用子类,但是直接调用子类是违背DIP的。

3. DIP是实现OCP的重要原则保障,一般违背了DIP很难不违背OCP,可以看这一篇【面向对象设计原则】之开闭原则(OCP)。

4.LSP 是实现DIP的基础,多态给实现DIP提供了可能。 可以看这一篇 【面向对象设计原则】之里氏替换原则(LSP)。


http://www.niftyadmin.cn/n/1980865.html

相关文章

连载:面向对象葵花宝典:思想、技巧与实践(34) - DIP原则

DIP&#xff0c;dependency inversion principle&#xff0c;中文翻译为“依赖倒置原则”。DIP是大名鼎鼎的Martin大师提出来的。他在1996 5月的C Reporter发表“ The Dependency Inversion Principle”的文章具体阐述了DIP原则&#xff0c;而且在他的经典著作《 Agile Softwar…

希捷银河声音大_技术成熟:希捷表示将直接推出 24TB HAMR 硬盘

本文转自&#xff1a;IT之家作者&#xff1a;信鸽随着 SSD 固态硬盘技术的稳步推进&#xff0c;以及国产 NAND 颗粒的成功量产&#xff0c;传统机械硬盘厂商面临着越来越大的压力&#xff0c;而 HAMR(热辅助磁记录)技术&#xff0c;是提高存储密度的新一代解决方案。据外媒 BLO…

Kali渗透测试——WOL-E

网络唤醒工具WOL-E 网络唤醒&#xff08;WOL&#xff09;是非常有用的一个功能。电脑开启WOL功能后&#xff0c;当电脑长期没人使用会进入待机状态。这时&#xff0c;其他电脑就可以借助WOL功能&#xff0c;通过网络唤醒该计算机。在渗透测试中&#xff0c;寻找网络中支持网络唤…

zemax迈克尔逊干涉仪_诺贝尔物理学奖史演义系列(六)——迈克尔逊

迈克尔逊干涉仪是光学领域里最常见的干涉仪&#xff0c;是近代物理学的重要实验仪器之一。清晨起床&#xff0c;一缕阳光洒进房间&#xff0c;寓示着新的一天开始了。虽然太阳离地球非常遥远&#xff0c;但阳光只需要几秒钟就可以到达地面&#xff0c;由此可见光的速度非常快。…

家用简单电线路图_口诀来了!家用中央空调怎么选?一听二看三感觉

家用中央空调舒适、美观&#xff0c;现在很多业主在装修时都会考虑一下。但由于对家用中央空调不是很了解&#xff0c;面对市场上各品类空调时&#xff0c;往往无从下手。今天&#xff0c;未来舒适家小编就教大家一个简单的小诀窍&#xff0c;“一听、二看、三感觉”&#xff0…

mysql5.7.10 源码编译安装记录 (centos6.4)【转】

一、准备工作 1.1 卸载系统自带mysql 查看系统是否自带MySQL, 如果有就卸载了, 卸载方式有两种yum, rpm, 这里通过yum卸载 rpm -qa | grep mysql //查看系统自带mysql yum -y remove mysql-* //卸载mysql rpm -e --nodeps mysql-5.1.73-3.el6_5.x86_64 //卸载mysql 1.2 卸…

诸葛io的技术架构图_诸葛io携手飞书,助力企业数字化转型升级

近日&#xff0c;北京诸葛云游科技有限公司(以下简称诸葛公司)与飞书正式达成战略合作&#xff0c;推动产品层面的融合&#xff0c; 积极探索创新协作的商业模式&#xff0c;双方将在业务合作&#xff0c;客户资源&#xff0c;渠道增值等多方面开展深度合作&#xff0c;携手推进…