二十三種設(shè)計模式
資料來源于老師講解以及大佬的設(shè)計模式倉庫 zhengqingya
結(jié)構(gòu)型
將對象和類按某種布局組成更大的結(jié)構(gòu),并同時保持結(jié)構(gòu)的靈活和?效。
1.適配器
適配器就是將原先無法直接使用的某個接口或者類通過適配器模式轉(zhuǎn)換為可以使用的接口或者類。將一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口,使得原本由于接口不兼容而不能一起工作的那些類能一起工作。適配器模式的好處就是可以再不改變原有代碼的基礎(chǔ)之上也可以實現(xiàn)相關(guān)的功能。
角色:
- 目標接口(Target):客戶所期待的接口??梢允蔷唧w類 或 抽象的類,也可以是接口
- 需要適配的類(Adaptee)
- 適配器(Adapter):通過包裝一個需要適配的對象,把原接口轉(zhuǎn)換成目標接口
區(qū)別:
- 類適配器:單繼承,一次最多只能適配一個適配者類
- 對象適配器:可以把多個不同的適配者適配到同一個目標
適配器模式分為:類適配器、對象適配器、接口適配器。
tips: 推薦使用對象適配器
1.1 接口適配器
接口中有很多的抽象方法,但是再使用接口的時候我們只關(guān)注其中某一個方法,其他方法我們不需要,但是因為接口的特性,如果我們要使用這個接口,必須重寫接口中所有方法。
創(chuàng)建一個接口的適配器,接口的適配器是一個Java類,適配器需要將接口的所有抽象方法給重寫了,但是重寫之后只做空實現(xiàn),后期如果使用接口,只需要繼承適配器類。
Java中GUI中有很多接口適配器,MouseAdpter
代碼示例:
package com.xsuek.adpter;
/**
* 二十三種設(shè)計模式之一,適配器模式
* 在某些情況下Java類只想重寫使用接口中的某個方法 而不是所有的抽象方法
* 但是因為接口的特性 子類實現(xiàn)接口 必須重寫所有方法
*
* 適配器模式:一個適配類由這個適配類是實現(xiàn)接口,并且重寫了所有的方法,但是所有的方法都是空實現(xiàn)。
* 如果某個類想要去重寫使用接口中的某個方法,就不要直接實現(xiàn)接口,而是繼承適配器類。
* @author lenovo
*
*/
public interface MouseInter {
void mouseClick();
int mouseMove();
String mouseEnter();
void mouseleave();
}
class MouseAdpter implements MouseInter{
@Override
public void mouseClick() {
}
@Override
public int mouseMove() {
return 0;
}
@Override
public String mouseEnter() { return null;
}
@Override
public void mouseleave() {
// TODO Auto-generated method stub
}
}
class Button extends MouseAdpter{
}
1.2 類適配器
有一個電壓220V實現(xiàn)手機充電的效果電源適配器將220V的電壓轉(zhuǎn)換成為可以充電的東西。
讓適配器類繼承被適配器
package com.sxuek.designmodel;
public class U {
public void out() {
System.out.println("220V");
}
}
interface Chongdian{
void chongdian();
}
class ChrageAapter extends U implements Chongdian{
@Override
public void chongdian(){
out();
//xxxxxxx
}
}
1.3 對象適配器
讓被適配的類稱為適配器的一個屬性。
代碼示例:
public class AC220 {
public int outputAC220V() {
int output = 220;
System.out.println("輸出電壓:" + output + "V");
return output;
}
}
public interface DC5 {
int outputDC5V();
}
public class PowerAdapter implements DC5 {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
@Override
public int outputDC5V() {
int adapterInput = ac220.outputAC220V();
int adapterOutput = adapterInput / 44;
System.out.println("輸入AC" + adapterInput + "輸出DC" + adapterOutput);
return adapterOutput;
}
}
public class Test {
public static void main(String[] args) {
DC5 adapter = new PowerAdapter(new AC220());
adapter.outputDC5V();
}
}
2.代理模式
為某對象提供一種代理以控制對該對象的訪問。即客戶端通過代理間接地訪問該對象,從而限制、增強或修改該對象的一些特性。
AOP核心:代理模式
在某些情況下,一個客戶不想或者不能直接引用一個對 象,此時可以通過一個稱之為“代理”的第三者來實現(xiàn) 間接引用。代理對象可以在客戶端和目標對象之間起到 中介的作用,并且可以通過代理對象去掉客戶不能看到 的內(nèi)容和服務(wù)或者添加客戶需要的額外服務(wù)。
通過引入一個新的對象(如小圖片和遠程代理 對象)來實現(xiàn)對真實對象的操作或者將新的對 象作為真實對象的一個替身,這種實現(xiàn)機制即 為代理模式,通過引入代理對象來間接訪問一 個對象,這就是代理模式的模式動機。
2.1 模式結(jié)構(gòu)
- Subject: 抽象主題角色
- Proxy: 代理主題角色
- RealSubject: 真實主題角色
2.1.1 靜態(tài)代理
以租房為例,我們一般用租房軟件、找中介或者找房東。這里的中介就是代理者。
首先定義一個提供了租房方法的接口。
public interface IRentHouse {
void rentHouse();
}
定義租房的實現(xiàn)類
public class RentHouse implements IRentHouse {
@Override
public void rentHouse() {
System.out.println("租了一間房子。。。");
}
}
我要租房,房源都在中介手中,所以找中介
public class IntermediaryProxy implements IRentHouse {
private IRentHouse rentHouse;
public IntermediaryProxy(IRentHouse irentHouse){
rentHouse = irentHouse;
}
@Override
public void rentHouse() {
System.out.println("交中介費");
rentHouse.rentHouse();
System.out.println("中介負責維修管理");
}
}
這里中介也實現(xiàn)了租房的接口。
再main方法中測試
public class Main {
public static void main(String[] args){
//定義租房
IRentHouse rentHouse = new RentHouse();
//定義中介
IRentHouse intermediary = new IntermediaryProxy(rentHouse);
//中介租房
intermediary.rentHouse();
}
}
返回信息
交中介費
租了一間房子。。。
中介負責維修管理
這就是靜態(tài)代理,因為中介這個代理類已經(jīng)事先寫好了,只負責代理租房業(yè)務(wù)
2.1.2.強制代理
如果我們直接找房東要租房,房東會說我把房子委托給中介了,你找中介去租吧。這樣我們就又要交一部分中介費了,真坑。
來看代碼如何實現(xiàn),定義一個租房接口,增加一個方法。
public interface IRentHouse {
void rentHouse();
IRentHouse getProxy();
}
這時中介的方法也稍微做一下修改
public class IntermediaryProxy implements IRentHouse {
private IRentHouse rentHouse;
public IntermediaryProxy(IRentHouse irentHouse){
rentHouse = irentHouse;
}
@Override
public void rentHouse() {
rentHouse.rentHouse();
}
@Override
public IRentHouse getProxy() {
return this;
}
}
其中的getProxy()方法返回中介的代理類對象
我們再來看房東是如何實現(xiàn)租房:
public class LandLord implements IRentHouse {
private IRentHouse iRentHouse = null;
@Override
public void rentHouse() {
if (isProxy()){
System.out.println("租了一間房子。。。");
}else {
System.out.println("請找中介");
}
}
@Override
public IRentHouse getProxy() {
iRentHouse = new IntermediaryProxy(this);
return iRentHouse;
}
/**
* 校驗是否是代理訪問
* @return
*/
private boolean isProxy(){
if(this.iRentHouse == null){
return false;
}else{
return true;
}
}
}
房東的getProxy方法返回的是代理類,然后判斷租房方法的調(diào)用者是否是中介,不是中介就不租房。
main方法測試:
public static void main(String[] args){
IRentHouse iRentHosue = new LandLord();
//租客找房東租房
iRentHouse.rentHouse();
//找中介租房
IRentHouse rentHouse = iRentHouse.getProxy();
rentHouse.rentHouse();
}
}
請找中介
租了一間房子。。。
看,這樣就是強制你使用代理,如果不是代理就沒法訪問。
2.1.3 動態(tài)代理
我們知道現(xiàn)在的中介不僅僅是有租房業(yè)務(wù),同時還有賣房、家政、維修等得業(yè)務(wù),只是我們就不能對每一個業(yè)務(wù)都增加一個代理,就要提供通用的代理方法,這就要通過動態(tài)代理來實現(xiàn)了。
中介的代理方法做了一下修改
public class IntermediaryProxy implements InvocationHandler {
private Object obj;
public IntermediaryProxy(Object object){
obj = object;
}
/**
* 調(diào)用被代理的方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(this.obj, args);
return result;
}
}
在這里實現(xiàn)InvocationHandler接口,此接口是JDK提供的動態(tài)代理接口,對被代理的方法提供代理。其中invoke方法是接口InvocationHandler定義必須實現(xiàn)的, 它完成對真實方法的調(diào)用。動態(tài)代理是根據(jù)被代理的接口生成所有的方法,也就是說給定一個接口,動態(tài)代理就會實現(xiàn)接口下所有的方法。通過 InvocationHandler接口, 所有方法都由該Handler來進行處理, 即所有被代理的方法都由 InvocationHandler接管實際的處理任務(wù)。
這里增加一個賣房的業(yè)務(wù),代碼和租房代碼類似。
main方法測試:
public static void main(String[] args){
IRentHouse rentHouse = new RentHouse();
//定義一個handler
InvocationHandler handler = new IntermediaryProxy(rentHouse);
//獲得類的class loader
ClassLoader cl = rentHouse.getClass().getClassLoader();
//動態(tài)產(chǎn)生一個代理者
IRentHouse proxy = (IRentHouse) Proxy.newProxyInstance(cl, new Class[]{IRentHouse.class}, handler);
proxy.rentHouse();
ISellHouse sellHouse = new SellHouse();
InvocationHandler handler1 = new IntermediaryProxy(sellHouse);
ClassLoader classLoader = sellHouse.getClass().getClassLoader();
ISellHouse proxy1 = (ISellHouse) Proxy.newProxyInstance(classLoader, new Class[]{ISellHouse.class}, handler1);
proxy1.sellHouse();
}
租了一間房子。。。
買了一間房子。。。
在main方法中我們用到了Proxy這個類的方法,
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
loder:類加載器,interfaces:代碼要用來代理的接口, h:一個 InvocationHandler 對象 。
InvocationHandler 是一個接口,每個代理的實例都有一個與之關(guān)聯(lián)的 InvocationHandler 實現(xiàn)類,如果代理的方法被調(diào)用,那么代理便會通知和轉(zhuǎn)發(fā)給內(nèi)部的 InvocationHandler 實現(xiàn)類,由它決定處理。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler 內(nèi)部只是一個 invoke() 方法,正是這個方法決定了怎么樣處理代理傳遞過來的方法調(diào)用。
因為,Proxy 動態(tài)產(chǎn)生的代理會調(diào)用 InvocationHandler 實現(xiàn)類,所以 InvocationHandler 是實際執(zhí)行者。
總結(jié):
1、靜態(tài)代理,代理類需要自己編寫代碼寫成。
2、動態(tài)代理,代理類通過 Proxy.newInstance() 方法生成。
3、JDK實現(xiàn)的代理中不管是靜態(tài)代理還是動態(tài)代理,代理與被代理者都要實現(xiàn)兩樣接口,它們的實質(zhì)是面向接口編程。CGLib可以不需要接口。
4、動態(tài)代理通過 Proxy 動態(tài)生成 proxy class,但是它也指定了一個 InvocationHandler 的實現(xiàn)類。
創(chuàng)建型
1.單例模式
某個類只能生成一個實例,該類提供了一個全局訪問點供外部獲取該實例,其拓展是有限多例模式。
單例模式包含如下角色:
- Singleton:單例
1.1單例模式的幾種實現(xiàn)方式
單例模式的實現(xiàn)有多種方式,如下所示:
1.1.1 懶漢式,線程不安全
是否 Lazy 初始化: 是
是否多線程安全: 否
實現(xiàn)難度: 易
描述: 這種方式是最基本的實現(xiàn)方式,這種實現(xiàn)最大的問題就是不支持多線程。因為沒有加鎖 synchronized,所以嚴格意義上它并不算單例模式。
這種方式 lazy loading 很明顯,不要求線程安全,在多線程不能正常工作。
實例
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
接下來介紹的幾種實現(xiàn)方式都支持多線程,但是在性能上有所差異。
1.1.2 懶漢式,線程安全
是否 Lazy 初始化: 是
是否多線程安全: 是
實現(xiàn)難度: 易
描述: 這種方式具備很好的 lazy loading,能夠在多線程中很好的工作,但是,效率很低,99% 情況下不需要同步。
優(yōu)點:第一次調(diào)用才初始化,避免內(nèi)存浪費。
缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。
getInstance() 的性能對應(yīng)用程序不是很關(guān)鍵(該方法使用不太頻繁)。
實例
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
1.1.3 餓漢式
是否 Lazy 初始化: 否
是否多線程安全: 是
實現(xiàn)難度: 易
描述: 這種方式比較常用,但容易產(chǎn)生垃圾對象。
優(yōu)點:沒有加鎖,執(zhí)行效率會提高。
缺點:類加載時就初始化,浪費內(nèi)存。
它基于 classloader 機制避免了多線程的同步問題,不過,instance 在類裝載時就實例化,雖然導(dǎo)致類裝載的原因有很多種,在單例模式中大多數(shù)都是調(diào)用 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載,這時候初始化 instance 顯然沒有達到 lazy loading 的效果。
實例
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
1.1.4 雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)
JDK 版本: JDK1.5 起
是否 Lazy 初始化: 是
是否多線程安全: 是
實現(xiàn)難度: 較復(fù)雜
描述: 這種方式采用雙鎖機制,安全且在多線程情況下能保持高性能。
getInstance() 的性能對應(yīng)用程序很關(guān)鍵。
實例
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
1.1.5 登記式/靜態(tài)內(nèi)部類
是否 Lazy 初始化: 是
是否多線程安全: 是
實現(xiàn)難度: 一般
描述: 這種方式能達到雙檢鎖方式一樣的功效,但實現(xiàn)更簡單。對靜態(tài)域使用延遲初始化,應(yīng)使用這種方式而不是雙檢鎖方式。這種方式只適用于靜態(tài)域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。
這種方式同樣利用了 classloader 機制來保證初始化 instance 時只有一個線程,它跟第 3 種方式不同的是:第 3 種方式只要 Singleton 類被裝載了,那么 instance 就會被實例化(沒有達到 lazy loading 效果),而這種方式是 Singleton 類被裝載了,instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用,只有通過顯式調(diào)用 getInstance 方法時,才會顯式裝載 SingletonHolder 類,從而實例化 instance。想象一下,如果實例化 instance 很消耗資源,所以想讓它延遲加載,另外一方面,又不希望在 Singleton 類加載時就實例化,因為不能確保 Singleton 類還可能在其他的地方被主動使用從而被加載,那么這個時候?qū)嵗?instance 顯然是不合適的。這個時候,這種方式相比第 3 種方式就顯得很合理。
實例
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
1.1.6 枚舉
JDK 版本: JDK1.5 起
是否 Lazy 初始化: 否
是否多線程安全: 是
實現(xiàn)難度: 易
**描述:**這種實現(xiàn)方式還沒有被廣泛采用,但這是實現(xiàn)單例模式的最佳方法。它更簡潔,自動支持序列化機制,絕對防止多次實例化。
這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還自動支持序列化機制,防止反序列化重新創(chuàng)建新的對象,絕對防止多次實例化。不過,由于 JDK1.5 之后才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。
不能通過 reflection attack 來調(diào)用私有構(gòu)造方法。
實例
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
經(jīng)驗之談: 一般情況下,不建議使用第 1 種和第 2 種懶漢方式,建議使用第 3 種餓漢方式。只有在要明確實現(xiàn) lazy loading 效果時,才會使用第 5 種登記方式。如果涉及到反序列化創(chuàng)建對象時,可以嘗試使用第 6 種枚舉方式。如果有其他特殊的需求,可以考慮使用第 4 種雙檢鎖方式。
相關(guān)資料
-
23種設(shè)計模式-思維導(dǎo)圖
-
23種設(shè)計模式-UML類圖
-
圖說設(shè)計模式
-
菜鳥教程
-
《重學Java設(shè)計模式》
-
設(shè)計模式之于我文章來源:http://www.zghlxwxcb.cn/news/detail-478604.html
-
25000 字詳解 23 種設(shè)計模式(多圖 + 代碼)文章來源地址http://www.zghlxwxcb.cn/news/detail-478604.html
到了這里,關(guān)于二十三種設(shè)計模式(待更)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!