您好朋友,感謝您關注xiaosilent,我在blogjava的博客已經(jīng)停止更新,請訪問http://kuan.fm了解我的最新情況,謝謝!
          隨筆-82  評論-133  文章-0  trackbacks-0

          策略模式 (The Strategy Pattern)

          概述

          在這一章里我們將會討論一個電子商務方面的例子。我們將會在這個例子中使用到 Strategy 模式。

          在這一章里我們將

          • 繼續(xù)探討如何處理項目中的新需求和一些業(yè)務邏輯方面的變化

          • 根據(jù) GOF 的理論,尋求到最適合的解決這些新需求和變化的方案

          • 引入一個新的例子 ( 和電子商務有關的例子 )

          • 講解 Strategy 模式,并在例子中演示如何用它處理新業(yè)務需求

          • 講述 Strategy 模式的主要功能


          處理新業(yè)務需求的方法

          在實際生活中,以及在軟件開發(fā)中,我們時常都需要選擇一些很普遍的常用的方法來完成任務或是解決問題等。我們也都知道,為了一時的方便而走所謂的“捷徑”到最后反而使問題復雜化。舉個例子吧,我們必須定期給我們的汽車更換機油。沒有人可以忽視這個問題。當然了,不是說每三千公里就必須更換一次,但是,總不可能汽車都跑了三萬公里了,卻不把更換機油的事情放在心上吧?(如果有誰真的是這么做的話,他就不用再換了,因為他的汽車已經(jīng)下崗了!)再舉個例子,把一張辦公桌當成對方檔案的檔案柜。一天、兩天還成,還能在桌面上找到我們需要的檔案,但是時間一長,檔案一多,怎么找?這些災難性的后果不都是因為為了眼前的短期的利益而做出一些不恰當?shù)臎Q定而造成的么?

          遺憾的是,在軟件開發(fā)中,很多人也還沒有意識到這一點。有很多項目都是只考慮到眼前的短期的需求,并沒有考慮到日后該如何維護的問題。忽略日后維護的借口有很多:

          • 并不知道日后該項目會有什么新的業(yè)務需求

          • 如果一直在考慮業(yè)務將怎么變化,那么將永遠停留在業(yè)務分析上

          • 如果在嘗試著寫這個項目的時候又考慮到可以加入哪些新功能,將永遠停留在設計上

          • 預算不夠

          • 客戶催得緊,時間不夠

          • 維護的問題,以后再考慮也不遲

          照這樣下去,就只有兩個選擇:

            1. 一直停留在分析和設計上

            2. 只考慮當前的業(yè)務需求,快速完成開發(fā)。然后投入到新的項目當中。

          因為大多數(shù)時候我們要做的是盡早把項目交出去,至于維護的事情,好像不在考慮范圍之內(nèi),所以,這樣的結果并不足以為奇。但是,想一下,是不是還有其他的方案呢?是不是有什么東西促使我們看不到其他的方案呢?其實,這一切都是因為,我們一直都認為在設計過程中考慮到業(yè)務邏輯的變化比不考慮所花費的成本更高。

          但是,通常,這么想是不對的。事實上,恰恰相反。在設計過程中加入對隨著時間推移,業(yè)務邏輯可能出現(xiàn)的變化的考慮,通常這樣的設計將會是個不錯的設計。甚至開發(fā)周期會更短。而且,高質(zhì)量的代碼更加益于閱讀、測試還有修改。這些好處相對于設計過程中多花的那點點時間而言完全是值得的。

          下面將要講到的例子就是一個考慮到可能存在的變化的不錯的例子。要說明的是,我們并不是要預定變化,而是要揣測在什么地方將會有怎樣的變化。這一切都基于 GoF 書中的一些概念:

          • 對接口而不是某個確切的實現(xiàn)進行編程

          • 用單獨的對象、聚合等方法而不是用類的繼承

          • 考慮什么是可以不用重新設計就可以改變的,而不是去考慮什么樣的變化會導致重新設計。焦點問題就是把要變化的事務進行封裝——這也是很多設計模式的主旨所在。

          我的一點建議是:在需要改變代碼來實現(xiàn)新功能的時候,你應該遵循這些策略,前提是遵循這些策略并不會給新功能的實現(xiàn)造成太大的困難,不會使得研發(fā)費用開銷過大。你可以通過這樣的一點小小投入而在今后收獲頗多,何樂而不為?

          但是,我并不會盲目的遵循這些策略。我將會和其他不遵循這些策略的實現(xiàn)方案進行比較,看它們中誰更符合面向對象設計的一些概念。這種比較將還會在第十章介紹橋式的時候繼續(xù)用到。在那一章,我也會衡量備用方案和當前方案誰更符合面向對象的概念。


          國際電子商務系統(tǒng)個案:系統(tǒng)初始需求

          在這個案例里面,我們將討論到美國的一個國際電子商務系統(tǒng)當中的訂單處理系統(tǒng)。該訂單處理系統(tǒng)要求能夠處理來自各個國家的銷售訂單。這一章,讓我們一起來接受由系統(tǒng)需求的改變所帶給我們的挑戰(zhàn)。

          通常這個系統(tǒng)將會通過一個控制對象 (TaskController) 來處理銷售請求。當有訂單下達的時候,該對象將把“下訂單”的這個請求傳遞給一個 SalesOrder 類的對象來處理,就象圖 9-1 所描述的那樣。


          9-1 訂單處理的業(yè)務結構

          SalesOrder 應該會包含有如下的一些功能:

          • 給出一個圖形的用戶界面,讓用戶填寫表單,提交訂單信息

          • 處理銷售稅的計算問題

          • 處理用戶提交的訂單信息,并給出訂單處理的回執(zhí)信息

          其中的一些功能應該會在其他對象的協(xié)助下完成。例如, SalesOrder 并不會自己完成給出訂單回執(zhí)的操作,相反,它僅僅保存訂單的相關信息。在實際的操作中, SalesOrder 可以調(diào)用一個 SalesTicket 的對象來完成 SalesOrder 處理完成后的訂單回執(zhí)的打印操作。


          處理新的業(yè)務需求

          我們已經(jīng)把上面提到的這些功能完成了。現(xiàn)在,假設我們被要求改變原先銷售稅的計算方式。比如,現(xiàn)在要求該系統(tǒng)可以處理來自美國本土以外的其他國家的客戶的訂單信息。至少,我們需要添加其他國家的客戶的銷售稅計算辦法。

          那么,我們將通過什么辦法來實現(xiàn)呢?

          通常,我們有哪些方法來處理需求的變化呢? 至少,我們可以很快想到下面的這些方法:

          • 復制-粘貼

          • switch 或者是 if-else

          • 用功能指示器 (function pointers)

          • 繼承,創(chuàng)建一個新的派生類來處理新需求

          • 連同舊需求,統(tǒng)統(tǒng)轉到一個新對象里面去處理


          一個陳舊的處理方式。已經(jīng)擁有了一些代碼,而且它們的功能和現(xiàn)在所要求的相似,那么,復制、粘貼這些代碼到新的功能模塊里面,再對這些代碼稍作修改,就能夠達到目的。但是,很顯然,這樣將會使得代碼很難維護,因為,在今后,不得不維護兩份相似的代碼。我們忽略了對象的重用。這樣使得維護費用的增加。

          一個趨于合理的處理方式。隨著時間的推移,程序可能會出現(xiàn)這樣那樣的問題。我們來看一下,當有變量被大范圍使用的時候,這對程序整體的耦合性和易測性有什么影響。例如,假設我們用變量 myNation 來標識客戶的國籍。如果客戶的國籍是限定在美國和加拿大以內(nèi),可能用 switch 語句就可以很好地完成。我們可以象下面的代碼所演示的那樣:

          // Handle Tax switch (myNation) {

          case US:

          // US Tax rules here

          break;

          case Canada:

          // Canada Tax rules here

          break;

          }

          // Handle Currency switch (myNation) {

          case US:

          // US Currency rules here

          break;

          case Canada:

          // Canada Currency rules here

          break;

          }

          // Handle Date Format switch (myNation) {

          Case US:

          // use mm/dd/yy format

          Break;

          case Canada:

          // use dd/mm/yy format

          Break;

          }



          但是,當有很多變量的時候怎么辦呢?假設現(xiàn)在又需要加入對德國客戶的支持。那么,上面的代碼也許就會變成下面這樣:

          // Handle Tax switch (myNation) {

          case US:

          // US Tax rules here

          break;

          case Canada:

          // Canada Tax rules here

          break;

          case Germany:

          // Germany Tax rules here

          Break;

          }

          // Handle Currency switch (myNation) {

          case US:

          // US Currency rules here

          break;

          case Canada:

          // Canada Currency rules here

          break;

          case Germany:

          // Germany Currency rules here

          Break;

          }

          // Handle Date Format switch (myNation) {

          Case US:

          // use mm/dd/yy format

          Break;

          case Canada:

          case Germany:

          // use dd/mm/yy format

          Break;

          }

          // Handle Language switch (myNation) {

          case US:

          case Canada:

          // use English

          break;

          case Germany:

          //use German

          break;

          }


          這看起來好像還不是太壞,畢竟才 3 個國家而已嘛,但我們已經(jīng)感覺到,這和前面的示例相比復雜了不少,并不是那么方便了,不是么?最后,我開始需要在每個 case 里面添加變量來完成任務了。這,使得整個代碼變得一團糟。例如,加拿大的魁北克說的是法語。如果我要考慮到這點,代碼就又變了:

          // Handle Language switch (myNation0 {

          case Canada:

          if (inQuebec) {

          // use French

          break;

          }

          case US:

          // use English

          break;

          case Germany:

          // use Germany

          break;

          }


          switch 內(nèi)部的程序流程都變得復雜了,更不用說整個 switch 語句了,那么的難以閱讀,那么的難以解釋。當一個新的 case 到來的時候,程序員需要找到整個 switch 語句里每一個可能由此受到牽連的地方,并且,還要對它們作出正確的修改。

          新的備用方案。人們時常會誤用繼承,由此還給繼承帶來不好的聲譽。事實上錯的并不是繼承本身,而是程序員。是因為程序員誤用繼承才導致不良設計的出現(xiàn)。而這一切的根源可能是在于那些教授面向對象的概念的人。

          當面向對象的程序設計成為主流的時候,“重用”被吹捧成面向對象的主要優(yōu)點。要達到重用的效果,人們被告知:你只要繼承你已有的類,并在繼承類中稍作修改就可以了。

          在我們計算銷售稅的例子中,我們可以考慮重用 SalesOrder 。我們可以把新的計稅規(guī)則當成是一種新的銷售訂單來處理,只是這些訂單的計稅規(guī)則不一樣而已。例如,來自加拿大的訂單。我們可以從 SalesOrder 派生一個 CanadianSalesOrder 類,并覆蓋其中的計稅規(guī)則。


          9-2 電子商務系統(tǒng)銷售訂單結構示例

          這種方法的缺點在于,你只能用一次。例如,當處理德國訂單,或者是獲取其他變量(如:數(shù)據(jù)格式,語言,配貨方式等。),我們正在建立的繼承層次并不能夠很輕易地處理好這些變化。我們一而再的特殊化,要么使得代碼難以理解,要么出現(xiàn)冗余代碼。這也是人們一直在對面向對象設計抱怨的地方:特殊化最終導致一個長長的繼承結構。然而不幸的是,這些很難以理解,冗余,難以測試,多種概念交叉在一起。人們抱怨說面向對象設計其實被吹捧得太高了,特別是因為“重用”,也就不足以為奇了。

          我們還有沒有其他的方法呢?想想我們在剛開始時候說到的原則:考慮設計中什么是可變的,封裝要變化的概念,用對象的聚合而不是繼承。

          要這么做,我需要做下面的兩件事情:

          1. 找到什么是可變的并把他們用類封裝起來

          2. 在另外一個類里包含前面創(chuàng)建的類

          在這個例子里,我們已經(jīng)知道銷售稅的計算方式是可變的。要完成這一點,我們需要先建立一個在概念上完成銷售稅計算的抽象類,并為每個變化派生出一個固定的類。也就是說,我要建立一個定義了完成該任務的接口的 CalcTax 類對象,然后就可以派生我們需要的特定版本的類的。


          9-3 封裝銷售稅的計算

          繼續(xù),現(xiàn)在我們用聚合而不再是繼承。這意味著,我將在 SalesOrder 對象里聚合一個 CalcTax 的對象,而不是創(chuàng)建不同版本的 SalesOrder


          9-4 用聚合而不是繼承

          9-1 Java 代碼片段:實現(xiàn)策略 (Strategy) 模式

          public ? class ?TaskController?{

          ??
          public ? void ?process?()?{

          ????
          // ?this?code?is?an?emulation?of?a?processing?task?controller

          ????
          // ?…

          ????
          // ?figure?out?which?country?you?are?in

          ????CalcTax?myTax;

          ????myTax?
          = ?getTaxRulesForCountry?();

          ????SalesOrder?mySO?
          = ? new ?SalesOrder?();

          ????mySO.process?(?myTax?);

          ??}


          ??
          private ?CalcTax?getTaxRulesForCountry?()?{

          ????
          // ?In?real?life,?get?the?tax?rules?based?on?country?you?are?in.

          ????
          // ?You?may?have?the?logic?here?or?you?may?have?it?in?a?configuration?file.

          ????
          // ?Here,?just?return?an?USTax?so?this?will?compile.

          ????
          return ? new ?USTax?();

          ??}

          }



          public ? class ?SalesOrder?{

          ??
          public ? void ?process?(CalcTax?taxToUse)?{

          ????
          long ?itemNumber? = ? 0 ;

          ????
          double ?price? = ? 0 ;

          ????
          // ?give?the?tax?object?to?use

          ????
          // ?…

          ????
          // ?calculate?tax

          ????
          double ?tax? = ?taxToUse.taxAmount?(?itemNumber?,?price);

          ??}

          }


          public ? abstract ? class ?CalcTax?{

          ??
          abstract ? public ? double ?taxAmount?(? long ?itemSold?,? double ?price?);

          }


          public ? class ?CanTex? extends ?CalcTax?{

          ??
          public ? double ?taxAmount?(? long ?itemSold?,? double ?price?)?{

          ????
          // ?in?real?life,?figure?out?tax?according?to?the?rules?in?Canada?and?return?it

          ????
          // ?Here,?return?0?so?this?will?compile

          ????
          return ? 0.0 ;

          ??}

          }


          public ? class ?USTax? extends ?CalcTax?{

          ??
          public ? double ?taxAmount?(? long ?itemSold?,? double ?price?)?{

          ????
          // ?in?real?life,?figure?out?tax?according?to?the?rules?in?the?US?and?return?it

          ????
          // ?Here,?return?0?so?this?will?compile

          ????
          return ? 0.0 ;

          ??}

          }


          我已經(jīng)給 CalcTax 對象創(chuàng)建了一個通用的接口。可能,我將會有一個定義了什么是正在銷售的 Saleable ( 并在該類里說明該商品如何計稅 ) SalesOrder 對象將把 Saleable 的對象和商品的數(shù)量和價格一起同時傳遞給 CalcTax 對象。這些,將會是 CalcTax 對象所需要的所有信息。

          本方法的一個優(yōu)點是使對象間結合更緊密。銷售稅的計算是在 CalcTax 類里面自己完成的。另一個優(yōu)點是當我得到新需求的時候,我只需要從 CalcTax 派生一個新類,并實現(xiàn)該類中的抽象方法就可以了。

          最后,職能轉換也變得非常方便。例如,在使用繼承機構的方法中,我必須讓 TaskController 去判斷程序該調(diào)用哪一類型的 SalesOrder 。而,這個新結構中,我可以用 TaskController 或者是 SalesOrder 來完成這點。讓 SalesOrder 來完成,我需要一些配置對象以使得它知道用那種類型的稅務對象。 (TaskController 對象來控制的情形與此相似 )


          9-5 SalesOrder 對象使用配置對象來感知 CalcTax 的適當類型

          很多人都已經(jīng)發(fā)現(xiàn),這個方法也有使用到繼承。確實如此。但是,這和從 SalesOrder 派生出 CanadianSalesOrder 是不同的。在完全繼承的方法中,我們在 SalesOrder 類中使用繼承來處理變化。而在用到設計模式的方法中,我們使用的是對象聚合方式。 ( 也就是說, SalesOrder 包含了一個能處理這些變化的對象的引用,也就是稅 (tax)) 。從 SalesOrder 的角度來看,我所使用的就是聚合而不是繼承。至于,他所包含的對象是如何處理這個變化的和 SalesOrder 無關。

          也許有人會問:你這么做,不是把難題推給另一個對象而已嗎?要回答這個問題,有三個方面。第一,不錯,的確是這樣。但是,這么做,可以使復雜的問題簡化。第二,原來的設計中在派生鏈中收集了太多獨立的變量 ( 稅,數(shù)據(jù)格式…… ) ,而新方法里,我們在把每個變量都固定在它自己的派生鏈里面了。這樣,他們就可以任意的繼承而不會影響到其他的變量。最后,在新方法里,系統(tǒng)里的其他部分可以獨立地使用 SalesOrder 里的功能。總之,借用設計模式的方法使系統(tǒng)各部分在功能上達到平衡 (scale) ,而單純使用繼承的方法是不可能辦到這點的。

          本方法允許 SalesOrder 使用到的對象的業(yè)務邏輯獨立發(fā)生變化。這個方法不僅僅在目前運行良好,就是在將來也沒有問題。總的來說,在抽象類中封裝運算法則,并適時使用這些抽象類,這就是策略模式 (Strategy Pattern)


          策略模式

          正如 GOF 的經(jīng)典著作《設計模式》所說,策略模式 (The Strategy Pattern) 的目的是,定義一組運算法則,并挨個進行封裝,最后讓它們之間可以相互作用。策略模式使客戶端用到的這些運算法則可以獨立變化。

          策略模式基于以下幾點:

          • 我們需要用對象去完成某些功能

          • 這些功能的不同實現(xiàn)方案通過多態(tài)得以表現(xiàn)

          • 需要在一個運算法則上有多個不同的實現(xiàn)

          在設計中把不同行為分解成獨立的模塊,這看起來是個不錯的主意。這樣,我們就可以隨意改變某個類的功能而不用影響到其他類。


          如何使用策略模式

          當我在課堂上講解這個電子商務的例子的時候,有同學問我:“你有沒有注意到,在美國,一定年齡以上的人是不用付食品稅的呢?” 我的確沒有注意到這點, CalcTax 類的接口也沒有處理這個問題。但是,我至少可以通過以下的三種方式來解決這個問題:

          1. Customer 的年齡 (age) 傳遞給 CalcTax ,并在 CalcTax 里調(diào)用

          2. 常用一點的做法是把 Customer 的引用直接傳遞給 SalesOrder

          3. 更常用的做法則是,在 SalesOrder 里把自己 (this) 傳遞給它自己,再讓 CalcTax 來查詢調(diào)用

          雖然這樣我必須修改 SalesOrder CalcTax 來處理這個問題,但是,我很清楚我該怎么做,并不會因此而引入新的問題。

          理論上說,策略模式就是用來封裝算法的。然而,實際上,我發(fā)現(xiàn),它可以用來封裝任何的規(guī)則。通常,在我做系統(tǒng)分析的時候,我遇到在不同時候用不同業(yè)務規(guī)則的時候,我就會考慮用策略模式來處理這些變化。

          策略模式要求被封裝的算法 ( 業(yè)務規(guī)則 ) 要和用到這些算法的類相獨立。也就是說,這些算法所需要的信息要么是客戶端對象傳遞過來,要么是算法本身通過其他方式獲取。

          策略模式可以簡化單元測試,因為每個算法都在自己單獨的類里面,并且可以通過它自身的接口而接受測試。如果算法并不象策略模式中這樣彼此隔離開來,那么會由于對象間的耦合而使測試變成一件煩人的事情。例如,在初始化某個對象以前,你可能需要一些前提條件,或者該對象需要用某種方式來操縱一個受保護的數(shù)據(jù)成員。 當有多個不同算法同時存在是,測試將會進一步得以簡化。這是因為在使用策略模式以后,開發(fā)人員就不再需要去考慮對象耦合后的對象間的交互問題。即是說,可以單獨對某個算法進行測試而不用去考慮所有算法間的結合 (combination)

          在前面的訂單處理的例子中,當 SalesOrder 每需要到銷售稅算法的時候,我就通過 TaskController 傳遞一個算法對象給他。細想一下,除非我在不同客戶間重用 SalesOrder ,否則我將會一直在每個 SalesOrder 對象里用同一個策略對象。策略模式的一個常見變化就是,在客戶對象的構造函數(shù)里使用策略對象。然后客戶對象的其他方法就可以直接使用到策略對象而不用再從外界傳入。然而,由于客戶對象并不知道策略對象的確切類型,本模式的功能就受到了限制了。這個方法適用于那種在客戶對象創(chuàng)建的時候就已經(jīng)知道將要使用到的策略對象的確切類型的情況。

          有時,有學生向我抱怨說策略模式使得他們要多寫好幾個類。雖然,我并不認為這有什么問題,當我可以控制到所有算法的時候,我還是在想辦法盡量減少類的個數(shù)。如果用 C ++我可以在抽象算法的頭文件里包含所有具體算法將會用到的頭文件。我還可以在抽象算法的 cpp 文件里包含所有具體算法會用到的代碼。如果是 Java ,在抽象算法里用內(nèi)部類包含所有的具體算法。如果,其他程序員需要去實現(xiàn)他們自己的具體算法的話,我就不能這做了。


          總結

          策略模式就是定義一組算法,所有的這些算法都是做同一件事情,只是他們實現(xiàn)的方式不同。

          給大家講了一個銷售稅算法的例子。在一個國際性的電子商務系統(tǒng)里,對不同國家的客戶可能會存在不同的銷售稅算法。策略模式使得我們可以封裝這些算法到一個抽象類中,并從這個抽象類派生出一系列的具體類來完成不同國家的具體算法。

          通過從抽象類派生出不同算法的方式,程序的主模塊 ( 如上例中的 SalesOrder) 不必考慮確切算法類型,這樣就允許新變化的出現(xiàn),隨后在 16 章,我們繼續(xù)講如何處理這些變化。

          策略模式主要功能

          目標

          使我們可以根據(jù)不同客戶端對象采用不同算法

          環(huán)境

          具體算法的選擇取決于具體的客戶對象的類型

          解決方式

          把算法的選擇和算法的實現(xiàn)分離開來,再根據(jù)客戶對象進行選擇

          參與方式

          • Strategy 指明不同算法將如何被使用

          • ConcreteStrategies 具體實現(xiàn)不同算法

          • Context 通過 Strategy 使用確切的 ConcreteStrategy Strategy Context 進行交互以實現(xiàn)算法的選擇 ( 有時 Strategy 必須查詢 Context) Context 把請求從客戶端傳遞給 Strategy

          結果

          • 策略模式定義一組算法

          • 使用 Switches 語句和 if-else 的方式被拋棄

          • 必須以相同方式調(diào)用所有算法 ( 所有算法必須有相同接口 ) ConcreteStrategies Context 的交互可能需要額外的方法以從 Context 里獲取程序的狀態(tài)。

          執(zhí)行方式

          讓使用到算法的類 (Context) 包含一個包含了如何調(diào)用具體算法的抽象方法的抽象類 (Strategy) 。每個派生類根據(jù)需要實現(xiàn)具體算法。


          9-6 策略模式通用結構


          posted on 2006-11-05 20:57 xiaosilent 閱讀(2629) 評論(3)  編輯  收藏 所屬分類: 設計模式

          評論:
          # re: 策略模式 (The Strategy Pattern) 譯 《Design Patterns Expalined》的第九章-The Strategy Pattern 2006-11-05 21:01 | xiaosilent
          哎 opera里項目符號前面的圓點顯示不出來,導致布局也亂了... 今后注意一下  回復  更多評論
            
          # re: 策略模式 (The Strategy Pattern) 譯 《Design Patterns Expalined》的第九章-The Strategy Pattern 2006-11-11 21:50 | 冰川
          恩,不錯!
          支持。。。  回復  更多評論
            
          # re: 策略模式 (The Strategy Pattern) 譯 《Design Patterns Expalined》的第九章-The Strategy Pattern 2007-04-05 20:34 | kris
          寫的很好啊
          清晰明了  回復  更多評論
            
          主站蜘蛛池模板: 秦皇岛市| 新泰市| 马尔康县| 金川县| 奉化市| 陇西县| 大荔县| 安平县| 阿拉善右旗| 即墨市| 清远市| 方城县| 阳江市| 泰来县| 农安县| 黄陵县| 常熟市| 巴彦淖尔市| 丰台区| 建始县| 泰兴市| 玛纳斯县| 福泉市| 平乡县| 南阳市| 玛曲县| 双城市| 新津县| 东乌| 通河县| 定陶县| 龙陵县| 兴海县| 石泉县| 清水河县| 长泰县| 巴彦县| 密山市| 奈曼旗| 称多县| 若尔盖县|