這里的數據指的大概就是字段(貌似這章有些東西也是過時了,因為現在的.NET已經發展了很久了,包括java也是)
1、自封裝字段(其實就是屬性啦,過時了)
修改點:直接訪問字段,但是與字段間的耦合關系逐漸變得笨拙
做法:為這個字段建立一個取值/設值函數,并且只以這些函數來訪問數據
好吧,現在明白屬性是怎么來的了嗎,就是因為這個原因,所以有的人會干脆和你說,你不要寫公共字段,直接寫公共屬性。
因為公共字段能做的公共屬性都能做,不能做的公共屬性也能做。
public string 我叫公共屬性 { get; set; }
多寫個這東西好用多了,以后萬一有個什么反射的需要,也簡單多了,舉手之勞,所以忘記還有公共字段這回事吧。
2、以對象取代字段
修改點:你有一個字段,需要與其它數據和行為一起使用才有意義
做法:用對象取代字段
簡單地說,你玩過DateTime這個類嗎,就是系統的類,其實就是對數據的一個封裝啊。
如果沒有這個東西,那么你是不是要用年,月,日(就算沒有時分秒吧),三個字段來表示。搞的多了自然就像自己封裝一個Date類,哪天又多了個按格式輸出的要求,是不是要再寫個格式化函數
所以那么就把這些好基友都放在一起好了,于是DateTime就成了基友之家。(然而我們需要更多的DateTime樣的基友之家)
3、將值對象改為引用對象
修改點:你從一個類衍生出許多彼此相等的實例,希望將它們替換為同一個對象
做法:將這個值對象變為引用對象
簡單地說,這里所謂的值對象是指DateTime這種對象,即可以建立很多次,純數據的傳遞,而所謂的引用對象是指緩存的東西,比如淘寶界面的分類數據,因為所有人看到的分類都是一樣的,那么我們不需要每次都從數據庫取值
緩存一下就好,不需要每次New一個分類對象,然后調用查詢函數,而是去找緩存去看是否有了緩存,有了緩存,那么就調用,沒有緩存那么就再從數據庫里面取值。只要緩存一更改,大家看到的分類就都更改了。
好了,就是這么個意思
其中為了適應也許引用對象會根據不同的類型創建一個不同的對象,(就像有個墻類,上節課Troy教小朋友們如何搬磚砌墻,所以新建正常的墻的對象,這節課話題換了,Troy教小朋友們碼長城,于是就新建一個長城對象)
當涉及到切換子類的時候,可以用這種方法使得客戶端調用的時候不需要知道還有長城這個類,只需要用墻類里的工廠函數就好了,當然也許你下次還得搞個扛水泥去搞個水泥墻的類,那么在 工廠函數里再加就好了。
所以提到的一個重構方法:
將構造函數改為調用工廠函數,即
public class Wall{ public Wall(string length){ //啦啦啦 } }
改為
public class Wall{ public static Wall Create(string type){ if (type=="Great"){ return new GreatWall("一萬里"); } else{ return new Wall("一米"); } } PRotected Wall(string length) { //啦啦啦 } } public class GreatWall:Wall { public GreatWall(string length):base(length) { //啦啦啦 } }
這種方法的用處在于客戶調用不需要去知道子類,只需要知道type就行了,當然具體情況具體對待,你也可以不用這么寫
然后這個所謂的引用變量的穩妥一點的做法還是要判斷一下這個對象存不存在,不存在就去取值,存在就直接調用。
4、將引用對象改為值對象
是的,你沒有看錯,當你千辛萬苦改成了值對象之后,有個需求第二天可能又會讓你改回來。。
動機:因為你的引用對象很小且不可變,而且不易管理。
不可變是什么鬼?我看了《重構》這個看了半天,最后才弄明白。
還是用分類緩存來說,淘寶的緩存就是可變的,因為淘寶的緩存假如作為商家的你可以自己去去增加分類,然后刷新它,那么所有的客戶得到的分類都變了。
那么不可變是就是:
我在本地把百度首頁改成這樣,但是你們這些人并不知道我已經在用谷歌了╮(╯▽╰)╭
換句話說,有天,馬老板說咱淘寶分類就這么搞了,無論怎樣就這么多分類了,不許商家去增加分類,那么,這個緩存就是不可變的了,某天他說不要那么多分類了,就假貨和真貨兩個類的時候,那么引用對象足夠小了
那么這個引用對象也就不需要了,改成值對象就好了,比如像百度一下直接作為文本,寫死在頁面上了。
好吧,我就是這么理解的。
正經話:引用對象就是客戶公用的,當我去改動后,其他客戶看到的也就變了,而值對象不是,當我改動后只有我自己這邊變了。(懂了嗎,我覺得自己已經講得很清楚了)
5、以對象取代數組
修改點:你有一個數組,其中的元素代表不同的東西
做法:以對象替換數組,對于數組中的每個東西,以一個字段來表示。
換句話說,有個混蛋把DateTime的年月日寫在一個int date[3];這樣的東西里面,其中date[0]代表年,date[1]代表月,你是否想要打死他?
6、復制"被監視數據"(大白話:別把數據放在表現層)
修改點:你又一些領域數據置身于GUI控件中,而領域函數需要訪問數據。
做法:將該數據復制到一個領域對象中。建立一個Observer模式,用以同步領域對象和GUI對象內的重復數據。
一個分層良好的系統,應該將處理用戶界面和處理業務邏輯的代碼分開。
動機:(1)你可能需要使用不同的用戶界面來表現相同的業務邏輯,如果同時承擔兩種責任,用戶界面會變得過分復雜;(手機端微信和PC端微信)
?。?)與GUI隔離之后,領域對象的維護和演化都會很容易,你甚至可以讓不同的開發者負責不同部分的開發。
作為WEB開發者,MVC都知道吧,所以這里講得就是業務邏輯別寫在表現層,當然也包括數據。
做法:
話說這小節的做法真的超長,一大堆java代碼,我最終還是沒敢看。
由于我在客戶端程序這方面基本上就只寫過一些小程序,總是業務邏輯和表現層混在一起,所以我就寫個自己經常寫的垃圾代碼改一下好了。
功能:
每個按鈕點一次講一句話
好了,下面貼上這段典型的混合果汁代碼:
private void btnWangyi_Click(object sender, EventArgs e) { txtYongBoy.Text += getNewLineString("網易:")+ getNewLineString("我是一個有態度的媒體,我每天引誘一堆人互噴賺流量"); } private void BtnWumao_Click(object sender, EventArgs e) { txtYongBoy.Text += getNewLineString("五毛(一群整天陰謀論的人):") + getNewLineString("美狗SB,你們這些美分就是成天造謠,臭傻叉,汪汪汪"); } private void btnMeifen_Click(object sender, EventArgs e) { txtYongBoy.Text += getNewLineString("美分(一群整天抱怨國家社會父母,自己又沒能力的low逼):")+ getNewLineString("五毛SB,只有我清醒著,你們這群被洗腦的人,我最清醒,我為什么沒被洗腦,因為汪汪汪"); } private void btnZhengfu_Click(object sender, EventArgs e) { txtYongBoy.Text += getNewLineString("政府(話說真的想看,網絡對它不存在匿名,程序員一句代碼的事,太可怕了):") + getNewLineString("我就看看,不說話"); } private void btnSendFuck_Click(object sender, EventArgs e) { txtYongBoy.Text += getNewLineString("Troy:")+ getNewLineString("好好工作,好好賺錢,有能力不喜歡這里分分鐘移民,沒能力出去也是low,要不你去個唐人街去旅游看看,lol"); } private string getNewLineString(string str){ return str + Environment.NewLine + Environment.NewLine; }
好了,按照作者的話來講,應該是改成接下來這種樣子:
public partial class MainForm : Form { private Speak mySpeak; public MainForm() { InitializeComponent(); mySpeak = new Speak(this); } private void btnWangyi_Click(object sender, EventArgs e) { mySpeak.SendMySound("網易:", "我是一個有態度的媒體,我每天引誘一堆人互噴賺流量"); } private void BtnWumao_Click(object sender, EventArgs e) { mySpeak.SendMySound("五毛(一群整天陰謀論的人):", "美狗SB,你們這些美分就是成天造謠,臭傻叉,汪汪汪"); } private void btnMeifen_Click(object sender, EventArgs e) { mySpeak.SendMySound("美分(一群整天抱怨國家社會父母,自己又沒能力的low逼):", "五毛SB,只有我清醒著,你們這群被洗腦的人,我最清醒,我為什么沒被洗腦,因為汪汪汪"); } private void btnZhengfu_Click(object sender, EventArgs e) { mySpeak.SendMySound("政府(話說真的想看,網絡對它不存在匿名,程序員一句代碼的事,太可怕了):", "我就看看,不說話"); } private void btnSendFuck_Click(object sender, EventArgs e) { mySpeak.SendMySound("Troy:", "好好工作,好好賺錢,有能力不喜歡這里分分鐘移民,沒能力出去也是low,要不你去個唐人街去旅游看看,lol"); } public void UpdateSound(string text){ txtYongBoy.Text=text; } } public class Speak { private string Alltext; private MainForm speakForm; public Speak(MainForm form) { speakForm = form; } private void updateSounds(){ speakForm.UpdateSound(Alltext); } public void SendMySound(string speaker,string text){ Alltext += getNewLineString(speaker) + getNewLineString(text); this.updateSounds(); } private string getNewLineString(string str) { return str + Environment.NewLine + Environment.NewLine; } }
好吧,雖然上面代碼很渣,但是應該足夠清晰向你表示什么叫分離數據到業務層了。
如果你又興趣可以改寫我上面的渣代碼,比如給MainForm繼承某個接口,然后Speak類里的speakForm是這個接口而不是MainForm你覺得是不是好看多了,如果你繼續改下去,那么你就會玩觀察者模式了
7、單向關聯改為雙向關聯
修改點:兩個類都需要使用對方特性,但其間只有一條單向連接。
做法:添加一個反向指針,并使修改函數能夠同時更新兩條連接。
可以參考我上面的例子,MainForm和Speak就是雙向關聯的兩個類,你可能不知道指針是什么,你就理解為引用就行了。(或者自己去看一下C語言)
8、雙向關聯改為單向關聯
修改點:兩個類之間有雙向關聯,但其中一個類如今不再需要另一個類的特性。
做法:去除不必要的關聯
(這句話是我自己的見解,或許是錯誤的,你自己決定是否這么玩:我不太贊成雙向關聯,單向關聯就好了,我在A類要用B類,我自己在A類建個函數,接收B類的對象,到時候調用B類的對象的方法就好。)
大家各玩各的,分開好一點,不要攪到一起。所以如果是我寫正式的代碼,我會盡量把所有的雙向關系全換成單向的,除非我自己偷懶(我經常偷懶⊙﹏⊙‖∣,通常這意味著我或者將來的某人要為此付出代價/(^o^)/),好吧,仁者見仁,智者見智,也許我是錯的,僅僅我自己覺得雙向關系對我來說很不清晰,會造成耦合。
在這里Martin也說了,雙向關聯很有用,但是你必須為它付出代價,那就是維護雙向連接、確保對象被正確創建和刪除而增加的復雜度。
9、以字面常量來取代魔法數字
修改點:你有一個字面數值,帶有特別含義
做法:創造一個常量,根據其意義為它命名,并將上述的字面數值替換成這個常量。
舉個簡單例子:
static void Main(string[] args) { Console.Write("請問一天有多少個小時:"); if (24 != Console.Read()) { Console.WriteLine("你說什么就是什么咯!"); } else { Console.WriteLine("答對了"); } }
一般大家
const int HourNumOfOneDay = 24; Console.Write("請問一天有多少個小時:"); if (HourNumOfOneDay != Console.Read()) { Console.WriteLine("你說什么就是什么咯!"); } else { Console.WriteLine("答對了"); }
好吧,這里怎么寫都一樣,其實很好理解,那么你自己想象一下現在你是在一個業務很復雜的系統中,突然出現了一個21.23這樣詭異的數字,然后與某個變量做比較, 你可能就會覺得這段代碼理解起來有點吃力了,如果出現一大堆,也許你就要花很長時間去研究業務了。
10、封裝字段(好吧,已經過時了,就是屬性)
修改點:你的類中存在一個public字段
做法:將它聲明為private,并提供相應的訪問函數
11、封裝集合
修改點:有個函數返回一個集合
做法:將這個函數返回該集合的一個只讀副本,并在這個類中提供添加.移除集合元素的函數
因為現在給你的集合是個引用,所以你可以修改這個集合,而這個集合的擁有者一無所知。
好吧,在我的理解力,他的作用大概就是和封裝字段一樣的性質,為了數據隱藏,并且讓數據和行為保持一致性
并且保持數據嚴謹性和安全性(好吧,反正我感覺自己應該不會用到這項重構,這個重構的作用應該是為了防止一些非正常的編碼行為)。
那么我多句嘴吧:別用數組,用list和dictionary這種集合就好了(我也不知道多久沒用過數組了 )。
12、以數據類取代記錄
修改點:你需要面對傳統編程環境中的記錄結構
做法:為該記錄創建一個“啞”數據對象
好吧,這個東西你可以理解為,如何把一堆數組轉化為類對象,舉個例子:溝通數據庫的ORM
13、以類取代類型碼
修改點:類中有一個數值類型碼,但它并不影響類的行為
做法:以一個類替換該數值類型碼
好吧,在.NET里你用enum枚舉就好了,非常簡潔易用。
14、以子類取代類型碼
修改點:你有一個不可變的類型碼,它會影響類的行為
做法:以子類去取代這個類型碼
動機:它把對不同行為的了解,從類用戶那兒轉移到了類本身。如果加入新的行為變化,只需要添加一個子類就好了,如果不用多態機制來處理,那么你就要找到每一個條件表達式,然后逐一修改。
這種做法就是為了消除switch和if這樣的分支語句,所以用子類繼承父類的多態來處理,如果沒有出現條件表達式那么就用第13個重構行為就好了。
(好吧,你也許仔細想一下switch還是沒有消除。哈哈,當初我糾結了好久。其實還是很簡單,switch當然不會消除,但是它在客戶類調用的時候已經看不到了啊,客戶不需要知道內部邏輯,也就是說你寫的代碼,上一級調用起來會更簡單。
更重要的是,你可能通過這種方式就只需要一次switch語句了,因為你只需要知道創建哪個類的對象就好了)
說了這么多,如果你還是不懂那么就來看下面這個簡單例子吧。
class Program { static void Main(string[] args) { Console.Write("請輸入您要釋放技能的神奇寶貝:"); string name=Console.ReadLine(); new 神奇寶貝().釋放技能(name); new 神奇寶貝().叫一聲(name); } } public class 神奇寶貝 { public 神奇寶貝() { } public void 釋放技能(string name){ switch(name){ case "皮卡丘": Console.WriteLine("您的精靈釋放十萬伏特"); break; case "杰尼龜": Console.WriteLine("您的精靈釋放水槍"); break; default: Console.WriteLine("小火龍釋放火球"); break; } } public void 叫一聲(string name) { switch (name) { case "皮卡丘": Console.WriteLine("皮卡"); break; case "杰尼龜": Console.WriteLine("杰尼"); break; default: Console.WriteLine("呵呵噠"); break; } } }}
修改后:
class Program { static void Main(string[] args) { Console.Write("請輸入您要釋放技能的神奇寶貝:"); string name=Console.ReadLine(); var 我的神奇寶貝 = 神奇寶貝.Create(name); 我的神奇寶貝.釋放技能(); 我的神奇寶貝.叫一聲(); } } public abstract class 神奇寶貝 { public static 神奇寶貝 Create(string name) { switch (name) { case "皮卡丘": return new 皮卡丘(); case "杰尼龜": return new 杰尼龜(); default: return new 小火龍(); } } public abstract void 釋放技能(); public abstract void 叫一聲(); } public class 皮卡丘 : 神奇寶貝 { public 皮卡丘() { } public override void 叫一聲() { Console.WriteLine("皮卡"); } public override void 釋放技能() { Console.WriteLine("您的精靈釋放十萬伏特"); } } public class 杰尼龜 : 神奇寶貝 { public 杰尼龜() { } public override void 叫一聲() { Console.WriteLine("杰尼"); } public override void 釋放技能() { Console.WriteLine("您的精靈釋放水槍"); } } public class 小火龍 : 神奇寶貝 { public 小火龍() { } public override void 叫一聲() { Console.WriteLine("呵呵噠"); } public override void 釋放技能() { Console.WriteLine("小火龍釋放火球"); } }
修改完畢,首先我們可以看到整個代碼結構清晰了很多,其次,對于在Main函數里調用方法的你而言,你覺得你更喜歡哪種調用方法,最后想象一下寵物小精靈可是有五百多只,而且不只是叫一聲和釋放技能兩個行為,你如果加分支,那就真是毀童年系列了
15、用策略模式或者狀態模式取代類型碼
(類型碼:為什么你們總是要取代我,我就這么不招人喜歡嗎(#‵′)凸)
上面的兩個模式都是23種設計模式之一。
修改點:你有一個類型碼,它會影響類的行為,但你無法通過繼承手法消除它
做法:以狀態對象取代類型碼
以下是兩種你不能通過14來解決的情況:
1.類型碼的值在對象創建后發生了改變(皮卡丘在調用完釋放技能后,自動修改類型碼成了杰尼龜,然后再叫一聲的情況、)
2.由于某種原因,類型碼宿主,已經有了子類(因為你是用子類集成來消滅分支,所以人家有了子類你肯定就不能這么玩了)
一提起這兩個模式就感覺好厲害的樣子,根本就看不下去了。
其實很簡單啦,不要管這亂七八糟的東模式西模式的。
現在的問題在于我沒法用子類消滅分支了,所以解決問題。
因為我不能繼承現在的Current類,所以我新建一個type類(具體類名你自己?。缓蠼o有幾個類型碼我就繼承幾個子類,看我不就能用繼承的方式來消滅分支了嗎
那么能解決分支了,剩下的就是把Current類和type類關聯起來咯,你在Current類里加一個type的字段myType,然后寫一個設置type字段的函數,函數里面就用類型碼來switch區分創建哪一個type類的子類,這樣就OK了嘛。
什么設計模式嘛,根本就不需要記住,是不是?
臨時百度一下就OK,拿這幾套劍法套路用來裝裝B就好了,正式寫代碼不用想,需要什么就寫什么好了,我管你什么套路,一板磚能解決那扔過去就好了。
作為一個劍客,目的就是砍倒人,你真的這么在意用什么劍法嗎?
16、以字段取代子類
修改點:你的各個子類的唯一差別只在“返回常量數據”的函數上
做法:修改這些函數,使它們返回父類的某個(新增)字段,然后銷毀函數。
動機:建立子類的目的是為了增加新特性或變化和行為。有一種變化行為被稱為常量函數,它們返回一個硬編碼的值。
這個東西有其價值,但是如果子類里面僅僅只有這些常量函數的話,就沒有足夠的存在價值了,你可以在父類中加一個與常量函數相對應的字段,從而完全去除這樣的子類,如此一來就可以避免因繼承而帶來的額外復雜性。
新聞熱點
疑難解答