面向對象編程
面向對象編程與面向過程編程的區別
4、面向接口依然是面向對象編程,但他在分析系統提取對象的過程中,會首先找尋系統間的邊界,並為之定下協議。之后再依據協議來實現各個子系統。用戶不需要與他無關的東西,所以接口要細化,粒度要小,但又要把握尺度。
5、面向過程是以代碼為主,用代碼控制數據,描述解決問題的方法。面向對象是以抽象數據為主,所有處理都圍繞數據展開,用數據控制代碼,依賴抽象編程。
6、在面向對象編程中,也會局部地運用其他的編程思想,如具體的軟件設計過程中以及代碼實現里,依然會需要深入某業務的細節,去理解它的執行過程。還有為了更好地發揮多核的優勢,已經在普遍使用的函數式編程。
面向對象軟件開發的優點
- 代碼開發模塊化(將大問題拆成小問題,從而可以解決更大的難題),更易維護和修改
- 代碼復用
- 增強代碼的可靠性和靈活性
- 增強代碼的可理解性
面向對象編程語言
程序語言是為了尋求便捷而誕生的,正是因為對便捷的不同需求。 才促成了各種程序語言的發明。
語言設計的取舍:易用性與功能,安全性與效率,穩定性與可擴展性。
C,結構化編程 + 高效 + 功能強大 + 易於學習,誕生於1972年
the c programming language 1978由美國電話電報公司(AT&T)貝爾實驗室正式發表了C語言
1989.12 第一個完整的C語言標准,簡稱“C89”,不過人們也習慣稱其為“ANSI C”。
最新標准在2011年12月8日發布,稱為ISO/IEC9899: 2011,簡稱為“C11”。
C++,1979年發明,最初稱“帶類的C”,1983年正式更名為C++
重視代碼執行效率,語言規范相應的非常復雜。
JAVA,1991年構想,92年初步實現,95年公開發布。
目標是實現體系結構中立,而萬維網的發展成就了JAVA,因為萬維網需要可移植程序。JAVA對網絡編程的意義就如同C對系統編程的意義一樣:都是改變世界的革命性力量。
C#,1998年12月項目啟動,2000年6月發布
早期的C#與JAVA極其相似,但從3.0開始,與java的差異越來越大。
面向對象編程語言知識點
流程控制
使用if...else , while, break,continue 代替 過於強大、結構差、不便閱讀的goto。
同時增加 for, switch,以及后續的foreach 讓代碼更簡潔、清晰。
函數
因為隨着程序代碼變得越來越龐大,把握全局逐漸變得困難起來,同時有可能需要多次用到非常相似的操作,所以促成了函數 的誕生。
將代碼的一部分視作一個有機整體,切分出去並為之命名的程序設計機制(便於理解和可重復使用,早期有子程序的稱謂)。函數的實現需要在函數調用完成后返回原來的位置,同時函數還會嵌套乃至遞歸調用,目前普遍使用棧結構來實現。
只有代碼行數少了,才能很容易就把握到每一行執行什么功能。減小出錯概率。同時有助於理清思路,把握全局。
優秀的代碼書寫應該是每個函數僅實現一個功能,就如同類的單一職能原則一樣,這是一個度的把握。一般要求一個函數的代碼行數絕不能超過100行。一般不宜超過一個屏幕高。就如橫向上一行代碼不宜太長一樣(不要產生滾動條)。同時參數列表也不易太長,否則可考慮通過某種數據組織形式整合一下。
以上兩項是面向結構編程的基礎
結構化程序設計的初衷正是通過導入這些功能從而使代碼結構的理解變得簡單。
錯誤處理
常見的處理方法有兩種:使用返回值及引用參數傳達出錯信息。
- C,GO采用這一模式
使用可追加錯誤類型和可主動觸發錯誤的異常處理機制。
- C++ ,JAVA,C# 使用throw , try ... catch
- JAVA,C# 中 可以使用try ... catch ... finally。使用finally,出口只有一個,實現成對操作的無遺漏執行。
- JAVA 采用檢查型異常,要明確聲明可能拋出的異常,因為"The Trouble with Checked Exceptions",所以C#未引人。
- swift此前采用c的模式,2.0后引入異常處理機制。
命名和作用域(動態作用域、全局作用域、靜態作用域),命名空間
類型
數據如何讀取,需要依賴人們為數據附加的類型信息。
基本數據類型:
- CPU對操作數的存放方式:Little_endian(從低字節到高字節)和Big_endian(從高字節到低字節)。
- 以2進制表示10進制,約3.32(log10 / log2)個bit位表示一位。
- 整形:最高位為符號位。負數采用補碼的方式,從而將減法轉變為加法。(補碼:除符號位外 按位取反 再 加 1 )
- 浮點:
以32位為例:
最高位為符號位,
之后8位是表示位數的指數部分(0~255,減去127,得到-127~128,-127表下溢出:0,128表上溢出:無窮小或無窮大,剩下的為位數)
剩下23位為尾數部分,表示小數點以下的部分(通過移動小數點使整數部分變成1后的小數部分)。
如: 1.75 二進制:1.11 ,無需移位, 符號位:0,指數部分 0111,1111(127),尾數部分:110,0000,0000,0000,0000(0.5 + 0.25)
3.875 二進制:1.1111 * 2, 符號位:0,指數部分 1000,0000(128右移一位),尾數部分:111,1000,0000,0000,0000
范圍 :2-126 / 3.32 ~ 2127 / 3.32 約10-38~1038
精確度:23/ 3.32 ,6~7位有效數字
64位則指數部分為11位
范圍 :-21022 / 3.32 ~ 21023 / 3.32 約10-308~10308
精確度:52/ 3.32 ,15~16位有效數字
自定義類型:結構,聯合,枚舉,類。使用基本數據類型通過組合定義新的類型。
泛型、模版
動態類型
容器(數據結構):List,Set,Dictionary,……
對象與類
面向對象編程里的 對象是現實世界的模型。歸納並建立模型的方式多種多樣,語言不同,選擇也不同,類是最方便的,卻不是必須的。
JS中通過將函數和變量放入散列,以實現將相關聯的函數、變量集中到一起的功能。
函數式語言里,則會采用閉包的形式。
perl 使用包的形式
訪問權限:public private protected internal
訪問控制的作用:功能上實現封裝,代碼上解耦
具體:通過隱藏對象的字段來保護其內部的狀態
禁止對象之間的不良交互提高模塊化
提高代碼的可維護性,與外界的交互點越少,越好管理
繼承
采用封裝來建立抽象數據模型,但僅僅只有封裝還不夠,事物之間是普遍聯系的,還需要繼承和多態來描述他們之間的關系
繼承可以重用基類代碼,需要謹慎使用,否則代碼可讀性降低,而且擴展困難。使用中要遵守里氏替換原則
C++為多重繼承,多重繼承使用方便,但會產生沖突,JAVA的解決辦法就是只容許單繼承,用接口實現多重繼承的功能。同時單繼承便於統一管理內存。
JAVA1.8后允許接口中添加默認方法,但它的優先級低於基類中的同名方法。
抽象類,接口
抽象類可以有一個抽象方法,而接口則所有方法都是抽象方法,java中的接口可以有靜態變量,1.8后又為接口增加了默認方法和靜態方法。增強了代碼的擴展性,簡化了代碼實現。但接口與類依然有根本性區別存在,那就是接口不能持有狀態,因為它不能含有字段。
多態是指 從基類繼承並進行擴展的一系列子類,相互之間是不同的,但對於基類而言,他們依然還是基類的一個實例,即是他們都可以賦值給一個基類的引用變量,同理也可以賦值給抽象類和接口,后者是面向接口編程的基礎。
軟件設計通用原則
可維護代碼的基礎包含兩個原則: 高內聚和低耦合
高內聚和低耦合是為了支持結構化設計而引入的基本設計原則(1976年 Larry Constantine和Edward Yourdon)。但它們的適用范圍非常廣。好的面向對象設計也具備高內聚和低耦合的特點。
高內聚原則
衡量一個模塊(如類)中的各個方法、庫中的各個函數以及方法完成的各個動作表達的邏輯間直接的距離。
- 要求軟件模塊承擔的眾多職責關聯性很強。
- 高內聚類有助於維護,因為它們傾向於沒有依賴。而低內聚使類的目的難以理解,表現為類的職責(方法)上的共同點很少,並且應用不同的不相關活動。
- 高內聚原則 建議創建專注的類,有少量的方法表示邏輯上相關的操作。
低耦合原則
衡量兩個軟件模塊(如類)直接存在的依賴關系。 假設有A和B兩個類,如果修改A,就不得不修改B,那么就說它們耦合了。
- 低耦合並不是說兩個模塊完全隔離,它們依然允許通訊,但它們應該通過一組定義明確和穩定的接口來做。每個模塊都應該可以在沒有了解另一模塊的內部實現的情況下工作。
- 高耦合妨礙測試和重用代碼,使理解代碼變得困難。
- 高耦合是導致設計僵硬和脆弱的主要原因之一。
關注點分離
關注點分離原則(Separation of Concerns SoC) 由Edsger W. Dijkstra於1974年提出。
- 在系統中每個你想要的特性都是系統的一個關注點(一個方面)。特性、關注點、方面等術語一般可看作是同義詞。
- SoC要求每次只把注意力放在一個具體的關注點上。
- 關注點分離是實現高內聚低耦合的一個重要途徑。
eg:
1、MVC模式的設計理念就是分離業務邏輯、可視化界面的數據呈現、UI處理邏輯這三個關注點。
2、數據庫的讀寫分離
3、查詢集合操作
IList<Product> products = Product.GetSampleProducts(); foreach (var product in products) { if(product.Price > 100) { Console.WriteLine(product); } } //可調整為 foreach (var product in products.Where(p => p.Price > 100)) { Console.WriteLine(product); }
隔離
Soc具體通過使用模塊化代碼依據大量使用信息隱藏來實現。模塊化編程鼓勵為每個重要特性使用單獨模塊。模塊包含內部信息供自己使用。只有模塊的公共接口才是對其他模塊開放的,用於與其他模塊通訊。內部數據要么不暴露,要么封裝起來,有選擇地暴露。接口的實現包含模塊的行為,但其細節對其他模塊來說不可知,也不可訪問。
這種封裝隔離是保障高可維護性的常見方式。
面向對象設計6大原則
合成復用原則(Composite Reuse Principle)
組合/聚合(has) 和繼承(is) 是在面向對象中存在的兩種重用已有實現和設計的方法,合成復用原則強調的就是在重用已有設計和實現時要優先考慮組合/聚合。
原因:
- 繼承會破壞基類的封裝,如果基類發生變更,子類也要隨之受到影響,而且繼承來的方法是死的,雖然可以重寫,但無法在運行時發生改變,不具備靈活性,擴展不便。
- 繼承不但會繼承基類的代碼,也會基礎基類的Context。意味着派生類可以在任何接受父類的場景下使用,但是OO語言並不會保證這兩個類真的能替換(這是里氏替換原則的目標)。
- 組合/聚合的實現耦合性更低,相對更加靈活。 而且是一種防御性編程,更難引入面向對象中廣為人知的一些糟糕實踐相關的細微缺陷,如脆弱的基類、虛成員、構造函數等。
選擇繼承一定要小心慎用,在使用繼承時必須嚴格遵守里氏代換原則。使用繼承一般是為了兩個目的,一是父類里封裝的變量,方法對子類而言確實是基本不可能變更的,使用繼承可以減少代碼量,也可以在父子之間建立聯系。二是提供約束,比如模版方法模式。
//繼承 public class User { public virtual object DoWork() { //…… return new object(); } } public class RegisteredUser : User { public override object DoWork() { object o = base.DoWork(); return Process(o); } private object Process(object data) { //…… return data; } }
public interface IUser { object DoWork(); } public class User: IUser { public object DoWork() { //…… return new object(); } } //組合 public class RegisteredUser: IUser { private IUser _User; public RegisteredUser(IUser user) { this._User = user; } public object DoWork() { object o = _User.DoWork(); return Process(o); } private object Process(object data) { //…… return data; } } //聚合 public class RegisteredUser2 : IUser { private IList<User> _Users; public RegisteredUser2(IList<User> user) { this._Users = user; } public object DoWork() { if (_Users != null) { int index = new Random().Next(_Users.Count); object o = _Users[index].DoWork(); return Process(o); } return null; } private object Process(object data) { //…… return data; } } //泛型 public class RegisteredUser<T> : IUser where T : IUser { private T _User; public RegisteredUser(T user) { this._User = user; } public object DoWork() { object o = _User.DoWork(); return Process(o); } private object Process(object data) { //…… return data; } }
迪米特法則(Demeter Principle)
一個實體應當盡量少的與其他實體之間發生相互作用,使得系統功能模塊相對獨立。好的類應該是高內聚,低耦合的。
一方面要求自身的知識對方了解越少越好,另一方面要求對方的知識自己知道的越少越好。內部的實現多麽復雜,都和你沒關系,我只需要給你幾個public方法可以調用就行了。
四層含義:
- 只和親密的朋友說話,每個對象必然會與其他對象有耦合關系,一個類應該只和與自己有直接聯系的其他對象交流。就是不要出現X.getA().getB().getC(),X與C的業務應該委托給A去做,或者X直接與C建立聯系,而不是中間中轉多次。即 類與類之間的關系是建立在類之間的,而不是方法之間,一個方法盡量不引入一個類中不存在的對象。
- 朋友之間也要有距離,暴露太多方法和變量給對方,二者的關系就太親密了,耦合關系變得異常牢固,也就把修改變更的風險擴散了。所以需要訪問權限的控制。
- 是自己的就是自己的,如果一個方法放入本類中可以,放入其他類中也沒錯,那么判別標准就是:如果這個類放入本類中,既不增加類間關系,也不會對本類產生負面影響,那就應該放入本類中。
- 謹慎使用Serializable,一端發生變更,另一端必須同步修改。
核心觀念:類間解耦,弱耦合。這樣類的復用率才可以提高,代價則是會產生大量的中轉或跳轉類,導致系統復雜度提高。也會為維護帶來難度。
需要在高內聚低耦合與結構清晰二者之間的進行權衡。在實際應用中一個類如果需要跳轉兩次以上才能訪問到另一個類,一般就需要想辦法重構了。跳轉次數越多,系統越復雜,維護越困難。
迪米特法則可以表述為一系列的規則
- 在方法中,一個類的實例可以調用該類的其它方法
- 在方法中,實例可以查詢自己的數據,但不能查詢數據的數據(譯者注:即實例的數據比較復雜時,不能進行嵌套查詢)
- 當方法接收參數時,可以調用參數的第一級方法
- 當方法創建了一些局部變量的實例后,這個類的實例可以調用這些局部變量的方法
- 不要調用全局對象的方法。
public class Foo() { public Bar doSomething(Baz aParameter) { Bar bar = null; if (aParameter.getValue().isValid()) { aParameter.getThing().increment(); bar = BarManager.getBar(new Thing()); } return bar; } }
在這個示例方法中調用了方法鏈:aParameter.getValue().isValid() 和 aParameter.getThing().increment()。為了測試它們,我們需要明確地知道aParameter.getValue() 和 aParameter.getThing() 的返回結果類型,然后才可以在測試中構建恰當的模擬值。
接口隔離原則(Interface Segregation Principle)
- 接口要小,每個接口只負責服務一個子模塊或一個業務邏輯
- 要高內聚,減少對外的交互,要求接口盡量少公布public方法,接口是對外的承諾,承諾越少越有利,變更的風險也就越小。
- 要定制服務,有模塊就有交互耦合,就需要互相訪問的接口,接口設計時需要為各個訪問者定制服務,只提供訪問者需要的方法,即是要避免使用臃腫接口。
- 已經污染的接口要盡量去修改,若變更風險太大,則采用適配器模式進行轉化處理
- 接口需要精心設計,接口粒度太小,導致接口數據劇增,開發人員嗆死在接口海洋里;粒度太大,則靈活性降低,無法提供定制服務,給整體項目帶來無法預料的風險。
//門的接口拆分為門和定時門 public interface IDoor { void Lock(); void Unlock(); bool IsDoorOpen { get; } } public interface ITimedDoor { int OpenTimeOut { get; set; } event EventHandler DoorOpenTooLong; } // 電話的接口拆分為 協議管理 和 通話 interface IProtocal { void dial(String number); void hangUp(); } interface IChat { void chat(); }
依賴倒置原則(Dependence Inversion Principle)
程序開發從面向過程到面向對象,已經是從依賴代碼控制數據,步入依賴抽象數據控制代碼了。而依賴倒轉原則則要求我們要更進一步,實現類是對客觀事物或概念的抽象,而抽象類,接口都不能被實例化,是更高一層的抽象,稱為高級模塊。現實生活中,常見事物、理念都是高層依賴低層。而面向接口要求我們低層依賴高層。模塊間的依賴通過抽象來發生,實現類 之間不產生直接的依賴關系,而是通過接口、抽象類這樣的高層模塊來描述。
在swift語言中接口的關鍵字從interface變更為protocal,更精准地描述了它的新作用,通過抽象描述出來的依賴關系,就像是一份協議,它是對實現的約束。
依賴倒置是開閉原則的基礎,依賴倒置沒有實現,開閉原則也就是奢望。負面作用是大量應用接口,抽象類會使文件量大大增加,在小項目中它的優點難於體現,反而是增加了工作量,但在大中型項目中卻是必須的。
采用依賴倒置原則可以輕松擴展和維護,可以規避一些非技術因素引起的問題,如需求的變更,項目越大,變化的概率也越大。還有人員的變更,如果設計良好,代碼結構清晰,人員變化對項目的影響將基本為零,否則很可能是一場災難。
依賴倒置原則, 對應 IOC原則,對應好萊塢法則。
里氏代換原則(Liskov Substitution Principle)
任何基類可以出現的地方,子類一定可以出現(子類描述的模型集合是父類的一個子集)。只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對“開-閉”原則的補充。
當我們繼承一個父類時,可以對這個父類的方法進行重寫,也可以重載。
重寫時,參數列表與返回值都要與父類一致
重載時,如果要覆蓋父類的方法則要求(參數逆變或返回值協變):
1. 方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬松。
2. 當子類的方法實現父類的抽象方法時,方法的后置條件(即方法的返回值)要比父類更嚴格。
只有滿足上面的條件才算是遵循里氏替換原則,調用類時可使用基類或接口,如果不能使用,則違背了該原則。
重寫與重載的調用
class Father{ public Collection doSomething(HashMap map){ System.out.println("父類被執行"); return map.values(); } } class Son1 extends Father{ //重寫 public Collection doSomething(HashMap map){ System.out.println("子類1被執行"); return map.values(); } } class Son2 extends Father{ //重載,放大父類的輸入參數類型 public Collection doSomething(Map map){ System.out.println("子類2被執行"); return map.values(); } } public class MyClass { static void invoker(){ Father f = new Father(); HashMap map =new HashMap(); f.doSomething(map); } static void invoker1(){ Father f = new Son1(); HashMap map = new HashMap(); f.doSomething(map); } static void invoker2(){ Father f = new Son2(); HashMap map = new HashMap(); f.doSomething(map); } public static void main(String[] args) { invoker(); invoker1(); invoker2(); } } // 輸出 // 父類被執行 // 子類1被執行 // 父類被執行
重寫方法里對參數加了限制條件,也可能產生不符合里氏替換原則的后果
public class User { public virtual void DoSomething(int number) { Console.WriteLine(typeof(User).Name+":"+ number); } } public class RegisteredUser : User { public override void DoSomething(int number) { if (number < 0) { throw new ArgumentException("number < 0"); } base.DoSomething(number); } }
開閉原則(Open Close Principle)
- 一個軟件實體如類,模塊和函數應該對擴展開放,對修改關閉。從而建立一個穩定,靈活的系統。
- 軟件應該通過擴展來實現變化,而不是通過修改已有的代碼來實現變化。
開閉原則是最基礎的一個原則,其他原則都是其的具體形態,也即是其他五個是指導設計的工具和方法,而開閉原則才是精神領袖。開閉原則是抽象類,其他5個是具體實現類。
其重要性表現在4個方面:
- 對測試的影響,已經投產的代碼都是有意義的,並且都受系統規則的約束,這樣的代碼都要經過千錘百煉的測試過程,不僅保證邏輯是正確的,還要保證苛刻條件下(高壓,異常,錯誤)不產生有毒代碼。因此有變化提出是,也需要考慮下原有的健壯代碼是否可以不修改。僅僅通過擴展來實現變化呢。否則,就需要把原有的測試過程回籠一次。單元測試,功能測試,集成測試,驗收測試,太大的人力消耗。擴展增加新的類,只要保證新類正確就可以了。
- 提高復用性:所有邏輯都是從原子邏輯組合而來的,而不是在一個類中獨立實現一個業務邏輯。只有這樣的代碼才可以復用,粒度越小,被復用的可能性越大。為什么要復用呢?減少代碼量,避免相同的邏輯分散在多個角落,避免日后的維護人員為了修改一個微小缺陷或增加新功能而要在整個項目中到處查找相關的代碼。怎樣提高復用率呢?縮小邏輯粒度,直到一個邏輯不可再拆分你為止。
- 提高可維護性,維護人員的工作不僅僅是修改數據,也包括擴展程序,最方便的是擴展一個類,而最頭疼的是修改一個類。不管原有代碼寫得多么優秀還是多么糟糕。讓維護人員讀懂原有代碼,然后再修改,是一件痛苦的事情。
- 面向對象開發的需求,一切皆對象,但對象是在運動變化的,如何快速響應變化,就需要在設計之初就考慮到所有可能變化的因素,並留下接口,等待可能轉變為現實。
具體在實踐中的應用:
- 使用抽象約束,抽象沒有具體實現,也就表示它可以有非常多的可能性,可以跟隨需求的變化而變化,因此,通過接口或抽象類可以約束一組可能變化的行為,並且能夠對擴展開放。包括三層含義:
- 通過接口或抽象類約束擴展,對擴展進行邊界限定,不容許出現在接口或抽象類中不存在的public方法。
- 參數類型,引用對象盡量使用接口或者抽象類,而不是實現類。
- 抽象層盡量保持穩定,一但確定即不允許修改。可以擴展。
- 盡量使用元數據來控制程序的行為,減少重復開發。元數據是用來描述環境和數據的數據,就是配置參數,參數可以從文件中讀取,也可以從數據庫獲得。使用最多的是Spring容器。
- 制定項目章程,對項目來說,約定優於配置,就是統一風格,針對某種情況統一用某種實現。
- 封裝變化,把相同的變化封裝到一個接口或抽象類中,將不同的變化封裝到不同的接口或抽象類中。就是要找出預計有變化或不穩定的點,為這些變化創建穩定的接口,准確地講就是封裝可能發生的變化。
在實踐中,一旦發現有發生變化的可能或者變化曾經發生過,都需要考慮現有的架構能否可以輕松地實現這一變化。架構師設計一套系統不僅要符合現有的需求,也要適應可能發生的變化,這才是一個優秀的架構。開閉原則是一個終極目標,卻也不可能百分百做到。但朝着這個方向努力,可以非常顯著地改善一個系統的架構,真正做到“擁抱變化”。
SOLID原則
對象對象的設計原則還有另外一種表述
SRP | The Single Responsibility Principle | 單一責任原則 |
OCP | The Open Closed Principle | 開放封閉原則 |
LSP | The Liskov Substitution Principle | 里氏替換原則 |
DIP | The Dependency Inversion Principle | 依賴倒置原則 |
ISP | The Interface Segregation Principle | 接口分離原則 |
與上一表述的6大原則對比。
多了單一職責原則,少了組合聚合原則和迪米特法則。
單一責任原則(The Single Responsibility Principle)
當需要修改某個類的時候原因有且只能有一個(THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE)。換句話說就是讓一個類只做一種責任,當這個類需要承當其他責任的時候,就需要分解這個類。
在類中的一切都與該單一目的有關,即內聚性。這並不是說類只應該含有一個方法或屬性。
編碼向量
盡量簡單、一目了然(Keep it Simple Stupid,KISS 原則)
保持簡單化、傻瓜式。但需要的功能都不能缺,簡單而不代表簡陋。因為人們(包括產品的用戶以及服務的用戶)通常喜歡簡單的,容易學習和使用的東西。
- 不要讓人煩,不要讓大家做功課。提供一個有用的東西就夠了:把你的主要想法用一種方便理解、易於切入的形式呈現出來。如果你的想法很簡單,但是描述卻弄得十分繁瑣、令人失望就不好了。挫折感是吞噬好想法的惡魔。
- 簡單很好,但簡單並不是少,比如寫代碼,3行代碼能實現的邏輯如果用10行寫肯定不好,不夠簡單,但如果寫成了1行,同樣會不夠簡單。因為這行代碼包含了太多的內容,因為幾個月以后你自己再看,都有想哭的感覺。這是什么意思!這是另外一種復雜,隱藏的復雜,比直觀的復雜更害人。
適可而止(You Ain’t Gonna Need It,YAGNI 原則)
YAGNI 原則指的是只需要將應用程序必需的功能包含進來,而不要試圖添加任何其他你認為可能需要的功能,因為任何功能的增加和移除都是需要成本的。
在一個軟件項目中,往往 80% 的時間花費在 20% 的功能上。
當你准備列出一個項目清單時,試着考慮以下問題:
●通過降低抽象的層級,來實現低復雜度
●根據特性將功能獨立出來
●適度接受非功能性需求
●識別耗時的任務,並擺脫它們
不要重復自己(Don’t Repeat Yourself,DRY 原則)
DRY 原則規定,在整個系統中,每一個小的知識塊只可能發生一次,且每個知識塊必須有一個單一、明確、權威的表征。應該避免保持系統不同部分同步的麻煩。如果要改變任何地方,應該只做一次就夠了。用到代碼上,就是完成給定操作的代碼只寫一次。這也是重構的首要目標。
當你正在構建一個大型的軟件項目時,你通常會被整體復雜性搞得不知所措。解決復雜性的最基本的策略是將系統分成若干個容易處理的部分。起初,你可能想將系統按組件划分,每個組件代表了一個子系統,其中包含了完成特定功能所需的一切。
組件還可以往下再分,這樣復雜性將被降低到單一職責(single responsibility),每個職責可以使用一個類來實現,類包含了方法和屬性。方法實現算法,這些算法和算法的子部分是構成軟件業務邏輯的最小知識塊。你只需要保證這些塊不重復即可。
說,別問(Tell,Don't Ask)
軟件實體應該包含數據並暴露某些行為。調用的時候只需要跟蹤任務是否在允許的時間內完成,事件發生的細節都被委托和封裝到接受命令的對象了。
設計模式
創建型
- 單例: 保證一個類只有一個實例,並提供一個訪問它的全局訪問點。
- 工廠方法:定義一個用於創建對象的接口,但讓子類決定實例化哪一個類,Factory Method使一個類的實例化延遲到了子類。
- 抽象工廠: 提供一個接口,用於創建一系列相關或相互依賴的產品家族,而無須指定它們的具體類。抽象工廠的方法可用工廠方法的方式實現。
- 原型: 用原型實例指定創建對象的種類,並且通過拷貝這些原型來創建新的對象。
- 建造者: 將一個復雜對象的構建與他的表示相分離,使得同樣的構建過程可以創建不同的表示。
結構型
- 適配器:將一個或多個類的接口轉換成客戶希望的一個接口,Adapter模式使得原本由於接口不兼容而不能一起工作那些類可以一起工作。
- 裝飾器:動態地給一個對象增加一些額外的職責,若要附加擴展功能,Decorator模式提供了比繼承更有彈性的替代方案。
- 代理: 為其他對象提供一種代理以控制對這個對象的訪問。(就是把對該對象的調用攔截了下來,進行了一些中間操作)。
- 外觀: 為子系統中的一組接口提供一致的界面,facade提供了一高層接口,這個接口使得子系統更容易使用。(家庭影院的遙控器)
- 橋接: 將抽象部分與它的實現部分相分離,使他們可以獨立的變化。
- 組合: 將對象組合成樹形結構以表示部分整體的關系,Composite使得用戶對單個對象和組合對象的使用具有一致性。
- 享元: 享元模式以共享的方式高效的支持大量的細粒度對象。享元模式能做到共享的關鍵是區分內蘊狀態和外蘊狀態。內蘊狀態存儲在享元內部,不會隨環境的改變而有所不同。外蘊狀態是隨環境的改變而改變的。
行為型
- 策略:定義一系列的算法,把他們一個個封裝起來,並使他們可以互相替換,本模式使得算法可以獨立於使用它們的客戶。(游戲角色的武器)
- 模板方法:定義一個操作中的算法的骨架,而將一些步驟延遲到子類中,Template Method使得子類可以不改變一個算法的結構即可以重定義該算法得某些特定步驟。(具體依賴抽象,高層組件調用低層組件,避免環形依賴)
- 觀察者:定義對象間一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知自動更新。
- 迭代器:提供一個方法順序訪問一個聚合對象的各個元素,而又不需要暴露該對象的內部表示。
- 責任鏈:使多個對象都有機會處理請求,從而避免請求的送發者和接收者之間的耦合關系。
- 命令:將請求封裝為對象,從而使你可以用不同的請求對客戶進行參數化,對請求排隊和記錄請求日志,以及支持可撤銷的操作。(隊列請求,日志請求)
- 備忘錄:在不破壞對象的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。
- 狀態:允許對象在其內部狀態改變時改變他的行為。對象看起來似乎改變了他的類。
- 訪問者:表示一個作用於某對象結構中的各元素的操作,它使你可以在不改變各元素類的前提下定義作用於這個元素的新操作。
- 中介者:用一個中介對象封裝一些列的對象交互。
- 解釋器:給定一個上下文,定義他的配置參數,並定義一個解釋器,這個解釋器根據上下文的參數數據以特定的程式給出具體的解釋呈現。