面向對象的”六原則一法則”
- 單一職責原則:一個類只做它該做的事情。(單一職責原則想表達的就是”高內聚”,寫代碼最終極的原則只有六個字”高內聚、低耦合”,所謂的高內聚就是一個代碼模塊只完成一項功能,在面向對象中,如果只讓一個類完成它該做的事,而不涉及與它無關的領域就是踐行了高內聚的原則,這個類就只有單一職責。這個世界上任何好的東西都有兩個特征,一個是功能單一,好的相機絕對不是電視購物里面賣的那種一個機器有一百多種功能的,它基本上只能照相;另一個是模塊化,好的自行車是組裝車,從減震叉、剎車到變速器,所有的部件都是可以拆卸和重新組裝的,好的乒乓球拍也不是成品拍,一定是底板和膠皮可以拆分和自行組裝的,一個好的軟件系統,它里面的每個功能模塊也應該是可以輕易的拿到其他系統中使用的,這樣才能實現軟件復用的目標。)
- 開閉原則:軟件實體應當對擴展開放,對修改關閉。(在理想的狀態下,當我們需要為一個軟件系統增加新功能時,只需要從原來的系統派生出一些新類就可以,不需要修改原來的任何一行代碼。要做到開閉有兩個要點:①抽象是關鍵,一個系統中如果沒有抽象類或接口系統就沒有擴展點;②封裝可變性,將系統中的各種可變因素封裝到一個繼承結構中,如果多個可變因素混雜在一起,系統將變得復雜而換亂。
- 依賴倒轉原則:面向接口編程。(該原則說得直白和具體一些就是聲明方法的參數類型、方法的返回類型、變量的引用類型時,盡可能使用抽象類型而不用具體類型,因為抽象類型可以被它的任何一個子類型所替代,請參考下面的里氏替換原則。)
里氏替換原則:任何時候都可以用子類型替換掉父類型。(關于里氏替換原則的描述,Barbara Liskov女士的描述比這個要復雜得多,但簡單的說就是能用父類型的地方就一定能使用子類型。里氏替換原則可以檢查繼承關系是否合理,如果一個繼承關系違背了里氏替換原則,那么這個繼承關系一定是錯誤的,需要對代碼進行重構。例如讓貓繼承狗,或者狗繼承貓,又或者讓正方形繼承長方形都是錯誤的繼承關系,因為你很容易找到違反里氏替換原則的場景。需要注意的是:子類一定是增加父類的能力而不是減少父類的能力,因為子類比父類的能力更多,把能力多的對象當成能力少的對象來用當然沒有任何問題。)
- 接口隔離原則:接口要小而專,絕不能大而全。(臃腫的接口是對接口的污染,既然接口表示能力,那么一個接口只應該描述一種能力,接口也應該是高度內聚的。例如,琴棋書畫就應該分別設計為四個接口,而不應設計成一個接口中的四個方法,因為如果設計成一個接口中的四個方法,那么這個接口很難用,畢竟琴棋書畫四樣都精通的人還是少數,而如果設計成四個接口,會幾項就實現幾個接口,這樣的話每個接口被復用的可能性是很高的。Java中的接口代表能力、代表約定、代表角色,能否正確的使用接口一定是編程水平高低的重要標識。)
- 合成聚合復用原則:優先使用聚合或合成關系復用代碼。(通過繼承來復用代碼是面向對象程序設計中被濫用得最多的東西,因為所有的教科書都無一例外的對繼承進行了鼓吹從而誤導了初學者,類與類之間簡單的說有三種關系,Is-A關系、Has-A關系、Use-A關系,分別代表繼承、關聯和依賴。其中,關聯關系根據其關聯的強度又可以進一步劃分為關聯、聚合和合成,但說白了都是Has-A關系,合成聚合復用原則想表達的是優先考慮Has-A關系而不是Is-A關系復用代碼,需要說明的是,即使在Java的API中也有不少濫用繼承的例子,例如Properties類繼承了Hashtable類,Stack類繼承了Vector類,這些繼承明顯就是錯誤的,更好的做法是在Properties類中放置一個Hashtable類型的成員并且將其鍵和值都設置為字符串來存儲數據,而Stack類的設計也應該是在Stack類中放一個Vector對象來存儲數據。記住:任何時候都不要繼承工具類,工具是可以擁有并可以使用的,而不是拿來繼承的。)?
- 迪米特法則:迪米特法則又叫最少知識原則,一個對象應當對其他對象有盡可能少的了解。(迪米特法則簡單的說就是如何做到”低耦合”,門面模式和調停者模式就是對迪米特法則的踐行。對于門面模式可以舉一個簡單的例子,你去一家公司洽談業務,你不需要了解這個公司內部是如何運作的,你甚至可以對這個公司一無所知,去的時候只需要找到公司入口處的前臺美女,告訴她們你要做什么,她們會找到合適的人跟你接洽,前臺的美女就是公司這個系統的門面。再復雜的系統都可以為用戶提供一個簡單的門面,Java Web開發中作為前端控制器的Servlet或Filter不就是一個門面嗎,瀏覽器對服務器的運作方式一無所知,但是通過前端控制器就能夠根據你的請求得到相應的服務。調停者模式也可以舉一個簡單的例子來說明,例如一臺計算機,CPU、內存、硬盤、顯卡、聲卡各種設備需要相互配合才能很好的工作,但是如果這些東西都直接連接到一起,計算機的布線將異常復雜,在這種情況下,主板作為一個調停者的身份出現,它將各個設備連接在一起而不需要每個設備之間直接交換數據,這樣就減小了系統的耦合度和復雜度,如下圖所示。迪米特法則用通俗的話來將就是不要和陌生人打交道,如果真的需要,找一個自己的朋友,讓他替你和陌生人打交道。)
GC是什么?為什么要有GC?
Java語言沒有提供釋放已分配內存的顯示操作方法。Java程序員不用擔心內存管理,因為垃圾收集器會自動進行管理。要請求垃圾收集,可以調用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉顯示的垃圾回收調用。?
垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存。垃圾回收器通常是作為一個單獨的低優先級的線程運行,不可預知的情況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收。
垃圾回收機制有很多種,包括:分代復制垃圾回收、標記垃圾回收、增量垃圾回收等方式。標準的Java進程既有棧又有堆。棧保存了原始型局部變量,堆保存了要創建的對象。Java平臺對堆內存回收和再利用的基本算法被稱為標記和清除,但是Java對其進行了改進,采用“分代式垃圾收集”。這種方法會跟Java對象的生命周期將堆內存劃分為不同的區域,在垃圾收集過程中,可能會將對象移動到不同區域:?
- 伊甸園(Eden):這是對象最初誕生的區域,并且對大多數對象來說,這里是它們唯一存在過的區域。?
- 幸存者樂園(Survivor):從伊甸園幸存下來的對象會被挪到這里。?
- 終身頤養園(Tenured):這是足夠老的幸存對象的歸宿。年輕代收集(Minor-GC)過程是不會觸及這個地方的。當年輕代收集不能把對象放進終身頤養園時,就會觸發一次完全收集(Full-GC),這里可能還會牽扯到壓縮,以便為大對象騰出足夠的空間。
-方法區,Class和常量池,(Major-GC)
與垃圾回收相關的JVM參數:
- -Xms / -Xmx — 堆的初始大小 / 堆的最大大小
- -Xmn — 堆中年輕代的大小
- -XX:-DisableExplicitGC — 讓System.gc()不產生任何作用
- -XX:+PrintGCDetails — 打印GC的細節
- -XX:+PrintGCDateStamps — 打印GC操作的時間戳
- -XX:NewSize / XX:MaxNewSize — 設置新生代大小/新生代最大大小
- -XX:NewRatio — 可以設置老生代和新生代的比例
- -XX:PrintTenuringDistribution — 設置每次新生代GC后輸出幸存者樂園中對象年齡的分布
- -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設置老年代閥值的初始值和最大值
- -XX:TargetSurvivorRatio:設置幸存區的目標使用率
如何實現對象克隆??
有兩種方式:?
??1). 實現Cloneable接口并重寫Object類中的clone()方法;?
??2). 實現Serializable接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深度克隆。
Java 中會存在內存泄漏
理論上Java因為有垃圾回收機制(GC)不會存在內存泄露問題(這也是Java被廣泛使用于服務器端編程的一個重要原因);然而在實際開發中,可能會存在無用但可達的對象,這些對象不能被GC回收,因此也會導致內存泄露的發生。例如hibernate的Session(一級緩存)中的對象屬于持久態,垃圾回收器是不會回收這些對象的,然而這些對象中可能存在無用的垃圾對象,如果不及時關閉(close)或清空(flush)一級緩存就可能導致內存泄露。?
描述一下JVM加載class文件的原理機制?
JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實現的。由于Java的跨平臺性,經過編譯的Java源程序并不是一個可執行程序,而是一個或多個類文件。當Java程序需要使用某個類時,JVM會確保這個類已經被加載、連接(驗證、準備和解析)和初始化。類的加載是指把類的.class文件中的數據讀入到內存中,通常是創建一個字節數組讀入.class文件,然后產生與所加載類對應的Class對象。加載完成后,Class對象還不完整,所以此時的類還不可用。當類被加載后就進入連接階段,這一階段包括驗證、準備(為靜態變量分配內存并設置默認的初始值)和解析(將符號引用替換為直接引用)三個步驟。最后JVM對類進行初始化,初始化時機包括:1)如果類存在直接的父類并且這個類還沒有被初始化,那么就先初始化父類;2)如果類中存在初始化語句,就依次執行這些初始化語句(5條語句,new,static,reflect等)。?
類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)。類加載過程采取了父親委托機制(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根加載器,其他的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能為力時才由其子類加載器自行加載。JVM不會向Java程序提供對Bootstrap的引用。
如何理解AOP中的連接點(Joinpoint)、切點(Pointcut)、增強(Advice)、引介(Introduction)、織入(Weaving)、切面(Aspect)?
a. 連接點(Joinpoint):程序執行的某個特定位置(如:某個方法調用前、調用后,方法拋出異常后)。一個類或一段程序代碼擁有一些具有邊界性質的特定點,這些代碼中的特定點就是連接點。Spring僅支持方法的連接點。
b. 切點(Pointcut):如果連接點相當于數據中的記錄,那么切點相當于查詢條件,一個切點可以匹配多個連接點。Spring AOP的規則解析引擎負責解析切點所設定的查詢條件,找到對應的連接點。
c. 增強(Advice):增強是織入到目標類連接點上的一段程序代碼。Spring提供的增強接口都是帶方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。
d. 引介(Introduction):引介是一種特殊的增強,它為類添加一些屬性和方法。這樣,即使一個業務類原本沒有實現某個接口,通過引介功能,可以動態的未該業務類添加接口的實現邏輯,讓業務類成為這個接口的實現類。
e. 織入(Weaving):織入是將增強添加到目標類具體連接點上的過程,AOP有三種織入方式:①編譯期織入:需要特殊的Java編譯期(例如AspectJ的ajc);②裝載期織入:要求使用特殊的類加載器,在裝載類的時候對類進行增強;③運行時織入:在運行時為目標類生成代理實現增強。Spring采用了動態代理的方式實現了運行時織入,而AspectJ采用了編譯期織入和裝載期織入的方式。
f. 切面(Aspect):切面是由切點和增強(引介)組成的,它包括了對橫切關注功能的定義,也包括了對連接點的定義。
如何在Web項目中配置Spring的IoC容器?
如果需要在Web項目中使用Spring的IoC容器,可以在Web項目配置文件web.xml中做出如下配置:
<context-param>
? ? <param-name>contextConfigLocation</param-name>
? ? <param-value>classpath:applicationContext.xml</param-value>
</context-param>
?
<listener>
? ? <listener-class>
? ? ? ? org.springframework.web.context.ContextLoaderListener
? ? </listener-class>
</listener>
使用tomcat的監聽器ContextLoaderListener(實現ContextLoaderListener)初始化spring ioc
如何在Web項目中配置Spring MVC?
要使用Spring MVC需要在Web項目配置文件中配置其前端控制器DispatcherServlet,如下所示:
<web-app>
? ? <servlet>
? ? ? ? <servlet-name>example</servlet-name>
? ? ? ? <servlet-class>
? ? ? ? ? ? org.springframework.web.servlet.DispatcherServlet
? ? ? ? </servlet-class>
? ? ? ? <load-on-startup>1</load-on-startup>
? ? </servlet>
? ? <servlet-mapping>
? ? ? ? <servlet-name>example</servlet-name>
? ? ? ? <url-pattern>*.html</url-pattern>
? ? </servlet-mapping>?
</web-app>
說明:上面的配置中使用了*.html的后綴映射,這樣做一方面不能夠通過URL推斷采用了何種服務器端的技術,另一方面可以欺騙搜索引擎,因為搜索引擎不會搜索動態頁面,這種做法稱為偽靜態化。
大型網站在架構上應當考慮哪些問題?
- 分層:分層是處理任何復雜系統最常見的手段之一,將系統橫向切分成若干個層面,每個層面只承擔單一的職責,然后通過下層為上層提供的基礎設施和服務以及上層對下層的調用來形成一個完整的復雜的系統。計算機網絡的開放系統互聯參考模型(OSI/RM)和Internet的TCP/IP模型都是分層結構,大型網站的軟件系統也可以使用分層的理念將其分為持久層(提供數據存儲和訪問服務)、業務層(處理業務邏輯,系統中最核心的部分)和表示層(系統交互、視圖展示)。需要指出的是:(1)分層是邏輯上的劃分,在物理上可以位于同一設備上也可以在不同的設備上部署不同的功能模塊,這樣可以使用更多的計算資源來應對用戶的并發訪問;(2)層與層之間應當有清晰的邊界,這樣分層才有意義,才更利于軟件的開發和維護。?
- 分割:分割是對軟件的縱向切分。我們可以將大型網站的不同功能和服務分割開,形成高內聚低耦合的功能模塊(單元)。在設計初期可以做一個粗粒度的分割,將網站分割為若干個功能模塊,后期還可以進一步對每個模塊進行細粒度的分割,這樣一方面有助于軟件的開發和維護,另一方面有助于分布式的部署,提供網站的并發處理能力和功能的擴展。
- 分布式:除了上面提到的內容,網站的靜態資源(JavaScript、CSS、圖片等)也可以采用獨立分布式部署并采用獨立的域名,這樣可以減輕應用服務器的負載壓力,也使得瀏覽器對資源的加載更快。數據的存取也應該是分布式的,傳統的商業級關系型數據庫產品基本上都支持分布式部署,而新生的NoSQL產品幾乎都是分布式的。當然,網站后臺的業務處理也要使用分布式技術,例如查詢索引的構建、數據分析等,這些業務計算規模龐大,可以使用Hadoop以及MapReduce分布式計算框架來處理。?
- 集群:集群使得有更多的服務器提供相同的服務,可以更好的提供對并發的支持。
- 緩存:所謂緩存就是用空間換取時間的技術,將數據盡可能放在距離計算最近的位置。使用緩存是網站優化的第一定律。我們通常說的CDN、反向代理、熱點數據都是對緩存技術的使用。
- 異步:異步是實現軟件實體之間解耦合的又一重要手段。異步架構是典型的生產者消費者模式,二者之間沒有直接的調用關系,只要保持數據結構不變,彼此功能實現可以隨意變化而不互相影響,這對網站的擴展非常有利。使用異步處理還可以提高系統可用性,加快網站的響應速度(用Ajax加載數據就是一種異步技術),同時還可以起到削峰作用(應對瞬時高并發)。能推遲處理的都要推遲處理”是網站優化的第二定律,而異步是踐行網站優化第二定律的重要手段。
- 冗余:各種服務器都要提供相應的冗余服務器以便在某臺或某些服務器宕機時還能保證網站可以正常工作,同時也提供了災難恢復的可能性。冗余是網站高可用性的重要保證。
?