冒號(hào)課堂§10.1:多態(tài)類型

          冒號(hào)課堂

          第十課 多態(tài)機(jī)制(1)

          課前導(dǎo)讀

          本課通過(guò)實(shí)例編程和對(duì)抽象類型的解讀,顯示了OOP中多態(tài)機(jī)制和抽象類型的重要性,有助于培養(yǎng)和加深讀者的OOP語(yǔ)感。

          本課共分兩節(jié)——

          1.多態(tài)類型——靜中之動(dòng)

          2.抽象類型——實(shí)中之虛

          10.1 多態(tài)類型——靜中之動(dòng)

          鄭暉

          摘要

          通過(guò)實(shí)例展示多態(tài)類型的三種用法


          動(dòng)靜屈伸,唯變所適

          《王弼•周易略例》

          !預(yù)覽

          • 繼承是多態(tài)的基礎(chǔ),多態(tài)是繼承的目的

          • 多態(tài)是動(dòng)靜結(jié)合的產(chǎn)物,將靜態(tài)類型的安全性和動(dòng)態(tài)類型的靈活性融為一體

          • 前者(參數(shù)多態(tài))是發(fā)散式的,讓相同的實(shí)現(xiàn)代碼應(yīng)用于不同的場(chǎng)合

          • 后者(包含多態(tài))是收斂式的,讓不同的實(shí)現(xiàn)代碼應(yīng)用于相同的場(chǎng)合

          • 模板方法模式突出的是穩(wěn)定堅(jiān)固的骨架,策略模式突出的是靈活多變的手腕

          ?提問(wèn)

          • 多態(tài)與繼承有何關(guān)系?

          • 多態(tài)的重要意義何在?

          • 多態(tài)有哪幾種形式?它們各自有什么特點(diǎn)?

          • 什么是策略模式?它與模板方法模式有何相同點(diǎn)和不同點(diǎn)?多態(tài)在其中起到了什么作用?

          :講解

          當(dāng)冒號(hào)邁著不變的步伐出現(xiàn)在教室時(shí),手上有了一點(diǎn)變化:左手仍拎著筆記本包,右手卻多了一樣?xùn)|西。大家定睛一看,原來(lái)是個(gè)電腦主板,不由得暗自納悶:難道軟件課改成了硬件課?

          冒號(hào)照例直入主題:“上節(jié)課我們對(duì)繼承的利弊作了詳細(xì)的分析,其中最重要的觀點(diǎn)是:繼承的主要用途不是代碼重用,而是代碼被重用。這依賴于兩個(gè)前提,一個(gè)是在語(yǔ)義上遵循里氏代換原則,另一個(gè)是在語(yǔ)法上支持多態(tài)(polymorphism)機(jī)制。因此不妨說(shuō),對(duì)于靜態(tài)類型語(yǔ)言來(lái)說(shuō),繼承是多態(tài)的基礎(chǔ),多態(tài)是繼承的目的。”

          問(wèn)號(hào)忍不住問(wèn):“為什么要強(qiáng)調(diào)靜態(tài)類型呢?”

          “還記得鴨子類型[1]嗎?那就是一種不依賴于繼承的多態(tài)類型,也是動(dòng)態(tài)類型語(yǔ)言一大優(yōu)劣參半的特色。”冒號(hào)提醒道,“靜態(tài)類型語(yǔ)言中的多態(tài)是動(dòng)靜結(jié)合的產(chǎn)物,將靜態(tài)類型的安全性和動(dòng)態(tài)類型的靈活性融為一體。它一般有兩種實(shí)現(xiàn)方式:一種利用GP(泛型編程)中的參數(shù)多態(tài)(parametric polymorphism),一種利用OOP中的包含多態(tài)(inclusion polymorphism)或稱子類型多態(tài)(subtyping polymorphism)。從實(shí)現(xiàn)機(jī)制上看,二者的不同之處在于何時(shí)將一個(gè)變量與其實(shí)際類型所定義的行為掛鉤。前者在編譯期,屬于早綁定 (early binding)或靜態(tài)綁定(static binding)[2];后者在運(yùn)行期,屬于遲綁定 (late binding)或動(dòng)態(tài)綁定(dynamic binding)。從應(yīng)用形式上看,前者是發(fā)散式的,讓相同的實(shí)現(xiàn)代碼應(yīng)用于不同的場(chǎng)合;后者是收斂式的,讓不同的實(shí)現(xiàn)代碼應(yīng)用于相同的場(chǎng)合。從思維方式上看,前者是泛型式編程風(fēng)格,看重的是算法的普適性;后者是對(duì)象式編程風(fēng)格,看重的是接口與實(shí)現(xiàn)的分離度。盡管二者從范式到語(yǔ)法、語(yǔ)義都大相徑庭,但都是為著同一個(gè)目的:在保證必要的類型安全的前提下,突破編譯期間過(guò)于嚴(yán)苛的類型限制。對(duì)于既是靜態(tài)類型語(yǔ)言又是靜態(tài)語(yǔ)言、既支持OOP又支持GP的C++、Java和C#而言,多態(tài)機(jī)制是保證代碼的靈活性、可維護(hù)性和可重用性的終極武器。為了說(shuō)明問(wèn)題,我們看一個(gè)簡(jiǎn)單而實(shí)用的例子:編寫一個(gè)類,讓它能儲(chǔ)存用戶名和密碼,以作今后驗(yàn)證之用。”

          嘆號(hào)一愣:“這題是不是太簡(jiǎn)單了?還有別的要求嗎?”

          冒號(hào)搖搖頭。

          引號(hào)卻認(rèn)為:“要求太少反而不好做。比如是把數(shù)據(jù)放在內(nèi)存、還是文件或者數(shù)據(jù)庫(kù)?密碼以明文還是密文的形式存儲(chǔ)?”

          句號(hào)提出:“無(wú)論是數(shù)據(jù)的存放方式還是密碼的加密方式,都不應(yīng)該硬編碼。”

          “循此思路,我們就來(lái)編寫一個(gè)可重用的抽象類。”冒號(hào)投放了一段Java代碼——

          /** 一個(gè)可以驗(yàn)證用戶名和密碼的類  */
          abstract class Authenticator
          {
              
          /** 保存用戶名和密碼  */
              
          final public void save(String user, String password)
              {
                  
          if (password == null)
                      password 
          = "";
                  store(user, encrypt(password));
              }

              
          /** 驗(yàn)證用戶名和密碼  */
              
          final public boolean authenticate(String user, String password)
              {
                  String storedPassword 
          = retrieve(user);
                  
          if (storedPassword == nullreturn false// 無(wú)此用戶
                      
                  
          if (password == null)
                      password 
          = "";
                  
          return storedPassword.equals(encrypt(password));
              }

              
          /** 保存用戶名和加密過(guò)的密碼  */
              
          protected abstract void store(String user, String encryptedPassword);

              
          /** 從用戶名獲取相應(yīng)的加密過(guò)的密碼  */
              
          protected abstract String retrieve(String user);

              
          /** 給明文單向(one-way)加密,默認(rèn)不加密  */
              
          protected String encrypt(String text) { return text; }
          }

          冒號(hào)解說(shuō)道:“該抽象類有兩個(gè)public接口,一個(gè)用來(lái)保存,一個(gè)用來(lái)驗(yàn)證。它們用final修飾符來(lái)禁止子類覆蓋,因?yàn)檎嬲臄U(kuò)展點(diǎn)是三個(gè)protected方法。其中store和retrieve是抽象的,encrypt有一個(gè)平凡實(shí)現(xiàn)。以此為基礎(chǔ),再根據(jù)實(shí)際需要來(lái)編寫子類,具體實(shí)現(xiàn)這三個(gè)方法。”

          幻燈片轉(zhuǎn)到下一頁(yè)——

          import java.util.Map;
          import java.util.HashMap;

          /** 一個(gè)簡(jiǎn)單的驗(yàn)證類,數(shù)據(jù)放在內(nèi)存,密碼保持明文  */
          class SimpleAuthenticator extends Authenticator
          {
              
          private Map<String, String> usrPwd = new HashMap<String, String>();

              @Override 
          protected void store(String user, String encryptedPassword)
              {
                  usrPwd.put(user, encryptedPassword);  
              }

              @Override 
          protected String retrieve(String user)
              {
                  
          return usrPwd.get(user);  
              }
          }

          “我們利用HashMap來(lái)儲(chǔ)存數(shù)據(jù),密碼保持明文。這大概是最簡(jiǎn)單的一種子類了。”冒號(hào)仿佛在輕輕地把玩著一件小物什,“為安全起見(jiàn),最好還是將密碼加密。于是我們?cè)O(shè)計(jì)了稍微復(fù)雜一點(diǎn)的子類——”

          import java.security.MessageDigest;

          /** 一個(gè)安全的驗(yàn)證類,數(shù)據(jù)放在內(nèi)存,密碼經(jīng)過(guò)SHA-1加密  */
          class Sha1Authenticator extends SimpleAuthenticator
          {
              
          private static final String ALGORITHM = "SHA-1"// SHA-1算法
              private static final String CHARSET = "UTF-8"// 避免依賴平臺(tái)

              @Override 
          protected String encrypt(String plainText)
              {
                  
          try
                  {
                      MessageDigest md 
          = MessageDigest.getInstance(ALGORITHM);
                      md.update(plainText.getBytes(CHARSET));
                      
          byte digest[] = md.digest();
                      
          // BASE64編碼比十六進(jìn)制編碼節(jié)省空間
                     
          //為簡(jiǎn)便起見(jiàn)用到了非標(biāo)準(zhǔn)的API,因此以下代碼有警告 
                      return (new sun.misc.BASE64Encoder()).encode(digest); 
                  }
                  
          catch (java.security.NoSuchAlgorithmException e)
                  {
                      
          throw new InternalError(e.getMessage());  // 不可能發(fā)生
                  }
                  
          catch (java.io.UnsupportedEncodingException e)
                  {
                      
          throw new InternalError(e.getMessage());  // 不可能發(fā)生
                  }
              }
          }

          逗號(hào)質(zhì)疑道:“不是具體類不宜被繼承的嗎?怎么Sha1Authenticator類卻繼承了具體類SimpleAuthenticator?”

          冒號(hào)略表贊許:“很高興你沒(méi)有忘記這個(gè)原則。不過(guò)考慮到Sha1Authenticator類需要覆蓋父類的encrypt方法,這么做也是情有可原的。當(dāng)然最好選擇讓該類直接繼承抽象類Authenticator,但作為示例代碼,我們還是希望它簡(jiǎn)潔一些,不想讓過(guò)多的細(xì)枝末節(jié)掩蓋核心主干。下面是測(cè)試代碼——”

          public class TestAuthenticator 
          {  
          // 為避免額外依賴,沒(méi)有采用JUnit等單元測(cè)試工具
              public static void main(String[] args)
              {
                  test(
          new SimpleAuthenticator());
                  test(
          new Sha1Authenticator());
              }

              
          // 測(cè)試給定的Authenticator 
              private static void test(Authenticator authenticator) // 子類型多態(tài)
              {
                  test(authenticator, 
          "user""password");
                  test(authenticator, 
          "user""newPassword");
                  test(authenticator, 
          "admin""admin");
                  test(authenticator, 
          "guest"null);
                  test(authenticator, 
          null"pass");

                  authenticator.save(
          "scott""tiger");
                  
          assert(!authenticator.authenticate("scott""TIGER")); // 大小寫敏感
                  assert(!authenticator.authenticate("SCOTT""tiger")); // 大小寫敏感
              }

              
          private static void test(Authenticator authenticator, String user, String password)
              {
                  authenticator.save(user, password);
                  
          assert(authenticator.authenticate(user, password));
              }
          }

          引號(hào)覺(jué)得眼熟:“這不是上節(jié)課講的模板方法模式嗎?”

          “正是此公。”冒號(hào)確認(rèn),“該模式的核心思想是:固定整體框架和流程以保證可重用性,留出一些子類定制點(diǎn)以保證可擴(kuò)展性。在測(cè)試代碼的兩個(gè)test方法中,傳入的參數(shù)是Authenticator類,但數(shù)據(jù)存放和密碼加密的方式是在運(yùn)行中才確定的,即先后遵照SimpleAuthenticator類和Sha1Authenticator類的實(shí)現(xiàn)。這就是我們所說(shuō)的子類型多態(tài)的效果——讓不同的實(shí)現(xiàn)代碼應(yīng)用于相同的場(chǎng)合。假設(shè)沒(méi)有多態(tài)機(jī)制,這種效果就只能靠if/else或switch之類的條件語(yǔ)句才能實(shí)現(xiàn),非常地痛苦。”

          冒號(hào)的眉頭皺成了粗體的“川”字。

          “還有更好的方法嗎?”句號(hào)察言觀色,斷定老冒還留有后手。

          果不其然,冒號(hào)的眉毛立刻又舒展開(kāi)來(lái),中氣充沛地應(yīng)道:“有!諸位請(qǐng)看——”

          // 鍵值對(duì)的存取接口
          interface KeyValueKeeper
          {
              
          public void store(String key, String value);
              
          public String retrieve(String key);
          }

          // 加密接口
          interface Encrypter
          {
              
          public String encrypt(String plainText);
          }

          class Authenticator
          {
              
          private KeyValueKeeper keeper;
              
          private Encrypter encrypter;

              
          public Authenticator(KeyValueKeeper keeper, Encrypter encrypter)
              {
                  
          this.keeper = keeper;
                  
          this.encrypter = encrypter;
              }

              
          public void save(String user, String password)
              {
                  
          if (password == null)
                      password 
          = "";
                  keeper.store(user, encrypter.encrypt(password));
              }

              
          public boolean authenticate(String user, String password)
              {
                  String storedPassword 
          = keeper.retrieve(user);
                  
          if (storedPassword == nullreturn false;

                  
          if (password == null)
                      password 
          = "";
                  
          return storedPassword.equals(encrypter.encrypt(password));
              }
          }

          冒號(hào)加以引導(dǎo):“如果仔細(xì)比較兩種設(shè)計(jì),就會(huì)發(fā)現(xiàn)它們很相似。后者只不過(guò)把前者對(duì)子類開(kāi)放的接口合成為自己的兩個(gè)成員。再看接口的實(shí)現(xiàn)類——”

          class MemoryKeeper implements KeyValueKeeper
          {
               
          private Map<String, String> keyValue = new HashMap<String, String>();

              @Override 
          public void store(String key, String value)
              {
                  keyValue.put(key, value);  
              }

              @Override 
          public String retrieve(String key)
              {
                  
          return keyValue.get(key);  
              }
          }

          class PlainEncrypter implements Encrypter
          {
              @Override 
          public String encrypt(String plainText)
              {
                  
          return plainText;
              }
          }

          class Sha1Encrypter implements Encrypter
          {
              
          private static final String ALGORITHM = "SHA-1";
              
          private static final String CHARSET = "UTF-8"

              @Override 
          public String encrypt(String plainText)
              {
                  
          try
                  {
                      MessageDigest md 
          = MessageDigest.getInstance(ALGORITHM);
                      md.update(plainText.getBytes(CHARSET));
                      
          byte digest[] = md.digest();
                      
          return (new sun.misc.BASE64Encoder()).encode(digest); 
                  }
                  
          catch (java.security.NoSuchAlgorithmException e)
                  {
                      
          throw new InternalError(e.getMessage());
                  }
                  
          catch (java.io.UnsupportedEncodingException e)
                  {
                      
          throw new InternalError(e.getMessage());
                  }
              }
          }

          逗號(hào)比較后得出結(jié)論:“MemoryKeeper與SimpleAuthenticator、Sha1Encrypter與Sha1Authenticator除了超類型和方法訪問(wèn)修飾符外,其他毫無(wú)二致。”

          屏幕滾動(dòng)出另一段代碼——

          public class TestAuthenticator
          {
              
          public static void main(String[] args)
              {
                  test(
          new Authenticator(new MemoryKeeper(), new PlainEncrypter()));
                  test(
          new Authenticator(new MemoryKeeper(), new Sha1Encrypter()));
              }    

              
          private static void test(Authenticator authenticator) // 隱含子類型多態(tài)
              { /* 同上,略 */}
          }

          “測(cè)試代碼區(qū)別也不大,只是Authenticator的多態(tài)性更加隱蔽。”冒號(hào)如是說(shuō)。

          嘆號(hào)挑剔說(shuō):“后一種創(chuàng)建實(shí)例稍顯麻煩一些。”

          “但它是以小弊換大利。”冒號(hào)朗聲而道,“首先,后者用的是合成與接口繼承,比前者的實(shí)現(xiàn)繼承更值得推薦,理由在上堂課業(yè)已闡明。其次,假設(shè)共有M種數(shù)據(jù)存取方式,包括內(nèi)存、文件、數(shù)據(jù)庫(kù)等等;共有N種加密方式,包括明文、SHA-1、SHA-256、MD5等等。按第一種設(shè)計(jì),需要(M×N)個(gè)實(shí)現(xiàn)類;按第二種設(shè)計(jì),只要(M+N)個(gè)實(shí)現(xiàn)類。這還只是兩種變化因素,假如需要考慮更多的因素,二者差距將更大。比如增加編碼方式:加密后的數(shù)據(jù)可以選擇費(fèi)空間省時(shí)間的十六進(jìn)制編碼、費(fèi)時(shí)間省空間的BASE64編碼、省時(shí)間省空間卻包含非打印字符的原始形式等;比如增加安全強(qiáng)度:引入salt、nonce或IV等[3];比如增加密碼狀態(tài):已生效密碼、未生效密碼、已過(guò)期密碼等等。對(duì)比下面的UML類圖,孰優(yōu)孰劣更加一目了然。”

          眾人眼前出現(xiàn)了兩幅圖——

          圖10-1. Authenticator的UML類圖(模板方法模式)



          圖10-2. Authenticator的UML類圖(策略模式)



          冒號(hào)指著屏幕問(wèn):“圖二不僅比圖一少了三個(gè)實(shí)現(xiàn)類,而且可重用性也更高。大家說(shuō)是為什么?”

          引號(hào)應(yīng)答:“圖一中的九個(gè)Authenticator的子類只能作為驗(yàn)證類來(lái)重用,而圖二中六個(gè)實(shí)現(xiàn)類不僅可以合作完成驗(yàn)證類的功能,還能分別單獨(dú)提供鍵值存儲(chǔ)和加密字符串的功能。”

          冒號(hào)作出肯定:“這就是職責(zé)分離的好處。存儲(chǔ)與加密本是兩樣不相干的工作,必要時(shí)可以合作,但平時(shí)最好分開(kāi)管理,符合‘低耦合、高內(nèi)聚’的原則。”

          問(wèn)號(hào)注意到圖中的注釋,遂問(wèn):“第二種采用的是策略模式?”

          冒號(hào)頷首:“簡(jiǎn)單地說(shuō),策略模式(strategy pattern或policy pattern)的基本思想是:把一個(gè)模塊所依賴的某類算法委交其他模塊實(shí)現(xiàn)。比如Java中的Comparable和Comparator、C#中的IComparer就是比較算法的接口,當(dāng)一個(gè)類的某個(gè)方法接收了此種類型的參數(shù),實(shí)質(zhì)上就采用了策略模式。”

          逗號(hào)不以為奇:“這豈非很平常?”

          “你認(rèn)為設(shè)計(jì)模式真的高不可攀嗎?”冒號(hào)反問(wèn)道,“包括模板方法模式,你們很可能也在編程實(shí)踐中采用過(guò),只不過(guò)相交不相識(shí)罷了。”

          句號(hào)看出:“模板方法模式與策略模式非常神似,都是把一個(gè)類的可變部分移交給其他類處理。”

          “照你這么說(shuō),絕大多數(shù)設(shè)計(jì)模式都是神似的,這也是為什么我們不專門談設(shè)計(jì)模式的緣故。GoF設(shè)計(jì)模式是OOP大樹(shù)上結(jié)出的碩果,在你心中培養(yǎng)的OOP成熟之前,匆忙締結(jié)的果實(shí)多半是青澀弱小的。”冒號(hào)忠告,“我們也不會(huì)對(duì)設(shè)計(jì)模式避而不談,但凡提及都是水到渠成的產(chǎn)物。再說(shuō)回這兩種設(shè)計(jì)模式,雖然有相通的思想,也能解決相同的問(wèn)題,在穩(wěn)定性與靈活性之間都取得了某種平衡,但還是各有側(cè)重的。模板方法模式突出的是穩(wěn)定堅(jiān)固的骨架,策略模式突出的是靈活多變的手腕。不妨拿國(guó)家政策作比:一個(gè)強(qiáng)調(diào)對(duì)內(nèi)要穩(wěn),老一輩制訂了大政方針,下一代必須在堅(jiān)持原則的前提下進(jìn)行完善;一個(gè)強(qiáng)調(diào)對(duì)外要活,不能或不便自行開(kāi)發(fā)的技術(shù)不妨從國(guó)外引進(jìn)。”

          嘆號(hào)一樂(lè):“哈!設(shè)計(jì)模式上升到了政策模式。”

          冒號(hào)抽絲剝繭:“正如模板方法模式可看作控制反轉(zhuǎn)的特例,策略模式與依賴注射(Dependency Injection)也異曲同工。第二個(gè)Authenticator所依賴的兩個(gè)功能KeyValueKeeper和Encrypter,就是是通過(guò)構(gòu)造方法‘注射’進(jìn)來(lái)的[4]。當(dāng)然策略只是一種特殊的依賴,是自內(nèi)而外的——將算法抽出來(lái)外包;依賴注射的機(jī)制更復(fù)雜、涵蓋面更廣,是自外而內(nèi)的——從外部嵌入定制功能。后者被廣泛地用于框架應(yīng)用之中,尤以Spring Framework和Google Guice為代表。”

          引號(hào)聽(tīng)得起勁:“這下熱鬧了,設(shè)計(jì)模式、框架與OOP范式全攪和到一塊了。”

          “還有GP范式呢。”冒號(hào)順接話題,“讓我們?cè)儆肅++的模板來(lái)實(shí)現(xiàn)一下Authenticator類吧。沒(méi)有繼續(xù)采用Java,是因?yàn)樗姆盒腿噪x不開(kāi)子類型多態(tài)。”

          說(shuō)著,他換上了C++代碼——

          #include <string>
          #include 
          <map>

          using namespace std;

          template 
          <typename KeyValueKeeper, typename Encrypter>
          class Authenticator
          {
              
          private:
                  KeyValueKeeper keeper;
                  Encrypter encrypter;
              
          public:
                  
          void save(const string& user, const string& password)
                  {
                      keeper.store(user, encrypter.encrypt(password));
                  }

                  
          bool authenticate(const string& user, const string& password) const
                  {
                      
          string storedPassword;
                      
          if (!keeper.retrieve(user, storedPassword)) return false;

                      
          return storedPassword == encrypter.encrypt(password);
                  }
          };

          class MemoryKeeper
          {
              
          private:
                  map
          <stringstring> keyValue;
              
          public:
                  
          void store(const string& key, const string& value)
                  {
                      keyValue[key] 
          = value;  
                  }

                  
          bool retrieve(const string& key, string& value) const
                  {
                      map
          <stringstring>::const_iterator itr = keyValue.find(key);
                      
          if (itr == keyValue.end()) return false;

                      value 
          = itr->second;  
                      
          return true;
                  }
          };

          class PlainEncrypter
          {
              
          public:
                  
          string encrypt(const string& plainText) const { return plainText; }
          };

          class Sha1Encrypter
          {
              
          public:
                  
          string encrypt(const string& plainText) const { /* 省略代碼  */ }
          };

          namespace
          {
              template 
          <typename K, typename E>
              
          void test(Authenticator<K, E> authenticator) // 參數(shù)多態(tài)
              { /* 省略代碼  */ }
          }

          int main()

              test(Authenticator
          <MemoryKeeper, PlainEncrypter>());
              test(Authenticator
          <MemoryKeeper, Sha1Encrypter>());
              
          return 0;
          }

          “以上代碼與Java版的策略模式代碼很相似,主要的區(qū)別是把KeyValueKeeper和Encrypter兩個(gè)接口換成了模板參數(shù)。由于模板是在編譯期間實(shí)例化的,因此沒(méi)有動(dòng)態(tài)綁定的運(yùn)行開(kāi)銷,但缺點(diǎn)是不能動(dòng)態(tài)改變策略[5]。”冒號(hào)分析道,“至此,我們通過(guò)一個(gè)驗(yàn)證類的三種解法,分別展示了三種形式的多態(tài):基于類繼承的多態(tài)、基于接口繼承的多態(tài)和基于模板的多態(tài)。它們殊途同歸,都能讓代碼更簡(jiǎn)潔、更靈活、可重用性更高、更易維護(hù)和擴(kuò)展。”

          問(wèn)號(hào)想到一個(gè)問(wèn)題:“C語(yǔ)言既沒(méi)有子類型多態(tài)也沒(méi)有參數(shù)多態(tài),又如何保證高質(zhì)量的C程序呢?”

          冒號(hào)眉梢輕挑:“C語(yǔ)言有指針啊,C++、Java和C#的多態(tài)在底層就是用指針實(shí)現(xiàn)的。C中的函數(shù)指針比Java中的接口更加靈活高效,當(dāng)然對(duì)程序員的要求也更高。”

          引號(hào)驀地記起:“重載不也是一種多態(tài)嗎?”

          “剛才所說(shuō)的多態(tài)都屬于通用多態(tài)(universal polymorphism)。此外,還有一類特別多態(tài)(ad-hoc polymorphism),常見(jiàn)有兩種形式。一種是強(qiáng)制多態(tài)(coercion polymorphism),即一種類型的變量在作為參數(shù)傳遞時(shí)隱式轉(zhuǎn)換成另一種類型,比如一個(gè)整型變量可以匹配浮點(diǎn)型變量的函數(shù)參數(shù)。另一種就是重載多態(tài)(overloading polymorphism),它允許不同的函數(shù)或方法擁有相同的名字。特別多態(tài)淺顯易懂,其重要性與通用多態(tài)也不可同日而語(yǔ),故不在我們關(guān)注之列。只是要注意一點(diǎn),正如子類型應(yīng)遵守超類型的規(guī)范,同名的函數(shù)或方法也應(yīng)遵守相同的規(guī)范。如果為貪圖取名方便而濫用重載,早晚因小失大。”冒號(hào)告誡道。

          逗號(hào)突發(fā)奇論:“一個(gè)多態(tài)類型的對(duì)象可以在不同的類型之間變來(lái)變?nèi)ィ遣皇墙?#8216;變態(tài)類型’更生動(dòng)些?”

          “我看你就屬于典型的變態(tài)類型。”句號(hào)乘機(jī)拿他開(kāi)涮。

          全班哈哈大笑。

          ,插語(yǔ)

          1. 參見(jiàn)§5.2。

          2. 雖然C#具體的泛型類型是在運(yùn)行期間實(shí)例化的,但每類泛型對(duì)應(yīng)相同的實(shí)現(xiàn)代碼,故變量的行為仍是在編譯期間決定的。

          3. salt、nonce和IV都是密碼學(xué)中的術(shù)語(yǔ),是在加密過(guò)程中混入的一次性數(shù)據(jù),以增加預(yù)計(jì)算攻擊(如字典攻擊)的難度。

          4. 這被稱為constructor injection,另外兩種常用的注射方法是setter injection和interface injection。

          5. 對(duì)用Java實(shí)現(xiàn)的Authenticator類(策略模式版)稍作修改,就能讓客戶動(dòng)態(tài)改變策略。

          。總結(jié)

          • 在靜態(tài)類型語(yǔ)言中,繼承是多態(tài)的基礎(chǔ),多態(tài)是繼承的目的。

          • 多態(tài)結(jié)合了靜態(tài)類型的安全性和動(dòng)態(tài)類型的靈活性。

          • 多態(tài)可分為通用多態(tài)和特別多態(tài)兩種。

          • 通用多態(tài)主要包括參數(shù)多態(tài)和包含多態(tài)(或子類型多態(tài))。它們都是為了克服靜態(tài)類型過(guò)于嚴(yán)格的語(yǔ)法限制。

          • 特別多態(tài)主要包括強(qiáng)制多態(tài)和重載多態(tài)。

          • 參數(shù)多態(tài)是靜態(tài)綁定,重在算法的普適性,好讓相同的實(shí)現(xiàn)代碼應(yīng)用于不同的場(chǎng)合。

          • 包含多態(tài)是動(dòng)態(tài)綁定,重在接口與實(shí)現(xiàn)的分離度,好讓不同的實(shí)現(xiàn)代碼應(yīng)用于相同的場(chǎng)合。

          • 策略模式授予客戶自由選擇算法(策略)的權(quán)力。

          • 模板方法模式重在穩(wěn)定堅(jiān)固的骨架,策略模式重在靈活多變的手腕。

          • 合理地運(yùn)用基于類繼承的多態(tài)、基于接口繼承的多態(tài)和基于模板的多態(tài),能增強(qiáng)程序的簡(jiǎn)潔性、靈活性、可維護(hù)性、可重用性和可擴(kuò)展性。

          “”參考

          1. Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides.Design Patterns: Elements of Reusable Object-Oriented Software.Boston, MA:Addison-Wesley,1994.315-323

          2. Luca Cardelli,Peter Wegner.On understanding types, data abstraction, and polymorphism.Computing Surveys,1985,17(4):471-522

          友情提示:如果您對(duì)本文感興趣,歡迎到http://blog.zhenghui.org上發(fā)表評(píng)論

          posted on 2009-10-20 18:18 鄭暉 閱讀(3182) 評(píng)論(0)  編輯  收藏 所屬分類: 冒號(hào)課堂

          導(dǎo)航

          統(tǒng)計(jì)

          公告

          博客搬家:http://blog.zhenghui.org
          《冒號(hào)課堂》一書(shū)于2009年10月上市,詳情請(qǐng)見(jiàn)
          冒號(hào)課堂

          留言簿(17)

          隨筆分類(61)

          隨筆檔案(61)

          文章分類(1)

          文章檔案(1)

          最新隨筆

          積分與排名

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 张家港市| 比如县| 周至县| 夏邑县| 绥宁县| 汪清县| 志丹县| 新昌县| 西充县| 教育| 太谷县| 五大连池市| 贵溪市| 鸡西市| 江陵县| 合作市| 顺义区| 凤翔县| 新源县| 冕宁县| 聊城市| 奉化市| 高清| 东明县| 桦甸市| 靖江市| 樟树市| 会泽县| 诸城市| 彰化县| 龙南县| 彭水| 云阳县| 额济纳旗| 普兰县| 静乐县| 炉霍县| 读书| 乌审旗| 灌南县| 资中县|