前面說了一個狀態模式,總結過程當中發現和這個責任鏈的使用場景很相似,都是爲了解耦大量複雜邏輯判斷的,那麼他們有什麼不一樣呢?html
回憶狀態模式——策略模式的孿生兄弟——對狀態模式的深度複習總結:狀態模式容許經過改變對象的內部狀態而改變對象自身的行爲,這個對象表現得就好像修改了它的類同樣。狀態模式的關鍵是各個狀態子類必須知道下一個狀態是什麼,且要把邏輯判斷轉移到各個狀態子類中,客戶端不須要了解狀態遷移的順序。java
狀態模式雖然類圖和策略模式幾乎同樣,可是策略模式的目的是針對單一算法,在運行時能替換,客戶端須要事先了解策略,主動去選擇合適的策略,不存在狀態的自動遷移。c++
下面總結複習職責鏈模式。web
責任鏈,Chain of Responsibility(CoR),也叫職責鏈模式或者職責連鎖模式,同狀態模式同樣,也是對象的行爲模式之一,該模式構造一系列分別擔當不一樣的職責的類的對象來共同完成一個任務,對象由每個對象對其下家的引用而鏈接起來造成一條鏈,客戶端發出的請求在這個鏈上傳遞,直到鏈上的某一個對象決定能處理此請求。算法
注意:發出這個請求的客戶端並不知道鏈上的哪個對象最終處理這個請求,這使得系統能夠在不影響客戶端的狀況下動態地從新組織和分配責任,因此該模式被稱做職責鏈模式。apache
它的特色是各個職責類(類比狀態模式的狀態類們)職責單一不彼此依賴,且職責自動轉移,可是和狀態模式不一樣的是,責任鏈模式的責任類不知道本身的下一個須要轉移到的職責是哪一個,等價於——發出完成某任務請求的客戶端並不知道鏈上的哪個對象最終處理這個請求,這個組裝過程須要交給環境類去完成,因此很是靈活編程
好比Client要完成一個任務,這個任務包括a,b,c,d四個部分,首先Client把任務交給A,A完成a部分以後,把任務交給B,B完成b部分……直到D完成d部分。bootstrap
再看一個例子,好比政府部門主持的某項工做,縣政府先完成本身能處理的部分,不能處理的部分交給省政府,省政府再完成本身職責範圍內的部分,不能處理的部分交給中央政府,中央政府最後完成該項工做。設計模式
還有,軟件窗口的消息傳播……數組
可是以上的責任的轉移,或者說請求在責任鏈上的移動,各個責任類不知道具體順序和下一個責任,鏈條的組裝過程是環境類(或客戶端完成的)。如圖:
一個例子:汽配廠組裝汽車,有車身,車尾,車頭……如今須要一條生產線組裝汽車,代碼實現:
public abstract class CarController { /** * 控制組裝車的組裝過程 */ public abstract void ControlCar(); } public class CarHead extends CarController { /** * 具體的組裝責任(任務) */ @Override public void ControlCar() { System.out.println("組裝汽車的頭部"); } } public class CarBody extends CarController { @Override public void ControlCar() { System.out.println("組裝汽車的身體"); } } public class CarTail extends CarController { @Override public void ControlCar() { System.out.println("組裝汽車的尾部"); } } public class MainClass { public static void main(String[] args) { // 進行組裝 CarController head = new CarHead(); CarController body = new CarBody(); CarController tail = new CarTail(); // 手動的實現組裝過程 head.ControlCar(); body.ControlCar(); tail.ControlCar(); } }
貌似完成任務了,可是這樣搞有問題:
第一:之後組裝技術提升了,生產線上的組裝順序會變化,或者 多/少 幾個組裝部分。此時修改代碼,不只職責類須要修改,客戶端也須要修改。違反了開閉原則
第二:組裝過程是很low的,徹底沒有實現自動化,每次都要手動進行各個部件的組裝。其實我只須要給生產線下一個指令,一個事先設計的組裝流程就ok了,剩下的讓生產線全自動的運行
下面,又多了個汽車美容,和宣傳功能,如今優化代碼。
以下就是責任鏈模式的最基本的 demo
public abstract class CarControllerB { /** * 各個責任類要持有的下一個對象引用 */ protected CarControllerB successor; public CarControllerB getSuccessor() { return successor; } public void setSuccessor(CarControllerB successor) { this.successor = successor; } /** * 控制組裝車的組裝過程 */ public abstract void ControlCar(); } public class CarHeadB extends CarControllerB { /** * 具體的組裝責任(任務) */ @Override public void ControlCar() { System.out.println("組裝汽車的頭部"); if (getSuccessor() != null) { getSuccessor().ControlCar(); } } } public class CarBodyB extends CarControllerB { @Override public void ControlCar() { System.out.println("組裝汽車的身體"); if (getSuccessor() != null) { getSuccessor().ControlCar(); } } } public class CarTailB extends CarControllerB { @Override public void ControlCar() { System.out.println("組裝汽車的尾部"); if (getSuccessor() != null) { getSuccessor().ControlCar(); } } } public class CarDrumbeating extends CarControllerB { @Override public void ControlCar() { System.out.println("進行宣傳工做"); if (getSuccessor() != null) { getSuccessor().ControlCar(); } } } public class CarCosmetology extends CarControllerB { @Override public void ControlCar() { System.out.println("給車美容"); if (getSuccessor() != null) { getSuccessor().ControlCar(); } } }
記住,持有下一個責任類的對象不能是private的,不然沒法被擴展,下面編寫環境類
public class Client { /** * 在環境類(客戶端)裏按照業務須要,動態的組裝各個職責類爲一條鏈條 */ public void execute() { CarControllerB head = new CarHeadB(); CarControllerB body = new CarBodyB(); CarControllerB tail = new CarTailB(); CarControllerB drumbeating = new CarDrumbeating(); CarControllerB cosmetology = new CarCosmetology(); // 靈活的組裝生產線的順序,目前規定,先組裝頭,以後尾部,最後身子,完成以後美容,宣傳出去 head.setSuccessor(tail); tail.setSuccessor(body); body.setSuccessor(cosmetology); cosmetology.setSuccessor(drumbeating); // 自動的開啓生產線,調用鏈條頭部 head.ControlCar(); } }
客戶端調用
public class MainClassB { public static void main(String[] args) { Client client = new Client(); client.execute(); } }
結果:
組裝汽車的頭部
組裝汽車的尾部
組裝汽車的身體
給車美容
進行宣傳工做
後來,生成任務有變化,須要改變組裝順序,不宣傳了,先放一放。那麼直接在環境裏修改連接的順序,客戶端不須要改變(甚至對客戶端clinet,能夠抽象出一個接口,每一個組裝鏈條都做爲一個子類去實現該接口,我發現這裏又有了策略模式的影子)
/** * 在環境類(客戶端)裏按照業務須要,動態的組裝各個職責類爲一條鏈條 */ public void execute() { CarControllerB head = new CarHeadB(); CarControllerB body = new CarBodyB(); CarControllerB tail = new CarTailB(); CarControllerB drumbeating = new CarDrumbeating(); CarControllerB cosmetology = new CarCosmetology(); // 靈活的組裝生產線的順序,目前規定,先組裝頭,以後尾部,最後身子!完成以後美容,宣傳出去! head.setSuccessor(tail); tail.setSuccessor(body); body.setSuccessor(cosmetology); // 自動的開啓生產線,調用鏈條頭部 head.ControlCar(); }
打印:
組裝汽車的頭部
組裝汽車的尾部
組裝汽車的身體
給車美容
我曾經被人問過,每次都set一下寫一行,set一下寫一行,代碼量很多啊,也麻煩,咋辦?
其實任何模式都不是一成不變的,仍是那句話,設計模式最初於GoF提出,源碼是c++,也就是說,不要糾結具體的代碼實現,不要糾結具體的類圖,模式是一種思想,面向對象編程思想的體現。我這樣作,這是以前的抽象處理類(接口);
public abstract class CarControllerB { /** * 之後各個責任類要持有的下一個對象引用 */ protected CarControllerB successor; public CarControllerB getSuccessor() { return successor; } public void setSuccessor(CarControllerB successor) { this.successor = successor; } /** * 控制組裝車的組裝過程 */ public abstract void ControlCar(); }
改進以後:
public abstract class CarControllerB { /** * 之後各個責任類要持有的下一個對象引用 */ protected CarControllerB successor; public CarControllerB getSuccessor() { return successor; } public CarControllerB setSuccessor(CarControllerB successor) { this.successor = successor; return this.successor; } /** * 控制組裝車的組裝過程 */ public abstract void ControlCar(); }
固然以前的代碼仍是能夠用的,只不過client類變的更加簡單了:
public class Client { /** * 在環境類(客戶端)裏按照業務須要,動態的組裝各個職責類爲一條鏈條 */ public void execute() { CarControllerB head = new CarHeadB(); CarControllerB body = new CarBodyB(); CarControllerB tail = new CarTailB(); CarControllerB drumbeating = new CarDrumbeating(); CarControllerB cosmetology = new CarCosmetology(); // 靈活的組裝生產線的順序,目前規定,先組裝頭,以後尾部,最後身子!完成以後美容,宣傳出去! head.setSuccessor(tail).setSuccessor(body).setSuccessor(cosmetology).setSuccessor(drumbeating); // 自動的開啓生產線,調用鏈條頭部 head.ControlCar(); } }
注意,該例子比較簡單,具體任務都是用打印實現的,一下子介紹一個servlet的過濾器。
一、不一樣的職責對象須要完成不一樣的職責,且職責單一
二、對象鏈的組織,須要將某任務的全部職責執行對象以鏈的形式加以組織
三、消息或請求的傳遞。將消息或請求沿着對象鏈傳遞,以讓處於對象鏈中的對象獲得處理機會
四、任務的完成。任務對象自己不知道,也不負責職責鏈條的終止或者開始是哪個職責類,它們只須要持有下一個責任對象的引用,且判斷下引用空不空,而具體順序和開始,結束的設置都交給責任鏈的控制類(環境類)完成,若是有多個鏈條,那麼能夠抽象一個接口
一、抽象處理者(Handler)角色
定義出一個處理請求的抽象接口。若是須要接口能夠定義出一個方法以設定和返回對下家的引用。這個角色一般由一個Java抽象類或者Java接口實現
二、具體處理者(ConcreteHandler)角色
具體處理者做爲子類去繼承(實現)抽象處理角色,當他們接到請求後,能夠選擇將請求處理掉,或者忽略而將請求傳給下家。因爲具體處理者持有對下家的引用,所以,若是須要,具體處理者能夠訪問下家。
不能否認,狀態模式也好,責任鏈模式也罷,都能解耦和優化大量的邏輯判斷……
責任鏈模式使多個對象都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關係。將這個對象練成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。各個責任類不知道也不必知道下一個責任對象是誰,由環境類統一設置順序和誰鏈接到鏈條,誰不鏈接到鏈條……從代碼中咱們能夠看出,職責鏈在client(環境類)鏈接,也就是說,若是咱們的生產線一旦改變,好比說咱們不須要美容了,咱們須要增長新的組裝項目了,或者是先組裝車頭後,直接請求去保存到倉庫……這都是很容易實現的,職責鏈模式要比狀態模式靈活不少。
可是,這時候有人要問,既然他們均可以解決邏輯判斷的分支過多的問題,那麼,是否是責任鏈模式比狀態模式好呢?
職責鏈模式過於靈活,在客戶端使用時,須要環境去肯定下一個對象是誰,一些列的set操做……在屢次設置的時候很容易出問題。
狀態模式是一個對象的內在狀態發生改變(一個對象,相對比較穩定,處理完一個對象下一個對象的處理通常都已肯定),而職責鏈模式是多個對象之間的改變(多個對象之間的話,就會出現某個對象不存在的情景,就像以前講狀態模式時的公司請假系統,可能存在不一樣級別,不一樣類型員工請假流程不同,此時用狀態模式不太好),這也說明他們兩個模式處理的狀況不一樣
其實,這兩個設計模式最大的區別就是
一、狀態模式是讓各個狀態對象本身知道其下一個處理的對象是誰,即在編譯時便設定。至關於If ,else-if,else-if……, 設計思路是把邏輯判斷轉移到各個State類的內部實現(至關於If,else If),執行時客戶端經過調用環境—Context類的方法來間接執行狀態類的行爲,客戶端不直接和狀態交互
二、職責鏈模式中的各個對象並不指定其下一個處理的對象究竟是誰,只有在客戶端才設定某個類型的鏈條,請求發出後穿越鏈條,直到被某個職責類處理或者鏈條結束。本質更像 swich-case,設計思路是把各個業務邏輯判斷封裝到不一樣職責類,且攜帶下一個職責的對應引用,但不像狀態模式那樣須要明確知道這個引用指向誰,而是在環境類設置連接方式或者過程。使用時,向鏈的第一個子類的執行方法傳遞參數就能夠。客戶端去經過環境類調用責任鏈,全自動運轉起來。
針對具體業務,有人用狀態模式,從頭至尾提早定義好下一個處理的對象,有人採用責任鏈,隨時都有可能調整鏈的順序……甚至不復雜的業務判斷,或者只須要使用一次的情景下,那就不必搞這些雞毛模式,本着夠用原則和具體業務的適合原則
優勢:
一、責任的分擔。每一個類只須要處理本身該處理的工做(不應處理的傳遞給下一個對象完成),明確各種的責任範圍,符合類的最小封裝原則。
二、能夠根據須要自由組合工做流程。如工做流程發生變化,能夠經過從新分配對象鏈即可適應新的工做流程。
三、類與類之間能夠以鬆耦合的形式加以組織。
四、各個任務(責任)類不須要了解本身下一個責任(任務)是誰,交給客戶端完成
缺點:由於處理時以鏈的形式在對象間傳遞消息,根據實現方式不一樣,有可能會影響處理的速度,增長代碼量,同以前的道理,爲了提升靈活性,會犧牲代碼量
做爲一個補充知識,不是重點。
純的責任鏈模式要求一個具體的處理者對象只能在兩個行爲中選擇一個:一是承擔責任,二是把責任推給下家。不容許出現某一個具體處理者對象在承擔了一部分責任後又把該責任向下傳的狀況。在一個純的責任鏈裏,一個請求必須被某一個處理者對象徹底所接收。
不純的責任鏈模式要求一個請求能夠最終不被任何接收端對象所接收。純的責任鏈模式的實際例子很難找,通常看到的例子均是不純的責任鏈模式實現,若是認爲責任鏈不純便不是責任鏈模式,那麼責任鏈模式便不會有太大意義了。
這個模式很常見,除了常見的 servlet 的過濾器以外,還有好比大名鼎鼎的 Netty,其 handler 主要就是依賴的責任鏈模式進行組織的。
在Web應用裏,過濾器位於客戶端和Web應用程序之間,用於檢查和修改二者之間流過的請求和響應數據,在請求到達Servlet/JSP以前,過濾器截獲請求(攔截器),以後在進行實際業務的處理,處理完畢最後的響應返回給客戶端以前,過濾器再次截獲響應進行一些操做(檢驗等)。多個過濾器造成一個過濾器鏈,過濾器鏈中不一樣過濾器的前後順序由部署文件web.xml中過濾器映射<filter-mapping>的順序決定。最早截獲客戶端請求的過濾器將最後截獲Servlet/JSP的響應信息。
固然了,servlet的過濾器還使用了好比裝飾模式,之後總結。
servlet過濾器經典案例
下面簡單看一個小例子,測試類須要實現javax.servlet.Filter#doFilter()接口,demo例子
public class FilterDemo implements Filter { /** * 容器執行,完成過濾器初始化工做 * * @param filterConfig FilterConfig * @throws ServletException */ @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("過濾器初始化完畢"); } /** * 容器調用,每次請求前,響應前,都要通過該方法去過濾 * * @param servletRequest ServletRequest * @param servletResponse ServletResponse * @param filterChain FilterChain * @throws IOException * @throws ServletException */ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("過濾器執行了!"); // 讓下一個資源執行 filterChain.doFilter(servletRequest, servletResponse); } /** * 容器執行,完成過濾器銷燬工做 */ @Override public void destroy() { System.out.println("過濾器銷燬完畢"); } }
配置web.xml,指定哪些資源須要攔截
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <filter> <filter-name>FilterDemo</filter-name> <filter-class>com.dashuai.servlet1.FilterDemo</filter-class> </filter> <filter-mapping> <filter-name>FilterDemo</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
執行過濾器的結果:
信息: Server startup in 35 ms [2016-02-06 03:24:30,849] Artifact demo1:war exploded: Artifact is being deployed, please wait... Connected to server 二月 06, 2016 3:24:30 下午 org.apache.catalina.deploy.WebXml setVersion 警告: Unknown version string [3.1]. Default version will be used. 過濾器初始化完畢 [2016-02-06 03:24:31,125] Artifact demo1:war exploded: Artifact is deployed successfully [2016-02-06 03:24:31,126] Artifact demo1:war exploded: Deploy took 276 milliseconds 過濾器執行了! 過濾器執行了! 過濾器執行了! 過濾器執行了! 二月 06, 2016 3:24:40 下午 org.apache.catalina.startup.HostConfig deployDirectory 信息: Deploying web application directory D:\apache-tomcat-7.0.67\webapps\manager 二月 06, 2016 3:24:40 下午 org.apache.catalina.startup.HostConfig deployDirectory 信息: Deployment of web application directory D:\apache-tomcat-7.0.67\webapps\manager has finished in 68 ms D:\apache-tomcat-7.0.67\bin\catalina.bat stop Using CATALINA_BASE: "C:\Users\Administrator\.IntelliJIdea14\system\tomcat\Tomcat_7_0_67_jspservlet1" Using CATALINA_HOME: "D:\apache-tomcat-7.0.67" Using CATALINA_TMPDIR: "D:\apache-tomcat-7.0.67\temp" Using JRE_HOME: "D:\Java\jdk1.8.0_60" Using CLASSPATH: "D:\apache-tomcat-7.0.67\bin\bootstrap.jar;D:\apache-tomcat-7.0.67\bin\tomcat-juli.jar" 二月 06, 2016 3:24:46 下午 org.apache.catalina.core.StandardServer await 信息: A valid shutdown command was received via the shutdown port. Stopping the Server instance. 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol pause 信息: Pausing ProtocolHandler ["http-apr-8888"] 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol pause 信息: Pausing ProtocolHandler ["ajp-apr-21963"] 二月 06, 2016 3:24:46 下午 org.apache.catalina.core.StandardService stopInternal 信息: Stopping service Catalina 過濾器銷燬完畢 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol stop 信息: Stopping ProtocolHandler ["http-apr-8888"] 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol stop 信息: Stopping ProtocolHandler ["ajp-apr-21963"] 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol destroy 信息: Destroying ProtocolHandler ["http-apr-8888"] 二月 06, 2016 3:24:46 下午 org.apache.coyote.AbstractProtocol destroy 信息: Destroying ProtocolHandler ["ajp-apr-21963"] Disconnected from server
分析過濾器的執行過程,先看過濾器生命週期:
一、應用被加載時就完成了過濾器的實例化和初始化,只有一次
二、針對用戶的每次資源訪問,容器都會調用doFilter方法
三、應用被卸載或服務器中止時,會執行destory方法
首先訪問了 http://localhost:8888/index.jsp頁面,以後請求資源被攔截,進入過濾器,當處理邏輯完畢,返回響應資源時,相似數據結構裏的棧,最開始的過濾器最後一個被調用,反過來經過過濾器,才把資源返回給頁面。
使用debug進行源碼分析,我發現它是有這樣一個類調用的doFilter方法,它實現了FilterChain, CometFilterChain兩個接口,其中FilterChain接口是真正的抽象策略接口
ApplicationFilterChain類是個final類,在這裏能夠把它直接看成抽象的處理策略類(過濾器接口),它作了一些事情,針對責任鏈模式的使用,它用一個 ApplicationFilterConfig 類的數組 filters 保存各個具體的過濾器對象
ApplicationFilterConfig 是一個Filter 的容器,它的主要做用是讀取web.xml文件配置
並且該類的內部聚合了一個Filter接口的引用,該Filter就是以前測試類FilterDemo實現的那個接口
仔細觀察發現,Filter接口的方法doFilter的參數有一個FilterChain接口類型的參數,如此把兩個doFilter結合
這樣測試類實現Filter接口,同時該接口的doFilter方法裏又有一個責任鏈模式裏真正抽象的處理策略接口 FilterChain 的參數,在方法內部調用接口FilterChain的doFilter方法
在實現類FilterDemo裏使用
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("過濾器執行了!"); // 讓下一個資源執行 filterChain.doFilter(servletRequest, servletResponse); }
方法內的doFilter就是真正的抽象策略類接口的抽象方法,而ApplicationFilterChain類又實現了FilterChain接口,ApplicationFilterChain類內部聚合了全部的過濾器Filter,如此就清晰了。繼續debug,發現程序進入了這個類:StandardWrapperValve
其中有這樣兩句代碼:
我發現裏面有我想要的,開始說的責任鏈模式裏抽象處理策略類!ApplicationFilterChain類,而這個類的引用被一個ApplicationFilterFactory(應該是使用了工廠模式)的createFilterChain方法實例化,進入ApplicationFilterFactory查看createFilterChain方法:
發現別有洞天!原來該工廠類的createFilterChain方法裏實例化了以前的抽象處理策略類,繼續;
調用了addFilter方法,回到抽象處理策略類,發現這是在初始化Filter數組
void addFilter(ApplicationFilterConfig filterConfig) { ApplicationFilterConfig[] newFilters = this.filters; int len$ = newFilters.length; for(int i$ = 0; i$ < len$; ++i$) { ApplicationFilterConfig filter = newFilters[i$]; if(filter == filterConfig) { return; } } if(this.n == this.filters.length) { newFilters = new ApplicationFilterConfig[this.n + 10]; System.arraycopy(this.filters, 0, newFilters, 0, this.n); this.filters = newFilters; } this.filters[this.n++] = filterConfig; }
發現了貌似是JDK一個傻逼問題!?開頭明明定義了常量10啊,爲啥裏面還寫10這個魔鬼數字呢?且這個常量10沒有被使用!我去!!!匪夷所思。高人能夠指點!
StandardWrapperValue類在初始化過濾器保存的數組以後,就調用ApplicationFilterChain的doFilter方法
而ApplicationFilterChain的doFilter方法內部,又調用了本身的私有工具方法internalDoFilter方法
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if(Globals.IS_SECURITY_ENABLED) { final ServletRequest req = request; final ServletResponse res = response; try { AccessController.doPrivileged(new PrivilegedExceptionAction() { public Void run() throws ServletException, IOException { ApplicationFilterChain.this.internalDoFilter(req, res); return null; } }); } catch (PrivilegedActionException var7) { Exception e = var7.getException(); if(e instanceof ServletException) { throw (ServletException)e; } if(e instanceof IOException) { throw (IOException)e; } if(e instanceof RuntimeException) { throw (RuntimeException)e; } throw new ServletException(e.getMessage(), e); } } else { this.internalDoFilter(request, response); } }
顯然internalDoFilter方法裏的filter.doFilter(request, response, this);就是調用咱們前面建立的測試類FilterDemo中的doFilter()方法。
而FilterDemo 中的doFilter()方法會繼續調用 chain.doFilter(request, response); 方法,而這個 chain 其實就是 ApplicationFilterChain,因此調用過程又回到了上面調用 doFilter 和調用 internalDoFilter 方法,這樣執行直到裏面的過濾器所有執行。太多了,差很少能夠了,大致就是:
簡單的局部的類圖以下: