大型Java進階專題(二) 軟體架構設計原則(上)
阿新 • • 發佈:2020-03-10
## 前言
今天開始我們專題的第一課了,也是我開始進階學習的第一天,我們先從經典設計思想開始,看看大牛市如何寫程式碼的,提升技術審美、提高核心競爭力。本章節參考資料書籍《Spring 5核心原理》中的第一篇 Spring 內功心法(沒有電子檔,都是我取其精華並結合自己的理解,一個字一個字手敲出來的)。
## 開閉原則
開閉原則(Open-Closed Principle,OCP)是指一個軟體實體(如類,模組和函式)應該對擴充套件開發,對修改關閉。所謂的開閉,也正是對擴充套件和修改的兩種行為的一個原則。它強調的是用抽象構建框架,用實現擴充套件細節,可以提高軟體系統的可複用性及可維護性。開閉原則他是面向物件設計中最基礎的設計原則。它知道我們如何建立穩定、靈活的系統。例如版本更新,我們儘可能不修改原始碼,但可以增加新功能。常見於我們經常寫的介面與實現類的使用上,先介面定義好,對於不同的需求寫不同的實現類,後期有改動若不修改原有程式碼,可以重新寫一個實現類。
## 依賴倒置原則
依賴倒置原則(Dependence Inversion Principle,DIP)是指設計程式碼結構時,高層模組不應該依賴底層模組,二者都應該依賴其抽象。抽象不應該依賴細節,細節應該依賴抽象。通過依賴倒置,可以減少類與類之間的耦合性,提高系統的穩定性,提高程式碼的可讀性和可維護性,並且能夠降低修改程式所造成的風險。這是一個比較重要的設計原則,在我們日常開發中,經常有使用該思想的場景,能避免很多時候業務更改時,只需要改動少量程式碼就可以完成需求。下面我們通過一個例子,來深入理解該思想。
以學習課程為例:
```java
//Tom正在學習兩個課程
public class Tom {
public void studyJva() {
System.out.println("正在學習Java");
}
public void studyPython() {
System.out.println("正在學習Python");
}
}
```
```java
//這裡呼叫Tom的兩個學習方法
public static void main(String[] args) {
Tom tom = new Tom();
tom.studyJva();;
tom.studyPython();
}
```
看上去以上兩個類的方法與呼叫沒有毛病,但是隨著業務的擴充套件,Tom又想繼續學習Go語言,這時候我們就需要從低層到高層(呼叫層)依次修改程式碼。先在Tom裡面正在studyGo()方法,然後在main中加以呼叫。這樣一來,系統釋出以後,實際上非常不穩定,現在程式碼少能清晰知曉,但是實際專案中,程式碼量、涉及的業務非常多,修改多個類的程式碼,可能會程式碼意想不到的風險。
接下來我們根據依賴倒置的思想來優化程式碼,先建立一個ICourse介面。
```java
public interface ICourse {
/**
* 抽象出來一個專門用來學習的方法,抽象不依賴細節,不去關心去學習什麼課程
*/
void study();
}
```
```java
//建立一個Java課程學習
public class JavaCourse implements ICourse {
/**
*由實現類去覺得具體學習什麼課程(細節應該依賴抽象)
*/
public void study() {
System.out.println("Tom在學習Java");
}
}
```
```java
//建立一個Python課程學習
public class PythonCourse implements ICourse {
public void study() {
System.out.println("Tom在學習Python");
}
}
```
```java
//修改Tom
public class Tom {
public void study(ICourse course){
//應證了高層模組不應該依賴底層模組,應該依賴其抽象
course.study();
}
}
```
```java
//呼叫方程式碼
public static void main(String[] args) {
Tom tom = new Tom();
tom.study(new JavaCourse());
tom.study(new PythonCourse());
}
```
通過以上程式碼改造,可以看出了依賴倒置原則的核心思想。通過抽象課程學習的介面,減少了類與類之間的耦合性和可維護性。當又有新的課程新增,我們直接可以再新增一個實現類,通過傳參的方式告知Tom,而不在需要修改底層程式碼(也體現了開閉原則)。
大家要切記:以抽象為基準比以細節為基準搭建起來的架構要穩定的多,因此在拿到需求之後,要面向介面程式設計,先頂層再細節設計程式碼結構。
## 單一職責原則
單一職責(Simple Responsibility Pinciple,SRP)是指不要存在多於一個導致類變更的原因。假設我們有一個類負責兩個職責,一旦發生需求變更,修改其中一個職責的程式碼,有可能導致另外一個職責的功能發生故障。這樣一來,這個類就存在兩個導致類發生變更的原因。如何解決這個問題呢?將兩個職責用兩個類來實現,進行解耦。後期需求變更維護互不影響。這樣的設計,可以降低類的複雜度,提高類的可讀性,提高系統的可維護性,降低變更引起的風險。總體來說,就是一個類、介面或方法只負責一項職責。
接下來,我們來看程式碼示例,還用課程舉例,我們的課程有直播課和錄播課。直播課不能快進和快退,錄播課程可以任意反覆觀看,功能職責不一樣,還是想建立一個Course類:
```java
public class Course {
public void study(String courseName){
if ("直播課".equals(courseName)){
System.out.println(courseName + "不能快進");
}else {
System.out.println(courseName + "可以反覆回看");
}
}
}
```
```java
//看下呼叫程式碼
public static void main(String[] args) {
Course course = new Course();
course.study("直播課");
course.study("錄播課");
}
```
從上面的的程式碼來看,Course類承擔了兩種處理邏輯。假如現在要對課程進行加密,直播課與錄播課的加密邏輯不一樣,必須修改程式碼。而修改程式碼的邏輯勢必會相互影響,容易帶來不可控的風險。我們對職責進行解耦,來看程式碼,分別建立兩個類
```java
//直播
public class LiveCourse {
public void study(String courseName){
System.out.println(courseName + "不能快進看");
}
}
```
```java
//錄播
public class ReplayCourse {
public void study(String courseName){
System.out.println(courseName + "可以反覆回看");
}
}
```
```java
//呼叫程式碼
public static void main(String[] args) {
LiveCourse liveCourse = new LiveCourse();
liveCourse.study("直播課!");
ReplayCourse replayCourse = new ReplayCourse();
replayCourse.study("錄播課!");
}
```
此時業務繼續發展,課程要做許可權。沒有付費的學員可以獲得課程基本資訊,已經付費的學員可以獲得視訊流,即學習許可權。設計一個頂層介面,建立ICourse介面:
```java
public interface ICourse {
//獲取課程基本資訊
String getCourseName();
//獲取視訊流
byte[] getCourseVideo();
//學習課程
void studyCourse();
//退款
void refundCourse();
}
```
其實在控制課程層面上至少有兩個職責。我們可以把展示職責和管理職責分離開來,都實現同一個抽象依賴。我們可以把這個介面拆成兩個介面:ICourseInfo和ICourseManager。
```java
public interface ICourseInfo {
//獲取課程基本資訊
String getCourseName();
//獲取視訊流
byte[] getCourseVideo();
}
public interface ICourseManager {
//學習課程
void studyCourse();
//退款
void refundCourse();
}
```
我們看下類圖
![](https://img2020.cnblogs.com/blog/874710/202003/874710-20200310155751637-1220463640.png)
以上是類/介面的單一職,下面我們看下方法層面的單一職責,有時候我們會偷懶,把一個方法寫成下面這樣:
```java
/**
* 修改使用者資訊
*/
private void modityUserInfo(String userName,String address){
userName="LaoWang";
address = "BeiJin";
}
private void modityUserInfo(String userName,String... fileds){
userName="LaoWang";
for (String filed : fileds) {
//....
}
}
private void modityUserInfo(String userName,String address,boolean bool){
if (bool){
//...
}else {
//...
}
userName = "LaoWang";
address = "BeiJin";
}
```
顯然,上面的modityUserInfo()方法承擔了多個職責,既可以修改userName又可以修改address,甚至更多,明顯不符合單一職責。我們做如下修改,可以拆成兩個方法
```java
private void modifyUserName(String userName){
userName="LaoWang";
}
private void modifyAddress(String address){
address = "BeiJin";
}
```
修改之後,開發起來簡單,維護起來也容易。我們在實際開發中會有專案依賴、組合、聚合這些關係,還有的專案的規模、週期、技術人員的水平、對進度的把控,很多類都不符合單一職責。但是,我們在編寫程式碼的過程中,儘可能的讓介面和方法保持單一職責,對專案後期的維護是有很大的幫