最近在讀秦小波寫的設(shè)計(jì)模式之禪這本書,結(jié)構(gòu)類模式讀完了,寫下一篇文章做下總結(jié)。結(jié)構(gòu)類模式包括適配器模式、橋梁模式、組合模式、裝飾模式、門面模式、享元模式和代理模式。為什么叫結(jié)構(gòu)類模式呢?因?yàn)樗鼈兌际峭ㄟ^組合類或?qū)ο螽a(chǎn)生更大結(jié)構(gòu)以適應(yīng)更高層次的邏輯需求。
適配器模式(Adapter Pattern)
定義:將一個(gè)類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個(gè)類能夠在一起工作。通用類圖如下:

如何理解呢?
從通用類圖中可以看到有三個(gè)角色,Adaptee源角色、Target目標(biāo)角色、Adapter適配器角色,Target就是客戶端所期待的接口,Adaptee就是要被轉(zhuǎn)換的角色,通常它是已經(jīng)存在的、運(yùn)行良好的類或?qū)ο?,Adapter的作用就是把Adaptee轉(zhuǎn)換成為Target。簡(jiǎn)單來說就是有A和B兩個(gè)不同的類,如果想讓A類像B類一樣使用,這個(gè)時(shí)候就可以通過一個(gè)適配器C類,適配器C類負(fù)責(zé)把A類轉(zhuǎn)換成B類,這樣使用A類就可以像使用B類那樣。
適配器模式的使用場(chǎng)景有哪些?只要記住一點(diǎn)就可以了:你有動(dòng)機(jī)修改一個(gè)已經(jīng)投產(chǎn)的接口時(shí),就可以考慮適配器模式了。比如系統(tǒng)擴(kuò)展了,需要使用一個(gè)已有或新建立的類,但這個(gè)類又不符合系統(tǒng)的接口,可以使用適配器模式解決這個(gè)問題。
適配器模式通用源碼
public interface Target {
// 目標(biāo)角色有自己的方法
public void request();
}
public class ConcreteTarget implements Target {
@Override
public void request() {
System.out.println("If you need any help, please call me.");
}
}
public class Adaptee {
// 原有的業(yè)務(wù)邏輯
public void doSomething() {
System.out.println("我非常忙");
}
}
public class Adapter extends Adaptee implements Target {
@Override
public void request() {
super.doSomething();
}
}
// 在Client場(chǎng)景類中使用它
public class Client {
public static void main(String[] args) {
// 原有業(yè)務(wù)邏輯
Target target = new ConcreteTarget();
target.request();
// 增加了適配器后的角色
Target target1 = new Adapter();
target1.request();
}
}
適配模式的擴(kuò)展
前面所說的可以被稱為類適配器,下面介紹一種對(duì)象適配器,是適配器模式的一種擴(kuò)展,通用類圖如下:

比較兩個(gè)通用類圖,發(fā)現(xiàn)一種是Adapter繼承于Adaptee,一種是Adapter關(guān)聯(lián)Adaptee1和Adaptee2,所以通過繼承關(guān)系的叫做類適配器,通過關(guān)聯(lián)關(guān)系的叫做對(duì)象適配器。類適配器與對(duì)象適配器的區(qū)別:類適配器是類間繼承,對(duì)象適配器是對(duì)象的合成關(guān)系,也可以說是類的關(guān)聯(lián)關(guān)系。
注意點(diǎn):Adapter適配器不要加入自己的實(shí)現(xiàn),它所有的實(shí)現(xiàn)都是委托給Adaptee源角色來實(shí)現(xiàn)的。
代理模式(Proxy Pattern)
定義:為其他對(duì)象提供一種代理以控制這個(gè)對(duì)象的訪問。通用類圖如下:

如何理解代理模式呢?
通過一個(gè)例子來理解,比如一家公司請(qǐng)明星為自己的產(chǎn)品代言,這家公司如果直接聯(lián)系該明星,明星就會(huì)說這件事請(qǐng)和我的經(jīng)紀(jì)人聯(lián)系,換句話說就是想要請(qǐng)明星代言,先要找到其經(jīng)紀(jì)人,通過經(jīng)紀(jì)人在去和明星進(jìn)行商討。在這里經(jīng)紀(jì)人就可以理解為一個(gè)代理,通過經(jīng)紀(jì)人來對(duì)明星進(jìn)行訪問,這就是代理模式。
代理模式的使用場(chǎng)景?比如Spring AOP,非常典型的動(dòng)態(tài)代理。
代理模式通用源碼
public interface Subject {
public void request();
}
public class RealSubject implements Subject {
@Override
public void request() {
// 業(yè)務(wù)邏輯處理
}
}
public class Proxy implements Subject {
// 要代理哪個(gè)實(shí)現(xiàn)類
private Subject subject = null;
public Proxy(Subject subject) {
this.subject = subject;
}
@Override
public void request() {
this.before();
this.subject.request();
this.after();
}
// 預(yù)處理
private void before() {
// do something
}
// 善后處理
private void after() {
// do something
}
}
// Client場(chǎng)景類
public class Client {
public static void main(String[] args) {
Subject subject = new RealSubject();
Proxy proxy = new Proxy(subject);
proxy.request();
}
}
代理模式的擴(kuò)展
擴(kuò)展一:普通代理
它要求客戶端只能訪問代理角色,而不能訪問真實(shí)角色。
擴(kuò)展二:強(qiáng)制代理
要求客戶端必須通過真實(shí)角色找到代理角色,否則不能訪問真實(shí)角色。
擴(kuò)展三:代理是有個(gè)性的
一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,完成不同任務(wù)的整合。也就是說代理類不僅僅可以實(shí)現(xiàn)主題接口,也可以實(shí)現(xiàn)其他接口完成不同的任務(wù),而且代理的目的是在目標(biāo)對(duì)象的基礎(chǔ)上作增強(qiáng),這種增強(qiáng)的本質(zhì)通常就是對(duì)目標(biāo)對(duì)象的方法進(jìn)行攔截和過濾。
擴(kuò)展四:動(dòng)態(tài)代理
動(dòng)態(tài)代理通用類圖如下:

動(dòng)態(tài)代理是在實(shí)現(xiàn)階段不用關(guān)心代理誰,而在運(yùn)行階段才指定代理哪一個(gè)對(duì)象。
裝飾模式(Decorator Pattern)
定義:動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來說,裝飾模式比生成子類更為靈活。通用類圖如下:

如何理解裝飾呢?
裝飾類比較簡(jiǎn)單,看下其使用場(chǎng)景:需要擴(kuò)展一個(gè)類的功能,或給一個(gè)類增加附加功能;需要?jiǎng)討B(tài)地給一個(gè)對(duì)象增加功能,這些功能可以再動(dòng)態(tài)地撤銷;需要為一批兄弟類進(jìn)行改裝或加裝功能。
裝飾模式通用源碼
public abstract class Component {
public abstract void operate();
}
public class ConcreteComponent extends Component {
@Override
public void operate() {
System.out.println("do something");
}
}
public class Decorator extends Component {
private Component component = null;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operate() {
this.component.operate();
}
}
public class ConcreteDecorator1 extends Decorator {
public ConcreteDecorator1(Component component) {
super(component);
}
public void method1() {
System.out.println("method1 修飾");
}
@Override
public void operate() {
this.method1();
super.operate();
}
}
public class ConcreteDecorator2 extends Decorator {
public ConcreteDecorator2(Component component) {
super(component);
}
public void method2() {
System.out.println("method2 修飾");
}
@Override
public void operate() {
this.method2();
super.operate();
}
}
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
// 修飾1
component = new ConcreteDecorator1(component);
// 修飾2
component = new ConcreteDecorator2(component);
component.operate();
}
}
門面模式(Facade Pattern)
定義:要求一個(gè)子系統(tǒng)的外部與其內(nèi)部的通訊必須通過一個(gè)統(tǒng)一的對(duì)象進(jìn)行。門面模式提供一個(gè)高層次的接口,使得子系統(tǒng)更易于使用。通用類圖如下:

如何理解門面模式呢?
簡(jiǎn)單來說,門面對(duì)象是外界訪問子系統(tǒng)內(nèi)部的唯一通道。比如有A、B、C三個(gè)子系統(tǒng),要想訪問這三個(gè)子系統(tǒng),必須通過一個(gè)統(tǒng)一的門面對(duì)象來訪問。
門面模式的使用場(chǎng)景:為一個(gè)復(fù)雜的模塊或子系統(tǒng)提供一個(gè)供外界訪問的接口;子系統(tǒng)相對(duì)獨(dú)立(外界對(duì)系統(tǒng)的訪問只要黑箱操作即可)。
注意點(diǎn):門面不參與子系統(tǒng)內(nèi)的業(yè)務(wù)邏輯。
門面模式通用源碼
public class ClassA {
// 子系統(tǒng)A
public void doSomethingA() {
System.out.println("do something A");
}
}
public class ClassB {
// 子系統(tǒng)B
public void doSomethingB() {
System.out.println("do something B");
}
}
public class ClassC {
// 子系統(tǒng)C
public void doSomethingC() {
System.out.println("do something C");
}
}
public class Facade {
// 被委托的對(duì)象
ClassA classA = new ClassA();
ClassB classB = new ClassB();
ClassC classC = new ClassC();
// 提供給外部的訪問方法
public void methodA() {
this.classA.doSomethingA();
}
public void methodB() {
this.classB.doSomethingB();
}
public void methodC() {
this.classC.doSomethingC();
}
}
組合模式(Composite Pattern)
定義:將對(duì)象組合成樹形結(jié)構(gòu)以表示部分-整體的層次結(jié)構(gòu),使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性。通用類圖如下:

如何理解組合模式呢?
Component抽象構(gòu)件角色:定義參加組合對(duì)象的共有方法和屬性,可以定義一些默認(rèn)的行為或?qū)傩?;Leaf葉子構(gòu)件:葉子對(duì)象,其下再也沒有其他的分支,也就是遍歷的最小單位;Composite樹枝構(gòu)件:樹枝對(duì)象,它的作用是組合樹枝節(jié)點(diǎn)和葉子節(jié)點(diǎn)形成一個(gè)樹形結(jié)構(gòu)。
組合模式的使用場(chǎng)景:維護(hù)和展示部分-整體關(guān)系的場(chǎng)景,如樹形菜單、文件和文件夾管理;從一個(gè)整體中能夠獨(dú)立出部分模塊或功能的場(chǎng)景。
組合模式通用源碼
public abstract class Component {
// 個(gè)體和整體都具有的共享 抽象構(gòu)件
public void doSomething() {
// 編寫業(yè)務(wù)邏輯
System.out.println("個(gè)體和整體都具有的共享");
}
}
public class Composite extends Component {
// 構(gòu)件容器
private ArrayList<Component> componentArrayList = new ArrayList<Component>();
// 增加一個(gè)葉子構(gòu)件或樹枝構(gòu)件
public void add(Component component) {
this.componentArrayList.add(component);
}
// 刪除一個(gè)葉子構(gòu)件或樹枝構(gòu)件
public void remove(Component component) {
this.componentArrayList.remove(component);
}
// 獲得分支下的所有葉子構(gòu)件和樹枝構(gòu)件
public ArrayList<Component> getChildren() {
return this.componentArrayList;
}
}
public class Leaf extends Component {
// 樹葉構(gòu)件
}
// 場(chǎng)景類
public class Client {
public static void main(String[] args) {
// 創(chuàng)建一個(gè)根節(jié)點(diǎn)
Composite root = new Composite();
root.doSomething();
// 創(chuàng)建一個(gè)樹枝構(gòu)件
Composite branch = new Composite();
// 創(chuàng)建一個(gè)葉子節(jié)點(diǎn)
Leaf leaf = new Leaf();
// 建立整體
root.add(branch);
branch.add(leaf);
display(root);
}
// 通過遞歸遍歷樹
public static void display(Composite root) {
for (Component c : root.getChildren()) {
if (c instanceof Leaf) {
// 葉子結(jié)點(diǎn)
c.doSomething();
}else {
// 樹枝結(jié)點(diǎn)
display((Composite) c);
}
}
}
}
橋梁模式(Bridge Pattern)
定義:將抽象和實(shí)現(xiàn)解耦,使得兩者可以獨(dú)立地變化。通用類圖如下:

如何理解橋梁模式呢?
簡(jiǎn)單來理解就是把經(jīng)常變化的部分封裝到Implementor中,不經(jīng)常變化的留在抽象中。如果抽閑的實(shí)現(xiàn)類想要調(diào)用Implementor中的方法怎么辦?搭個(gè)橋過去,也就是在實(shí)現(xiàn)類中保留一個(gè)Implementor的引用,這也是為什么叫橋梁模式的原因。最終的目的就是把抽象和實(shí)現(xiàn)分離開來。
使用場(chǎng)景有哪些呢?不希望或不適用使用繼承的場(chǎng)景,例如繼承層次過渡、無法更細(xì)化設(shè)計(jì)顆粒等場(chǎng)景,需要考慮使用橋梁模式;接口或抽象類不穩(wěn)定的場(chǎng)景;重用性要求較高的場(chǎng)景,設(shè)計(jì)的顆粒度越細(xì),則被重用的可能性就越大;
橋梁模式通用源碼
public interface Implementor {
// 基本方法
public void doSomething();
public void doAnything();
}
public class ConcreteImplementor1 implements Implementor {
@Override
public void doSomething() {
// 業(yè)務(wù)邏輯處理
}
@Override
public void doAnything() {
// 業(yè)務(wù)邏輯處理
}
}
public class ConcreteImplementor2 implements Implementor {
@Override
public void doSomething() {
// 業(yè)務(wù)邏輯處理
}
@Override
public void doAnything() {
// 業(yè)務(wù)邏輯處理
}
}
public abstract class Abstraction {
private Implementor implementor;
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
// 自身的行為和屬性
public void request() {
this.implementor.doSomething();
}
// 獲得實(shí)現(xiàn)化角色
public Implementor getImplementor() {
return implementor;
}
}
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
@Override
public void request() {
super.request();
super.getImplementor().doAnything();
}
}
public class Client {
public static void main(String[] args) {
// 定義一個(gè)實(shí)現(xiàn)化角色
Implementor implementor = new ConcreteImplementor1();
// 定義一個(gè)抽象化角色
Abstraction abstraction = new RefinedAbstraction(implementor);
// 執(zhí)行行文
abstraction.request();
}
}
享元模式(Flyweight Pattern)
定義:使用共享對(duì)象可有效地支持大量的細(xì)粒度的對(duì)象。通用類圖如下:

如何理解享元模式呢?
享元模式比較簡(jiǎn)單,可以大大減少應(yīng)用程序創(chuàng)建的對(duì)象,降低程序內(nèi)存占用,增強(qiáng)程序的性能,但它同時(shí)也提高了系統(tǒng)的復(fù)雜性,需要分離出外部狀態(tài)和內(nèi)部狀態(tài),而且外部狀態(tài)具有固化特性,不應(yīng)該隨內(nèi)部狀態(tài)改變而改變,否則導(dǎo)致系統(tǒng)的邏輯混亂。
使用場(chǎng)景:系統(tǒng)中存在大量相似的對(duì)象;需要緩沖池的場(chǎng)景。
注意點(diǎn):使用享元模式可以實(shí)現(xiàn)對(duì)象池,但是這兩者還是有比較大的差異,對(duì)象池著重在對(duì)象的復(fù)用上,池中的每個(gè)對(duì)象是可替換的,從同一個(gè)池中獲得A對(duì)象和B對(duì)象對(duì)客戶端來說是完全相同的,它主要解決復(fù)用,而享元模式著重解決對(duì)象的共享問題,如何建立多個(gè)可共享的細(xì)粒度對(duì)象則是其關(guān)注的重點(diǎn)。
享元模式通用代碼
public abstract class Flyweight {
// 內(nèi)部狀態(tài)
private String intrinsic;
// 外部狀態(tài)
private final String extrinsic;
// 要求享元角色必須接受外部狀態(tài),外部狀態(tài)可以理解為key,這樣一個(gè)東西.
public Flyweight(String extrinsic) {
this.extrinsic = extrinsic;
}
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
// 定義業(yè)務(wù)操作
public abstract void operate();
}
public class ConcreteFlyweight1 extends Flyweight {
public ConcreteFlyweight1(String extrinsic) {
super(extrinsic);
}
// 根據(jù)外部狀態(tài)進(jìn)行邏輯處理
@Override
public void operate() {
// 業(yè)務(wù)邏輯處理
}
}
public class ConcreteFlyweight2 extends Flyweight {
public ConcreteFlyweight2(String extrinsic) {
super(extrinsic);
}
// 根據(jù)外部狀態(tài)進(jìn)行邏輯處理
@Override
public void operate() {
// 業(yè)務(wù)邏輯處理
}
}
public class FlyweightFactory {
// 定義一個(gè)池容器
private static HashMap<String, Flyweight> pool = new HashMap<String, Flyweight>();
// 享元工廠
public static Flyweight getFlyweight(String extrinsic) {
Flyweight flyweight = null;
if (pool.containsKey(extrinsic)) {
flyweight = pool.get(extrinsic);
}else {
// 根據(jù)外部狀態(tài)創(chuàng)建享元對(duì)象
flyweight = new ConcreteFlyweight1(extrinsic);
// 放置到池中
pool.put(extrinsic, flyweight);
}
return flyweight;
}
}
代理模式VS裝飾模式
首先要說的是裝飾模式就是代理模式的一個(gè)特殊應(yīng)用,兩者的共同點(diǎn)是都具有相同的接口,不同點(diǎn)則是代理模式著重對(duì)代理過程的控制,而裝飾模式則是對(duì)類的功能進(jìn)行加強(qiáng)或減弱,它著重類的功能變化。
裝飾模式VS適配器模式
相似點(diǎn):都是包裝作用,都是通過委托方式實(shí)現(xiàn)其功能。不同點(diǎn)是:裝飾模式包裝的是自己的兄弟類,隸屬于同一個(gè)家族(相同接口或父類),適配器模式則修飾非血緣關(guān)系類,把一個(gè)非本家族的對(duì)象偽裝成本家族的對(duì)象,注意是偽裝,本質(zhì)上還是非相同接口的對(duì)象。
國士梅花
歡迎大家關(guān)注國士梅花,技術(shù)路上與你陪伴。
