1. 程式人生 > >什麼情況下應用純虛類

什麼情況下應用純虛類

前幾天跟同事brainstorm,討論一個關於純虛類的使用問題,挺有意思。回來心中久久不能平靜,寫出來一吐為快。

不論在C++中還是C#中,純虛類都是不能例項化的,這是因為純虛類其實是一個對業務型別的一種高度抽象,本質上是不存在這種東西的,所以也就不能例項化它。對於C++中只要類中含有一個純虛擬函式就是純虛類,而C#中是abstract修飾的類就是純虛類,即使類中沒有虛方法也可以是純虛類,在這裡我覺得C#的純虛類沒有C++的嚴謹,如果純虛類中沒有純虛方法的話,那有何意義。

明白了純虛類的原理,那純虛類應該肯定需要被別的類繼承的,因為如果不繼承的話,它自己本身也不能例項化,就沒有存在的意義了,所以它肯定是需要被繼承的。那它需不需要含有一個或者多個純虛方法呢?答案是肯定的,因為既然純虛類是需要被繼承的,那沒有純虛方法又有何意義。沒有純虛方法它就應該是可以例項化的,那就沒有必要將其設定為純虛類的,這也是我吐槽C#的abstract的一個原因。

有了純虛類一定要有純虛方法的這個基礎,那就可以想象純虛類的業務場景應該是下面這個樣子的

    abstract class Goods
    {
        public int GetPrice()
        {
            int price = 0;
            //do some standard things
            price = this.CaculatePrice();
            //do some other steps
            return price;
        }


        protected abstract int CaculatePrice();
    }

我們有一類商品,它們都有一些統一的步驟需要做,先預處理,然後計算價格,這個計算價格是每種商品不一樣的,最後還要統一再處理下,對一個售貨員來說它其實並不關心這是什麼商品有什麼特性,她只關心這個商品的價格,如果購物車中有多個商品,計算出所有商品的總價格。所以一般會構建一個商品,它沒有任何意義,只是將所有商品標準化東西統一起來,將不同的東西抽象為純虛方法,這樣不同的商品就可以只關心它是如何計算價格的,其它不同的部分交給虛基類來處理。這個是比較經典的業務場景,這裡虛基類中可能會有多個public的方法,這些方法可以呼叫純虛方法也是可以的。總之到最後Goods的使用者是直接呼叫Goods索引,而不是操作具體的商品類。這種情況一般純虛類中的純虛方法一般都是private或者protected(在C++中純虛擬函式可以是private、protected和public,但是在C#中純虛擬函式只能是protected和public),因為Goods的使用者一般不會直接操作商品內部的CaculatePrice的,這個應該是需要對使用者隱藏的。設成protected是有些時候需要對Goods再進行抽象,這類商品有它特定的使用者,這個時候它又需要呼叫Goods的方法,這種情況就開發成為protected,比如有一類商品,它們需要提供另外一種方法,計算另外一種型別的價格,但是這種計算方法又是依賴商品的CaculatePrice的結果,繼承類需要再寫一種方法呼叫基類的CaculatePrice的方法,如果是private,繼承類就沒有辦法訪問,此時就需要將其修改成為protected。

看樣子純虛類中的純虛方法是不能為public的,一般是private或者protected的,那是否可以是public呢?答案是可以的。針對上面的例子,假設每種商品沒有統一的步驟,計算方法各不一樣,沒有統一的步驟,那這個時候就需要將GetPrice改成純虛擬函式,但是這裡需要考慮的另外一個問題就是當一個純虛類中有一個public的純虛方法,要考慮這個純虛方法是否是一種interface(這個是C#才有C++中沒有的,這個是一個亮點,將is a和支援的方法隔離開來,因為is a的關係太強了),在這個例子我覺得還是純虛類比較合適,interface不大合適。interface更多的像是一種技能,現實生活中不好想象這樣的例子,在基礎類庫中的IEnumerator這個介面,這個是linq實現的基礎,它就是一個典型的介面而不應該實現為純虛類,list,array和dict它們沒有公共關係,不好抽象出來一個基類來實現is a的關係,但是都需要支援遍歷資料結構中所有元素的方法,所以都實現IEnumerator的介面,所以interface更像一種技能。

在實際開發過程中需求往往是經常漸進,一開始我們並不知道要設計一個純虛類,更多的情況是一開始只有一種商品,設計了Goods,然後有另外一種商品,它們有很大的共性,於是將共性提出來,讓第二個商品繼承第一個商品,其實此時就應該考慮純虛類的,如果未來看得出來有更多同類的商品出現的話,應該重構一個純虛基類出來,但是暫時沒有讓商品2繼承商品1沒問題,因為我覺得增加一個純虛基類出來,而且未來還不知道能不能用上,沒有必要,因為增加純虛基類意味著強型別,將來很難再refactor,第二類太多對程式碼的維護成本也是很高的,基於done is enough的原則,上面直接將商品2繼承商品1沒有問題,即使看上去兩個不應該有繼承關係,所以此時為了更好的解決這個問題,可以考慮將商品1改個名字,讓它們的繼承關係合法化。當商品越來越多的時候大多時候是不會重構出純虛基類的,因為隨著程式碼的不斷增加,維護的成本會指數級增加,而且是修改一個基類,impact會比較大,一般也不會refactor一個純虛基類。除非整個程式碼需要重構,可以考慮這麼做,如果沒有的話,這個程式碼結構不應該進行重構。所以純虛基類一般都是程式碼整體重構的產物,當然如果在需求提出的時候就已經明確那是更好的。

在實際程式碼中我們會經常遇到一個虛方法是一個空函式的,那什麼情況下會將一個虛方法設定為一個空函式呢?

空函式存在的價值是它是有意義的才可以,比如針對上面Goods類,假設它不是純虛類,只是一個基類,如果有種商品就是沒有價格,比如贈品,CalculatePrice直接返回0,沒有任何計算,它的子類商品都有自己的計算價格方法,可以重寫這個方法。所以說針對空函式的話,如果有意義才可以,一般不需要將其設成空函式,沒有意義就可以直接將其改成純虛方法。

總之:

純虛類中是一定要有純虛擬函式的,如果是私有純虛方法那這個方法肯定是被純虛類中其它public呼叫的,如果純虛方法是public的時候是語義上更是is a的關係決定的,但是這個時候需要看看這個方法是否也算是has的方法,如果更多的是has的方法,需要考慮將這個方法改成interface,這個只有c#中才有這種功能,c++中是沒有的,只是一個純虛介面類而已。

相關推薦

什麼情況應用

前幾天跟同事brainstorm,討論一個關於純虛類的使用問題,挺有意思。回來心中久久不能平靜,寫出來一吐為快。 不論在C++中還是C#中,純虛類都是不能例項化的,這是因為純虛類其實是一個對業務型別的一種高度抽象,本質上是不存在這種東西的,所以也就不能例項化它。對於C++中

C++

純虛類有以下特徵: 含有一個純虛擬函式的類,叫做純虛類。純虛類不可以定義物件。 我個人覺得這個說法應該就是把純虛類的主要特點說明了: 1、只要有一個純虛擬函式。就稱為純虛類。 所以如果子類沒有實現純虛擬函式,相當子類也有純虛擬函式,所以子類也是純虛類。 2、其他類的定義與使

【重構C++知識體系】

類 /*在C++中,我們通過類來定義自己的資料結構*/ 類的作用(思想) 為了使C++遵守OOP基本法的資料抽象和封裝. 資料抽象:就是將”介面”和”實現”實現分離的程式設計技術. 封裝:隱藏物件的屬性和實現細節,僅對外公開介面. 拓展:設計模

函數和抽象

程序 uri 文件 hit 每一個 實現 use ble png -------------------siwuxie095 純虛函數 在 C++ 中,用 純 字來修飾虛函數,即 純虛函數 純虛函數沒有

4.6 C++抽象基成員函數

中新 error isp ... 先來 必須 pub 對象 c++ 參考:http://www.weixueyuan.net/view/6376.html 總結:   在C++中,可以通過抽象基類來實現公共接口   純虛成員函數沒有函數體,只有函數聲明,在純虛函數聲明結尾加

基本數據型在多線程的情況是否需要加鎖

等於 access mes 程序 大於 bold data 結構 全局 對於多線程訪問同一變量是否需要加鎖的問題,先前大家都討論過。今天用代碼驗證了一下之前的猜想:32位CPU與內存的最小交換數據為4字節/次,這也是結構體要對齊4字節的原因。在物理上,CPU對於同一4字節的

抽象 函數

code space 構造函數 point 構造 red ostream 意義 led 1 #include <iostream> 2 using namespace std; 3 /***********************************

C++多態、函數、函數、抽象

多態 內存泄漏 一份 並且 靜態成員函數 返回值 訪問 類對象 virt 一、C++多態 C++的多態包括靜態多態和動態多態。靜態多態包括函數重載和泛型編程,動態多態包括虛函數。靜態多態是指在編譯期間就可以確定,動態多態是指在程序運行時才能確定。 二、虛函數 1、虛函數為類

抽象函數,函數的意義

virt 性能 using 容易出錯 知識 浪費 中比 應該 public   C語言是面向過程的語言,C++是面向對象的語言,區分它們面向什麽的重要區別在於C++比C多個類。那麽在我看來,抽象就是類的升華。   一般剛學習C++的時候,抽象這個東西給人最大的感覺就是太抽象

C++——的綜合案例——函數與抽象( 加強對接口與多態,以及派生構造函數的理解 )

構造 set 由於 技術 als str wid choice gre 註意派生類構造函數的寫法。 1 #include <iostream> 2 3 using namespace std; 4 5 enum COLOR {

多態(3)—— 函數和抽象

接口類 rtu 存在 clas people 繼承 寶典 dong spa 1、基本概念   純虛函數是一個在基類中說明的純虛函數,在基類中沒有定義,要求任何派生類都定義自己的版本。   純虛函數為各派生類提供一個公共界面(接口的封裝和設計、軟件的模塊功能劃分)。 純虛函數

C++中抽象以及/、解構函式的區別與介紹

一、虛擬函式 在某基類中宣告為 virtual 並在一個或多個派生類中被重新定義的成員函式,用法格式為:virtual+函式返回型別+ 函式名(引數表) {函式體};實現多型性,通過指向派生類的基類指標或引用,訪問派生類中同名覆蓋成員函式。 二、純虛擬函式 純虛擬函式是一種

解決 “該擴充套件程式未列在 Chrome 網上應用店中,並可能是在您不知情的情況新增的”

其他參考https://segmentfault.com/a/1190000009682735 1、首先把需要安裝的第三方外掛,字尾.crx 改成 .rar,然後解壓,得到一個資料夾 2、再開啟chrome://extensions/谷歌擴充套件應用管理,點選右上角的開發者模式,就可以看到“載入

jQuery easyui dataGrid 動態改變排序欄位名,一般情況,在使用的時候,我們會點選相應欄位進行排序,這裡以JAVA為例,後端的實體欄位有可能和資料庫的欄位不一致; 如:實體中的

jQuery easyui dataGrid 動態改變排序欄位名,一般情況下,在使用的時候,我們會點選相應欄位進行排序,這裡以JAVA為例,後端的實體類欄位有可能和資料庫的欄位不一致; 如:實體類中的屬性為userName,前臺filed="userName"而資料庫的欄位

壓縮圖片工具(在保證圖片清晰度的情況 儘可能的壓縮圖片的大小)

倒入類庫 compile 'me.shaohui.advancedluban:library:1.3.1' 封裝的工具類 package com.wisdomschool.stu.utils; import android.content.Context; import

關於二維情況切蛋糕問題的思路

這個情況下,我們不再考慮切幾刀,而是圓周上有n個點,每一刀的刀痕都必須通過圓周上這n個點中的兩個點的情況。求最大分割數。 這個問題有一個非常有趣又詭異的答案 當只有1個點的時候,最多隻能有11個區域 當有2個點的時候,最多可以分成22個區域 當有3個

使用QFileInfo獲取檔案資訊(在NTFS檔案系統上,出於效能考慮,檔案的所有權和許可權檢查在預設情況下是被禁用的,通過qt_ntfs_permission_lookup開啟和操作。absolutePath()必須查詢檔案系統。而path()函式,可以直接作用於檔名本身,所以,path() 函

版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/Amnes1a/article/details/65444966QFileInfo類為我們提供了系統無關的檔案資訊,包括檔案的名字和在檔案系統中位置,檔案的訪問許可權,是否是目錄或符合連結,等等。並且,通過這個類

Python+Selenium框架設計篇之6-一個檔案多個測試方法情況測試韌體的寫法

      其實,到前面這一篇文章,簡單的Python+Selenium自動化測試框架就已經算實現了。接下來的主要是介紹,unittest管理指令碼,如何如何載入執行指令碼,再就是採用第三方外掛,實現輸出html的測試報告。本文來介紹下,在同一個類中,多個測試函式時候,測試

[收集]c++抽象虛擬函式以及巧用解構函式實現介面

在Java、C#中有關鍵詞abstract指明抽象函式、抽象類,但是在C++中沒有這個關鍵詞,很顯然,在C++也會需要只需要在基類宣告某函式的情況,而不需要寫具體的實現,那C++中是如何實現這一功能的,答案是純虛擬函式。 含有純虛擬函式的類是抽象類,不能生成物件,只能派生。

用Spring管理的專案,在不啟動服務的情況進行測試測試:@RunWith @ContextConfiguration

Demo如下: @RunWIth(SpringJunit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"} public cla