泛型的約束不止一面

来源:https://www.cnblogs.com/green-jcx/archive/2022/09/16/16700457.html
-Advertisement-
Play Games

1.介紹 泛型中的約束,其實就是針對類型參數的約束,限制類型參數的選擇只能在某個特定範圍內。其中的體現包括:限制類型參數必須是一個結構、限制類型參數必須是某個具體類型、限制類型參數必須派生自某個基類等等。在預設情況下,定義的泛型沒有任何約束,這意味著在調用泛型時,可以使用任何數據類型作為類型參數。如 ...


1.介紹

泛型中的約束,其實就是針對類型參數的約束,限制類型參數的選擇只能在某個特定範圍內。其中的體現包括:限制類型參數必須是一個結構、限制類型參數必須是某個具體類型、限制類型參數必須派生自某個基類等等。在預設情況下,定義的泛型沒有任何約束,這意味著在調用泛型時,可以使用任何數據類型作為類型參數。如果定義了約束,則在應用端調用泛型時,不傳入符合約束條件的類型參數,編譯器將提示錯誤。通過這種約束實現了編譯前類型檢查,確保了泛型在運行時對類型參數使用的安全性。

以上說的這種限制性的作用,只能體現約束錶面的用意,這種用意是比較淺顯易懂。但實際上泛型的約束還有另一層的用意:“定義約束可以告知編譯器,類型參數具備了哪些能力”。我們在為某個泛型類或泛型方法編碼時,面向的類型參數T,其實類似是一個模糊神秘的事物,因為你根部不會知道它有什麼能力(屬性、方法等成員),如果你想在編寫泛型時使用類型參數T的某些能力,那麼你就可以通過定義約束來實現。例如,你想要類型參數T調用“比較大小”的方法從而幫助你實現排序演算法,你就可以定義一個泛型的約束:“要求類型參數必須實現IComparer介面。這樣一來,你的類型參數T,就能夠在你編寫泛型類的代碼中“.”出Compare(比較的方法)。

基於上面對類型參數定義約束的用意分析,我針對約束主要的作用總結出以下兩點:

  1. 對外部使用形成了限制條件,從而確保泛型的類型安全;
  2. 對內部使用提供了更多能力,從而豐富功能的實現;

以上通過文字描述的形式介紹了泛型中類型參數的約束,為了更加形象的體會其中的含義和作用,下麵我將通過代碼示例的形式介紹類型參數定義約束的使用方式。


2.示例

假設我們在一個開發游戲的背景下,游戲比較簡單,其中目前有兩個職業:劍士和狙擊手,並且後期隨著游戲的普及會增加更多的職業。由於是戰鬥類型的游戲,所有每個職業都會使用特定的武器進行攻擊,從而實現戰鬥的體驗。對於該游戲職業設計相關的類圖如下:

 

由於這隻是一個為了講解泛型約束的一個示例,所以並沒有採用複雜的設計。由於劍士和狙擊手兩個職業都有相同的攻擊行為,故而將攻擊定義為了一個介面,具體的攻擊內容將交由這兩個職業類去實現。根據以上的類圖的設計,相應的代碼如下:

 1     //攻擊介面
 2     interface IAttack
 3     {
 4         void MeleeAttacks();  //近戰攻擊
 5     }
 6 
 7     //劍士
 8     class Swordman: IAttack
 9     {
10         public Swordman() => Sword = "倚天劍";
11 
12         public string Sword { get; set; }
13         public void MeleeAttacks()
14         {
15             Console.WriteLine("使用{0}進行刺擊。", Sword);
16         }
17     }
18 
19     
20     //狙擊手
21     class Sniper : IAttack
22     {
23         public Sniper() => Gun = "98k狙擊步槍";
24         public string Gun { get; set; } //
25  
26         public void MeleeAttacks()
27         {
28             Console.WriteLine("使用{0}進行射擊。", Gun);
29         }
30     }

3.能力

假設我們的游戲示例是一款戰鬥類型的游戲,那麼其中所有的職業都需要進行戰鬥。對於這個共同的行為,正好可以借鑒泛型的使用思想:即不同類型存在相同處理邏輯,那麼可以使用泛型作為一個代碼模板,從而實現不同類型的通用化處理。我們計劃將戰鬥的行為定義成一個泛型類,由這個泛型類統一實現各個職業的戰鬥。然而在編寫戰鬥泛型類的時候,由於戰鬥必須要使用職業的攻擊方法,但是我們在內部調用類型參數T並不能獲取到相應的方法,編譯器視乎將類型參數T看成了一個object類型。

怎麼辦?究竟如何能夠在戰鬥泛型類中調用游戲角色的攻擊方法呢?這個時候就輪到本文的主題“泛型的約束”閃亮登場了,接下來我們將針對戰鬥泛型類定義一個約束,在泛型類中使用類型參數T調用出攻擊的方法:

 1     /// <summary>
 2     /// 各個職業的戰鬥
 3     /// </summary>
 4     class Combat<T> where T :IAttack
 5     {
 6         public Combat(T combatant)
 7         {
 8             _combatant = combatant;
 9         }
10         private T _combatant;//參戰者
11 
12         public void Action()
13         {
14             Console.WriteLine("戰鬥開始");
15             _combatant.MeleeAttacks();
16             Console.WriteLine("戰鬥結束");
17         }
18 
19     }

果不其然,成功的在戰鬥泛型類中調用了角色的攻擊方法,這是因為設置了約束,類型參數T就可以根據約束的類型獲取相應的能力。這一點也正好可以印證了本文開頭總結泛型約束的作用之一:對內部使用提供了更多能力,從而豐富功能的實現”。示例的代碼已經基本編寫完成,接下來我們就可以在應用端,使用戰鬥泛型類針對不同的角色實施戰鬥行為了。

 


4.安全

假設你的小伙伴正在另一頭在編寫游戲中關於NPC部分的代碼,他得知你編寫了可以實現各種職業進行戰鬥的泛型類,於是乎他悄悄的使用一個NPC的對象來使用你的戰鬥泛型類。但是NPC在實際的需求中並沒有實現攻擊介面。NPC類的代碼結構如下:

我們在假定,泛型的約束不能夠對外部傳入的類型參數(NPC類)起到限製作用。那麼這個NPC的“戰鬥”情況可想而知,NPC是沒有主動攻擊的方法的,他盲目的使用戰鬥泛型類,只會無情的面臨“死亡”。還好,我們定義的類型參數約束對此進行了把關,我們約束的規則是:要求類型參數必須實現攻擊介面。而NPC並沒有實現攻擊介面,所以對於NPC使用戰鬥泛型類時編譯器會提示錯誤。

 

通過NPC濫用泛型類的這個示例,就可以從分的體現出本文開頭總結泛型約束的作用之一:“對外部使用形成了限制條件,從而確保泛型的類型安全”。


5.結語

理解泛型的約束,可能會覺得它是很語義化、片面化的東西。殊不知,其實泛型約束在實際中最有作用的是,為類型參數提供能力,讓我們在編碼的過程中更有針對性。所以學習不能只求錶面,必須通過反覆思考,才能讓獲取的知識更加立體。

對於泛型約束的使用方式,除了本文示例中要求實現一個特定介面的方式,另外還有很多使用方式。我們不可能將每一個使用細節瞭然於心,但是必須搞清楚事物的本質,以致於知道為什麼有它的存在、在什麼樣的情況下使用它。當不同的應用場景發生時,我們在結合當下應用場景的實際情況,通過查閱文檔來制定具體的方針。

知識改變命運
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、什麼是動態SQL之if語句 if很簡單了,就是滿足條件就執行,不滿足條件不執行。 那麼動態SQL中的if語句是怎麼樣的呢? 首先我們來看一張表blog: 如果我們執行下麵的SQL語句: select * from blog 肯定會將所有的數據都查出來。那麼我們可以在後面加上where條件進行篩選 ...
  • 一、什麼是動態SQL 官方文檔給出了這樣的說明: 動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要註意去掉列表最後一個列名的逗號。利用動態 SQL,可以徹底擺脫 ...
  • 關於面試題,“什麼是鏈路追蹤”? 我們應該怎麼回答呢? 大家好,我是Mic,一個工作了14年的Java程式員 這個問題,面試官想考察什麼呢? 問題解析 鏈路追蹤是分散式架構下的一種監控方式。 對於一些規模較大的分散式系統,一個用戶的請求,可能需要涉及到多個子系統的流轉。 而且隨著業務的不斷增長,服務 ...
  • 一、前言 我們在日常學習中,對一個java代碼有問題,不知道jvm內部怎麼進行解析的時候;有個偉大壯舉就是反編譯,這樣就可以看到jvm內部怎麼進行對這個java文件解析的!我們可以使用JDK自帶的javap命令來進行反編譯,反編譯出來的如果看不太明白,可以使用Jad工具來配合使用。還有就是把jar包 ...
  • 前期準備: 1.phpqrcode類文件下載,下載地址:https://sourceforge.net/projects/phpqrcode/2.PHP環境必須開啟支持GD2擴展庫支持(一般情況下都是開啟狀態) 以下為示例代碼,引入類文件後,調整相應的參數即可調用 1 /** 2 *第1個參數$te ...
  • 在MyBatis的映射中有column這麼一個屬性,我一直以為它映射的是資料庫表中的列名,但經過學習發現他似乎映射的是SQL語句中的列名,或者說是查詢結果所得到的表的列名。 下麵我們進行一個實驗。 首先我們有一張user表: 我還有一個實體類User,有著id、username、password三個 ...
  • 代碼生成器(CodeBuilder) 經過這幾個版本的完善,目前功能也趨於穩定,詳細的線上文檔也得到維護,不失為一款強大的代碼生成工具。 官網:http://www.fireasy.cn/codebuilder 多數據源 資料庫表、欄位和關係等元數據統稱為架構 Schema,它們是生成代碼的源材料。 ...
  • ​ 老婆公司的需求,公司給了一個星期的時間。讓她每天去複製粘貼。然後就有瞭如下代碼: 先說實現吧 一、抓包 Charles https://www.charlesproxy.com/ 打開Charles , 配置好SSL Proxy 。 ​ 編輯 證書OK後,配置允許抓取系統資源: ​ 編輯 啟動抓 ...
一周排行
    -Advertisement-
    Play Games
  • 一:背景 1.講故事 在分析的眾多dump中,經常會遇到各種奇葩的問題,僅通過dump這種快照形式還是有很多問題搞不定,而通過 perfview 這種粒度又太粗,很難找到問題之所在,真的很頭疼,比如本篇的 短命線程 問題,參考圖如下: 我們在 t2 時刻抓取的dump對查看 短命線程 毫無幫助,我根 ...
  • 在日常後端Api開發中,我們跟前端的溝通中,通常需要協商好入參的數據類型,和參數是通過什麼方式存在於請求中的,是表單(form)、請求體(body)、地址欄參數(query)、還是說通過請求頭(header)。 當協商好後,我們的介面又需要怎麼去接收這些數據呢?很多小伙伴可能上手就是直接寫一個實體, ...
  • 許多情況下我們需要用到攝像頭獲取圖像,進而處理圖像,這篇博文介紹利用pyqt5、OpenCV實現用電腦上連接的攝像頭拍照並保存照片。為了使用和後續開發方便,這裡利用pyqt5設計了個相機界面,後面將介紹如何實現,要點包括界面設計、邏輯實現及完整代碼。 ...
  • 思路分析 註冊頁面需要對用戶提交的數據進行校驗,並且需要對用戶輸入錯誤的地方進行提示! 所有我們需要使用forms組件搭建註冊頁面! 平時我們書寫form是組件的時候是在views.py裡面書寫的, 但是為了接耦合,我們需要將forms組件都單獨寫在一個地方,需要用的時候導入就行! 例如,在項目文件 ...
  • 思路分析 登錄頁面,我們還是採用ajax的方式提交用戶數據 唯一需要學習的是如何製作圖片驗證碼! 具體的登錄頁面效果圖如下: 如何製作圖片驗證碼 推導步驟1:在img標簽的src屬性里放上驗證碼的請求路徑 補充1.img的src屬性: 1.圖片路徑 2.url 3.圖片的二進位數據 補充2:字體樣式 ...
  • 哈嘍,兄弟們! 最近有許多小伙伴都在吐槽打工好難。 每天都是執行許多重覆的任務 例如閱讀新聞、發郵件、查看天氣、打開書簽、清理文件夾等等, 使用自動化腳本,就無需手動一次又一次地完成這些任務, 非常方便啊有木有?! 而在某種程度上,Python 就是自動化的代名詞。 今天就來和大家一起學習一下, 用 ...
  • 作者:IT王小二 博客:https://itwxe.com 前面小二介紹過使用Typora+PicGo+LskyPro打造舒適寫作環境,那時候需要使用水印功能,但是小二在升級LskyPro2.x版本發現有很多不如人意的東西,遂棄用LskyPro使用MinIO結合代碼實現自己需要的圖床功能,也適合以後 ...
  • OpenAI Gym是一款用於研發和比較強化學習演算法的工具包,本文主要介紹Gym模擬環境的功能和工具包的使用方法,並詳細介紹其中的經典控制問題中的倒立擺(CartPole-v0/1)問題。最後針對倒立擺問題如何建立控制模型並採用爬山演算法優化進行了介紹,並給出了相應的完整python代碼示例和解釋。要... ...
  • python爬蟲瀏覽器偽裝 #導入urllib.request模塊 import urllib.request #設置請求頭 headers=("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, l ...
  • 前端代碼搭建 主要利用的是bootstrap3中js插件里的模態框版塊 <li><a href="" data-toggle="modal" data-target=".bs-example-modal-lg">修改密碼</a></li> <div class="modal fade bs-exam ...