面向对象有可维护、可扩展、可复用和灵活性好的特点。正是因为面向对象这种强内聚、松耦合的特点使其风靡于软件工程体系中。前面已经写过了两个面向对象的基本设计原则,今天老四为大家浅析一下另外一个面向对象的基本原则,也是面向对象设计的主要实现机制之一,依赖倒转原则,也叫依赖倒置原则。一个字:要接口化编程而不要过程化编程。
定义
依赖倒转原则(Dependency Inversion Principle,简称DIP):
- 抽象不应该依赖细节,细节应该依赖于抽象
- 高层模块不应该依赖低层模块。两个都应该依赖抽象。
- 要针对接口编程,不要对实现编程。
依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即接口或抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。那么为什么依赖了抽象的接口或者抽象类,就不怕耦合和频繁的更改呢?
里氏代换原则(Liskov Substitution Principle,简称LSP):子类型必须能够替换掉它们的父类型。
一个软件实体如果使用的是一个父类的话,那么一定是用于其子类,而且它察觉不出父类对象和子类对象的区别.也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化。里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。
在使用里氏代换原则的时候也要注意一下三点:
- 子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。
- 尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法。这样增加一个新的功能可以通过增加一个新的子类来实现。里氏代换原则其实就是开放-封闭原则的具体实现方式的一种。
- 其实java代码在编译的过程中会检查程序是否符合里氏代换原则,但是由于是语法层面上的检查,具有局限性,还需要工程师时刻铭记该原则并利用其设计软件系统结构。
综合开放-封闭原则、里氏代换原则以及依赖倒转原则,总结几句话,希望让您印象深刻,有所启发。
- 多数情况之下,这三个设计原则会同时出现,开放-封闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段,它们相辅相成,相互补充,目标一致。
- 里氏代换原则是实现开闭原则的重要方式之一。也正是因为子类型的可替换性,才能让依赖倒转原则实现抽象化、接口化的编程。
- 依赖倒转是面向对象设计的标志,编码时考虑的都是针对抽象编程而不是针对细节编程,程序中所有的依赖关系都是终止于抽象类或者接口,这就是面向对象的设计,反之那就是面向过程的设计了。
最后,老四按照惯例奉上不成熟的代码,让看管再次体验一下依赖倒转原则为了实现开放-封闭原则而利用里氏代换原则对现实业务进行逻辑修改,使其符合面向对象的程序设计思想。同时,这三大原则也是后面20多种设计模式的最根本的基础。如果您对设计模式很感兴趣,那么这三大原则也是必须掌握的。
举例:老四的所在的公司是互联网公司,手里项目不可免的要用到日志体系,大家都知道日志体系中比较常用的四个日志输出级别 Info、Debug、Warn、Error。项目一开始比较薄,所以初期老四只定义了一个日志具体实现类来承接项目的日志体系。大概代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
package com.glorze.concretelogger; import java.text.SimpleDateFormat; import java.util.Date; /** * 面向过程的日志系统类 * @ClassName GlorzeLogger * @author: glorze.com * @since: 2018年3月14日 下午11:11:18 */ public class GlorzeLogger { /** * 格式化如期 */ SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** * 输出信息日志 * @Title: info * @param text * @return void * @author: 高老四博客 * @since: 2018年3月14日 下午11:12:25 */ public void info(String text) { System.out.println("[" + sdf.format(new Date()) + "] [info] " + text); } /** * 输出调试级别日志 * @Title: debug * @param text * @return void * @author: 高老四博客 * @since: 2018年3月14日 下午11:12:39 */ public void debug(String text) { System.out.println("[" + sdf.format(new Date()) + "] [debug] " + text); } /** * 输出警告级别日志 * @Title: warn * @param text * @return void * @author: 高老四博客 * @since: 2018年3月14日 下午11:12:54 */ public void warn(String text) { System.out.println("[" + sdf.format(new Date()) + "] [warn] " + text); } /** * 输出错误级别日志 * @Title: error * @param text * @return void * @author: 高老四博客 * @since: 2018年3月14日 下午11:13:05 */ public void error(String text) { System.out.println("[" + sdf.format(new Date()) + "] [error] " + text); } } |
当公司系统随着业务的拓展,项目的逐渐演进和庞大,老四发现如今这一个日志实现类已经不能满足当前系统的以及客户的要求,比如说技术老总要求我日志要写入数据库的表中,运维要求我要把error级别的日志输出到公司大显示屏的窗体中便于监控,产品经理要求我将四个级别的日志封装成图表的形式挂载产品风控系统中作为一个数据参考项。面对这么多的在我们程序员眼中这种很奇葩的要求,我们除了改还能有什么办法呢?所以回过头来看上面的代码根本就不能解决当前系统需求所带来的问题和需求,接下来就只能重构日志体系代码,利用我们我说的依赖倒转原则配合里氏代换原则,在满足开放-封闭原则的基础上,实现这堆奇葩的需求。
首先,定义日志接口,声明四个输出日志级别的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package com.glorze.dependencyinversion; /** * 根据依赖倒转原则声明日志接口 * @ClassName GlorzeLoggerInterface * @author: glorze.com * @since: 2018年3月14日 下午11:22:55 */ public interface GlorzeLoggerInterface { /** * 信息级别日志 * @Title: info * @param text * @return void * @author: 高老四博客 * @since: 2018年3月14日 下午11:23:23 */ void info(String text); /** * 调试级别日志 * @Title: debug * @param text * @return void * @author: 高老四博客 * @since: 2018年3月14日 下午11:23:33 */ void debug(String text); /** * 警告级别日志 * @Title: warn * @param text * @return void * @author: 高老四博客 * @since: 2018年3月14日 下午11:23:47 */ void warn(String text); /** * 错误级别日志 * @Title: error * @param text * @return void * @author: 高老四博客 * @since: 2018年3月14日 下午11:23:58 */ void error(String text); } |
接下来,根据需求书写不同的实现类,实现上面的接口,解决不同人的不同需求,比如说存储数据库的DatabaseLogger、监控屏幕的ScreenLogger以及图表的ChartLogger类,它们都实现GlorzeLogger接口,在保证各级别日志体系正常输出的情况下依然能对不同的需求对日志结果进行处理,这里正好应用了里氏代换原则,至于满足不同的人员需求即客户端调用的时候,在java中我们可以通过配置文件来实现,满足开放-封闭原则。注意:如果一个系统在扩展时只涉及到修改配置文件,而原有的Java代码没有做任何修改,该系统即可认为是一个符合开闭原则的系统。
在这里只展示入库的实现类,其余的实现请下载工程源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package com.glorze.dependencyinversion; import java.text.SimpleDateFormat; import java.util.Date; /** * 存储日志到数据库 * @ClassName DatabaseLogger * @author: glorze.com * @since: 2018年3月15日 下午9:51:35 */ public class DatabaseLogger implements GlorzeLoggerInterface { /** * 格式化如期 */ SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void info(String text) { System.out.println("[" + sdf.format(new Date()) + "] [info] " + text); } @Override public void debug(String text) { System.out.println("[" + sdf.format(new Date()) + "] [debug] " + text); } @Override public void warn(String text) { System.out.println("[" + sdf.format(new Date()) + "] [warn] " + text); } @Override public void error(String text) { System.out.println("[" + sdf.format(new Date()) + "] [error] " + text); } } |
接下来创建config.properties配置文件来配置使用哪一种日志处理形式,以满足不同的需求
1 2 |
#日志显示形式 logger=DatabaseLogger |
客户端可以通过Java反射获取对应对象实现日志输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.glorze.dependencyinversion; import com.glorze.util.PropertiesUtil; /** * 客户端调用日志类,根据配置文件的配置输出不同类型的日志 * @ClassName HandleLogger * @author: glorze.com * @since: 2018年3月15日 下午11:11:50 */ public class HandleLogger { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { String loggerType = PropertiesUtil.getValue("logger"); @SuppressWarnings("unchecked") Class clazz = Class.forName(loggerType); GlorzeLoggerInterface logger = (GlorzeLoggerInterface) clazz.newInstance(); logger.info("高老四博客_倚楼听风雨淡看江湖路"); } } |
这样设计之后,当某一天在接到新的需求,比如说对接合作公司的日志接口等,均可以直接实现GlorzeLoggerInterface接口类,最后通过配置文件实现动态需求,完全达到开放-封闭的面向对象设计原则。
更多源码以及完整项目下载地址文末自助获取。
更博不易,喜欢的老铁有能力烦请赞助盒烟钱,点我去赞助。抱拳。
资源下载
隐藏内容:******,购买后可见!
下载价格:0 G币
您需要先登录后,才能购买资源
欢迎访问高老四博客(glorze.com),本站技术文章代码均为老四亲自编写或者借鉴整合,其余资源多为网络收集,如涉及版权问题请与站长联系。如非特殊说明,本站所有资源解压密码均为:glorze.com。