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

时间:2021-10-18 08:30:46

依赖倒转原则(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();
}

输出:

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

这看起来还不错,一切都是那么自然。但是随着时间的推移,产品做的好买了很多客户,产品变得越来越大,使用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();
}

输出:

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

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)

【面向对象设计原则】之依赖倒置原则(DIP)的更多相关文章

  1. 7&period;12 其他面向对象设计原则3&colon; 依赖倒置原则DIP

    其他面向对象设计原则3: 依赖倒置原则DIP  The Dependency Inversion Principle7.1 依赖倒置原则DIP The Dependency Inversion Pr ...

  2. 设计模式学习--面向对象的5条设计原则之依赖倒置原则--DIP

    一.DIP简介(DIP--Dependency Inversion Principle): 1.高层模块不应该依赖于低层模块,二者都应该依赖于抽象.2.抽象不应该依赖于细节,细节应该依赖于抽象.   ...

  3. IOS设计模式的六大设计原则之依赖倒置原则&lpar;DIP&comma;Dependence Inversion Principle&rpar;

    定义 高层模块不应该依赖于低层模块,二者都应该依赖于抽象:抽象不应该依赖细节:细节应该依赖抽象. 定义解读 依赖倒置原则在程序编码中经常运用,其核心思想就是面向接口编程,高层模块不应该依赖低层模块(原 ...

  4. 深入理解JavaScript系列(22):S&period;O&period;L&period;I&period;D五大原则之依赖倒置原则DIP

    前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第5篇,依赖倒置原则LSP(The Dependency Inversion Principle ). 英文原文:htt ...

  5. 最简单直接地理解Java软件设计原则之依赖倒置原则

    理论性知识 定义 依赖倒置原则,Dependence Inversion Principle (DIP) 高层模块不应该依赖低层模块.二者都应该依赖其抽象. 抽象不应该依赖细节,细节应该依赖抽象. 针 ...

  6. Java设计模式(2:单一职责原则和依赖倒置原则详解)

    一.单一职责原则 不要存在多于一个导致类变更的原因.简单来说,就是一个Class/Interface/Method只负责一项职责. 这句话最为重要的就是这一段:一个Class/Interface/Me ...

  7. 设计模式——如何避免在OO设计中违反依赖倒置原则

    1 变量不可以包含具体类的引用.一旦new,就对具体类产生依赖,用工厂模式来避开. 2 类不要派生至具体类.用派生抽象类避开. 3 不要覆盖基类已经实现的方法.基类中已实现的方法应该由所有子类共享.

  8. C&num;软件设计——小话设计模式原则之:依赖倒置原则DIP

    前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身.群里面有朋友问博主是否改行做前端了,呵呵,其实博主是想做“全战”, ...

  9. zt 设计模式六大原则(3):依赖倒置原则

    下面说法对不对? 父类将算法(逻辑)封装起来,子类实现细节:这个就叫DIP(依赖倒置:Dependency Inversion Principles),模板模式就是这个原则的实现.如果在父类中加一个t ...

随机推荐

  1. MVC下分页的自定义分页一种实现

    1.引言 在MVC开发中我们经常会对数据进行分页的展示.通过分页我们可以从服务端获取指定的数据来进行展示.这样既节约了数据库查询的时间也节约了网络传输的数据量.在MVC开发中使用的比较多的应该是MVC ...

  2. HTML5音频,视频播放

    1.此方法可支持多种浏览器 <audio controls="controls"> <source src="1.mp3" ></ ...

  3. 服务器设置Apache对htaccess支持

    root权限下运行a2enmod(a2enmod是一个可以配置Apache的工具,a2enmod是属于apache2.2-common包下的一个工具),然后输入rewrite启动apache对于.ht ...

  4. Java和JavaScript的时间互传

    原创文章,转载请注明:Java和JavaScript的时间互传 By Lucio.Yang 1.从JavaScript到Java JavaScript: function query(){ var s ...

  5. C&sol;C&plus;&plus;中constkeyword

    今天在做一个趋势笔试题的时候.才让我有了系统把constkeyword好好总结一下的冲动,由于这个关键词大大小小好多地方都出现过,出现频率很高,而每次仅仅是简短的把答案看了一下,没有真正将其整个使用方 ...

  6. linux文件系统下的特殊权限

    SUID, SGID, Sticky 1 权限 r, w, x user, group, other 2 安全上下文 前提:进程有属主和属组:文件有属主和属组: (1) 任何一个可执行程序文件能不能启 ...

  7. Java 9 揭秘(17&period; Reactive Streams)

    Tips 做一个终身学习的人. 在本章中,主要介绍以下内容: 什么是流(stream) 响应式流(Reactive Streams)的倡议是什么,以及规范和Java API 响应式流在JDK 中的AP ...

  8. 多个Fragment的分开管理方案

    当项目里有多个Fragment的时候  我们希望让Fragment有个分类 并且展示的时候不会混淆在一起 例如:项目中导航栏有三个按钮   每个按钮对应一种分类的布局,每个分类的布局中有多个Fragm ...

  9. 利用谷歌网站的翻译网站,实现谷歌翻译api

    代码是之前网上找到的,地址为:http://www.crifan.com/teach_you_how_to_find_free_google_translate_api/ 原先的代码有不足,如果翻译里 ...

  10. range与xrange的区别

    一.Python中range()与xrange()有什么区别 range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长,生成一个序列 rang ...