软件开发原则
原则 | 介绍 |
单一职责原则 | 一个类或模块应该只负责一项任务或功能 |
开闭原则 | 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭 |
里氏替换原则 | 子类应该能够替换其父类并且不会破坏程序的正确性 |
接口隔离原则 | 客户端不应该强制依赖它不需要的接口,即应该将接口拆分成更小的部分 |
依赖倒置原则 | 高层模块不应该依赖于底层模块,它们都应该依赖于抽象接口 |
迪米特法则 | 一个类应该对自己需要耦合或调用的类知道得最少(提供最简化调用接口) |
聚合复用原则 | 尽量使用对象组合,而不是继承来达到复用的目的 |
以我个人的开源项目举例,来介绍几个基本软件开发原则的基本使用
【SpringBoot集成OnlyOffice实现文档预览】
单一职责原则
模块的单一职责
该开源项目可以作为一个jar
引入,其承担的职责就是对onlyoffice
集成,实现对office
文件的预览和编辑。在领域驱动设计中,每个领域对象和聚合根通常应该遵循单一职责原则,确保它们只负责一个明确定义的领域职责。这有助于保持领域模型的清晰性,同时也符合单一职责原则的要求。
类的单一职责
我定义了如下几个接口类,其中每个类只负责了单一的职能
存储服务接口
package org.lboot.onlyoffice.loader; | |
import org.lboot.onlyoffice.domain.Document; | |
import java.io.InputStream; | |
/** | |
* @author kindear | |
* office 文档存储服务 | |
* 该服务与第三方或者本地文件系统集成 | |
*/ | |
public interface OfficeStoreLoader { | |
/** | |
* 根据文件 key 获取文件信息 | |
* @param fileKey | |
* @return | |
*/ | |
Document readFile(String fileKey); | |
/** | |
* 修改文件 | |
* @param fileKey | |
* @param stream | |
* @return | |
*/ | |
boolean writeFile(String fileKey, InputStream stream); | |
} |
鉴权服务接口
package org.lboot.onlyoffice.loader; | |
/** | |
* @author kindear | |
* Office 鉴权信息加载 | |
*/ | |
public interface OfficeAuthLoader { | |
/** | |
* 获取当前登录用户ID | |
* @return | |
*/ | |
default String getUserId(){ | |
return "0"; | |
} | |
/** | |
* 获取当前登录用户名称 | |
* @return | |
*/ | |
default String getUserName(){ | |
return "guest"; | |
} | |
} |
配置接口
package org.lboot.onlyoffice.loader; | |
/** | |
* @author kindear | |
* OnlyOffice配置加载 | |
*/ | |
public interface OfficeConfigLoader { | |
/** | |
* 获取客制化LOGO地址 | |
* @return | |
*/ | |
default String getCustomLogo(){ | |
return ""; | |
} | |
/** | |
* 获取默认语言 | |
* 默认 zh-CN 中文 | |
* @return | |
*/ | |
default String getLang(){ | |
return "zh-CN"; | |
} | |
/** | |
* 获取回调地址 | |
* @return | |
*/ | |
default String getCallbackUrl(){ | |
return ""; | |
} | |
} |
开闭原则
对扩展开放,对修改关闭
将我的项目作为依赖引入后,自然而然符合对修改关闭
这个特点,
集成的业务系统又可以基于上面所定义的接口,来拓展实现功能,满足对拓展开放
具体可以查看【拓展】
里氏替换原则
子类可以扩展父类的功能,但不能改变父类原有的功能
我在代码设计中加入了基于spring
上下文的事件监听机制,该子类继承自父类ApplicationEvent
,该实现没有改变父类可以被spring
框架管理监听的特性,又拓展了新的字段属性,使得该子类可以在被spring
管理监听的基础上,携带了更多参数。
package org.lboot.onlyoffice.event; | |
import lombok.Getter; | |
import org.springframework.context.ApplicationEvent; | |
import java.time.Clock; | |
/** | |
* @author kindear | |
* office 文档编辑构建事件 传入文件ID 和 用户ID | |
*/ | |
public class OfficeEditBuildEvent extends ApplicationEvent { | |
String userId; | |
String fileKey; | |
public OfficeEditBuildEvent(Object source, String userId, String fileKey) { | |
super(source); | |
this.userId = userId; | |
this.fileKey = fileKey; | |
} | |
public OfficeEditBuildEvent(Object source, Clock clock) { | |
super(source, clock); | |
} | |
} |
接口依赖原则
接口仅仅提供客户端需要的行为,即所需的方法,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口
在完成配置项后,不需要用户关注底层的编辑回调,文件装载,文件信息获取如何实现,该依赖为用户提供了最简单的调用接口OfficeCtl
,所有引入该依赖的,都只需要该类即可。
package org.lboot.onlyoffice.service; | |
import org.lboot.onlyoffice.domain.DocEditor; | |
import org.lboot.onlyoffice.domain.Document; | |
import org.springframework.web.servlet.ModelAndView; | |
import javax.servlet.http.HttpServletResponse; | |
import java.util.Map; | |
/** | |
* @author kindear | |
* onlyoffice 服务实现类 | |
*/ | |
public interface OfficeCtl { | |
/** | |
* 根据文件后缀 获取office 中类型 | |
* @param extName | |
* @return 文件类型 | |
*/ | |
String getDocumentType(String extName); | |
/** | |
* 构建远程文档访问 Document | |
* @param remoteUrl | |
* @return | |
*/ | |
Document buildRemoteDocument(String remoteUrl); | |
/** | |
* 构建文档预览 DocEditor | |
* @param document | |
* @return | |
*/ | |
DocEditor buildPreviewDocEditor(Document document); | |
/** | |
* 构建文档编辑 DocEditor | |
* @param document | |
* @return | |
*/ | |
DocEditor buildEditDocEditor(Document document); | |
/** | |
* 预览远程文件 | |
* @return file-temp | |
*/ | |
ModelAndView previewRemoteFile(String remoteUrl, HttpServletResponse servletResponse); | |
/** | |
* 预览远程文件 | |
* @param remoteUrl | |
* @return | |
*/ | |
ModelAndView previewRemoteFile(String remoteUrl); | |
/** | |
* 移动端预览远程文件 | |
* @param remoteUrl | |
* @return | |
*/ | |
ModelAndView previewRemoteFileOnMobile(String remoteUrl); | |
/** | |
* 嵌入式预览远程文件 | |
* @param remoteUrl | |
* @return | |
*/ | |
ModelAndView previewRemoteFileOnEmbedded(String remoteUrl); | |
/** | |
* 根据文件key 预览文件 | |
* @param fileKey | |
* @return 预览试图 | |
*/ | |
ModelAndView previewFile(String fileKey); | |
/** | |
* 文件预览 | |
* @param editor | |
* @return | |
*/ | |
ModelAndView previewFile(DocEditor editor); | |
/** | |
* 文件预览 制定预览标题 | |
* @param editor | |
* @param title | |
* @return | |
*/ | |
ModelAndView previewFile(DocEditor editor, String title); | |
/** | |
* 编辑远程文件 | |
* @param remoteUrl | |
* @return | |
*/ | |
ModelAndView editRemoteFile(String remoteUrl); | |
/** | |
* 文件编辑 | |
* @param fileKey | |
* @return | |
*/ | |
ModelAndView editFile(String fileKey); | |
/** | |
* 文件编辑 | |
* @param document | |
* @return | |
*/ | |
ModelAndView editFile(Document document); | |
/** | |
* 文件编辑 | |
* @param editor | |
* @return | |
*/ | |
ModelAndView editFile(DocEditor editor); | |
/** | |
* 编辑回调 | |
* @param params | |
* @return | |
*/ | |
Object editCallback(Map<String,Object> params); | |
/** | |
* 将远程访问文件转化为 Pdf | |
* @param remoteUrl | |
* @return pdf 下载地址 | |
*/ | |
String covertToPdf(String remoteUrl); | |
/** | |
* 将文件转化未 pdf | |
* @param document | |
* @return | |
*/ | |
String covertToPdf(Document document); | |
/** | |
* 文件生成缩略图 | |
* @param remoteUrl | |
* @return | |
*/ | |
String generateThumbnail(String remoteUrl); | |
/** | |
* 文件生成缩略图 | |
* @param document | |
* @return | |
*/ | |
String generateThumbnail(Document document); | |
} |
依赖倒置原则
高层模块不应该依赖于底层模块,它们都应该依赖于抽象接口
以OfficeCtl
接口的实现举例,注入的接口全都是抽象接口,无论是基于默认的服务实现还是拓展实现,都可以注入。
@Slf4j | |
@Service | |
@AllArgsConstructor | |
public class OfficeCtlImpl implements OfficeCtl { | |
OnlyOfficeProperties officeProps; | |
OfficeConfigLoader configLoader; | |
OfficeAuthLoader authLoader; | |
OfficeStoreLoader storeLoader; | |
@Resource | |
ApplicationContext context; | |
//... | |
} |
迪米特法则
一个类应该对自己需要耦合或调用的类知道得最少(提供最简化调用接口)
例如读取本地存储系统文件并预览的接口
public ModelAndView previewFile(String fileKey) { | |
Document document = storeLoader.readFile(fileKey); | |
DocEditor docEditor = buildPreviewDocEditor(document); | |
return previewFile(docEditor); | |
} |
我们只需要关注调用storeLoader.readFile(fileKey);
可以获取对应的信息,对于该接口中如何获取文件并读取信息的实现不需要关注。
合成复用原则
OfficeCtl
的实现类即是几种服务的合成复用的案例