【前言】
做了幾個可大可小的項目,或多或少的接觸了代碼複用,代碼解耦,代碼高擴展等一系列優秀的代碼的實用性,提高了代碼的可讀性,可擴展性,維護成本,複雜的業務問題。而我們生活上也是如此,如果一件事情,總要不斷重複去做,雖然我們可以把事情做好,但是總是雜亂無章,這不免會浪費我們的時間成本,對生活深度分析,並且用代碼實現這種思想,不斷思考,思想與代碼並駕齊驅,靈活運用設計模式,理清應該有的脈絡,才更加有趣。最近在設計模式之禪裏面看到作者提了一系列問題來引入學習設計模式的歷程:這個定義是這樣嗎?是應該用抽象類還是用接口?爲什麼在這裏不能抽取抽象呢?爲什麼在項目中這個模式要如此蛻化?相信我們都會有這樣的疑問,這篇博客,我們拿出來幾個典型的模式來做一下。
【六大原則】
單一職責原則:一個接口只幹一件事情,例如一個接口定義了撥通電話和通話以及掛電話三個方法,
如果是按照打
電話這件事情來說,這三個方法可以放到一個接口中,如果按照另一種方式解釋的話:它可以包含
兩個職責:協議管理以及數據傳送,通話爲數據傳送,撥通電話和掛電話爲協議管理。
里氏轉化原則:只要父類能出現的地方子類就可以出現,而且替換爲子類也不會出現任何錯誤或異
常,使用者可能根本不需要知道是父類還是子類。但是,反過來就不行了,有子類出現的地方,
父類未必就能適應。
依賴倒置原則:模塊間的依賴通過抽象發生,實現類之間不發生直接的依賴關係,其依賴關係是
通過接口活抽象類產生的;接口或抽象類不依賴於實現類;實現類依賴接口或抽象類。
接口隔離原則:建立單一接口,不建立臃腫龐大的接口。在符合單一職責原則上,進行接口隔離
原則。
迪米特法則:我的知識,你知道的越少越好 、
開閉原則:對擴展開發,對修改關閉
【簡單工廠,工廠方法,抽象工廠】
簡單工廠核心代碼:
public class SimpleFactory {
public Milk getMilk(String name){
if("特侖蘇".equals(name)){
return new Telunsu();
}else if("伊利".equals(name)){
return new Yili();
}else if("蒙牛".equals(name)){
return new Mengniu();
}else {
System.out.println("不能生產您所需的產品");
return null;
}
}
}
當我們調用這個工廠的是時候,我們只需要把我們的需求告訴工廠。然後給我們返回結果。相當於小作坊。統一管理所有的事情,結合生活,哆啦a夢的百寶箱是不存在的。spring中的bean也是如此,有很多種,我們不可能所有的都放到一起,體現不出專業性。因爲專一,才能顯得專業,這時候就出現了工廠方法:
public interface Factory {
//工廠必然具有生產產品技能,統一的產品出口
Milk getMilk();
}
public class MengniuFactory implements Factory {
@Override
public Milk getMilk() {
return new Mengniu();
}
}
public class SanluFactory implements Factory {
@Override
public Milk getMilk() {
return new Sanlu();
}
}
public class TelunsuFactory implements Factory {
@Override
public Milk getMilk() {
return new Telunsu();
}
}
這時候我們就可以發現,每一種牛奶都有各自牛奶的品牌的廠商來生產,顯得專業了吧。但是我們再想一想,如果我需要特侖蘇,我得需要去特侖蘇廠商取,如果需要蒙牛,得去蒙牛取,在加如一家廠商,我也需要在添加一個工廠,這樣也會麻煩的。這時候我們需要用抽象工廠了。
/**
*
* 抽象工廠是用戶的主入口
* 在Spring中應用得最爲廣泛的一種設計模式
* 易於擴展
*/
public abstract class AbstractFactory {
//公共的邏輯
//方便於統一管理
/**
* 獲得一個蒙牛品牌的牛奶
* @return
*/
public abstract Milk getMengniu();
/**
* 獲得一個伊利品牌的牛奶
* @return
*/
public abstract Milk getYili();
/**
* 獲得一個特侖蘇品牌的牛奶
* @return
*/
public abstract Milk getTelunsu();
public abstract Milk getSanlu();
}
public class MilkFactory extends AbstractFactory {
@Override
public Milk getMengniu() {
return new Mengniu();
}
@Override
public Milk getYili() {
return new Yili();
}
@Override
public Milk getTelunsu() {
return new Telunsu();
}
@Override
public Milk getSanlu() {
return new Sanlu();
}
}
這時候你如果想要牛奶,你只需要告訴牛奶工廠就行,再一個,對於用戶來說,你如果添加一個牛奶品牌,只需要再牛奶工廠進行添加就行,具體的內部修改放到抽象類裏面,很好的符合了開放封閉原則,對擴展開放,對修改關閉
【單例模式】
保證一個類只允許有一個實例,例如後端配置文件
具體實現的方式有:餓漢式單例模式,懶漢式單例模式,註冊登記式,枚舉式,序列化與反序列化
餓漢式單例模式
package singleton.hungry;
/**
* Created by 張偉光 on 2019/6/3.
* 懶漢式
*/
public class Hungry {
private Hungry(){}
//類加載機制,先靜態,後動態,先屬性,後方法
private static final Hungry hungry = new Hungry();
public static Hungry getInstance() {
return hungry;
}
}
性能還可以,但是類加載的時候就初始化了,不管你佔不佔用。下面看一下懶漢式單例模式
public class LazyOne {
private LazyOne(){}
private static LazyOne lazy = null;
public static LazyOne getInstance() {
if(lazy == null) {
lazy = new LazyOne();
}
return lazy;
}
}
如果存在併發的情況下這種,會出現兩個對象,不太可靠。如果給這個方法加個鎖,變成同步的話,就又會出現時間延遲的問題,性能會差很多。這時候又想到了其它的方法
package singleton.lazy;
/**
* Created by 張偉光 on 2019/6/3.
*/
public class LazyThree {
private static boolean initialized = false;
//默認使用LazyThree 的時候,會先初始化內部類
//如果沒使用的話,內部類是不加載的
private LazyThree(){
synchronized (LazyThree.class) {
if(initialized == false) {
initialized = !initialized;
}else {
throw new RuntimeException("單例已被侵犯");
}
}
}
public static final LazyThree getInstance() {
return lazyHolder.LAZY;
}
private static class lazyHolder {
private static final LazyThree LAZY = new LazyThree();
}
}
我們通過靜態內部類,通過調用內部類來進行實例化的方法保證單一。被稱爲史上最牛B的單例模式的實現方式。
再提一個在spring中常用的設計模式註冊式單例模式
package singleton.register;
import java.util.HashMap;
import java.util.Map;
/**
* Created by 張偉光 on 2019/6/3.
*/
public class RegisterMap {
private static Map<String,Object> register = new HashMap<String,Object>();
public static RegisterMap getInstance(String name){
if(name == null){
name = RegisterMap.class.getName();
}
if(register.get(name) == null) {
try {
register.put(name,new RegisterMap());
} catch (Exception e){
e.printStackTrace();
}
}
return (RegisterMap)register.get(name);
}
}
將生成單例放到map中,下次實例化的時候先判斷map中是否已經存在這個對象。其它的單例模式就不贅述了。
【適配器模式】
適配器模式主要是爲了兼容,例如老系統只能用一般輸入賬號密碼的方式進行登錄,現如今加了微信登錄,qq登錄,手機驗證碼等登錄方式,我們這個時候要兼容原先的登錄方式,我們需要新添加一個類。
public class SiginService {
/**
* 註冊方法
* @param username
* @param password
* @return
*/
public ResultMsg regist(String username,String password){
return new ResultMsg(200,"註冊成功",new Member());
}
/**
* 登錄的方法
* @param username
* @param password
* @return
*/
public ResultMsg login(String username,String password){
return null;
}
}
public class SiginForThirdService extends SiginService {
public ResultMsg loginForQQ(String openId){
//1、openId是全局唯一,我們可以把它當做是一個用戶名(加長)
//2、密碼默認爲QQ_EMPTY
//3、註冊(在原有系統裏面創建一個用戶)
//4、調用原來的登錄方法
return loginForRegist(openId,null);
}
public ResultMsg loginForWechat(String openId){
return null;
}
public ResultMsg loginForToken(String token){
//通過token拿到用戶信息,然後再重新登陸了一次
return null;
}
public ResultMsg loginForTelphone(String telphone,String code){
return null;
}
public ResultMsg loginForRegist(String username,String password){
super.regist(username,null);
return super.login(username,null);
}
}
這個是爲了兼容,而裝飾器模式是動態的覆蓋或者增加方法,兩者是不一樣的,但是可以說裝飾器模式是一種非常特殊的適配器模式。
【總結】
其它設計模式就先不逐一介紹了,不過多少都有聯繫,設計模式並不是單一存在,也不是固定於某一種形式,當真正體會到其中的變得時候,可能纔有意思。