菜雞與物件導向 (3): 封裝

封裝包含了兩個重要的觀念:

  • 控制物件和外部進行互動的出入口
  • 隱藏物件內部的細節資訊

強者我同事整理的文章裡的例子就舉得不錯:當你按下鍵盤的A鍵,螢幕隨即出現了A,你不必知道中間發生了什麼事,你只需要知道怎麼操作和最後得到什麼就可以了。

其中鍵盤提供的按鍵,就是我們對電腦進行互動的出入口;而電腦實際上做了什麼事情,也被隱藏了起來,讓我們只需要關注結果就好。

此外我也看到過販賣機的例子,當你去販賣機買飲料,你也不需要知道裡面的構造,只要知道你選了飲料投了錢,飲料就會跑出來就行

從上面的兩個例子,相信大家已經掌握到封裝的概念了:將物件視作一個整體,把內部的實作內容隱藏起來,讓使用者只需要知道怎麼使用這個物件即可。(相似的思路,我們後續的介面會再提到)

如果封裝做得夠好,除了可以將程式碼整理得井井有條以外,也能讓物件內部的修改不會直接影響到使用物件的地方,達成了降耦合的目標

並且也能讓物件的使用者直覺地知道如何使用物件提供的方法,如此使用者就可以專注在更高層次的抽象,而不用被物件內部的細節所干擾。

最後,從上面的敘述中我們可以察覺到要實現封裝,最重要的就是:對外的開放程度(存取範圍)的控制。或是套一句前輩的說法:給程式碼隱私的空間

補充:如果想問「什麼是耦合?」的朋友,建議可以看看這篇:實務上的高內聚與低耦合

或是參照本系列後續的 內聚與耦合

存取範圍與存取子

先讓我們從存取範圍開始說起吧,因為我個人慣用的是 C#,因此就介紹一下 C# 是怎麼控制存取範圍的。

在 C# 之中,類別裡控制可見度是使用修飾子來定義存取範圍,也就是當我們替類別宣告欄位時常看到的 PublicPrivate

  • Public: 這是公開的,所有人都看得到
  • Private: 這是私有的,只有自己看得到

除了最常用的這兩個以外,還有其他的修飾子可以先知道一下:

  • Protected: 這是受到保護的,只有自己和繼承的孩子們看得到
  • internal: 這是內部的,只有身為同一個組件的朋友們看得到
  • Protected internal:組合上面兩個,也就是可以給同個組件的朋友們,或是其他組件繼承的孩子們看見

接下來的部分會以最常見的 PublicPrivate 來繼續說明,對存取範圍的這些修飾子有興趣的朋友,可以參照 存取範圍層級 的說明。

現在我們已經知道了有哪些修飾子可以用來控制存取範圍,但為什麼我們會需要宣告存取範圍的大小呢?其根本是為了將控制權掌握在物件本身

就像大話設計模式比喻的:物件就像間房子,我們不希望被看光光,可以看見的 Public 就像門和窗,而不該看見的 Private 則是用牆壁隱藏起來,而對於這間房子而言,門窗是可以控制的。

……

閱讀全文



菜雞與物件導向 (2): 建構式、多載

我就直接說了,有用前面的程式碼區塊的人,絕對執行不了。因為我們建立哥布林和戰士這兩張卡片的時候,根本就沒有給他們數值呀!

雖然可以先呼叫出來再賦值…

var goblin = new Card();
goblin.Name = "哥布林";
goblin.Attack = 3;
goblin.Health = 2;
/* ...其他賦值 */

這實在相當占空間,也有點奇怪。畢竟如果是阿福(狗),一出生的時候應該就確定了一些特徵才對,例如品種、血型、眼睛顏色這種。並不會出生後過一陣子,才突然決定這些東西,既然如此,我們在產生物件的時候,當然也會希望在建立的同時就先決定好一部份內容

這時候我們就可以藉由建構式的方式,在建立物件時就進行一些我們想要的操作。

建構式

事實上,當我們呼叫 new Card() 的時候(不覺得這個 () 很有呼叫方法的感覺嗎?)我們就是正在調用 Card 的建構式。而當我們沒有特別去定義建構式的時候,就會直接使用內建的建構式去幫我們產生物件。

現在我們替 Card 新增一個建構式:

public class Card
{
    public Card (string name, int attack, int health)
    {
        this.Name = name;
        this.Attack = attack;
        this.Health = health;
    }
    /* ... 其他屬性和方法 */
}

在 C# 的時候,建構式必須和類別同名,且不需要定義回傳類型。當我們有了建構式,剛剛的例子就可以改寫成:

var goblin = new Card(name: "哥布林", attack: 3, health: 2);
var warrior = new Card(name: "戰士" , attack: 4, health: 3);
warrior.Hit(goblin);

建構式也就是建立這個物件時執行的函式,通常會用來進行初始化,也就是做一些建立物件必要的準備。例如傳遞必要屬性或是建構需要的其他物件、或是給予私有屬性初始值等等,例如說我們的卡牌一建立,就會需要知道它的名字和戰鬥力,這樣才有卡牌的感覺,而不該像一些 壞決鬥者 邊打牌邊偷偷印卡。

……

閱讀全文



菜雞與物件導向 (1): 類別、物件

直覺上你當然知道什麼是物件;物件就在你的身邊。

汽車、iPhone、收音機、吐司機、廚房用具等等,你說得出來的都是。

  ——《深入淺出學會編寫程式》

什麼是物件?一切都是物件。

物件導向試圖讓抽象的程式碼,更貼近於我們的實際生活,其認為一切是由各式各樣的人事物互動所組成的,因此有了物件這個共通、最基本的概念。

假設現實世界存在一頭狗,叫做阿福。而我們想要在虛擬世界裡表達「有一隻叫做阿福的狗」這件事

這時候就要在系統裡有一個能代表「阿福(狗)」的東西存在,也就是「阿福」這個物件。

關於「把現實世界的物件,抽象化成程式世界裡的物件」的邏輯,可以參考 一個語言如果不改變你的思考方式,就不值得學?談程式語言的本質 這篇,尤其是選擇保留哪些資訊的部份我認為描述得很好。

現在我們知道,物件就是用來在虛擬世界中代表「某個特定的東西」,例如說叫做阿福的狗就是一個物件,阿福今天晚上要吃的飼料罐也是一個物件。

理解物件的概念是相當直覺且迅速的,畢竟你我身邊有著數不清的東西,它們都是一件一件的物件,但這樣的理解還不夠明確。

就像前文所引的 談程式語言的本質 文中所提到的,在抽象化的同時我們必然要選擇保留哪些資訊。

例如說阿福這隻狗,是一個物件;飼料罐也是一個物件

而這些物件之間還會彼此互動,例如說阿福是一隻狗,而我們觀察到狗都有吃東西這個動作,例如「阿福吃了飼料」

同時物件也會有一些專屬於它的特徵,例如說阿福是黑色的,我們就知道狗有毛色的差別。

那麼我們要怎麼表達「阿福」作為一隻「狗」擁有的那些動作和特徵呢?狗的毛色?狗可以吃飼料?

我們需要選擇怎麼去描述「狗」--也就是阿福這隻狗,被我們抽象化後的樣子,我們需要將它用程式碼的方式定義出來。

這時候我們就會需要類別,來定義出我們觀察到同一類的物件該有哪些特徵和動作,也就是我們替物件「分門別類」後、篩選出特定資訊的抽象化結果

延續前面的例子,假設今天我們從阿福身上觀察到進食跟毛色兩個狗的重要資訊,我們就可以建立類別 Dog:

public class Dog
{
    public string color;
    public void Eat(IFood food) { /* 進食與消化之類的 */ };
}

藉由我們定義的類別,就可以從類別中實例化(=建立)出物件。

也就是說,現在我們終於可以用「狗」這個類別,來表達出我們需要的「阿福」了:

Dog afu = new Dog(); // 阿福是一隻狗
Console.Write(afu.Color); // 阿福是黑色的
afu.Eat(food); // 阿福會吃食物
……

閱讀全文



菜雞與物件導向 (0): 前言

在訂便當告一段落之後,其實就已經和同事約好要來整理公司新訓的筆記。但儘管已經到職快一年了,有些工具已經在專案碰過好幾次。但遇到需要跟朋友討論,或是聽前輩說明觀念的時候,還是不自主地會想「我真的懂嗎?」故一直是挺畏懼的。

但幸虧同事的鼓勵和以身作則,最終還是開啟了這個新系列,決定直接開坑把當初前輩新訓指導過的部分整理下來,也算是讓自己能趁著這機會好好複習一番,把自己的想法跟心得記錄下來。

另外,如果你是真心希望弄懂物件導向的朋友,這邊推薦《大話設計模式》的附錄,內容對物件導向的介紹清晰易懂且循序漸進,非常適合作為了解物件導向的起頭。

本篇的段落將會分成以下幾個部份,由於只是筆記一下,因此會附上一些知識點的參考資料,看見的時候可以先行閱讀;末尾也會附上有關的參考資料及文章,對於這類概念性的東西,一向是推薦多方閱讀以增強理解,就像保哥寫的:「如果你問 100 個人這個問題,可能會得到 200 個答案,所以你一定要有自己獨到、有自信、精闢的見解或描述方式。」如果有寫得不錯的文章想推薦給我,或是有地方需要補充和指證,還請不吝指教。共勉之。

……

閱讀全文



AutoMapper —— 類別轉換超省力

類別間的轉換幾乎是每個專案每個工程師都會碰到的動作,舉凡是分層架構每層之間的轉換,如 Dto 轉換成 ViewModel;或是接收到資料要塞進自定義的類別時也需要進行轉換。但在遠古時代,當我們要把一個類別的資料倒進另一個類別時,總免不了一番折騰

例如一個卡片對戰遊戲的資料庫,光是要先把卡片資料讀取出來就需要:

有些時候也會看見用 Foreach 然後逐一傳值的場景,或是各種差不多的變種情況。同樣的是,光是將一個簡單的卡片資訊轉換成 ViewModel,就花了一大段在做對映的處理。這個過程本身枯燥乏味又占空間,更可怕的是,如果有個陳年資料表,動不動就上百個欄位,那這個轉換過程的恐怖程度可想而知

幸好!天無絕人之路,這種時候就是本日的主角 —— AutoMapper 出場的時候了。

當 AutoMapper 一出手,轉換的過程瞬間就變成:

是不是精簡很多呢?接著就讓我們來看看怎麼開始使用吧!

……

閱讀全文



Electron.net —— 把網頁包成桌面應用吧

因緣際會下想要弄出一些單機小工具來跑,這時候正巧接觸到 Electron.net 這個神器,特別紀錄一下以免忘記。這是 Electron 搭配 .net Core 的框架, Electron 是用 Chromium 和 Node.js 將網頁封裝成桌面應用程式,像是 Visual Studio Code、Slack 也都有使用到 Electron。而 Electron.net 顧名思義就是 .net 用的 Electron 框架囉。

這邊記錄一下自己嘗試時載入套件和建置的流程,主要參考自黑大的 用 ASP.NET Core 寫桌面 GUI 應用程式 - Electron.NETElectron.NET API 快速巡覽 這兩篇文章,以及官方的 API DEMO,特此感謝。

本篇小節:

……

閱讀全文



讀《黑馬思維》

每個人都是天才。但如果你用爬樹能力來斷定一條魚有多少才幹,牠整個人生都會相信自己愚蠢不堪。

這邊整理一下這陣子讀《黑馬思維》這本書的筆記,以及一些個人心得。直接破題說,我個人覺得值得一讀。

本書的目的是研究那些橫空出世的黑馬。但在研究少數的黑馬之前,就必須先說明何謂多數,所以本書前段著重在介紹什麼是標準化

從工業革命至今,為了能大量生產、品質穩定、降低成本、最終達到「一致、大量、簡單、有效」的效果,最直接且有效的做法就是制定流程和規定。不管是製造業,或者是教育,甚至是人生,群眾總是試圖找出一個固定的流程,鋪設一條筆直的道路,並且告訴大家:只要遵循這個路線,就能夠達到成功

雖然比起更加以前的階級制而言,標準化的做法的確更加公平了。然而為了達到標準一致,勢必得要重視群體、忽視個人。標準化的做法將所有人一視同仁,如同工廠的機器,或是程式的流程,認為設定好的輸入,經過了制定好的流程,就該有期待的產出。為了達到標準,因此無法認為每個人是特別的,甚至那些過於特別的,反而對標準化而言是個麻煩。

標準化的成功很直接:跟別人做一樣的事,但做得更好,就是最佳路線。

但有一些人,並不遵循標準化建議的路線,仍然取得了成功,他們就是黑馬。在個人化崛起的這個時代,媒體、醫療、廣告,甚至教育都開始有了量身打造的選項,隨處可見精準投放,個人菜單,標準化漸漸被個人化取代,於是作者們提出了疑問:什麼是「個人化的成功」呢?他們開始研究這些黑馬。

直接結論:黑馬們的個性、背景和領域都不大一樣,然而他們有一部分是相似的。有些人說:我認為自己做的事情是值得的,有些人說他感覺到熱忱,也有人說這是他的天職。黑馬的共通點在於:他們都並非為了想要成功或是達到目標才努力前進,而是靠著追求自我實現而成就卓越

Chase Excellence, Success will follow. - 3 Idiots

而對於這些黑馬如何追求自我實現,如何走出和標準化不同的路。本書提出以下四個重點:

  • 知道你的微動力
  • 清楚你的選擇
  • 了解你的策略
  • 忽略你的目的地
……

閱讀全文



C#: 位元旗標 (Bit flag) 與列舉

前陣子碰到個資料表儲存方式,因為這種位元運算的方式也常用在權限管理等地方,這邊就順手紀錄一下。

平常遇到二元的情形(例如 開/關、有/沒有),我們會直接宣告個 Boolean 來處理。但這次遇到的是同時有多個「有/沒有」的狀況,但我遇到的程式碼並沒有分成多個 Bool 去做處理,而是直接儲存成一個數值。

由於「有/沒有」只佔據一個位元,那麼將多個狀況按照位元順序排列的話,就只需要一個數字就可以紀錄或傳遞給其他系統了。例如最常見的用處是在權限系統,若有「讀」、「寫」、「執行」等權限,那麼我們按照這個順序去排列,當 讀=可、寫=不可、執行=可 的時候,就記做 101 = 5。這種直接用一組位元表示狀態的方式就叫做位元旗標(Bit flag)

1 讀   = 可
0 寫   = 不可
1 執行 = 可

/* 橫放 */
=> 101 (2進位) 
=> 5 

假使某天老闆靈光一現,決定接下來的新人員工都要記錄他們會的程式語言,並且他們報到的時候就會發一張公司列好的程式語言清單請他們勾選。

Enum & Flags

在 C# 中已經有方便的工具可以處理數字列表,我們可以建立一個叫做 SkillEnum 的列舉(Enum),並且按照上面說明的,將老闆提到的每個技能各自用一個位元來表示。

[Flags]
public enum SkillEnum
{
    C = 1,     // 0001
    PHP = 2,   // 0010
    SQL = 4,   // 0100
    Java = 8,  // 1000
}

註:[Flags]的標籤是指 C# 專門提供給位元旗標使用的 Enum,請參見 FlagsAttribute

只要在 enum 上加上 Flags 的屬性,除了自動按照 2 的次元增加以外,在使用 ToString() 也能更方便看見旗標內容

有了這個列舉之後,我們就可以表達不同排列組合的狀況了。例如:

* C: C
* P: PHP
* S: SQL
* J: Java
===========
J S P C
0 0 0 0 => 什麼都不會
0 0 0 1 => 只會 C
0 0 1 1 => 同時會 C 和 PHP
1 0 1 0 => 同時 PHP 和 Java
1 1 1 1 => 全部都會
……

閱讀全文



WakaTime —— 我 Coding 了多久?

這一周又是 偷懶週 繁忙週,因此就跟大家分享一個有趣的網站:WakaTime

WakaTime 能夠幫你自動記錄寫程式的時間、常用語言及專案,只要簡單地在常用的 IDE 設定好擴充套件就可以達成。 WakaTime 支援的 IDE 可以參閱 WakaTime - Plugin

註冊相當簡單,此處就不贅述。稍微介紹一下儀表板的各區塊的功能:

左上會有最近的專案及花費時間,每個色塊都是一個專案,滑鼠移上去就會顯示專案名稱。

點擊其中一天更會顯示當天的時間軸,可說是無所遁形。至於它判斷專案名稱的方法是看你編輯的檔案所在的資料夾名稱。

……

閱讀全文



我要訂便當 (5): Heroku 填坑小記

在上一集的 將 Python 腳本部署上 Heroku 中,記錄了將 Python 腳本放上 Heroku 的過程,但仍然沒有將我們的 訂便當小幫手 給放上去。之前有稍微提到是因為過程中遇到了一些問題,最後決定將這些問題和找到的前人解決文記錄在這一篇,將來再遇到的時候就可以參考。

  1. SQLite 要改成用 PostgreSQL
  2. 在 Heroku 上執行 Selenium

一、SQLite 要改成用 PostgreSQL

在之前的 我要訂便當(2) —— 用 Python + Sqlite 儲存訂單 中,為了方便及簡單性,選擇了較輕便的 SQLite 來儲存我們爬回來的訂單資訊。然而,SQLite 將資料儲存在小檔案以便於攜帶的做法,在 Heroku 上將會遭遇像是各個 dyno 的資料不同步等許多問題,因此 Heroku 是不建議使用 SQLite 的。

在 Heroku 的開發文件中的 SQLite on Heroku 有關於這部分的詳細說明,並直接提到「If you were to use SQLite on Heroku, you would lose your entire database at least once every 24 hours.」同時,官方也提供了他們的建議:PostgreSQL。相較於輕便但只能同時單一寫入而且還會在 dyno 炸掉的 SQLite,PostgreSQL 的完整性更符合 Heroku 對服務的要求。

關於在 Heroku 上使用 PostgreSQL 的做法,這篇 佈署 Python Flask 網站留言板應用程式到 Heroku + PostgreSQL 資料庫系統 說明得相當詳細。

……

閱讀全文