Java设计模式(2:单一职责原则和依赖倒置原则详解)
一、单一职责原则
不要存在多于一个导致类变更的原因。简单来说,就是一个Class/Interface/Method只负责一项职责。
这句话最为重要的就是这一段:一个Class/Interface/Method只负责一项职责。
我们先来举一个例子,我们在日常生活中都或多或少的听过LOL(英雄联盟)这个游戏,而这个游戏在各个直播平台都很火爆,那我们就以此为例:
某个游戏直播平台会将主播直播时的视频录制下来,等到主播下播后再上传到平台,这样就形成了录播。对于这两种视频观看模式,平台有着这样的规定:观看直播不允许快进、回放,而录播则可以,那我们首先想到的应该是这样方式:
/**
* 平台
*/
public class UuSee {
private final String LiveName = "直播";
private final String ReplayName = "录播";
// 观看
public void watch(String name){
if (LiveName.equals(name)){
System.out.println("观看LOL "+name+",不能快进、回放!");
} else if(ReplayName.equals(name)) {
System.out.println("观看LOL "+name+",可以快进、回放!");
}
}
}
我们来写一个测试的代码看看:
public static void main(String[] args) {
UuSee uuSee = new UuSee();
uuSee.watch("直播");
uuSee.watch("录播");
}
从测试的代码来看的话,
UuSee类承担了两种不同的处理逻辑。那么现在来增加一个需求:对直播和录播的视频进行加密,而直播和录播视频的加密方式不同。那么我们必须要修改源码,而这可能影响其他地方的调用。所以现在我们来将两种观看方式分开,让它们互不干扰:
直播(LiveVideo类):
/**
* 直播
*/
public class LiveVideo {
public void watch(String name){
System.out.println("直播视频加密......");
System.out.println("观看LOL "+name+",不能快进....");
}
}
录播(RePlayVideo类):
/**
* 录播
*/
public class RePlayVideo {
public void watch(String name){
System.out.println("录播视频加密......");
System.out.println("观看LOL "+name+",可以快进.....");
}
}
调用代码:
public static void main(String[] args) {
RePlayVideo rePlayVideo = new RePlayVideo();
rePlayVideo.watch("录播");
LiveVideo liveVideo = new LiveVideo();
liveVideo.watch("直播");
}
这样看的话,直播类
LiveVideo和录播类RePlayVideo都调用自己的处理逻辑,两者互不干扰。那么如果业务继续发展:增加VIP用户,并且只有VIP用户才能观看录播(获得视频流);而普通用户虽然不能观看录播,但可以获取录播视频的基本信息。这样就会增加两个方法:getReplayVideoInfo()和getReplayVideo(),这时候发现录播类RePlayVideo和直播类LiveVideo都会拥有其中的两个方法,那不如设计一个顶级接口,将他们一起纳入管理:UuSeeInterface
public interface UuSeeInterface {
// 获得 录播视频信息
public String getReplayVideoInfo();
// 获得 录播视频 视频流
public byte[] getReplayVideo();
// 观看视频
public void watch(String name);
}
写完这个接口,我们发现从控制视频播放权限的层面来看的话,可以分为两个职责:管理职责和展示职责;
getReplayVideoInfo()和getReplayVideo()方法都属于展示职责,watch()方法属于管理职责,这样的话我们就可以将上面的接口拆分一下:
管理职责接口(UuSeeManagement)
/**
* 管理职责
*/
public interface UuSeeManagement {
// 观看视频
public void watch(String name);
}
展示职责接口(UuSeeInfo)
/**
* 展示职责
*/
public interface UuSeeInfo {
// 获得 录播视频信息
public String getReplayVideoInfo();
// 获得 录播视频 视频流
public byte[] getReplayVideo();
}
说完了一个Class/Interface只负责一项职责的事情后,我们再来看一看一个Method只负责一项职责的问题。
假如有这么一个方法:更改一个用户的用户名和地址,我们可能会偷懒写成这样。
// 修改用户名称和地址
public void modifyUserInfo(String userName,String address){
System.out.println("用户名改为:"+userName);
System.out.println("用户地址改为:"+address);
}
那么当我们又增加一个需求:只更改用户名称,不更改用户地址。那么我们在调用
modifyUserInfo()方法时,还要去千方百计的获得用户的地址,非常的麻烦,倒不如将上面的方法拆分为两个方法:
// 修改用户名称
public void modifyUserName(String userName){
System.out.println("用户名改为:"+userName);
}
// 修改用户地址
public void modifyUserAddress(String address){
System.out.println("用户地址改为:"+address);
}
这样来看,我们需要修改用户名称的时候就调用
modifyUserName()方法,需要修改用户地址的时候就调用modifyUserAddress()方法,两个方法独立分开,不相互干扰,后期也好维护。当然,在日常工作中,我们不可能面面俱到,也不要为了某种原则而将自己陷入某个陷阱中,还是要根据实际情况做出不同的选择。但是,编写代码时,要尽量的满足单一原则,这样也方便我们后期的代码维护。
二、依赖倒置原则
这个原则是开闭原则的基础,是指设计结构代码时,高层模块不应该依赖于底层模块,二者应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。即:针对接口编程,依赖于抽象而不依赖于具体。
我们来先看一段代码:
/**
* 动物园
*/
public class Zoom {
// 老虎
public void tiger(){
System.out.println("老虎在吃鸡肉.....");
}
// 猴子
public void monkey(){
System.out.println("猴子在吃香蕉.....");
}
}
// 测试
public static void main(String[] args) {
Zoom zoom = new Zoom();
zoom.tiger();
zoom.monkey();
}
小明周末去动物园游玩,正值晌午,动物饲养员在给动物喂食,觉得有趣就想记录一下。在记录完tiger和monkey之后,小明又发现了熊猫(panda)在吃竹子,于是兴冲冲的准备记录下来,可动笔时却发现一个问题:
tiger()、monkey()方法已经写好了,调用没有任何问题,但在增加一个panda()方法的话,不就修改了Zoom类的源代码,这样会不会有额外的风险呢?似乎也不符合设计模式中的开闭原则。思考良久,小明将上述代码改成了下面这样:
/**
* 动物园
*/
public interface Zoom {
// 吃午饭
public void eat();
}
// 老虎
public class Tiger implements Zoom {
@Override
public void eat() {
System.out.println("老虎在吃鸡肉.....");
}
}
// 猴子
public class Monkey implements Zoom{
@Override
public void eat() {
System.out.println("猴子在吃香蕉......");
}
}
// 调用类
public class Xming {
public void eat(Zoom zoom){
zoom.eat();
}
}
// 测试
public static void main(String[] args) {
Xming xming = new Xming();
xming.eat(new Tiger()); // 老虎
xming.eat(new Monkey()); // 猴子
}
这样一看,
Tiger和Monkey互不干扰,当需要记录熊猫吃竹子时,只需要在创建一个Panda类就可以了,不用去修改现有的源代码:
// 熊猫
public class Panda implements Zoom {
@Override
public void eat() {
System.out.println("熊猫正在吃竹子......");
}
}
// 测试
public static void main(String[] args) {
Xming xming = new Xming();
xming.eat(new Tiger()); // 老虎
xming.eat(new Monkey()); // 猴子
xming.eat(new Panda());// 熊猫
}
这个时候我们发现
Xming类调用eat()方法时注入方式很熟悉,想了想是依赖注入,而依赖注入的方式还有构造器注入和setter注入。我们先来看看构造器注入的方式:
// 调用类
public class Xming {
private Zoom zoom;
public Xming(Zoom zoom) {
this.zoom = zoom;
}
public void eat(){
zoom.eat();
}
}
调用代码:
public static void main(String[] args) {
Xming tiger = new Xming(new Tiger());
tiger.eat();
Xming panda = new Xming(new Panda());
panda.eat();
}
构造器注入的方式在每次调用时都要创建实例。那么,如果
Xming是全局单例的话,我们就只能选择setter注入的方式了:
// 调用类
public class Xming {
private Zoom zoom;
public void setZoom(Zoom zoom) {
this.zoom = zoom;
}
public void eat(){
zoom.eat();
}
}
// 调用
public static void main(String[] args) {
Xming tiger = new Xming();
tiger.setZoom(new Tiger());
tiger.eat();
Xming panda = new Xming();
panda.setZoom(new Panda());
panda.eat();
}
结语:依赖倒置原则的本质还是面向接口编程,而事实就是以抽象为基准比以细节为基准搭建起来的框架要稳定的多,后期维护与查看都相对的容易与清晰一些。所以,我们在日常的开发中,要根据实际的业务需求来分析,尽量的使用面向接口编程,先顶层再细节的步骤来设计代码架构。
Java设计模式(2:单一职责原则和依赖倒置原则详解)的更多相关文章
- 7.12 其他面向对象设计原则3: 依赖倒置原则DIP
其他面向对象设计原则3: 依赖倒置原则DIP The Dependency Inversion Principle7.1 依赖倒置原则DIP The Dependency Inversion Pr ...
- IOS设计模式的六大设计原则之依赖倒置原则(DIP,Dependence Inversion Principle)
定义 高层模块不应该依赖于低层模块,二者都应该依赖于抽象:抽象不应该依赖细节:细节应该依赖抽象. 定义解读 依赖倒置原则在程序编码中经常运用,其核心思想就是面向接口编程,高层模块不应该依赖低层模块(原 ...
- 设计模式学习--面向对象的5条设计原则之依赖倒置原则--DIP
一.DIP简介(DIP--Dependency Inversion Principle): 1.高层模块不应该依赖于低层模块,二者都应该依赖于抽象.2.抽象不应该依赖于细节,细节应该依赖于抽象. ...
- 最简单直接地理解Java软件设计原则之依赖倒置原则
理论性知识 定义 依赖倒置原则,Dependence Inversion Principle (DIP) 高层模块不应该依赖低层模块.二者都应该依赖其抽象. 抽象不应该依赖细节,细节应该依赖抽象. 针 ...
- 深入理解JavaScript系列(22):S.O.L.I.D五大原则之依赖倒置原则DIP
前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第5篇,依赖倒置原则LSP(The Dependency Inversion Principle ). 英文原文:htt ...
- JAVA设计模式之单一职责原则
概念: 就一个类而言应该只有一个因其他变化的原因. 流程: 问题由来:设类或接口类C负责两个不同不同的职责:职责T1,职责T2.当由于职责T1需求改变进而需要修改类C时,可能导致职责T2收到不可预知的 ...
- 想真正了解JAVA设计模式看着一篇就够了。 详解+代码实例
Java 设计模式 设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结 设计模式分为 23 种经典的模式,根据用途我们又可以分为三大类.分别是创建型模式.结构型模式和行为型模式 列举几种设 ...
- C#软件设计——小话设计模式原则之:依赖倒置原则DIP
前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身.群里面有朋友问博主是否改行做前端了,呵呵,其实博主是想做“全战”, ...
- zt 设计模式六大原则(3):依赖倒置原则
下面说法对不对? 父类将算法(逻辑)封装起来,子类实现细节:这个就叫DIP(依赖倒置:Dependency Inversion Principles),模板模式就是这个原则的实现.如果在父类中加一个t ...
随机推荐
- hdu3400 两重三分
题意: 题意给你两个公路 A-B C-D 和三个速度V(ab) V(cd) 和 V(两条公路之间) 问你从A到D的最短时间是多少. 思路: 一开始暴力了其中的一条边,每次加0.01,另 ...
- Python模块化编程
目录 模块化 自定义模块 模块的内置属性 导入模块 安装第三方模块 查看模块的属性和方法 模块化 在Python中,一个.py文件就称之为一个模块(Module),为了避免模块名冲突,Python又引 ...
- POJ1178枚举三个地方(所有点都去同一个点)
题意: 有一个国王和很多骑士,他们都要到某一个点去集合,然后问所有人都到达某个终点的距离和最小是多少?过程中如果国王遇到了一个骑士的话,国王就可以和骑士一起按照骑士的走法走,这是两个人算一 ...
- 如何在jQuery的Ajax调用后管理一个重定向请求
1 success:function(data){ 2 if(data.xx == "xx") 3 { 4 //code... 5 window.location.href =&q ...
- 分布式事务与Seate框架(1)——分布式事务理论
前言 虽然在实际工作中,由于公司与项目规模限制,实际上所谓的微服务分布式事务都不会涉及,更别提单独部署构建Seata集群.但是作为需要不断向前看的我,还是有必要记录下相关的分布式事务理论与Seate框 ...
- ResNet学习笔记
ResNet学习笔记 前言 这篇文章实在看完很多博客之后写的,需要读者至少拥有一定的CNN知识,当然我也不知道需要读者有什么水平,所以可能对一些很入门的基本的术语进行部分的解释,也有可能很多复杂的术语 ...
- 【python】Leetcode每日一题-二叉搜索迭代器
[python]Leetcode每日一题-二叉搜索迭代器 [题目描述] 实现一个二叉搜索树迭代器类BSTIterator ,表示一个按中序遍历二叉搜索树(BST)的迭代器: BSTIterator(T ...
- layui处理表单/按钮进行多次提交
在一个项目中,我们最频繁的操作是CRUD,所以一定有涉及到按钮的操作.比如:确认保存,确认编辑,确认删除等等.所以,为了避免表单进行多次提交就显得特别地重要. 代码实现 知识点 $(':button' ...
- Win10安装Ubuntu子系统(WSL)
一:设置子系统环境 关闭所有运行的程序,打开 控制面板→卸载程序→启用或关闭windows功能→勾选上适用于Linux的windows子系统 ,然后确定,完成会提示重启电脑,确定重启,等重启电脑后在操 ...
- Docker搭建开发环境(Nginx+MySQL+PHP)
注意事项 1.像MySQL配置文件.Nginx配置文件.网站根目录这种比较经常操作的需要先使用 docker cp 将文件从容器里复制到主机目录,docker run的时候直接挂载目录就可以了 2.d ...