## 軟體開發原則 | 原則 | 介紹 | | | | | 單一職責原則 | 一個類或模塊應該只負責一項任務或功能 | | 開閉原則 | 軟體實體(類、模塊、函數等)應該對擴展開放,對修改關閉 | | 里氏替換原則 | 子類應該能夠替換其父類並且不會破壞程式的正確性 | | 介面隔離原則 | 客戶端 ...
軟體開發原則
原則 | 介紹 |
---|---|
單一職責原則 | 一個類或模塊應該只負責一項任務或功能 |
開閉原則 | 軟體實體(類、模塊、函數等)應該對擴展開放,對修改關閉 |
里氏替換原則 | 子類應該能夠替換其父類並且不會破壞程式的正確性 |
介面隔離原則 | 客戶端不應該強制依賴它不需要的介面,即應該將介面拆分成更小的部分 |
依賴倒置原則 | 高層模塊不應該依賴於底層模塊,它們都應該依賴於抽象介面 |
迪米特法則 | 一個類應該對自己需要耦合或調用的類知道得最少(提供最簡化調用介面) |
聚合復用原則 | 儘量使用對象組合,而不是繼承來達到復用的目的 |
以我個人的開源項目舉例,來介紹幾個基本軟體開發原則的基本使用
【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
*/
@Deprecated
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
*/
@Getter
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
*/
@Deprecated
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;
//...
}
迪米特法則
一個類應該對自己需要耦合或調用的類知道得最少(提供最簡化調用介面)
例如讀取本地存儲系統文件並預覽的介面
@SneakyThrows
@Override
public ModelAndView previewFile(String fileKey) {
Document document = storeLoader.readFile(fileKey);
DocEditor docEditor = buildPreviewDocEditor(document);
return previewFile(docEditor);
}
我們只需要關註調用storeLoader.readFile(fileKey);
可以獲取對應的信息,對於該介面中如何獲取文件並讀取信息的實現不需要關註。
合成復用原則
OfficeCtl
的實現類即是幾種服務的合成復用的案例