結構型模式
結構型模式描述如何將類或對象按某種布局組成更大的結構。它分為類結構型模式和對象結構型模式,前者采用繼承機制來組織接口和類,后者釆用組合或聚合來組合對象。
由于組合關系或聚合關系比繼承關系耦合度低,滿足“合成復用原則”,所以對象結構型模式比類結構型模式具有更大的靈活性。
1.代理模式
由于某些原因需要給某對象提供一個代理以控制對該對象的訪問。這時,訪問對象不適合或者不能直接引用目標對象,代理對象作為訪問對象和目標對象之間的中介。
Java中的代理按照代理類生成時機不同又分為靜態(tài)代理和動態(tài)代理。靜態(tài)代理代理類在編譯期就生成,而動態(tài)代理代理類則是在Java運行時動態(tài)生成。動態(tài)代理又有JDK代理和CGLib代理兩種。
代理(Proxy)模式分為三種角色:
- 抽象主題(Subject)類: 通過接口或抽象類聲明真實主題和代理對象實現(xiàn)的業(yè)務方法。
- 真實主題(Real Subject)類: 實現(xiàn)了抽象主題中的具體業(yè)務,是代理對象所代表的真實對象,是最終要引用的對象。
- 代理(Proxy)類 : 提供了與真實主題相同的接口,其內部含有對真實主題的引用,它可以訪問、控制或擴展真實主題的功能。
1.1 靜態(tài)代理
如果要買火車票的話,需要去火車站買票,坐車到火車站,排隊等一系列的操作,顯然比較麻煩。而火車站在多個地方都有代售點,我們去代售點買票就方便很多了。這個例子其實就是典型的代理模式,火車站是目標對象,代售點是代理對象。
靜態(tài)代理類。
//賣票接口
public interface SellTickets {
void sell();
}
//火車站 火車站具有賣票功能,所以需要實現(xiàn)SellTickets接口
public class TrainStation implements SellTickets {
public void sell() {
System.out.println("火車站賣票");
}
}
//代售點
public class ProxyPoint implements SellTickets {
private TrainStation station = new TrainStation();
public void sell() {
System.out.println("代理點收取一些服務費用");
station.sell();
}
}
//測試類
public class Client {
public static void main(String[] args) {
ProxyPoint pp = new ProxyPoint();
pp.sell();
}
}
從上面代碼中可以看出測試類直接訪問的是ProxyPoint類對象,也就是說ProxyPoint作為訪問對象和目標對象的中介。同時也對sell方法進行了增強(代理點收取一些服務費用)。
1.2 JDK動態(tài)代理
Java中提供了一個動態(tài)代理類Proxy,Proxy并不是我們上述所說的代理對象的類,而是提供了一個創(chuàng)建代理對象的靜態(tài)方法(newProxyInstance方法)來獲取代理對象。
//賣票接口
public interface SellTickets {
void sell();
}
//火車站 火車站具有賣票功能,所以需要實現(xiàn)SellTickets接口
public class TrainStation implements SellTickets {
public void sell() {
System.out.println("火車站賣票");
}
}
//代理工廠,用來創(chuàng)建代理對象
public class ProxyFactory {
private TrainStation station = new TrainStation();
public SellTickets getProxyObject() {
//使用Proxy獲取代理對象
/*
newProxyInstance()方法參數(shù)說明:
ClassLoader loader : 類加載器,用于加載代理類,使用真實對象的類加載器即可
Class<?>[] interfaces : 真實對象所實現(xiàn)的接口,代理模式真實對象和代理對象實現(xiàn)相同的接口
InvocationHandler h : 代理對象的調用處理程序
*/
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/*
InvocationHandler中invoke方法參數(shù)說明:
proxy : 代理對象
method : 對應于在代理對象上調用的接口方法的 Method 實例
args : 代理對象調用接口方法時傳遞的實際參數(shù)
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理點收取一些服務費用(JDK動態(tài)代理方式)");
//執(zhí)行真實對象
Object result = method.invoke(station, args);
return result;
}
});
return sellTickets;
}
}
//測試類
public class Client {
public static void main(String[] args) {
//獲取代理對象
ProxyFactory factory = new ProxyFactory();
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}
使用了動態(tài)代理,我們思考下面問題:
- ProxyFactory是代理類嗎?
ProxyFactory不是代理模式中所說的代理類,而代理類是程序在運行過程中動態(tài)的在內存中生成的類。通過阿里巴巴開源的 Java 診斷工具(Arthas【阿爾薩斯】)查看代理類的結構:
package com.sun.proxy;
import com.itheima.proxy.dynamic.jdk.SellTickets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements SellTickets {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void sell() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
從上面的類中,我們可以看到以下幾個信息:
- 代理類($Proxy0)實現(xiàn)了SellTickets。這也就印證了我們之前說的真實類和代理類實現(xiàn)同樣的接口。
- 代理類($Proxy0)將我們提供了的匿名內部類對象傳遞給了父類。
動態(tài)代理的執(zhí)行流程是什么樣?下面是摘取的重點代碼
//程序運行過程中動態(tài)生成的代理類
public final class $Proxy0 extends Proxy implements SellTickets {
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
}
public final void sell() {
this.h.invoke(this, m3, null);
}
}
//Java提供的動態(tài)代理相關類
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
this.h = h;
}
}
//代理工廠類
public class ProxyFactory {
private TrainStation station = new TrainStation();
public SellTickets getProxyObject() {
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理點收取一些服務費用(JDK動態(tài)代理方式)");
Object result = method.invoke(station, args);
return result;
}
});
return sellTickets;
}
}
//測試訪問類
public class Client {
public static void main(String[] args) {
//獲取代理對象
ProxyFactory factory = new ProxyFactory();
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}
執(zhí)行流程如下:
1. 在測試類中通過代理對象調用sell()方法
2. 根據(jù)多態(tài)的特性,執(zhí)行的是代理類($Proxy0)中的sell()方法
3. 代理類($Proxy0)中的sell()方法中又調用了InvocationHandler接口的子實現(xiàn)類對象的invoke方法
4. invoke方法通過反射執(zhí)行了真實對象所屬類(TrainStation)中的sell()方法
1.3 CGLIB動態(tài)代理
如果沒有定義SellTickets接口,只定義了TrainStation(火車站類)。很顯然JDK代理是無法使用了,因為JDK動態(tài)代理要求必須定義接口,對接口進行代理。
CGLIB是一個功能強大,高性能的代碼生成包。它為沒有實現(xiàn)接口的類提供代理,為JDK的動態(tài)代理提供了很好的補充。
CGLIB是第三方提供的包,所以需要引入jar包的坐標:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
//火車站
public class TrainStation {
public void sell() {
System.out.println("火車站賣票");
}
}
//代理工廠
public class ProxyFactory implements MethodInterceptor {
private TrainStation target = new TrainStation();
public TrainStation getProxyObject() {
//創(chuàng)建Enhancer對象,類似于JDK動態(tài)代理的Proxy類,下一步就是設置幾個參數(shù)
Enhancer enhancer =new Enhancer();
//設置父類的字節(jié)碼對象
enhancer.setSuperclass(target.getClass());
//設置回調函數(shù)
enhancer.setCallback(this);
//創(chuàng)建代理對象
TrainStation obj = (TrainStation) enhancer.create();
return obj;
}
/*
intercept方法參數(shù)說明:
o : 代理對象
method : 真實對象中的方法的Method實例
args : 實際參數(shù)
methodProxy :代理對象中的方法的method實例
*/
public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("代理點收取一些服務費用(CGLIB動態(tài)代理方式)");
TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
return result;
}
}
//測試類
public class Client {
public static void main(String[] args) {
//創(chuàng)建代理工廠對象
ProxyFactory factory = new ProxyFactory();
//獲取代理對象
TrainStation proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}
1.4 三種代理的比較
- jdk代理和CGLIB代理
使用CGLib實現(xiàn)動態(tài)代理,CGLib底層采用ASM字節(jié)碼生成框架,使用字節(jié)碼技術生成代理類,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能對聲明為final的類或者方法進行代理,因為CGLib原理是動態(tài)生成被代理類的子類。
在JDK1.6、JDK1.7、JDK1.8逐步對JDK動態(tài)代理優(yōu)化之后,在調用次數(shù)較少的情況下,JDK代理效率高于CGLib代理效率,只有當進行大量調用的時候,JDK1.6和JDK1.7比CGLib代理效率低一點,但是到JDK1.8的時候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK動態(tài)代理,如果沒有接口使用CGLIB代理。
- 動態(tài)代理和靜態(tài)代理
動態(tài)代理與靜態(tài)代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數(shù)量比較多的時候,我們可以進行靈活處理,而不需要像靜態(tài)代理那樣每一個方法進行中轉。
如果接口增加一個方法,靜態(tài)代理模式除了所有實現(xiàn)類需要實現(xiàn)這個方法外,所有代理類也需要實現(xiàn)此方法。增加了代碼維護的復雜度。而動態(tài)代理不會出現(xiàn)該問題。
1.6 優(yōu)缺點
優(yōu)點:
- 代理模式在客戶端與目標對象之間起到一個中介作用和保護目標對象的作用;
- 代理對象可以擴展目標對象的功能;
- 代理模式能將客戶端與目標對象分離,在一定程度上降低了系統(tǒng)的耦合度;
缺點:
- 增加了系統(tǒng)的復雜度;
1.6 使用場景
- 遠程(Remote)代理
本地服務通過網(wǎng)絡請求遠程服務。為了實現(xiàn)本地到遠程的通信,我們需要實現(xiàn)網(wǎng)絡通信,處理其中可能的異常。為良好的代碼設計和可維護性,我們將網(wǎng)絡通信部分隱藏起來,只暴露給本地服務一個接口,通過該接口即可訪問遠程服務提供的功能,而不必過多關心通信部分的細節(jié)。
- 防火墻(Firewall)代理
當你將瀏覽器配置成使用代理功能時,防火墻就將你的瀏覽器的請求轉給互聯(lián)網(wǎng);當互聯(lián)網(wǎng)返回響應時,代理服務器再把它轉給你的瀏覽器。
- 保護(Protect or Access)代理
控制對一個對象的訪問,如果需要,可以給不同的用戶提供不同級別的使用權限。
2.適配器模式
定義:
?將一個類的接口轉換成客戶希望的另外一個接口,使得原本由于接口不兼容而不能一起工作的那些類能一起工作。
?適配器模式分為類適配器模式和對象適配器模式,前者類之間的耦合度比后者高,且要求程序員了解現(xiàn)有組件庫中的相關組件的內部結構,所以應用相對較少些。
結構
適配器模式(Adapter)包含以下主要角色:
- 目標(Target)接口:當前系統(tǒng)業(yè)務所期待的接口,它可以是抽象類或接口。
- 適配者(Adaptee)類:它是被訪問和適配的現(xiàn)存組件庫中的組件接口。
- 適配器(Adapter)類:它是一個轉換器,通過繼承或引用適配者的對象,把適配者接口轉換成目標接口,讓客戶按目標接口的格式訪問適配者。
2.1 類適配器模式
實現(xiàn)方式:定義一個適配器類來實現(xiàn)當前系統(tǒng)的業(yè)務接口,同時又繼承現(xiàn)有組件庫中已經(jīng)存在的組件。
【例】讀卡器
現(xiàn)有一臺電腦只能讀取SD卡,而要讀取TF卡中的內容的話就需要使用到適配器模式。創(chuàng)建一個讀卡器,將TF卡中的內容讀取出來。
//SD卡的接口
public interface SDCard {
//讀取SD卡方法
String readSD();
//寫入SD卡功能
void writeSD(String msg);
}
//SD卡實現(xiàn)類
public class SDCardImpl implements SDCard {
public String readSD() {
String msg = "sd card read a msg :hello word SD";
return msg;
}
public void writeSD(String msg) {
System.out.println("sd card write msg : " + msg);
}
}
//電腦類
public class Computer {
public String readSD(SDCard sdCard) {
if(sdCard == null) {
throw new NullPointerException("sd card null");
}
return sdCard.readSD();
}
}
//TF卡接口
public interface TFCard {
//讀取TF卡方法
String readTF();
//寫入TF卡功能
void writeTF(String msg);
}
//TF卡實現(xiàn)類
public class TFCardImpl implements TFCard {
public String readTF() {
String msg ="tf card read msg : hello word tf card";
return msg;
}
public void writeTF(String msg) {
System.out.println("tf card write a msg : " + msg);
}
}
//定義適配器類(SD兼容TF)
public class SDAdapterTF extends TFCardImpl implements SDCard {
public String readSD() {
System.out.println("adapter read tf card ");
return readTF();
}
public void writeSD(String msg) {
System.out.println("adapter write tf card");
writeTF(msg);
}
}
//測試類
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCard sdCard = new SDCardImpl();
System.out.println(computer.readSD(sdCard));
System.out.println("------------");
SDAdapterTF adapter = new SDAdapterTF();
System.out.println(computer.readSD(adapter));
}
}
類適配器模式違背了合成復用原則。類適配器是客戶類有一個接口規(guī)范的情況下可用,反之不可用。
2.2 對象適配器模式(好好看一下)
實現(xiàn)方式:對象適配器模式可釆用將現(xiàn)有組件庫中已經(jīng)實現(xiàn)的組件引入適配器類中,該類同時實現(xiàn)當前系統(tǒng)的業(yè)務接口。
【例】讀卡器
我們使用對象適配器模式將讀卡器的案例進行改寫。
//創(chuàng)建適配器對象(SD兼容TF)
public class SDAdapterTF implements SDCard {
private TFCard tfCard;
public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}
public String readSD() {
System.out.println("adapter read tf card ");
return tfCard.readTF();
}
public void writeSD(String msg) {
System.out.println("adapter write tf card");
tfCard.writeTF(msg);
}
}
//測試類
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCard sdCard = new SDCardImpl();
System.out.println(computer.readSD(sdCard));
System.out.println("------------");
TFCard tfCard = new TFCardImpl();
SDAdapterTF adapter = new SDAdapterTF(tfCard);
System.out.println(computer.readSD(adapter));
}
}
注意:還有一個適配器模式是接口適配器模式。當不希望實現(xiàn)一個接口中所有的方法時,可以創(chuàng)建一個抽象類Adapter ,實現(xiàn)所有方法。而此時我們只需要繼承該抽象類即可。
2.3 應用場景
- 以前開發(fā)的系統(tǒng)存在滿足新系統(tǒng)功能需求的類,但其接口同新系統(tǒng)的接口不一致。
- 使用第三方提供的組件,但組件接口定義和自己要求的接口定義不同。
2.4 源碼解析
Reader(字符流)、InputStream(字節(jié)流)的適配使用的是InputStreamReader。
InputStreamReader繼承自java.io包中的Reader,對他中的抽象的未實現(xiàn)的方法給出實現(xiàn)。如:
public int read() throws IOException {
return sd.read();
}
public int read(char cbuf[], int offset, int length) throws IOException {
return sd.read(cbuf, offset, length);
}
如上代碼中的sd(StreamDecoder類對象),在Sun的JDK實現(xiàn)中,實際的方法實現(xiàn)是對sun.nio.cs.StreamDecoder類的同名方法的調用封裝。
從上圖可以看出:
- InputStreamReader是對同樣實現(xiàn)了Reader的StreamDecoder的封裝。
- StreamDecoder不是Java SE API中的內容,是Sun JDK給出的自身實現(xiàn)。但我們知道他們對構造方法中的字節(jié)流類(InputStream)進行封裝,并通過該類進行了字節(jié)流和字符流之間的解碼轉換。
結論:
從表層來看,InputStreamReader做了InputStream字節(jié)流類到Reader字符流之間的轉換。而從如上Sun JDK中的實現(xiàn)類關系結構中可以看出,是StreamDecoder的設計實現(xiàn)在實際上采用了適配器模式。
3.裝飾者模式
定義:
指在不改變現(xiàn)有對象結構的情況下,動態(tài)地給該對象增加一些職責(即增加其額外功能)的模式。
結構:
裝飾(Decorator)模式中的角色:
- 抽象構件(Component)角色 :定義一個抽象接口以規(guī)范準備接收附加責任的對象。
- 具體構件(Concrete Component)角色 :實現(xiàn)抽象構件,通過裝飾角色為其添加一些職責。
- 抽象裝飾(Decorator)角色 : 繼承或實現(xiàn)抽象構件,并包含具體構件的實例,可以通過其子類擴展具體構件的功能。
- 具體裝飾(ConcreteDecorator)角色 :實現(xiàn)抽象裝飾的相關方法,并給具體構件對象添加附加的責任。
3.1 實現(xiàn)
快餐店有炒面、炒飯這些快餐,可以額外附加雞蛋、火腿、培根這些配菜,當然加配菜需要額外加錢,每個配菜的價錢通常不太一樣,那么計算總價就會顯得比較麻煩。
使用繼承的方式存在的問題:
-
擴展性不好
如果要再加一種配料(火腿腸),我們就會發(fā)現(xiàn)需要給FriedRice和FriedNoodles分別定義一個子類。如果要新增一個快餐品類(炒河粉)的話,就需要定義更多的子類。
-
產(chǎn)生過多的子類
我們使用裝飾者模式對快餐店案例進行改進,體會裝飾者模式的精髓。
//快餐接口
public abstract class FastFood {
private float price;
private String desc;
public FastFood() {
}
public FastFood(float price, String desc) {
this.price = price;
this.desc = desc;
}
public void setPrice(float price) {
this.price = price;
}
public float getPrice() {
return price;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public abstract float cost(); //獲取價格
}
//炒飯
public class FriedRice extends FastFood {
public FriedRice() {
super(10, "炒飯");
}
public float cost() {
return getPrice();
}
}
//炒面
public class FriedNoodles extends FastFood {
public FriedNoodles() {
super(12, "炒面");
}
public float cost() {
return getPrice();
}
}
//配料類
public abstract class Garnish extends FastFood {
private FastFood fastFood;
public FastFood getFastFood() {
return fastFood;
}
public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}
public Garnish(FastFood fastFood, float price, String desc) {
super(price,desc);
this.fastFood = fastFood;
}
}
//雞蛋配料
public class Egg extends Garnish {
public Egg(FastFood fastFood) {
super(fastFood,1,"雞蛋");
}
public float cost() {
return getPrice() + getFastFood().getPrice();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
//培根配料
public class Bacon extends Garnish {
public Bacon(FastFood fastFood) {
super(fastFood,2,"培根");
}
@Override
public float cost() {
return getPrice() + getFastFood().getPrice();
}
@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
//測試類
public class Client {
public static void main(String[] args) {
//點一份炒飯
FastFood food = new FriedRice();
//花費的價格
System.out.println(food.getDesc() + " " + food.cost() + "元");
System.out.println("========");
//點一份加雞蛋的炒飯
FastFood food1 = new FriedRice();
food1 = new Egg(food1);
//花費的價格
System.out.println(food1.getDesc() + " " + food1.cost() + "元");
System.out.println("========");
//點一份加培根的炒面
FastFood food2 = new FriedNoodles();
food2 = new Bacon(food2);
//花費的價格
System.out.println(food2.getDesc() + " " + food2.cost() + "元");
}
}
好處:
-
飾者模式可以帶來比繼承更加靈活性的擴展功能,使用更加方便,可以通過組合不同的裝飾者對象來獲取具有不同行為狀態(tài)的多樣化的結果。裝飾者模式比繼承更具良好的擴展性,完美的遵循開閉原則,繼承是靜態(tài)的附加責任,裝飾者則是動態(tài)的附加責任。
-
裝飾類和被裝飾類可以獨立發(fā)展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態(tài)擴展一個實現(xiàn)類的功能。
3.2 使用場景
-
當不能采用繼承的方式對系統(tǒng)進行擴充或者采用繼承不利于系統(tǒng)擴展和維護時。
不能采用繼承的情況主要有兩類:
- 第一類是系統(tǒng)中存在大量獨立的擴展,為支持每一種組合將產(chǎn)生大量的子類,使得子類數(shù)目呈爆炸性增長;
- 第二類是因為類定義不能繼承(如final類)
-
在不影響其他對象的情況下,以動態(tài)、透明的方式給單個對象添加職責。
-
當對象的功能要求可以動態(tài)地添加,也可以再動態(tài)地撤銷時。
3.3 JDK源碼解析
IO流中的包裝類使用到了裝飾者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。
我們以BufferedWriter舉例來說明,先看看如何使用BufferedWriter
public class Demo {
public static void main(String[] args) throws Exception{
//創(chuàng)建BufferedWriter對象
//創(chuàng)建FileWriter對象
FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
BufferedWriter bw = new BufferedWriter(fw);
//寫數(shù)據(jù)
bw.write("hello Buffered");
bw.close();
}
}
使用起來感覺確實像是裝飾者模式,接下來看它們的結構:
小結:
?BufferedWriter使用裝飾者模式對Writer子實現(xiàn)類進行了增強,添加了緩沖區(qū),提高了寫數(shù)據(jù)的效率。
3.4 代理和裝飾者的區(qū)別
靜態(tài)代理和裝飾者模式的區(qū)別:
- 相同點:
- 都要實現(xiàn)與目標類相同的業(yè)務接口
- 在兩個類中都要聲明目標對象
- 都可以在不修改目標類的前提下增強目標方法
- 不同點:
- 目的不同
裝飾者是為了增強目標對象
靜態(tài)代理是為了保護和隱藏目標對象 - 獲取目標對象構建的地方不同
裝飾者是由外界傳遞進來,可以通過構造方法傳遞
靜態(tài)代理是在代理類內部創(chuàng)建,以此來隱藏目標對象
- 目的不同
4.橋接模式
將抽象與實現(xiàn)分離,使它們可以獨立變化。它是用組合關系代替繼承關系來實現(xiàn),從而降低了抽象和實現(xiàn)這兩個可變維度的耦合度。
結構:
橋接(Bridge)模式包含以下主要角色:
- 抽象化(Abstraction)角色 :定義抽象類,并包含一個對實現(xiàn)化對象的引用。
- 擴展抽象化(Refined Abstraction)角色 :是抽象化角色的子類,實現(xiàn)父類中的業(yè)務方法,并通過組合關系調用實現(xiàn)化角色中的業(yè)務方法。
- 實現(xiàn)化(Implementor)角色 :定義實現(xiàn)化角色的接口,供擴展抽象化角色調用。
- 具體實現(xiàn)化(Concrete Implementor)角色 :給出實現(xiàn)化角色接口的具體實現(xiàn)。
4.1 實現(xiàn)
現(xiàn)在有一個需求,需要創(chuàng)建不同的圖形,并且每個圖形都有可能會有不同的顏色。我們可以利用繼承的方式來設計類的關系:
我們可以發(fā)現(xiàn)有很多的類,假如我們再增加一個形狀或再增加一種顏色,就需要創(chuàng)建更多的類。
試想,在一個有多種可能會變化的維度的系統(tǒng)中,用繼承方式會造成類爆炸,擴展起來不靈活。每次在一個維度上新增一個具體實現(xiàn)都要增加多個子類。為了更加靈活的設計系統(tǒng),我們此時可以考慮使用橋接模式。
【例】視頻播放器
需要開發(fā)一個跨平臺視頻播放器,可以在不同操作系統(tǒng)平臺(如Windows、Mac、Linux等)上播放多種格式的視頻文件,常見的視頻格式包括RMVB、AVI、WMV等。該播放器包含了兩個維度,適合使用橋接模式。
//視頻文件
public interface VideoFile {
void decode(String fileName);
}
//avi文件
public class AVIFile implements VideoFile {
public void decode(String fileName) {
System.out.println("avi視頻文件:"+ fileName);
}
}
//rmvb文件
public class REVBBFile implements VideoFile {
public void decode(String fileName) {
System.out.println("rmvb文件:" + fileName);
}
}
//操作系統(tǒng)版本
public abstract class OperatingSystemVersion {
protected VideoFile videoFile;
public OperatingSystemVersion(VideoFile videoFile) {
this.videoFile = videoFile;
}
public abstract void play(String fileName);
}
//Windows版本
public class Windows extends OperatingSystem {
public Windows(VideoFile videoFile) {
super(videoFile);
}
public void play(String fileName) {
videoFile.decode(fileName);
}
}
//mac版本
public class Mac extends OperatingSystemVersion {
public Mac(VideoFile videoFile) {
super(videoFile);
}
public void play(String fileName) {
videoFile.decode(fileName);
}
}
//測試類
public class Client {
public static void main(String[] args) {
OperatingSystem os = new Windows(new AVIFile());
os.play("戰(zhàn)狼3");
}
}
好處:
-
橋接模式提高了系統(tǒng)的可擴充性,在兩個變化維度中任意擴展一個維度,都不需要修改原有系統(tǒng)。
如:如果現(xiàn)在還有一種視頻文件類型wmv,我們只需要再定義一個類實現(xiàn)VideoFile接口即可,其他類不需要發(fā)生變化。
-
實現(xiàn)細節(jié)對客戶透明
4.2 使用場景
- 當一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴展時。
- 當一個系統(tǒng)不希望使用繼承或因為多層次繼承導致系統(tǒng)類的個數(shù)急劇增加時。
- 當一個系統(tǒng)需要在構件的抽象化角色和具體化角色之間增加更多的靈活性時。避免在兩個層次之間建立靜態(tài)的繼承聯(lián)系,通過橋接模式可以使它們在抽象層建立一個關聯(lián)關系。
5.外觀模式
又名門面模式,是一種通過為多個復雜的子系統(tǒng)提供一個一致的接口,而使這些子系統(tǒng)更加容易被訪問的模式。該模式對外有一個統(tǒng)一接口,外部應用程序不用關心內部子系統(tǒng)的具體的細節(jié),這樣會大大降低應用程序的復雜度,提高了程序的可維護性。
? 外觀(Facade)模式是“迪米特法則”的典型應用
結構
外觀(Facade)模式包含以下主要角色:
- 外觀(Facade)角色:為多個子系統(tǒng)對外提供一個共同的接口。
- 子系統(tǒng)(Sub System)角色:實現(xiàn)系統(tǒng)的部分功能,客戶可以通過外觀角色訪問它。
5.1 實現(xiàn)
【例】智能家電控制
小明的爺爺已經(jīng)60歲了,一個人在家生活:每次都需要打開燈、打開電視、打開空調;睡覺時關閉燈、關閉電視、關閉空調;操作起來都比較麻煩。所以小明給爺爺買了智能音箱,可以通過語音直接控制這些智能家電的開啟和關閉。類圖如下:
//燈類
public class Light {
public void on() {
System.out.println("打開了燈....");
}
public void off() {
System.out.println("關閉了燈....");
}
}
//電視類
public class TV {
public void on() {
System.out.println("打開了電視....");
}
public void off() {
System.out.println("關閉了電視....");
}
}
//控制類
public class AirCondition {
public void on() {
System.out.println("打開了空調....");
}
public void off() {
System.out.println("關閉了空調....");
}
}
//智能音箱
public class SmartAppliancesFacade {
private Light light;
private TV tv;
private AirCondition airCondition;
public SmartAppliancesFacade() {
light = new Light();
tv = new TV();
airCondition = new AirCondition();
}
public void say(String message) {
if(message.contains("打開")) {
on();
} else if(message.contains("關閉")) {
off();
} else {
System.out.println("我還聽不懂你說的?。?!");
}
}
//起床后一鍵開電器
private void on() {
System.out.println("起床了");
light.on();
tv.on();
airCondition.on();
}
//睡覺一鍵關電器
private void off() {
System.out.println("睡覺了");
light.off();
tv.off();
airCondition.off();
}
}
//測試類
public class Client {
public static void main(String[] args) {
//創(chuàng)建外觀對象
SmartAppliancesFacade facade = new SmartAppliancesFacade();
//客戶端直接與外觀對象進行交互
facade.say("打開家電");
facade.say("關閉家電");
}
}
好處:
- 降低了子系統(tǒng)與客戶端之間的耦合度,使得子系統(tǒng)的變化不會影響調用它的客戶類。
- 對客戶屏蔽了子系統(tǒng)組件,減少了客戶處理的對象數(shù)目,并使得子系統(tǒng)使用起來更加容易。
缺點:
- 不符合開閉原則,修改很麻煩
5.2使用場景
- 對分層結構系統(tǒng)構建時,使用外觀模式定義子系統(tǒng)中每層的入口點可以簡化子系統(tǒng)之間的依賴關系。
- 當一個復雜系統(tǒng)的子系統(tǒng)很多時,外觀模式可以為系統(tǒng)設計一個簡單的接口供外界訪問。
- 當客戶端與多個子系統(tǒng)之間存在很大的聯(lián)系時,引入外觀模式可將它們分離,從而提高子系統(tǒng)的獨立性和可移植性。
5.3源碼分析
使用tomcat作為web容器時,接收瀏覽器發(fā)送過來的請求,tomcat會將請求信息封裝成ServletRequest對象,如下圖①處對象。但是大家想想ServletRequest是一個接口,它還有一個子接口HttpServletRequest,而我們知道該request對象肯定是一個HttpServletRequest對象的子實現(xiàn)類對象,到底是哪個類的對象呢?可以通過輸出request對象,我們就會發(fā)現(xiàn)是一個名為RequestFacade的類的對象。
RequestFacade類就使用了外觀模式。先看結構圖:
為什么在此處使用外觀模式呢?
? 定義 RequestFacade 類,分別實現(xiàn) ServletRequest ,同時定義私有成員變量 Request ,并且方法的實現(xiàn)調用 Request 的實現(xiàn)。然后,將 RequestFacade上轉為 ServletRequest 傳給 servlet 的 service 方法,這樣即使在 servlet 中被下轉為 RequestFacade ,也不能訪問私有成員變量對象中的方法。既用了 Request ,又能防止其中方法被不合理的訪問。
6.組合模式
定義:
? 又名部分整體模式,是用于把一組相似的對象當作一個單一的對象。組合模式依據(jù)樹形結構來組合對象,用來表示部分以及整體層次。這種類型的設計模式屬于結構型模式,它創(chuàng)建了對象組的樹形結構。
結構
組合模式主要包含三種角色:
- 抽象根節(jié)點(Component):定義系統(tǒng)各層次對象的共有方法和屬性,可以預先定義一些默認行為和屬性。
- 樹枝節(jié)點(Composite):定義樹枝節(jié)點的行為,存儲子節(jié)點,組合樹枝節(jié)點和葉子節(jié)點形成一個樹形結構。
- 葉子節(jié)點(Leaf):葉子節(jié)點對象,其下再無分支,是系統(tǒng)層次遍歷的最小單位。
6.1 實現(xiàn)
軟件菜單
如下圖,我們在訪問別的一些管理系統(tǒng)時,經(jīng)??梢钥吹筋愃频牟藛?。一個菜單可以包含菜單項(菜單項是指不再包含其他內容的菜單條目),也可以包含帶有其他菜單項的菜單,因此使用組合模式描述菜單就很恰當,我們的需求是針對一個菜單,打印出其包含的所有菜單以及菜單項的名稱。
不管是菜單還是菜單項,都應該繼承自統(tǒng)一的接口,這里姑且將這個統(tǒng)一的接口稱為菜單組件。
//菜單組件 不管是菜單還是菜單項,都應該繼承該類
public abstract class MenuComponent {
protected String name;
protected int level;
//添加菜單
public void add(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
//移除菜單
public void remove(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
//獲取指定的子菜單
public MenuComponent getChild(int i){
throw new UnsupportedOperationException();
}
//獲取菜單名稱
public String getName(){
return name;
}
public void print(){
throw new UnsupportedOperationException();
}
}
這里的MenuComponent定義為抽象類,因為有一些共有的屬性和行為要在該類中實現(xiàn),Menu和MenuItem類就可以只覆蓋自己感興趣的方法,而不用搭理不需要或者不感興趣的方法,舉例來說,Menu類可以包含子菜單,因此需要覆蓋add()、remove()、getChild()方法,但是MenuItem就不應該有這些方法。這里給出的默認實現(xiàn)是拋出異常,你也可以根據(jù)自己的需要改寫默認實現(xiàn)。
public class Menu extends MenuComponent {
private List<MenuComponent> menuComponentList;
public Menu(String name,int level){
this.level = level;
this.name = name;
menuComponentList = new ArrayList<MenuComponent>();
}
@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}
@Override
public MenuComponent getChild(int i) {
return menuComponentList.get(i);
}
@Override
public void print() {
for (int i = 1; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
for (MenuComponent menuComponent : menuComponentList) {
menuComponent.print();
}
}
}
Menu類已經(jīng)實現(xiàn)了除了getName方法的其他所有方法,因為Menu類具有添加菜單,移除菜單和獲取子菜單的功能。
public class MenuItem extends MenuComponent {
public MenuItem(String name,int level) {
this.name = name;
this.level = level;
}
@Override
public void print() {
for (int i = 1; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
}
}
MenuItem是菜單項,不能再有子菜單,所以添加菜單,移除菜單和獲取子菜單的功能并不能實現(xiàn)。
6.2 組合模式的分類
在使用組合模式時,根據(jù)抽象構件類的定義形式,我們可將組合模式分為透明組合模式和安全組合模式兩種形式。
-
透明組合模式
透明組合模式中,抽象根節(jié)點角色中聲明了所有用于管理成員對象的方法,比如在示例中
MenuComponent
聲明了add
、remove
、getChild
方法,這樣做的好處是確保所有的構件類都有相同的接口。透明組合模式也是組合模式的標準形式。透明組合模式的缺點是不夠安全,因為葉子對象和容器對象在本質上是有區(qū)別的,葉子對象不可能有下一個層次的對象,即不可能包含成員對象,因此為其提供 add()、remove() 等方法是沒有意義的,這在編譯階段不會出錯,但在運行階段如果調用這些方法可能會出錯(如果沒有提供相應的錯誤處理代碼)
-
安全組合模式
在安全組合模式中,在抽象構件角色中沒有聲明任何用于管理成員對象的方法,而是在樹枝節(jié)點
Menu
類中聲明并實現(xiàn)這些方法。安全組合模式的缺點是不夠透明,因為葉子構件和容器構件具有不同的方法,且容器構件中那些用于管理成員對象的方法沒有在抽象構件類中定義,因此客戶端不能完全針對抽象編程,必須有區(qū)別地對待葉子構件和容器構件。
6.3 優(yōu)點
- 組合模式可以清楚地定義分層次的復雜對象,表示對象的全部或部分層次,它讓客戶端忽略了層次的差異,方便對整個層次結構進行控制。
- 客戶端可以一致地使用一個組合結構或其中單個對象,不必關心處理的是單個對象還是整個組合結構,簡化了客戶端代碼。
- 在組合模式中增加新的樹枝節(jié)點和葉子節(jié)點都很方便,無須對現(xiàn)有類庫進行任何修改,符合“開閉原則”。
- 組合模式為樹形結構的面向對象實現(xiàn)提供了一種靈活的解決方案,通過葉子節(jié)點和樹枝節(jié)點的遞歸組合,可以形成復雜的樹形結構,但對樹形結構的控制卻非常簡單。
6.4 使用場景
組合模式正是應樹形結構而生,所以組合模式的使用場景就是出現(xiàn)樹形結構的地方。比如:文件目錄顯示,多級目錄呈現(xiàn)等樹形結構數(shù)據(jù)的操作。
7.享元模式
定義:
? 運用共享技術來有效地支持大量細粒度對象的復用。它通過共享已經(jīng)存在的對象來大幅度減少需要創(chuàng)建的對象數(shù)量、避免大量相似對象的開銷,從而提高系統(tǒng)資源的利用率。
結構
享元(Flyweight )模式中存在以下兩種狀態(tài):
- 內部狀態(tài),即不會隨著環(huán)境的改變而改變的可共享部分。
- 外部狀態(tài),指隨環(huán)境改變而改變的不可以共享的部分。享元模式的實現(xiàn)要領就是區(qū)分應用中的這兩種狀態(tài),并將外部狀態(tài)外部化。
享元模式的主要有以下角色:
- 抽象享元角色(Flyweight):通常是一個接口或抽象類,在抽象享元類中聲明了具體享元類公共的方法,這些方法可以向外界提供享元對象的內部數(shù)據(jù)(內部狀態(tài)),同時也可以通過這些方法來設置外部數(shù)據(jù)(外部狀態(tài))。
- 具體享元(Concrete Flyweight)角色 :它實現(xiàn)了抽象享元類,稱為享元對象;在具體享元類中為內部狀態(tài)提供了存儲空間。通常我們可以結合單例模式來設計具體享元類,為每一個具體享元類提供唯一的享元對象。
- 非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元類的子類都需要被共享,不能被共享的子類可設計為非共享具體享元類;當需要一個非共享具體享元類的對象時可以直接通過實例化創(chuàng)建。
- 享元工廠(Flyweight Factory)角色 :負責創(chuàng)建和管理享元角色。當客戶對象請求一個享元對象時,享元工廠檢査系統(tǒng)中是否存在符合要求的享元對象,如果存在則提供給客戶;如果不存在的話,則創(chuàng)建一個新的享元對象。
7.1 實現(xiàn)
【例】俄羅斯方塊
下面的圖片是眾所周知的俄羅斯方塊中的一個個方塊,如果在俄羅斯方塊這個游戲中,每個不同的方塊都是一個實例對象,這些對象就要占用很多的內存空間,下面利用享元模式進行實現(xiàn)。
俄羅斯方塊有不同的形狀,我們可以對這些形狀向上抽取出AbstractBox,用來定義共性的屬性和行為。
public abstract class AbstractBox {
public abstract String getShape();
public void display(String color) {
System.out.println("方塊形狀:" + this.getShape() + " 顏色:" + color);
}
}
接下來就是定義不同的形狀了,IBox類、LBox類、OBox類等。
public class IBox extends AbstractBox {
@Override
public String getShape() {
return "I";
}
}
public class LBox extends AbstractBox {
@Override
public String getShape() {
return "L";
}
}
public class OBox extends AbstractBox {
@Override
public String getShape() {
return "O";
}
}
提供了一個工廠類(BoxFactory),用來管理享元對象(也就是AbstractBox子類對象),該工廠類對象只需要一個,所以可以使用單例模式。并給工廠類提供一個獲取形狀的方法。
public class BoxFactory {
private static HashMap<String, AbstractBox> map;
private BoxFactory() {
map = new HashMap<String, AbstractBox>();
AbstractBox iBox = new IBox();
AbstractBox lBox = new LBox();
AbstractBox oBox = new OBox();
map.put("I", iBox);
map.put("L", lBox);
map.put("O", oBox);
}
public static final BoxFactory getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final BoxFactory INSTANCE = new BoxFactory();
}
public AbstractBox getBox(String key) {
return map.get(key);
}
}
7.2 優(yōu)缺點和使用場景
1,優(yōu)點
- 極大減少內存中相似或相同對象數(shù)量,節(jié)約系統(tǒng)資源,提供系統(tǒng)性能
- 享元模式中的外部狀態(tài)相對獨立,且不影響內部狀態(tài)
2,缺點:
為了使對象可以共享,需要將享元對象的部分狀態(tài)外部化,分離內部狀態(tài)和外部狀態(tài),使程序邏輯復雜
3,使用場景:
- 一個系統(tǒng)有大量相同或者相似的對象,造成內存的大量耗費。
- 對象的大部分狀態(tài)都可以外部化,可以將這些外部狀態(tài)傳入對象中。
- 在使用享元模式時需要維護一個存儲享元對象的享元池,而這需要耗費一定的系統(tǒng)資源,因此,應當在需要多次重復使用享元對象時才值得使用享元模式。
7.3 JDK源碼解析
Integer類使用了享元模式。我們先看下面的例子:
public class Demo {
public static void main(String[] args) {
Integer i1 = 127;
Integer i2 = 127;
System.out.println("i1和i2對象是否是同一個對象?" + (i1 == i2));
Integer i3 = 128;
Integer i4 = 128;
System.out.println("i3和i4對象是否是同一個對象?" + (i3 == i4));
}
}
運行上面代碼,結果如下:
為什么第一個輸出語句輸出的是true,第二個輸出語句輸出的是false?通過反編譯軟件進行反編譯,代碼如下:
public class Demo {
public static void main(String[] args) {
Integer i1 = Integer.valueOf((int)127);
Integer i2 Integer.valueOf((int)127);
System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString());
Integer i3 = Integer.valueOf((int)128);
Integer i4 = Integer.valueOf((int)128);
System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString());
}
}
上面代碼可以看到,直接給Integer類型的變量賦值基本數(shù)據(jù)類型數(shù)據(jù)的操作底層使用的是 valueOf()
,所以只需要看該方法即可文章來源:http://www.zghlxwxcb.cn/news/detail-543869.html
public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
}
可以看到 Integer
默認先創(chuàng)建并緩存 -128 ~ 127
之間數(shù)的 Integer
對象,當調用 valueOf
時如果參數(shù)在 -128 ~ 127
之間則計算下標并從緩存中返回,否則創(chuàng)建一個新的 Integer
對象。文章來源地址http://www.zghlxwxcb.cn/news/detail-543869.html
到了這里,關于設計模式--------結構型模式的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!