天天看點

UML2.0之類圖詳解

UML 2 中的陰和陽

UML2.0之類圖詳解
參考 UML 基礎系列的其他文章和教程
  • 統一模組化語言(UML) 版本 2.0
  • UML基礎: 統一模組化語言簡介
  • UML 基礎: 序列圖
  • UML 基礎: 元件圖
  • 繪制整潔的 UML 圖
  • 用例模組化技巧
  • UML 序列圖簡介
  • 養成良好的繪制 UML 序列圖的習慣
  • 利用 UML 進行實體關系模組化
UML2.0之類圖詳解
UML2.0之類圖詳解
UML2.0之類圖詳解
訂閱 UML 相關文章和教程的 RSS 提要
UML2.0之類圖詳解

在 UML 2 中有二種基本的圖範疇:結構圖和行為圖。每個 UML 圖都屬于這二個圖範疇。結構圖的目的是顯示模組化系統的靜态結構。它們包括類,元件和(或)對象圖。另一方面,行為圖顯示系統中的對象的動态行為,包括如對象的方法,協作和活動之類的内容。行為圖的執行個體是活動圖,用例圖和序列圖。

UML2.0之類圖詳解
UML2.0之類圖詳解
UML2.0之類圖詳解

大體上的結構圖

如同我所說的,結構圖顯示模組化系統的靜态結構。關注系統的元件,無需考慮時間。在系統内,靜态結構通過顯示類型和它們的執行個體進行傳播。除了顯示系統類型和它們的執行個體,結構圖至少也顯示了這些元素間的一些關系,可能的話,甚至也顯示它們的内部結構。

貫穿整個軟體生命周期,結構圖對于各種團隊成員都是有用的。一般而言,這些圖支援設計驗證,和個體與團隊間的設計交流。舉例來說,業務分析師可以使用類或對象圖,來為目前的資産和資源模組化,例如分類賬,産品或地理層次。架構師可以使用元件和部署圖,來測試/确認他們的設計是否充分。開發者可以使用類圖,來設計并為系統的代碼(或即将成為代碼的)類寫文檔。

特殊的類圖

UML 2 把結構圖看成一個分類;這裡并不存在稱為“結構圖”的圖。然而,類圖提供結構圖類型的一個主要執行個體,并為我們提供一組記号元素的初始集,供所有其它結構圖使用。由于類圖是如此基本,本文的剩餘部分将會把重點集中在類圖記号集。在本文的結尾,你将對于如何畫UML 2類圖有所了解,而且對于了解在後面文章中将涉及的其他結構圖有一個穩固的基礎。

UML2.0之類圖詳解
UML2.0之類圖詳解

基礎

如先前所提到的,類圖的目的是顯示模組化系統的類型。在大多數的 UML 模型中這些類型包括:

  • 接口
  • 資料類型
  • 元件

UML 為這些類型起了一個特别的名字:“分類器”。通常地,你可以把分類器當做類,但在技術上,分類器是更為普遍的術語,它還是引用上面的其它三種類型為好。

類名

類的 UML 表示是一個長方形,垂直地分為三個區,如圖 1 所示。頂部區域顯示類的名字。中間的區域列出類的屬性。底部的區域列出類的操作。當在一個類圖上畫一個類元素時,你必須要有頂端的區域,下面的二個區域是可選擇的(當圖描述僅僅用于顯示分類器間關系的高層細節時,下面的兩個區域是不必要的)。圖 1 顯示一個航線班機如何作為 UML 類模組化。正如我們所能見到的,名字是 Flight,我們可以在中間區域看到Flight類的3個屬性:flightNumber,departureTime 和 flightDuration。在底部區域中我們可以看到Flight類有兩個操作:delayFlight 和 getArrivalTime。

UML2.0之類圖詳解

圖 1: Flight類的類圖

類屬性清單

類的屬性節(中部區域)在分隔線上列出每一個類的屬性。屬性節是可選擇的,要是一用它,就包含類的清單顯示的每個屬性。該線用如下格式:

name : attribute type      
flightNumber : Integer      

繼續我們的Flight類的例子,我們可以使用屬性類型資訊來描述類的屬性,如表 1 所示。

表 1:具有關聯類型的Flight類的屬性名字

屬性名稱 屬性類型
flightNumber Integer
departureTime Date
flightDuration Minutes

在業務類圖中,屬性類型通常與機關相符,這對于圖的可能讀者是有意義的(例如,分鐘,美元,等等)。然而,用于生成代碼的類圖,要求類的屬性類型必須限制在由程式語言提供的類型之中,或包含于在系統中實作的、模型的類型之中。

在類圖上顯示具有預設值的特定屬性,有時是有用的(例如,在銀行賬戶應用程式中,一個新的銀行賬戶會以零為初始值)。UML 規範允許在屬性清單節中,通過使用如下的記号作為預設值的辨別:

name : attribute type = default value      

舉例來說:

balance : Dollars = 0      

顯示屬性預設值是可選擇的;圖 2 顯示一個銀行賬戶類具有一個名為 balance的類型,它的預設值為0。

UML2.0之類圖詳解

圖 2:顯示預設為0美元的balance屬性值的銀行賬戶類圖。

類操作清單

類操作記錄在類圖長方形的第三個(最低的)區域中,它也是可選擇的。和屬性一樣,類的操作以清單格式顯示,每個操作在它自己線上。操作使用下列記号表現:

name(parameter list) : type of value returned      

下面的表 2 中Flight類操作的映射。

表 2:從圖 2 映射的Flight類的操作

操作名稱 傳回參數 值類型
delayFlight
Name Type
numberOfMinutes
N/A
getArrivalTime

圖3顯示,delayFlight 操作有一個Minutes類型的輸入參數 -- numberOfMinutes。然而,delayFlight 操作沒有傳回值。 1當一個操作有參數時,參數被放在操作的括号内;每個參數都使用這樣的格式:“參數名:參數類型”。

UML2.0之類圖詳解

圖 3:Flight類操作參數,包括可選擇的“in”辨別。

當文檔化操作參數時,你可能使用一個可選擇的訓示器,以顯示參數到操作的輸入參數、或輸出參數。這個可選擇的訓示器以“in”或“out”出現,如圖3中的操作區域所示。一般來說,除非将使用一種早期的程式程式設計語言,如Fortran ,這些訓示器可能會有所幫助,否則它們是不必要的。然而,在 C++和Java中,所有的參數是“in”參數,而且按照UML規範,既然“in”是參數的預設類型,大多數人将會遺漏輸入/輸出訓示器。

繼承

在面向對象的設計中一個非常重要的概念,繼承,指的是一個類(子類)繼承另外的一個類(超類)的同一功能,并增加它自己的新功能(一個非技術性的比喻,想象我繼承了我母親的一般的音樂能力,但是在我的家裡,我是唯一一個玩電吉他的人)的能力。為了在一個類圖上模組化繼承,從子類(要繼承行為的類)拉出一條閉合的,單鍵頭(或三角形)的實線指向超類。考慮銀行賬戶的類型:圖 4 顯示 CheckingAccount 和 SavingsAccount 類如何從 BankAccount 類繼承而來。

UML2.0之類圖詳解

圖 4: 繼承通過指向超類的一條閉合的,單箭頭的實線表示。

在圖 4 中,繼承關系由每個超類的單獨的線畫出,這是在IBM Rational Rose和IBM Rational XDE中使用的方法。然而,有一種稱為 樹标記的備選方法可以畫出繼承關系。當存在兩個或更多子類時,如圖 4 中所示,除了繼承線象樹枝一樣混在一起外,你可以使用樹形記号。圖 5 是重繪的與圖 4 一樣的繼承,但是這次使用了樹形記号。

UML2.0之類圖詳解

圖 5: 一個使用樹形記号的繼承執行個體

抽象類及操作

細心的讀者會注意到,在圖 4 和 圖5 中的圖中,類名BankAccount和withdrawal操作使用斜體。這表示,BankAccount 類是一個抽象類,而withdrawal方法是抽象的操作。換句話說,BankAccount 類使用withdrawal規定抽象操作,并且CheckingAccount 和 SavingsAccount 兩個子類都分别地執行它們各自版本的操作。

然而,超類(父類)不一定要是抽象類。标準類作為超類是正常的。

關聯

當你系統模組化時,特定的對象間将會彼此關聯,而且這些關聯本身需要被清晰地模組化。有五種關聯。在這一部分中,我将會讨論它們中的兩個 -- 雙向的關聯和單向的關聯,而且我将會在Beyond the basics部分讨論剩下的三種關聯類型。請注意,關于何時該使用每種類型關聯的詳細讨論,不屬于本文的範圍。相反的,我将會把重點集中在每種關聯的用途,并說明如何在類圖上畫出關聯。

雙向(标準)的關聯

關聯是兩個類間的聯接。關聯總是被假定是雙向的;這意味着,兩個類彼此知道它們間的聯系,除非你限定一些其它類型的關聯。回顧一下Flight 的例子,圖 6 顯示了在Flight類和Plane類之間的一個标準類型的關聯。

UML2.0之類圖詳解

圖 6:在一個Flight類和Plane類之間的雙向關聯的執行個體

一個雙向關聯用兩個類間的實線表示。線上的任一端,你放置一個角色名和多重值。圖 6 顯示Flight與一個特定的Plane相關聯,而且Flight類知道這個關聯。因為角色名以Plane類表示,是以Plane承擔關聯中的“assignedPlane”角色。緊接于Plane類後面的多重值描述0...1表示,當一個Flight實體存在時,可以有一個或沒有Plane與之關聯(也就是,Plane可能還沒有被配置設定)。圖 6 也顯示Plane知道它與Flight類的關聯。在這個關聯中,Flight承擔“assignedFlights”角色;圖 6 的圖告訴我們,Plane實體可以不與flight關聯(例如,它是一架全新的飛機)或與沒有上限的flight(例如,一架已經服役5年的飛機)關聯。

由于對那些在關聯尾部可能出現的多重值描述感到疑惑,下面的表3列出了一些多重值及它們含義的例子。

表 3: 多重值和它們的表示

可能的多重值描述
表示 含義
0..1 0個或1個
1 隻能1個
0..* 0個或多個
*
1..* 1個或我個
3 隻能3個
0..5 0到5個
5..15 5到15個

單向關聯

在一個單向關聯中,兩個類是相關的,但是隻有一個類知道這種聯系的存在。圖 7 顯示單向關聯的透支财務報告的一個執行個體。

UML2.0之類圖詳解

圖 7: 單向關聯一個執行個體:OverdrawnAccountsReport 類 BankAccount 類,而 BankAccount 類則對關聯一無所知。

一個單向的關聯,表示為一條帶有指向已知類的開放箭頭(不關閉的箭頭或三角形,用于标志繼承)的實線。如同标準關聯,單向關聯包括一個角色名和一個多重值描述,但是與标準的雙向關聯不同的時,單向關聯隻包含已知類的角色名和多重值描述。在圖 7 中的例子中,OverdrawnAccountsReport 知道 BankAccount 類,而且知道 BankAccount 類扮演“overdrawnAccounts”的角色。然而,和标準關聯不同,BankAccount 類并不知道它與 OverdrawnAccountsReport 相關聯。 2

軟體包

不可避免,如果你正在為一個大的系統或大的業務領域模組化,在你的模型中将會有許多不同的分類器。管理所有的類将是一件令人生畏的任務;是以,UML 提供一個稱為 軟體包的組織元素。軟體包使模組化者能夠組織模型分類器到名字空間中,這有些象檔案系統中的檔案夾。把一個系統分為多個軟體包使系統變成容易了解,尤其是在每個軟體包都表現系統的一個特定部分時。 3

在圖中存在兩種方法表示軟體包。并沒有規則要求使用哪種标記,除了用你個人的判斷:哪種更便于閱讀你畫的類圖。兩種方法都是由一個較小的長方形(用于定位)嵌套在一個大的長方形中開始的,如圖 8 所示。但是模組化者必須決定包的成員如何表示,如下:

  • 如果模組化者決定在大長方形中顯示軟體包的成員,則所有的那些成員 4需要被放置在長方形裡面。另外,所有軟體包的名字需要放在軟體包的較小長方形之内(如圖 8 的顯示)。
  • 如果模組化者決定在大的長方形之外顯示軟體包成員,則所有将會在圖上顯示的成員都需要被置于長方形之外。為了顯示屬于軟體包的分類器屬于,從每個分類器畫一條線到裡面有加号的圓周,這些圓周粘附在軟體包之上(圖9)。
UML2.0之類圖詳解

圖 8:在軟體包的長方形内顯示軟體包成員的軟體包元素例子

UML2.0之類圖詳解

圖 9:一個通過連接配接線表現軟體包成員的軟體包例子

了解基礎重要性

在 UML 2 中,了解類圖的基礎更為重要。這是因為類圖為所有的其他結構圖提供基本的建構塊。如元件或對象圖(僅僅是舉了些例子)。

UML2.0之類圖詳解
UML2.0之類圖詳解

超過基礎

到此為止,我已經介紹了類圖的基礎,但是請繼續往下讀!在下面的部分中,我将會引導你到你會使用的類圖的更重要的方面。這些包括UML 2 規範中的接口,其它的三種關聯類型,可見性和其他補充。

接口

在本文的前面,我建議你以類來考慮分類器。事實上,分類器是一個更為一般的概念,它包括資料類型和接口。

關于何時、以及如何高效地在系統結構圖中使用資料類型和接口的完整讨論,不在本文的讨論範圍之内。既然這樣,我為什麼要在這裡提及資料類型和接口呢?你可能想在結構圖上模仿這些分類器類型,在這個時候,使用正确的記号來表示,或者至少知道這些分類器類型是重要的。不正确地繪制這些分類器,很有可能将使你的結構圖讀者感到混亂,以後的系統将不能适應需求。

一個類和一個接口不同:一個類可以有它形态的真實執行個體,然而一個接口必須至少有一個類來實作它。在 UML 2 中,一個接口被認為是類模組化元素的特殊化。是以,接口就象類那樣繪制,但是長方形的頂部區域也有文本“interface”,如圖 10 所示。 5

UML2.0之類圖詳解

圖 10:Professor類和Student類實作Person接口的類圖執行個體

在圖 10 中顯示的圖中,Professor和Student類都實作了Person的接口,但并不從它繼承。我們知道這一點是由于下面兩個原因:1) Person對象作為接口被定義 -- 它在對象的名字區域中有“interface”文本,而且我們看到由于Professor和Student對象根據畫類對象的規則(在它們的名字區域中沒有額外的分類器文本)标示,是以它們是 類對象。 2) 我們知道繼承在這裡沒有被顯示,因為與帶箭頭的線是點線而不是實線。如圖 10 所示,一條帶有閉合的單向箭頭的點 線意味着實作(或實施);正如我們在圖 4 中所見到的,一條帶有閉合單向箭頭的實線表示繼承。

更多的關聯

在上面,我讨論了雙向關聯和單向關聯。現在,我将會介紹剩下的三種類型的關聯。

關聯類

在關聯模組化中,存在一些情況下,你需要包括其它類,因為它包含了關于關聯的有價值的資訊。對于這種情況,你會使用 關聯類 來綁定你的基本關聯。關聯類和一般類一樣表示。不同的是,主類和關聯類之間用一條相交的點線連接配接。圖 11 顯示一個航空工業執行個體的關聯類。

UML2.0之類圖詳解

圖 11:增加關聯類 MileageCredit

在圖 11 中顯示的類圖中,在Flight類和 FrequentFlyer 類之間的關聯,産生了稱為 MileageCredit的關聯類。這意味當Flight類的一個執行個體關聯到 FrequentFlyer 類的一個執行個體時,将會産生 MileageCredit 類的一個執行個體。

聚合

聚合是一種特别類型的關聯,用于描述“總體到局部”的關系。在基本的聚合關系中, 部分類 的生命周期獨立于 整體類 的生命周期。

舉例來說,我們可以想象,車 是一個整體實體,而 車輪 輪胎是整輛車的一部分。輪胎可以在安置到車時的前幾個星期被制造,并放置于倉庫中。在這個執行個體中,Wheel類執行個體清楚地獨立地Car類執行個體而存在。然而,有些情況下, 部分 類的生命周期并 不 獨立于 整體 類的生命周期 -- 這稱為合成聚合。舉例來說,考慮公司與部門的關系。 公司和部門 都模組化成類,在公司存在之前,部門不能存在。這裡Department類的執行個體依賴于Company類的執行個體而存在。

讓我們更進一步探讨基本聚合群組合聚合。

基本聚合

有聚合關系的關聯指出,某個類是另外某個類的一部分。在一個聚合關系中,子類執行個體可以比父類存在更長的時間。為了表現一個聚合關系,你畫一條從父類到部分類的實線,并在父類的關聯末端畫一個未填充棱形。圖 12 顯示車和輪胎間的聚合關系的例子。

UML2.0之類圖詳解

圖 12: 一個聚合關聯的例子

組合聚合

組合聚合關系是聚合關系的另一種形式,但是子類執行個體的生命周期依賴于父類執行個體的生命周期。在圖13中,顯示了Company類和Department類之間的組合關系,注意組合關系如聚合關系一樣繪制,不過這次菱形是被填充的。

UML2.0之類圖詳解

圖 13: 一個組合關系的例子

在圖 13 中的關系模組化中,一個Company類執行個體至少總有一個Department類執行個體。因為關系是組合關系,當Company執行個體被移除/銷毀時,Department執行個體也将自動地被移除/銷毀。組合聚合的另一個重要功能是部分類隻能與父類的執行個體相關(舉例來說,我們例子中的Company類)。

反射關聯

現在我們已經讨論了所有的關聯類型。就如你可能注意到的,我們的所有例子已經顯示了兩個不同類之間的關系。然而,類也可以使用反射關聯與它本身相關聯。起先,這可能沒有意義,但是記住,類是抽象的。圖 14 顯示一個Employee類如何通過manager / manages角色與它本身相關。當一個類關聯到它本身時,這并不意味着類的執行個體與它本身相關,而是類的一個執行個體與類的另一個執行個體相關。

UML2.0之類圖詳解

圖 14:一個反射關聯關系的執行個體

圖 14 描繪的關系說明一個Employee執行個體可能是另外一個Employee執行個體的經理。然而,因為“manages”的關系角色有 0..*的多重性描述;一個雇員可能不受任何其他雇員管理。

可見性

在面向對象的設計中,存在屬性及操作可見性的記号。UML 識别四種類型的可見性:public,protected,private及package。

UML 規範并不要求屬性及操作可見性必須顯示在類圖上,但是它要求為每個屬性及操作定義可見性。為了在類圖上的顯示可見性,放置可見性标志于屬性或操作的名字之前。雖然 UML 指定四種可見性類型,但是實際的程式設計語言可能增加額外的可見性,或不支援 UML 定義的可見性。表4顯示了 UML 支援的可見性類型的不同标志。

表 4:UML 支援的可見性類型的标志

标志 可見性類型
+ Public
# Protected
- Private
~ Package

現在,讓我們看一個類,以說明屬性及操作的可見性類型。在圖 15 中,所有的屬性及操作都是public,除了 updateBalance 操作。updateBalance 操作是protected。

UML2.0之類圖詳解

圖 15:一個 BankAccount 類說明它的屬性及操作的可見性

UML2.0之類圖詳解
UML2.0之類圖詳解
UML2.0之類圖詳解

UML 2 補充

既然我們已經覆寫了基礎和進階主題,我們将覆寫一些由UML 1. x增加的類圖的新記号。

執行個體

當一個系統結構模組化時,顯示例子類執行個體有時候是有用的。為了這種結構模組化,UML 2 提供 執行個體規範 元素,它顯示在系統中使用例子(或現實)執行個體的值得注意的資訊。

執行個體的記号和類一樣,但是取代頂端區域中僅有的類名,它的名字是經過拼接的:

Instance Name : Class Name      
Donald : Person      

因為顯示執行個體的目的是顯示值得注意的或相關的資訊,沒必要在你的模型中包含整個實體屬性及操作。相反地,僅僅顯示感興趣的屬性及其值是完全恰當的。如圖16所描述。

UML2.0之類圖詳解

圖 16:Plane類的一個執行個體例子(隻顯示感興趣的屬性值)

然而,僅僅表現一些執行個體而沒有它們的關系不太實用;是以,UML 2 也允許在實體層的關系/關聯模組化。繪制關聯與一般的類關系的規則一樣,除了在模組化關聯時有一個附加的要求。附加的限制是,關聯關系必須與類圖的關系相一緻,而且關聯的角色名字也必須與類圖相一緻。它的一個例子顯示于圖 17 中。在這個例子中,執行個體是圖 6 中類圖的例子執行個體。

UML2.0之類圖詳解

圖 17:圖 6 中用執行個體代替類的例子

圖 17 有Flight類的二個執行個體,因為類圖指出了在Plane類和Flight類之間的關系是 0或多。是以,我們的例子給出了兩個與NX0337 Plane執行個體相關的Flight執行個體。

角色

模組化類的執行個體有時比期望的更為詳細。有時,你可能僅僅想要在一個較多的一般層次做類關系的模型。在這種情況下,你應該使用 角色 記号。角色記号類似于執行個體記号。為了建立類的角色模型,你畫一個方格,并在内部放置類的角色名及類名,作為實體記号,但是在這情況你不能加下劃線。圖 18 顯示一個由圖 14 中圖描述的雇員類扮演的角色執行個體。在圖 18 中,我們可以認為,即使雇員類與它本身相關,關系确實是關于雇員之間扮演經理及團隊成員的角色。

UML2.0之類圖詳解

圖 18:一個類圖顯示圖14中扮演不同角色的類

注意,你不能在純粹類圖中做類角色的模組化,即使圖 18顯示你可以這麼做。為了使用角色記号,你将會需要使用下面讨論的内部結構記号。

内部的結構

UML 2 結構圖的更有用的功能之一是新的内部結構記号。它允許你顯示一個類或另外的一個分類器如何在内部構成。這在 UML 1. x 中是不可能的,因為記号限制你隻能顯示一個類所擁有的聚合關系。現在,在 UML 2 中,内部的結構記号讓你更清楚地顯示類的各個部分如何保持關系。

讓我們看一個執行個體。在圖 18 中我們有一個類圖以表現一個Plane類如何由四個引擎和兩個控制軟體對象組成。從這個圖中省略的東西是顯示關于飛機部件如何被裝配的一些資訊。從圖 18 的圖,你無法說明,是每個控制軟體對象控制兩個引擎,還是一個控制軟體對象控制三個引擎,而另一個控制一個引擎。

UML2.0之類圖詳解

圖 19: 隻顯示對象之間關系的類圖

繪制類的内在結構将會改善這種狀态。開始時,你通過用二個區域畫一個方格。最頂端的區域包含類名字,而較低的區域包含類的内部結構,顯示在它們父類中承擔不同角色的部分類,角色中的每個部分類也關系到其它類。圖 19 顯示了Plane類的内部結構;注意内部結構如何澄清混亂性。

UML2.0之類圖詳解

圖 20:Plane類的内部結構例子。

在圖 20 中Plane有兩個 ControlSoftware 對象,而且每個控制二個引擎。在圖左邊上的 ControlSoftware(control1)控制引擎 1 和 2 。在圖右邊的 ControlSoftware(control2)控制引擎 3 和 4 。

UML2.0之類圖詳解
UML2.0之類圖詳解

結論

至少存在兩個了解類圖的重要理由。第一個是它顯示系統分類器的靜态結構;第二個理由是圖為UML描述的其他結構圖提供了基本記号。開發者将會認為類圖是為他們特别建立的;但是其他的團隊成員将發現它們也是有用的。業務分析師可以用類圖,為系統的業務遠景模組化。正如我們将會在本系列關于 UML 基礎的文章中見到的,其他的圖 -- 包括活動圖,序列圖和狀态圖——參考類圖中的類模組化和文檔化。

關于“UML 基礎”的本系列的後面的元件圖。

UML2.0之類圖詳解
UML2.0之類圖詳解

腳注

1 delayFlight沒有傳回值,因為我作出了設計決定,不要傳回值。有一點可以争論的是,延遲操作應該傳回新的到達時間,而且,如果是這種情形,操作屬性将顯示為

delayFlight(numberOfMinutes : Minutes) : Date。

2可能看起來很奇怪, BankAccount 類不知道 OverdrawnAccountsReport 類。這個模組化使報表類可以知道它們報告的業務類,但是業務類不知道它們正在被報告。這解開兩個對象的耦合,并是以使系統變得更能适應變化。

-----------------------------------------------------------------

  • 我做的小程式們
  • 【推薦】Web版短信管理平台源碼
  • WinForm版短信管理平台源碼
  • 移動短信程式源碼Win服務版(CMPP3.0/CMPP2.0協定)
  • 移動物聯網卡短信源碼(CMPP3.0協定,支援MsSql/MySql資料庫)
  • C#實作聯通短信Sgip協定程式源碼
  • C#實作電信短信SMGP協定程式源碼
  • C#實作移動短信CMPP服務端程式源碼

繼續閱讀