隨筆程式設計雜談錄:[-封裝-]
製造輪子和創造輪子兩者的區別在於:一者為複用,一者為封裝
一、與封裝的初遇
現在回到第一次我接觸封裝的時候:
兩年前,class這個詞進入了我的世界,但class並不是我封裝思想的啟蒙師。 在此之前,讓我初次領略封裝的強大之物是電子元件的引腳和它的真值表。 下面的例子希望你可以好好理解一下:怎麼在邏輯上實現一位二進位制的加法的邏輯運算單元 如果你看不下去,就直接return到第6小點
1.與門(AND)和非門(NOT)
你覺得很簡單?(少年,你對於力量一無所知)
下面的兩個東西的組合是計算機邏輯單元的一切

與門和非門.png
------與門真值表--------------------------非門真值表---------- 輸入A輸入B輸出Y輸入A輸出Y 00001 01010 100 111 ------------------------------------------------------------- 簡單解釋一下: A和B你可以看成兩根導線,只有高電平和低電平兩種形式 1代表高電平,0代表低電平: 對於A B的每次輸入,通過非門都會進行輸出, 真值表就列出了這個元件的所有輸入對應輸出的情況表
2.一位加法的邏輯設計
首先我們要明確一位加法的邏輯是什麼樣的
0 + 0 = 00 + 1 = 10 + 1 = 11 + 1 = 0 (超出長度1溢位) 如果將輸入A和B看做加數 ,結果看成Y:可以畫出下面的真值表: --------------------------元件真值表------------------------- 輸入A輸入B輸出Y 000 011 101 110 ------------------------------------------------------------- |--而現在需要做的就是如何通過與門和非門的拼接來形成一個元件 |--使得這個真值表成立,你可以驗證一下上面的四種情況: 比如 A B 都輸入高電壓(即1),來校驗一下 上面:非A 得 0 ---> 0 與 B 得 1--->取非得 0 --->0 與 0 得 0即輸出Y是0 下面:非B 得 0 ---> 0 與 A 得 1--->取非得 0

1位加法器.png
這裡簡單說一下怎麼知道要這樣畫: 注:這裡為了方便書寫,用位運算的符號表述,~ 代表非 &代表與 |代表或 --------------------------元件真值表------------------------- 輸入A輸入B輸出Y 000 011 101 110 ------------------------------------------------------------- [1].找出Y=1的所有行 [2].寫出上步中的邏輯表示式並用或連線 (~A & B) | ( A & ~B) [3].通過與和非替換掉或: 替換公式 X|Y = ~(~X & ~Y) ~((~(~A & B)) & (~(A & ~B))) [4].根據與非關係自內向外畫圖: |---上行:~A 即上面的 非門A,然後和 B 一起進入或門 ,結果再取非 即:~(~A & B) |---下行:~B 即下面的 非門B,然後和 A 一起進入或門 ,結果再取非 即:~(A & ~B) |---上下兩行的結果進入或門,之後再入非門 ~((上行結果) & (下行結果))
3.加法器封裝
說到這裡貌似和封裝也搭不上啊?但我已經實現了一個邏輯單元
這個單元可以將兩個輸入按照1位二進位制的邏輯執行,於是封裝的價值便體現了
現在將輸入的線連起來之後,再套上一個外殼,它便是一個 有邏輯價值
獨立元件

1位加法器.png

1位加法器.png
4.進位器邏輯封裝
首先我們要明確進位的邏輯是什麼樣的
邏輯單元:0 + 0 = 00 + 1 = 01 + 0 = 01 + 1 = 1 --------------------------元件真值表------------------------- 輸入A輸入B輸出Z 000 010 100 111 ------------------------------------------------------------- 按照上面的步驟來: A & B發現原來一個與門就能進行進位操作

現在封裝的價值就體現了,也就是輪子的價值,可複用,
現在將兩個元件進行組裝,再加個套子,就能實現更復雜些的邏輯處理單元


加法器.png
5.小結
對使用者而言:哥管你裡面什麼邏輯,我給輸入,你給我我想要的輸出就行了
確實一個封裝體就做到了, 隱藏內部的邏輯實現
,將最簡潔的使用方式告訴使用者
下面的一幅圖和上面的封裝體能完成相同的功能,你更用哪個?

加法器.png
--------------元件真值表------------------ 輸入A輸入B輸出Z輸出Y 0000 0101 1001 1110 這是將Z,Y兩個輸出存順序排列: 0+0=000+1=011+0=011+1=10 而這個進一位加法器的邏輯單元已經完善了,就可以當做一個元件來使用 形象而簡潔地描述一下: 在執行1 + 1 的時候 高電平經過A,高電平經過B,通過電子元件的內部邏輯單元CRA輸出1,通過ADD輸出0, 即 Z輸出 1,Y輸出 0 ,按Z Y進行輸出的到了結果 10
為了更形象說明,這裡拿一個 74HC138N
看一下,大概三毛錢一個,
傳說中的三八譯碼器,以前玩微控制器做電子鐘的時候用到過,現在差不多忘完了...

IMG20190215193730.jpg

74HC138.png
在此強調一點:電子元件內部是封裝符合真值表的邏輯單元
電子元件都有輸入和輸出,即 輸入+ 邏輯單元處理 ----> 希望的輸出
至此為止,不再延伸,有興趣的自己玩...
6. 昇華
我們針對 輸入
和 輸出
暴露 引腳
,將 邏輯單元
封裝其中
這樣對於指輸入就會有期望的輸出,在便是邏輯單元的封裝
它在軟體領域有一個俗稱:輪子,這裡暫時稱為 封裝體
封裝體的優越之處:(只要封裝體能實現預期的功能,那麼:) |--無論時間,空間的變化,你的輸入都會變成你期望的輸出 |--這便具有可複用性,再需要它時便無需再次設計 |--隱藏內部的邏輯實現,以保護封裝體的內部封裝不被破壞 |--僅暴露介面提供輸入和輸出,簡化使用方式
下面關於封裝做一個類比:
-
| 電子元件 | 電腦 | 開源類庫 | 人
---|---|---|---|---
封裝物| 硬質外殼 | 塑料/金屬外殼 | .jar,.so包等 | 軀殼
介面| 引腳 | 鍵盤,USB,電源鍵等 | api方法 | 口 ,耳,眼,鼻,面板
輸入| 高低電平 | 鍵盤輸入,U盤頭 | 方法呼叫 | 食物,音樂,書籍,氣味,觸控
輸出| 運算結果 | 螢幕顯示,U盤資訊| 運算結果 | 能量,思想,勞動力,汗液等
處理核心| 邏輯單元 | CPU | 類 | 大腦
使用前提| 真值表 | 說明書 | API文件 | 不可描述(宇宙造物手冊?)
核心組成元| 與門+非們 | 各種電子元件 | 屬性+方法 | 脫氧核糖核酸
7.封裝沒有缺點嗎?
我還沒發現世上有什麼東西沒有缺點
就連我們這麼偉大的人類都可能發生基因突變,更何況是人類製造的封裝體 對於電子元件來說,由於環境因素,人為因素可能導致元件內部的損壞, 而無法拆封的你只能選擇更換一個封裝體,這就造成了浪費,雖然在我們眼裡不算什麼 對於一個開源框架來說,一個bug可能導致所有使用者的崩潰,這是很嚴重的 也就是使用一個封裝體是具有一定的風險性的,當然大廠的框架會相對完善 再者就是介面的複雜,有種必須按照別人意志去做的壓迫感 沒有真值表和介面圖,使用的人來說是災難,這種感覺就像... 必須使用的類庫沒有API文件,並且方法命名為a(),b(),c()一樣讓人抓狂 可惜對於人類,宇宙並沒有留下一份API文件,一切都要靠我們自己來編寫...
好了,引入完成,下面進入正文
二、程式設計中初遇封裝
1.與class的初遇
兩年前,一開始class 以及它 的 private 是我非常難理解的
對類的認識是在C++裡,印象最深的是圓這個類,從獲取圓的面積開始
---->[標頭檔案]------------------- #ifndef BSAE_CIRCLE_H #define BSAE_CIRCLE_H class Circle { const double PI = 3.141592654; public: double getArea(); double getRadius() const; void setRadius(double radius); private: double mRadius; }; #endif //BSAE_CIRCLE_H ---->[cpp檔案]------------------- #include "Circle.h" double Circle::getArea() { return mRadius * mRadius * PI; } double Circle::getRadius() const { return mRadius; } void Circle::setRadius(int radius) { mRadius = radius; } ---->使用類---------------- #include <iostream> #include "circle/Circle.h" int main() { Circle circle; circle.setRadius(10); std::cout << circle.getArea() << std::endl; return 0; }
2.現在來看
一個類便是類的設計者對於某種概念的封裝
我理解類存在的意義確實費了不少時間,當時疑問:
為什麼一行程式碼解決的事要拆成一個類?而且又是頭又是cpp的
現在發現有這種疑問的根源在於當時沒有認清自己的角色 認清自己的角色,這對入門的人來說是非常困難的,類的本身就是一個邏輯處理單元 而程式設計師的角色是設計類的人,就像電子元件的設計者在設計邏輯單元一樣 但任何一個程式設計師都必定是第一個使用者,所以兩個角色在同一個人身上 對於入門的來說,只能是一個使用者,因為你只是在意獲取結果,而沒有程式設計師的設計之魂 就會感覺很混亂,站在一個使用者的角度,類確實將半徑為10的圓面積這個問題變得複雜了

關係.png
但如果封裝的思想到位,就可以明確這個類的價值
setRadius進行輸入,getRadius,getArea進行輸出,其中的一切都是邏輯單元

內部分析.png
如果你還不清楚,看下圖:這就是封裝
看不見內部了,該怎麼用? 電子元件有真值表,類有API文件
至此我想對於類和封裝的關係應該講的淋漓盡致了

封裝.png
----------Circle API-------------------------- double getArea(); 獲取圓的面積 double getRadius(); 獲取圓的半徑 void setRadius(double radius); 設定圓的半徑 -----------------------------------------------
當你在非Circle中使用的時候,你的角色就變成了Circle元件的使用者
你需要關注的就只用它的輸入和輸出,內部邏輯實現已經不需要你考慮了
不過一旦你進入了Circle中,你的角色就是Creator,你可以把自己當成造物主來實現邏輯單元
Circle circle; circle.setRadius(10); circle.getArea()//314.159