設計模式之我的歸納總結

算法和設計模式在大廠面試比較重要 所以歸納一下 只講述一些比較有意義的點 其他的很多基礎性知識網絡上都有

首先要弄清楚,很多設計模式解決方案或者代碼實現是很相似的 主要需要知道設計意圖 爲了解決什麼場景,不同設計模式的主要區別就在於這裏

可維護性:
修改添加代碼不引入新BUG 不必花很長的時間     維護代碼的工作一般會佔很多 所以很重要
可讀性:
代碼可讀性 評價代碼質量最重要的指標之一  也會影響代碼可維護性  
	編碼規範、命名是否達意、註釋是否詳盡、函數是否長短合適、模塊劃分是否清晰、是否符合高內聚低耦合等等
可擴展性:
代碼應對未來需求變化的能力 新功能直接插入而不需要改動大量原始代碼
靈活性:
可擴展 易複用(底層已經寫了很多可複用的模塊) 接口可以複用很多應用場景
可複用性
可測試性

面向對象

面向對象編程:類 對象 封裝 抽象 繼承 多肽
面向對象編程語言是支持類或對象的語法機制,並有現成的語法機制,能方便地實現面向對象編程四大特性(封裝、抽象、繼承、多態)的編程語言。

多肽不只是java(靜態語言)中的繼承,像python(動態語言)的duck typing也是多胎:只要類有相同的方法即可 並不需要這兩個類有具體的關係

1)注意不要濫用get set方法:沒必要全部屬性都加上get set 按需  否則就退化成面向過程的方法了 破壞了封裝性
			注意尤其是數組 不要隨意的暴露對象出去  外界可以拿到這個數據直接進行清空或者更改數據的操作 ----------要清空修改數據就要改造成在類裏面加一個方法 透明地提供給外界使用(封裝性)
	
	如果一定需要get獲取到數據 可以調用Collections.unmodifiableList() 來返回一個不可被修改的list 這個是Collections集合的內部類		----這種方法有問題 就是獲取到的數組沒法改 但是get到的數組內部的數據,單個的對象數據可以被改
	還有種方法就是利用內部封裝的方法直接返回結果 

在這裏插入圖片描述

2)不要濫用全局變量和方法
	例如不要把全部的靜態變量放在一個配置類中 
			很容易導致更改這個類後 所有引用這個配置類的類重編譯
	可以把配置類分門別類  也可以把需要用到的靜態屬性直接寫在類裏面--這樣子這個類的複用性和內聚性大大提升

	不要濫用Utils類 如果可以寫在其他類裏面內聚是比較好的   沒有屬性只有方法的Utils類其實是面向過程的,雖然這沒有多大的影響 
	
	很多時候在我們實際開發中並沒有用好面對對象的開發 而是直接寫成了面向過程的開發:pojo BO VO ENTITY都只保存着數據 業務邏輯都在service裏  

接口和抽象類

實際開發中: 如果爲了表示is a關係 並且實現代碼複用 就用抽象類(自下而上 類有複用的代碼了 就想辦法抽象成抽象類 複用代碼 設計模版 複用變量)
					  如果爲了表示has a關係 是提供功能接口的 爲了解決抽象而不是複用的 就用接口(自上而下的 想提供一個功能的接口了 就設計一個接口 再設計具體的接口實現)

 越抽象 越頂層 越脫離具體實現 越能提高靈活性 
 注意抽象意識   接口意識 (只定義上層真正有益抽象出來的方法 具體其他實現方法交給具體類 而且最好這些方法在具體類中保持private       接口只表明做什麼 而不是怎麼做     接口儘量保證替換接口具體實現類的時候不用改變接口)  封裝意識(沒必要對外暴露的不暴露)

如果某個功能只有一種實現方式 也不可能未來被替代了 就可以不用接口了 ,對於不穩定的系統 將來需要更改維護的尤其重要保證其擴展性

該用組合還是繼承???

繼承的問題:
繼承層次過深、繼承關係過於複雜會影響到代碼的可讀性和可維護性 


完全可以利用 組合+接口 來完全替代掉繼承 :
例如寫不同的功能接口 每個接口用實現類實現這個接口 完成方法書寫    在需要使用的類中實現這個接口(多肽)  在類中利用組合加入實現了具體功能的類(複用),在這個類的方法中利用成員變量調用相應的方法即可 而無需自己再實現一遍

可知組合的問題:
需要定義更多的接口和類 類的劃分更加細粒度(代碼的複雜程度和維護成本)

如果系統 繼承結構不穩定經常變動 層次很深 繼承關係複雜 =====儘量用組合代替繼承
有的情況又只能用繼承:我們繼承外部給的類(沒權限修改) 重寫裏面所需的方法
有的情況只能用組合: 類和類用需要公用的方法 但是兩者並沒有父子關係   所以利用組合 例如util工具類 

基於充血模型的DDD開發模式

輕 service   重domain    在pojo對象中寫業務邏輯,從一開始就要設計好針對數據要暴露哪些操作
需要我們前期做大量的業務調研、領域模型設計 前期設計上    對複雜的系統很好 
充血模型的DDD開發更復用 ,所有擴展都是基於之前定義好的領域模型(可複用的業務中間層),而不是像傳統貧血模式開發 充斥着大量的只有小改變 大部分重複的sql(很多功能都充斥在sql中)

充血模型只是爲了擴大複雜業務系統的可複用性  所以沒必要在controller或者repository上也換成充血模型

充血模型中 service負責一些不適合放在 Domain 類中的功能。比如,負責與 Repository 層打交道、跨領域模型的業務聚合功能、冪等事務等非功能性的工作

類和類之間的關係

在設計領域模型的時候 拿到需要我們分析類的關係的時候   所以要知道這些設計模式
 (1)泛化:就是繼承
 (2)實現
 (3)聚合:類似組合 但是對象一般是從外部傳遞進來的 所以生命週期不受本對象的控制
 (4)組合:被組合的對象生命週期依賴組合的對象  因爲是在組合的對象內部創建的
 (5)關聯:聚合 組合
 (6)依賴:只要兩個對象有任何的關係 就交依賴 即使是從方法參數

單一職責原則(SRP)

一個類只負責完成一個職責或功能 不要設計大而全的類,要設計粒度小、功能單一的類 
如果調用者只使用部分接口或接口的部分功能 那接口的設計就不夠職責單一

高內聚 低耦合

判斷不符合單一職責的
類中的代碼行數、函數或者屬性過多---這是一條抽象的規則 也是爲了複用 好管理 擴展
類依賴的其他類過多,或者依賴類的其他類過多  ----
私有方法過多 ----拆成適當的其他類 共有方法  提高複用率    可以把原本public的方法抽象到頂層service 利用底層多個帶有public方法的可複用類來完成業務 (充血型ooD)
比較難給類起一個合適的名字;----因爲你難以知道這個類的功能 所以纔沒辦法起名字
類中大量的方法都是集中操作類中的某幾個屬性。

開閉原則

並不是說不可以修改原有的類  只要它沒有破壞原有的代碼的正常運行,沒有破壞原有的單元測試

大部分的設計模式都是爲了解決系統擴展性 即開閉原則想要達到的目的

里氏替換原則

很像多肽  可以替換父類出現的任何位置,並且原來代碼的邏輯行爲不變且正確性也沒有被破壞 不能改變結果!例如拋出個異常  
			函數聲明要實現的功能;對輸入、輸出、異常的約定;甚至包括註釋中所羅列的任何特殊說明

接口隔離

如果部分接口只被部分調用者使用,那我們就需要將這部分接口隔離出來,單獨給對應的調用者使用,而不是強迫其他調用者也依賴這部分不會被用到的接口

部分調用者只需要函數中的部分功能,那我們就需要把函數拆分成粒度更細的多個函數,讓調用者只依賴它需要的那個細粒度函數

接口的設計要儘量單一,不要讓接口的實現類和調用者,依賴不需要的接口函數

控制反轉 依賴注入 依賴反轉

控制反轉
程序員利用框架進行開發的時候,只需要往預留的擴展點上,添加跟自己業務相關的代碼,就可以利用框架來驅動整個程序流程的執行 程序執行流程的控制從程序元解放到了框架

依賴注入
不通過 new() 的方式在類內部創建依賴類對象,而是將依賴的類對象在外部創建好之後,通過構造函數、函數參數等方式傳遞(或注入)給類使用。

我們只需要通過依賴注入框架提供的擴展點,簡單配置一下所有需要創建的類對象、類與類之間的依賴關係,就可以實現由框架來自動創建對象、管理對象的生命週期、依賴注入等原本需要程序員來做的事情

依賴反轉
高層模塊不要依賴底層模塊 而是共同依賴一個抽象(規則),例如tomcat和應用程序 都依賴servlet規則

kiss YAGNI原則

kiss原則:
要保持代碼simple and stupid  -------  方便檢查 易懂 少bug

並不是說代碼少就是簡單的  還要考慮邏輯複雜度 性能 實現難度 代碼可讀性  ,如果優化這段代碼需要很複雜 但是對性能貧瘠又很重要 那就適合kiss原則
設計模式 開發中一個通用的概念:越是能用簡單的方法處理複雜的問題 越能體現你的能力!

yagni:
不要設計過度 不要設計當前用不到的功能和代碼  但是還是要保存好代碼的擴展節點
沒有必要提前引入所有開發庫

DRY:
不要寫功能重複的代碼  功能不重複但是語意重複的代碼 可以抽象出公用的方法來,也保證未來需要重構某個方法的時候更方便 複用 可擴展(entity vo bo的設計是相似的)
	也不要執行流程上代碼重複  例如多次查詢數據庫 多次校驗

重構項目代碼

保持功能不變的前提下,利用設計思想、原則、模式、編程規範等理論來優化代碼,修改設計上的不足,提高代碼質量,需要養成重構的思維      初步設計+不斷重構纔是一個項目的正確發展路線

代碼質量評判標準來評判代碼的整體質量  -》 對照設計原則來發現代碼存在的具體問題  -》 用設計模式或者編碼規範對存在的問題進行改善

函數的返回值

異常
		--下面這兩個一般在不是異常但是找不到的情況返回
null			找不到對象
數字				找不到數據
		---下面這兩個好處就是不用判斷null  可以直接調用length或者for循環 
空字符串
空集合

設計模式:
設計模式—創建型

1)單例模式
		可以處理資源訪問的衝突
		保證全局唯一
----餓漢 懶漢 雙重檢查 靜態內部類 最好的:枚舉(重置序列化 反射調用構造器無效)2)工廠模式  :簡單工廠、工廠方法和抽象工廠    ---單獨的工廠類來創建對象
簡單工廠模式類:DateFormat Calender就是簡單工廠模式  --- 直接new對象 而不是通過實現類創建對象
		1 可以每次方法傳參生成一個新的對象
		2 也可以結合單例模式 用hashmap存儲string - 對象    每次調用去拿到對應的已經生成在內存中的類即可
		spring 對應的就是 scope=prototype 表示返回新創建的對象,scope=singleton 表示返回單例對象

工廠方法:更符合開筆原則 , 
	使用接口實現類模式 沒需要一種新的對象時候就實現一個新的實現類
	可以用工廠對象的工廠類來去除掉硬編碼的if else 邏輯

抽象工廠:用組合方式包容多個工廠類對象  可以用這個工廠類創建多個不同的類對象 而不是一個工廠類創建一種類對象

依賴注入容器底層就是用的工廠模式 負責整個應用中所有類對象的創建
DI容器:
DI容器的功能:配置解析 對象創建 對象生命週期的管理
(1)通過配置文件創建需要的對象和對象中的參數 
(2)所有對象通過BeansFactory來創建即可 -- 抽象工廠模式 ,不需要每個工廠類創建具體對象這樣項目類太龐大,
	是通過反射來動態創建對象的 所以不需要擔心BeansFactory代碼跟着類的增加膨脹
(3)對象生命週期的管理
怎麼實現一個自己的簡單的DI容器呢:
	1配置文件解析
	2根據配置文件通過“反射”語法來創建對象。
入口類包括 一個factory 一個parse ,加載類路徑下的配置文件 然後通過parse進行解析成對象的類 通過factory來動態創建類對象,  入口類提供getBean方法來從factory中獲取你要的類文件

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

BeansFactory通過解析到的BeanDefinition 利用反射 來創建對象
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();  
private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();  
利用singletonObjects來存儲單例對象 在創建容器的時候就直接創建,利用beanDefinitions來存儲id 和具體的BeanDefinition 後面動態創建對象的時候來使用

(3)建造者模式
 可以把建造者builder放在類的內容,功能是校驗邏輯,創建建造者,並且通過 set() 方法設置建造者的變量值,然後在使用 build() 方法進行集中的校驗,然後真正創建對象  , 也可以使得對象不存在無效的狀態 構造者創建出來的對象一定是有效的

雖然也可以在構造函數中一次性把對象創建好 但是參數過多的時候就很沒有靈活性了,代碼可讀性也差

(4)原型模式
利用對已有對象(原型)進行復制(或者叫拷貝)的方式來創建新對象 (對象創建時間成本大 例如排序  RPC、網絡、數據庫、文件系統等)

在更新對象的時候 可以利用拷貝對象進行更新 更新完畢後一次性替換對象 

淺拷貝得到的屬性中的對象跟原始對象共享數據,是指向同一個對象
深拷貝得到的是一份完完全全獨立的對象

設計模式—結構型

主要總結了一些類或對象組合在一起的經典結構,這些經典的結構可以解決特定應用場景的問題

(1)代理模式:
		方式1實現同一個接口  jdk動態代理
		方式2繼承被代理類  cglib
		經常用來開發非功能性需求:監控、統計、鑑權、限流、事務、冪等、日誌
(2)橋接模式:
	JDBC就是僑接模式 
	詳細的分析系統功能,將各個獨立的緯度都抽象出來,使用時按需組合
	將抽象和實現解耦,讓它們可以獨立變化
(3)裝飾器模式:
1裝飾器類和原始類繼承同樣的父類,這樣我們可以對原始類“嵌套”多個裝飾器類
2裝飾器類是對功能的增強,這也是裝飾器模式應用場景的一個重要特點
代理類附加的是跟原始類無關的功能,而在裝飾器模式中,裝飾器類附加的是跟原始類相關的增強功能。

舉個例子: bufferdinpustream和 FilterInputStream 和 inputsteam的設計就很有意思:
		FilterInputStream充當真正裝飾器的類 獨自繼承inputsteam ,而其他的bufferdinpustream繼承FilterInputStream,利用FilterInputStream裏裝配好的INputsteam   而不是把每次裝備inputsteram的任務交給每一個有增強功能的類 例如bufferdinpustream,datainputstream等  這樣就避免了重複實現,無論是重複實現裝飾器模式 還是重複實現一些沒有增強功能的代碼(否則外界無法使用)
(4)適配器模式:
1類適配器   使用繼承

在這裏插入圖片描述

2對象適配器

在這裏插入圖片描述

接口方法很多的話可以用類適配器(適配器類繼承外部需要被接口的類 而不是組合)因爲可以複用父類的代碼,但是如果接口定義和外部類都大部分不同 那隻能用組合了

適配器模式主要用來解決接口不兼容問題
1隔離設計上的缺陷,我們希望對外部系統提供的接口進行二次封裝,抽象出更好的接口設計
	或者說外部類來自外部sdk 我們無權修改代碼 就只能利用自己的適配器類來進行封裝接入
2某個功能的實現依賴多個外部系統(或者說類)。通過適配器模式,將它們的接口適配爲統一的接口定義,然後我們就可以使用多態的特性來複用代碼邏輯
3把項目中依賴的一個外部系統替換爲另一個外部系統的時候,利用適配器模式,可以減少對代碼的改動,達到擴展的目的 而不是修改原代碼框架
4解決版本升級 例如jdk的 Enumeration ,已經廢棄 使用 iterator 但是爲了兼容老版本 返回的仍然是Enumeration 只不過內部邏輯封裝的是iterator
5適配不同格式的數據

小總結:
代理模式:主要目的是控制訪問,而非加強功能
橋接模式:橋接模式的目的是將接口部分和實現部分分離,從而讓它們可以較爲容易、也相對獨立地加以改變。
裝飾器模式:裝飾者模式在不改變原始類接口的情況下,對原始類功能進行增強,並且支持多個裝飾器的嵌套使用。
適配器模式:適配器模式是一種事後的補救策略。適配器提供跟原始類不同的接口,而代理模式、裝飾器模式提供的都是跟原始類相同的接口。

(5)門面模式:解決接口粒度粗細問題導致的可複用性(通用性)和易用性之間的矛盾
例如後端提供一個調用了三個接口的總接口給前端使用 這樣就解決了前端需要調用三個接口導致的性能問題
1封裝系統的底層實現,隱藏系統的複雜性,提供一組更加簡單易用、更高層的接口
2解決性能問題
3解決分佈式事務問題:
  可以利用 分佈式事務框架   或者 事後補償機制
  最簡單的是用:利用數據庫事務或者 Spring 框架提供的事務 在一個事務中 利用門面模式 在一個接口中調用兩個分佈式的sql操作—— 利用spring事務的傳播特性來處理!
(6)組合模式:
組合模式並不是組合關係,數據需要滿足樹型結構 一般用到遞歸(鏈表) 遞歸遍歷(樹)   是一種用java面嚮對象語言抽象出來的數據結構    所有的樹型結構都可以當作file director模型進行構造-------目錄下面包括file 和目錄 都是extends一樣的父類
(7)享元模式:複用對象,節省內存,前提是享元對象是不可變對象  (或者相似對象提取共同屬性造成享元對象 大家都引用他  因爲大家都引用他 避免屬性修改所以弄成不可變對象 屬性不可改變 對外不提供set方法) 也就是說享元對象只提供read方法
		大部分對象的某些屬性用的是同一部份的對象 提升效率 節省內存
享元模式和單例模式代碼結構不一樣(跟多例像一點) 設計意圖也不一樣,跟緩存的目的也不同 主要是爲了複用 
	和線程池等池化技術也不一樣  池化爲了節省創建時間 併發 每個對象是被一個使用的   而享元模式更多的是被多個大部分對象共享以節省空間

設計模式—行爲型
解決 類或對象之間的交互 問題

(1)觀察者模式:被觀察者通知觀察者 
同步阻塞 異步非阻塞 進程內 進程間(例如消息中間介)
(2)模版模式:  解決複用和擴展
定義一個算法骨架,並將某些步驟推遲到子類中實現,讓子類在不改變算法整體結構的情況下,重新定義算法中的某些步驟。 ----例如 各種生命週期流程中的鉤子函數
  1 複用  : 父類定義好的流程模版可以被任何子類重複使用 只需要自己實現好自己的模版函數即可
  2擴展:指的是框架的擴展性 例如servlet框架 doget dopost請求都是在service的流程中調用的 ,這樣用戶不需要修改servlet框架或者知道具體細節

講一下回掉函數 : 依然是給框架提供了擴展的功能 框架A的a方法中提供擴展點  B調用A的a方法 傳入B的b方法 a方法流程中就會調用B的b方法  這就是回掉

一般來說回調 比 模板會好 --- 組合和繼承的分別
(3)策略模式:
將每個算法分別封裝起來,讓它們可以互相替換。策略模式可以使算法的變化獨立於使用它們的客戶端
1省略 if else 語句  就像是工廠模式中用hashmap保存對象來獲取一樣的套路
2可以寫在配置文件中 或者 註解標誌  通過反射動態獲取 再到1中去獲取來減少代碼的改動

(4)責任鏈模式:
將請求的發送和接收解耦,讓多個接收對象都有機會處理這個請求。將這些接收對象串成一條鏈,並沿着這條鏈傳遞這個請求,直到鏈上的某個接收對象能夠處理它爲止
A ——》 b ——》c    依次按照自己職責處理任務,可以設置一個chain類來管理鏈條 head tail屬性即可 然後調用的時候就是加入handle類 調用handle方法 一個一個按照鏈上的邏輯進行遞歸調用查詢是否可以處理(也可以用數組保存handler對象而不是一定要鏈表)
可以結合模版模式 定製抽象類的責任鏈模版 子類只需要重寫流程中的定製方法

應用場景:過濾敏感詞,springmvc的攔截器 web容器的filter都是數組責任鏈模式
主要目的:解耦ifelse 連着判斷的語句  爲了擴展性 複雜系統 開閉原則 更靈活(可以選擇你要做的方法)
(5)狀態模式:
首先理解狀態機:
1if else邏輯判斷witch case   來完成狀態轉移  對於複雜的狀態機肯定不好 
2查表法    通過保存二維數組來保存 狀態和對應的動作  通過配置文件就可以控制 
3狀態模式:
狀態模式通過將事件觸發的狀態轉移和動作執行,拆分到不同的狀態類中,來避免分支判斷邏輯
原理: 雙向綁定  
狀態機類 持有狀態類---》持有狀態 和 動作   
狀態類 持有狀態機類  調用動作函數  更改所指向的狀態機類的 狀態類 並且更改狀態機類的值(例如如果有socre等需要增減的話)

狀態特別多的情況用查表法會更好否則會有很多的狀態類    而狀態少動作業務邏輯複雜的用狀態模式比較好

(6)
迭代器模式:    用來遍歷集合對象,將集合對象的遍歷操作從集合類中拆分出來,放到迭代器類中
迭代器(基於接口編程) 通過組合方式 放在容器(基於接口編程)中
迭代器中需要定義 hasNext()、currentItem()、next() 三個最基本的方法 不一定要這樣。待遍歷的容器對象通過依賴注入傳遞到迭代器類中。容器通過 iterator() 方法來創建迭代器
遍歷方式總結:
1for循環遍歷
2迭代器遍歷(foreach是語法糖 底層也是迭代器)
		迭代器好處:(1)處理複雜遍歷 例如圖和樹的遍歷(將遍歷操作拆分到迭代器類中 而不是通過程序員重複在業務上實現)
							  (2)可以創建多個不同的迭代器,同時對同一個容器進行遍歷而互不影響(通過遊標)							(3)迭代器基於接口編程 更改算法 添加迭代器都方便很多 擴展性好 符合開閉原則
注意: 在遍歷的過程中刪除集合元素,結果是不可預期的(不可預期的錯誤很有可能引來很多隱藏很深的bug) 有可能會導致此次遍歷某些元素遍歷不到(被刪除了+遊標位置導致)
		在遍歷中增加元素 可能會導致重複遍歷
所以java語言選擇了在迭代中增刪進行報錯處理:設置一個變量 遍歷時候檢查是否相等(調用修改函數就增加變量) 若不等,選擇 fail-fast 解決方式,拋出運行時異常,結束掉程序
java iterator的remove方法設置cursor變量來保證遍歷中刪除不會出錯(只能刪除遍歷的前一個元素
 且不可以連續調用remove進行刪除)

可以用快照來解決讀遍歷和寫的問題(我發現java並沒有實現):
實現方式(1):每次調用iterator方法的時候拷貝一份到iterator中   --- 佔用內存   ,但是因爲java核心類實現的拷貝的淺拷貝 不會有內存問題,被引用的對象也不會失效 因爲被iterator引用了
			(2):兩個數組(一個用來支持時間戳 一個用來支持數組隨機查詢)
						兩個時間戳數組  記錄數組中元素的加入時間和刪除時間(一個數組不真正進行刪除 記錄時間戳 提供快照功能   一個數組進行真正的刪除操作 用來客戶端需要針對隨機訪問功能--因爲記錄快照的數組沒有進行真正的刪除無法提供隨機訪問功能--相對的   不一定要這樣 有很多種方法可以實現)
(7)訪問者模式:
允許一個或者多個操作應用到一組對象上,解耦操作和對象本身

精髓---- 如果某個類的方法有重載 但是都是子類  而傳遞的函數是用了多態的編譯期父類 這種情況可以在子類內定義方法 傳遞進來重載的函數  調用this當作參數 這個this是可以通過編譯的!

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

Single Dispatch:執行哪個對象的方法,根據對象的運行時類型來決定;執行對象的哪個方法,根據方法參數的編譯時類型來決定
Double Dispatch:是執行哪個對象的方法,根據對象的運行時類型來決定;執行對象的哪個方法,根據方法參數的運行時類型來決定   ----   訪問者模式思想的核心 用single dispatch的語言來實現double dispatch,如果語言支持了double dispatch就沒必要用訪問者模式了
	當前主流的面向對象編程語言(比如,Java、C++、C#)都只支持 Single Dispatch,不支持 Double Dispatch
如果工具的功能很多的話推薦使用訪問者模式(類定義要相比工廠模式少很多),否則可以使用工廠模式 畢竟訪問者模式不好理解
(8)備忘錄模式:防止丟失、撤銷、恢復   (快照模式)
在不違背封裝原則的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,以便之後恢復對象爲先前的狀態   像linux的快照一樣
最簡單的方法 維持一個stack 每次輸入一個對象 在內存中保持這個對象最新的狀態  把沒被修改的狀態push進去保存

爲了應付內存 可以使用 低頻率 全量備份 + 高頻率增量備份   的方法
也可以不記錄完整的內容而記錄其他信息來進行恢復 例如一個大文件 可以記錄字節數或者文字數 配合手頭的對象進行恢復
(9)命令模式:
把函數封裝成對象 因爲對象可以存儲 以此達到: 控制命令的執行 異步、延遲、排隊執行命令、撤銷重做命令、存儲命令、給命令記錄日誌等
一般處理客戶端服務器交互兩種方式:(1)主線程接受 開啓新的線程處理 (2)同一個線程接受處理 不斷輪詢   這個在手遊開發中比較多使用  避免io密集型業務中線程不斷切換帶來的性能消耗
(10)解釋器模式:
根據語法規則對“語言”進行解讀
將語法規則拆分成一些小的獨立的單元,然後對每個單元進行解析,最終合併爲對整個語法規則的解析(例如加減乘除  用類表示數字 用類表示操作)
(11)	中介模式:
定義了一個單獨的(中介)對象,來封裝一組對象之間的交互。將這組對象之間的交互委派給與中介對象交互,來避免對象之間的直接交互
例如把分散在不同控件(或者類)中的業務邏輯集中到中介類,但是可能會使得中介類龐大,根據實際的情況,平衡對象之間交互的複雜度和中介類本身的複雜度

中介和觀察者的差別:
(1)可以按自定義順序調用參與者 而觀察者是不可以的
(2)參與者交互關係錯綜複雜 維護成本很高的時候才使用中介模式   有可能產生大而複雜的上帝類

接下來結合實際應用實戰演練介紹設計模式

 (1)canlendar類:getInstance方法是工廠方法 耦合在了canlendar類中與其他功能方法一起
 							內置了一個Builder構造者模式定製化創建canlendar子類對象
(2)Collection類的裝飾器模式:內部類UnmodifiableCollection 的構造函數接收一個 Collection 類對象,然後對其所有的函數進行了包裹(Wrap),來增強Collecttion類的功能(變成無法修改)
(3)Collections中的適配器模式:
public static <T> Enumeration<T> enumeration(final Collection<T> c) {
        return new Enumeration<T>() {
            private final Iterator<T> i = c.iterator();

            public boolean hasMoreElements() {
                return i.hasNext();
            }

            public T nextElement() {
                return i.next();
            }
        };
       				 模板模式:sort函數
(4)jdk觀察者模式 Observable  Observer:       ---------特殊說明 vector類不是線程安全的 只是每個方法是安全的 但是符合操作不一定安全    可以(1)把符合操作變成原子操作  (2)system。copy複製一個數組局部變量(快照 多線程編程中減小鎖粒度、提高併發性能的常用方法  但是也會有其他問題 例如新增元素無法通知到 刪除元素其他線程無法當作是刪除的 具體好不好用看具體的業務場景)  對這個數組的刪除不回影響到原數組該位置上的元素
(5)runtime類中的單例模式:  就是餓漢 							

怎麼應對複雜的系統(業務代碼量很大的 而不是技術難度很高的–人工智能等的項目)

(1)抽象封裝 例如unix系統的一切皆文件
(2)模塊化 ,每個小的團隊聚焦於一個獨立的高內聚模塊來開發
(3)分層    計算機任何問題都可以用分層來解決 每一層都對上層封裝實現細節,暴露抽象的接口來調用    任意一層都可以被重新實現,不會影響到其他層的代碼,把容易複用、跟具體業務關係不大的代碼,儘量下沉到下層,把容易變動、跟具體業務強相關的代碼,儘量上移到上層
(4)不同層 模塊之間通過接口進行通信
(5)高內聚 低耦合
(6)爲擴展而設計
(7)可讀性 很重要 比可擴展還重要 二者只選一個 那就是選可讀性
(8)要遵守統一的開發規範,避免反直覺的設計

嚴格執行代碼規範
編寫高質量的單元測試 減少底層細粒度的bug
code review
沒開發前一定要寫開發文檔
持續重構
拆分團隊和代碼(分層 模塊化)

如何開發通用的模塊

在業務代碼開發中 善於發現非業務的、可複用的功能點 從業務邏輯中解耦出來 開發成獨立的模塊
	1類庫
	2框架:讓業務代碼嵌套在裏面開發 目的是爲了更聚焦於業務
	3功能組件(重量級的類庫 且更聚焦)
	共同特點: 複用 + 和業務無關  (複用且和業務有關的話 抽離出來  就是微服務了)

Immutable模式
即:多線程設計模式

一個對象的狀態在對象創建之後就不再改變,這就是所謂的不變模式。其中涉及的類就是不變類,對象就是不變對象
1普通的不變模式:對象中包含的引用對象是可以改變的
2深度不變模式:對象包含的引用對象也不可變
所有的成員變量都通過構造函數一次性設置好,不暴露任何 set 等修改成員變量的方法。所以不存在併發讀寫問題,因此常用在多線程環境下,避免加鎖。 

何爲函數式編程?

把程序用一系列數學表達式或者數學的函數來表示  主要適用於科學計算 分析和統計 數據處理等
	無狀態: 變量都是局部變量 函數執行結果只受入參影響  跟其他的任何外部變量都沒有關係   --》 只要輸入函數的參數是相同的 返回結果就是相同的  (有狀態函數會使用外部的共享變量(甚至是全局變量)  就有可能每次執行外部變量不同而導致即使本次輸入的參數是相同的 結果卻不同)

java中的函數式編程:stream lambda 和 函數式接口

說幾個在spring中存在的設計模式:

1約定好過配置思想
提供配置的默認值,優先使用默認值  因爲開發中8成的配置都是可以依照規定來的

2低侵入 松耦合
要替換一個框架的時候 對原來業務代碼改動很少 
		例如:ioc 不會對類強行切入什麼代碼  不用繼承實現   通過配置來引入類   換一個ioc框架原本的bean都不需要更改 改改配置即可
			 AOP 將非業務代碼集中放到切面中 
3模塊化 輕量級:
上面的模塊依賴下層模塊 同層之間不依賴     且按需引入

4再封裝、再抽象
對市面上主流的中間件、系統的訪問類庫,做了進一步的封裝和抽象

具體的設計模式
5觀察者模式:
event listener publish,  自己定義事件  監聽器 往applicationcontext中發送事件即可 不需要修改任何代碼的情況下 擴展新的事件和監聽

6模板模式:
spring bean的創建過程:
	1對象的創建 :反射
	2對象的初始化:
			(1)自定義一個初始化函數 通過配置文件告訴spring在bean創建的時候調用哪個初始化函數  --》初始化函數並不固定 需要spring通過反射運行時動態調用這個函數 反射會影響執行性能
			(2)讓類實現Initializingbean接口 ,spring在需要初始化的時候直接調用afterPropertiesSet方法 ---》耦合的業務代碼和框架代碼,因爲實現了Initializingbean接口 替換框架的成本就高了
	
	再說一下類似的對象的銷燬過程:
				(1)配置destroy-method
				(2)實現DisposableBean接口
	初始化前置 初始化後置:BeanPostProcessor   (在初始化前後進行的操作)
	具體的執行順序: 創建對象 -》 BeanPostProcessor 前置操作 -> afterPropertiesSet ->init-method -> BeanPostProcessor 的後置操作 ->類的使用  -> DisposableBean  ->destroy-method
	
7適配器模式:
spring通過HandlerAdapter接口來管理不同的handler的實現類(handler是映射url+controller 而controller有三種方式可以實現:實現controller接口 繼承servlet 註解方式  對應不同的方法需要調用不同的方法 註解則是通過反射調用),通過請求的url來從handlermapping中獲取對應的HandlerAdapter然後直接調用handle方法即可
   handler(url+controller) 保存在 handlermapping中  在handlermapping中通過url來獲取對應的handler 然後發送此handler給handlerapdapters數組 遍歷此數組找到可以處理此handler的handleradapter然後調用handle 傳入handler進行處理

8策略模式:根據狀態值 環境變量  計算結果等參數來動態決定使用哪個策略
aop ,通過不同的策略選擇jdk動態代理還是cglib   ,通過策略工廠來創建具體的策略接口的實現子類 通過該類的策略方法來創建具體的代理對象    ,這種思路可以推廣到其他

9組合模式:
緩存管理功能CacheManager ,用組合模式(樹形結構)來管理緩存結構

10裝飾器模式:
TransactionAwareCacheDecorator 在事務提交和回滾後 把緩存也對應地處理了  ,實現了cache接口 包含成員變量cache 就是對spring cache的增強

11工廠模式
通過配置factory-method 來指定工廠類裏創建對象的方法 傳入參數來返回所創建的(或者緩存好的)對象

12解釋器模式:
SpEL

對比一下操作數據庫的類庫或者框架

(1)JdbcTemplate:
優點  : 輕量級  性能好
缺點:   sql和代碼耦合 ,不具備orm功能需要自己解析數據庫數據和對象的映射

(2)Hibernate:
優點:可以根據業務需求自動生成sql 全自動orm    易用性高!
缺點: sql性能可能不行 也沒有手寫的針對

追求易用性,性能就差一些。追求性能,易用性就差一些。越簡單方便,靈活性就越差
mybatis就基於性能和易用性在二者之間

mybatisplugin與職責鏈

統計sql操作耗時   分庫分表 自動分頁  數據脫敏 加密解密。。。

攔截myhbatis在執行sql過程中涉及的方法,例如統計sql操作的耗時 可以攔截executor parameterHandler(設置sql佔位符參數) resultsethandler(封裝執行結果) statementhandler(執行sql語句)

底層原理: 藉助動態代理實現的職責連
	Interceptor + InterceptorChain  + Plugin(用來生成被攔截對象,即上面寫的executor等 的動態代理)
動態代理給職責連添加代理後的對象 並且是經過了多層invocation的封裝後的 一層一層執行intercept後纔會執行executor parameterhandler等的方法

filter通過遞歸實現責任鏈
springmvc的interceptor通過在攔截方法前後加方法實現
mybatis plugin通過嵌套動態代理方式實現
	

mybatis所設計到的設計模式:

1利用建造者模式SqlSessionFactoryBuilder來創建 SqlSessionFactory,但是並不是爲了設置參數 其實參數並不多  主要是因爲在創建SqlSessionFactory需要創建Configuration,創建Configuration比較複雜 ,利用SqlSessionFactoryBuilder封裝創建configuration的細節
2SqlSessionFactory工廠模式重載了很多同名方法創建sqlsession
	以上兩個都不是傳統的建造者 / 工廠模式
3BaseExecutor模版方法  例如 update 和 dpupdate(抽象的方法 在update中執行 交給子類來實現)
4sqlnode:解釋器模式解釋mybatis的動態sql
5利用threadlocal來實現線程唯一的單例
6PerpetualCache緩存類 + 其他9個包括增強的緩存類  ----   裝飾器模式
7PropertyTokenizer迭代器模式
8Log 適配器模式

限流算法和限流模式

限流算法:
固定時間窗口限流算法:
當前時間窗口內,如每秒鐘最大允許 100 次接口請求,累加訪問次數超過限流值,就觸發限流熔斷,拒絕接口請求。當進入下一個時間窗口之後,計數器清零重新計數。
		缺點:在兩個窗口臨接的時間短期內可能會有最大的訪問 導致限流框架檢測不出來卻讓系統癱瘓

滑動時間窗口限流算法、令牌桶限流算法、漏桶限流算法


限流模式:
單機限流:針對某個服務的單個實例的訪問頻率進行限制

分佈式限流:針對某個服務的多個實例的總的訪問頻率進行限制

接口冪等

在接口連接超時的時候可以直接重傳 重傳需要系統保證冪等姓:
可以在項目代碼中或者 遠程調用框架中加入超時重發的算法

冪等號:保證 針對同一個接口,多次發起同一個業務請求,必須保證業務只執行一次
			調用方生成併發送冪等號,接口實現方接受解析比較 存在直接返回 不存在記錄繼續執行業務
 			可以通過aop或者直接寫入代碼

一般請求三個流程:  如果發生異常
接受:
	異常  冪等還未記錄 重試請求會執行   安全
業務:
	業務異常: 不刪除冪等號 因爲刪除也是徒勞
	系統異常:刪除冪等號 需要重新執行
	業務系統宕機:在應該刪除冪等號前系統卻宕機了 
					 (1)	利用分佈式事務同時讓冪等號的記錄(redis)和業務系統的數據庫記錄 失敗一起回滾			
					 (2)在業務數據庫中建立表存儲冪等號 先存儲到mysql  後同步到冪等框架的redis,直接利用業務數據庫本身的事務屬性,保證業務數據和冪等號的寫入操作,要麼都成功,要麼都失敗,只要mysql沒有寫入 意味着任務沒有執行成功 redis不會存儲    這種情況下甚至不用自動同步mysql和redis 畢竟系統已經宕機   只需要人工比對即可
					 	
返回:
	冪等好已經記錄 返回異常   重試也不會影響 安全

冪等框架一旦出問題 業務流程也得中止  這個跟限流框架不同  冪等框架出差錯業務也需要中止否則容易出現例如多轉賬

怎麼生成冪等號:
(1)集中生成並且分派給調用方 
		調用方通過調用遠程接口來獲取冪等號  算法也是冪等系統管理
(2)直接由調用方生成
	調用方按照跟接口實現方預先商量好的算法,自己來生成冪等號 ,但是不同接口需要有不同的冪等號生成 算法也需要自己調整

灰度發佈

老接口繼續承擔大部分的調用 新接口承擔一部分 再慢慢得變多 這就是灰度

用配置中心配置一個變量 if else來調控新舊代碼的邏輯,根據灰度對象的值來判斷走哪個接口

灰度框架需要提供:
1配置
需要設置一個 key 值,來唯一標識要灰度的功能
選擇一個灰度對象(比如用戶 ID)
配置這個 key 對應的灰度規則和功能開關
2提供接口判斷是否灰度



灰度規則熱更新:
定時器定時讀取灰度規則配置信息,並且解析加載到內存中,替換掉老的灰度規則
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章