java設計模式--結構型模式
-
前言
結構型模式主要是描述如何將類和物件按某種佈局組成更大的結構。可以分為類結構型模式和物件結構型模式。
- 類結構型模式:採用繼承機制來組織介面和類
- 物件結構型模式:採用組合或聚合來組合物件
結構型模式:
- 介面卡模式:將一個類的介面轉化成客戶希望的另外一個介面,使原本介面不相容而不能在一起工作的類可以正常執行
- 橋接模式:將抽象與實現非農,使得兩部分均可以獨立變化。根據合成複用原則,以組合代替繼承關係,降低抽象和實現兩個可變緯度的耦合度。根據業務場景,這個也可以拆分多個維度
- 裝飾模式:動態的給物件增加一些職責,增加其額外功能
- 組合模式:將物件組合程梳妝層次結構,使使用者對單個物件和組合物件具有一致的訪問型
- 外觀模式:為多個複雜的子系統提供一個直直的介面,使得子系統更容易被訪問
- 享元模式:通過共享拘束有效支援大量的細粒度物件複用,例如執行緒池,資料庫連線池。
- 代理模式:為某物件提供一種代理以控制對該物件的訪問。即客戶端通過代理間接訪問該物件,從而限制增強或修改該物件的一些特性
正文
6. 介面卡模式(Adapter)
將某個類的介面轉化為客戶端期望的另外一個介面。解決相容性問題,讓原本因介面不匹配不能一起工作的兩個類可以協同工作。該模式主要分為類結構模型和物件結構型模式,類結構模式耦合性會比較高。如之前所說。組合優於繼承。
優點:
- 客戶端可以透明的呼叫目標介面
- 複用現存的類,不需要修改原有程式碼就可以重用現存的適配者類
- 將目標類和適配者類結構,解決目標類與適配者類介面不一致
- 符合開閉原則
缺點:
- 編寫過程需要結合業務場景綜合考慮,可能會增加系統的複雜性
- 程式碼閱讀難度增加,可讀性變差,尤其是過多使用介面卡最終會導致系統程式碼變得凌亂
應用場景
- 現存的系統滿足新系統功能需求的類,但是介面與新系統介面不一致
- 使用第三方提供的組建,但元件介面定義與自己要求的介面定義不同
類介面卡UML:
適配者程式碼如下,返回的是Integer
public class Adaptee {
public Integer specificRequest(){
return 1;
}
}
介面卡介面以及介面卡
public interface Target {
String request();
}
public class ClassAdapter extends Adaptee implements Target {
@Override
public String request() {
Integer value = specificRequest();
return String.valueOf(value);
}
}
測試
public class Client {
public static void main(String[] args) {
Target target = new ClassAdapter();
String request = target.request();
System.out.println(request); // 1
}
}
物件介面卡UML:
適配者如下,介面不同,返回的資料是String
public class Adaptee {
String specificRequest(){
return "2";
}
}
介面卡如下
public interface Target {
Integer request();
}
public class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee){
this.adaptee = adaptee;
}
@Override
public Integer request() {
String value = adaptee.specificRequest();
return Integer.valueOf(value);
}
}
呼叫者
public class Client {
public static void main(String[] args) {
Target target = new ObjectAdapter(new Adaptee());
Integer value = target.request();
System.out.println(value);
}
}
小結:
介面卡模式使用場景應當是現存介面仍在正常使用,或者業務邏輯複雜暫時無法明確,可以通過介面卡進行適配介面。
7. 橋接模式(Bridge)
橋接模式就是將實現與抽象放在兩個不同類層次中,基於最小設計原則,用過封裝,聚合及繼承等行為讓不同的類承擔不同職責。總之,就是將抽象與行為實現分離,保證各部分獨立性以及功能擴充套件
優點:
- 抽象與實現分離,擴充套件能力強
- 符合開閉原則
- 符合合成複用原則
- 實現細節對客戶端透明
缺點:
- 聚合關係建立在抽象層,針對抽象化設計程式設計,需要正確識別出系統中兩個獨立變化文緯度,增加了系統理解與設計難度
應用場景:
- 一個類存在兩個獨立變化的緯度,並且兩緯度都需要進行擴充套件
- 系統不希望使用繼承或者多層次繼承導致系統類個數急劇增加
- 一個系統需要在構建的抽象化角色和具體化角色之間增加更多的靈活性
角色
- 抽象化角色(Abstraction):定義抽象類,幷包含一個對實現化物件的引用
- 擴充套件抽象化角色(Refined Abstraction):抽象化角色的子類,實現父類中的業務方法,並通過組合關係調用實現角色中的業務方法
- 實現化角色(Implementor):定義實現化角色的介面,供擴充套件抽象化角色呼叫
- 具體實現化角色(Concrete Implementor):給出實現化角色介面的具體實現
程式碼實現
實現化
public interface Implementor { void operationImpl(); } public class ConcreteImplementorA implements Implementor { @Override public void operationImpl() { System.out.println("ConcreteImplementorA"); } } public class ConcreteImplementorB implements Implementor { @Override public void operationImpl() { System.out.println("ConcreteImplementorB"); } }
抽象化
public abstract class Abstraction { protected Implementor implementor; protected Abstraction(Implementor implementor){ this.implementor = implementor; } public abstract void operation(); } // 根據業務場景,如果有多個,可以構造多個擴充套件抽象化角色 public class RefinedAbstraction extends Abstraction { protected RefinedAbstraction(Implementor implementor) { super(implementor); } @Override public void operation() { System.out.println("RefinedAbstraction, if there needs, the could be more RefinedAbstraction"); implementor.operationImpl(); } }
呼叫
public class Client { public static void main(String[] args) { Implementor implementorA = new ConcreteImplementorA(); Implementor implementorB = new ConcreteImplementorB(); RefinedAbstraction abstractionA = new RefinedAbstraction(implementorA); RefinedAbstraction abstractionB = new RefinedAbstraction(implementorB); abstractionA.operation(); System.out.println("---------"); abstractionB.operation(); /** * RefinedAbstraction, if there needs, the could be more RefinedAbstraction * ConcreteImplementorA * --------- * RefinedAbstraction, if there needs, the could be more RefinedAbstraction * ConcreteImplementorB */ } }
小結:
橋接模式常見的使用場景就是替換多層繼承,可以減少子類的個數,降低系統的管理和維護成本,繼承確實有較多優點,繼承可以很好實現程式碼複用的功能,但是這個封裝特性也是繼承的缺點,因為子類會繼承父類所有的方法,根據合成複用原則,優先使用組合或者聚合。
8. 裝飾模式(Decorator)
在不改變現有物件結構情況下,動態給物件增加職責。舉個例子,對於登陸展示功能,要求增加一個一句歡迎的話語,並且要求不能修改原有介面,這個時候就可以使用裝飾模式
優點:
- 對繼承的不成,相對於繼承靈活,在不改變原有物件前提下,動態給物件擴充套件功能,遵從開閉原則
- 通過裝飾器的排列組合實現不同功能
缺點:
- 會增加子類個數
- 適用場景:
- 需要給某個現有類新增額外功能,且不能修改原有類。例如,對final修改的類進行功能擴充
- 對想有一組基本功能進行排列組合會產生較多的功能,採用繼承較難實現
- 物件的功能要求可以動態新增
裝飾模式UML
程式碼如下:
首先定義介面以及具體構件(構件也可以投多種實現)
public interface Component { void operation(); } public class ConcreteComponent implements Component { @Override public void operation() { System.out.println("i am concrete component"); } }
裝飾:
public abstract class Decorator implements Component { protected Component component; public Decorator(Component component){ this.component = component; } public abstract void operation(); } public class ConcreteDecorateA extends Decorator { public ConcreteDecorateA(Component component) { super(component); } @Override public void operation() { component.operation(); this.addFunction(); } private void addFunction() { System.out.println("add function in concrete decorate A"); } } public class ConcreteDecorateB extends Decorator { public ConcreteDecorateB(Component component) { super(component); } @Override public void operation() { this.addFunction(); component.operation(); } private void addFunction() { System.out.println("this add function in concrete decorator B"); } }
實現:
public class Client { public static void main(String[] args) { Component component = new ConcreteComponent(); Component decorateA = new ConcreteDecorateA(component); Component decorateB = new ConcreteDecorateB(decorateA); decorateB.operation(); /** * this add function in concrete decorator B * i am concrete component * add function in concrete decorate A */ } }
9. 組合模式(Composite Pattern)
又稱整體-部分(Part-Whole)模式,將物件組合成樹狀的層次介面的模式。用以表示“整體-部分”關係,是客戶端對單個物件以及組合隊形具有一致的訪問性
優點:
- 客戶端可以一致性的處理單個物件與組合物件,簡化客戶端程式碼
- 組合體內加入新物件,不用修改客戶端原始碼,符合開閉原則
缺點:
- 設計較複雜,客戶端需要時間理清類之間的層次關係
- 不容易限制容器中的構件
- 不易用繼承方法增加構件新功能
- 樹葉構件許阿喲實現部分無用的方法
適用場景:
- 需要表示一個物件整體與部分的層次結構,方便建立複雜的層次結構,客戶端午休會內部的組成細節
- 需要保證物件與組合的一致性介面
- 要求遍歷組織結構或者處理的物件具有誰能夠結構是
- 要求有較高的抽象性,樹葉構件以及樹枝構件不能有太大的差異性
組合模式UML:
程式碼如下:
構件介面
public interface Component { void add(Component component); void remove(Component component); Component getChild(int t); void operation(); }
樹枝構件
public class Composite implements Component { private String name; private List<Component> children = new ArrayList<>(); public Composite(String name){ this.name = name; } @Override public void add(Component component) { children.add(component); } @Override public void remove(Component component) { children.remove(component); } @Override public Component getChild(int t) { if (children.size() > t) return children.get(t); return null; } @Override public void operation() { System.out.println("this is composite, name : " + this.name); for (Component child : children) { child.operation(); } } }
樹葉構件
public class Leaf implements Component { String name; public Leaf(String name) { this.name = name; } @Override public void add(Component component) { throw new RuntimeException(); } @Override public void remove(Component component) { throw new RuntimeException(); } @Override public Component getChild(int t) { throw new RuntimeException(); } @Override public void operation() { System.out.println("this is leaf, name: " + this.name); } }
客戶端
public class Client { public static void main(String[] args) { Leaf leafA = new Leaf("leafA"); Leaf leafB = new Leaf("leafB"); Composite compositeA = new Composite("compositeA"); Composite compositeB = new Composite("compositeB"); compositeA.add(leafA); compositeB.add(leafB); Composite compositeC = new Composite("compositeC"); compositeC.add(compositeA); compositeC.add(compositeB); compositeC.operation(); /** * this is composite, name : compositeC * this is composite, name : compositeA * this is leaf, name: leafA * this is composite, name : compositeB * this is leaf, name: leafB */ } }
小結:
組合模式組號保證樹葉構件與樹枝構件有較好的抽象性,否則會增加較多冗餘方法。
10. 外觀模式(Facade)
通過為多個複雜的子系統提供一致性訪問介面,從而使子系統更容易被訪問。舉個例子,比如在進行使用阿里雲oss時,我們會進行封裝將配置資訊以及呼叫分別進行封裝,最終暴露給業務端使用的就是一個簡單的介面,業務端很容易呼叫這個介面進行儲存。
優點:
- 降低子系統與客戶端之間的耦合度,使得子系統的變化不會影響客戶端
- 對客戶端遮蔽子系統元件,減少客戶端處理專案,使得客戶端可以容易使用子系統
- 減低依賴性
缺點:
- 不宜限制客戶端使用的子系統類
- 增加新的子系統,需要修改外觀類或者客戶端,違背開閉原則
適用場景:
- 複雜系統的子系統很多,需要提供一個統一的對外提供介面
- 客戶端多個子系統之間存在較大聯絡,引入外觀模式可將其分離
外觀模式UML:
程式碼:
public class SubSystem1 { public void method1(){ System.out.println("SubSystem1"); } } public class SubSystem2 { public void method2(){ System.out.println("SubSystem2"); } } public class SubSystem3 { public void method3(){ System.out.println("SubSystem3"); } } public class Facade { private SubSystem1 subSystem1 = new SubSystem1(); private SubSystem2 subSystem2 = new SubSystem2(); private SubSystem3 subSystem3 = new SubSystem3(); public void method() { subSystem1.method1(); subSystem2.method2(); subSystem3.method3(); } } public class Client { public static void main(String[] args) { Facade facade = new Facade(); facade.method(); /** * SubSystem1 * SubSystem2 * SubSystem3 */ } }
小結:
外觀模式通過定義一個一致介面,用以遮蔽內部子系統的細節,是的呼叫端只需要跟這個介面發生呼叫,無需關心內部細節。使用外觀模式可以幫助客戶端與子系統進行解耦,是的子系統內部模組更以維護以及擴充套件。合理使用外觀模式,可以幫助我們更好的劃分訪問的層次。如果子系統很簡單,就不要過多使用外觀模式,增加溫乎成本。所有的思想都是系統有層次,利於維護。
11. 享元模式(Flyweight Pattern)
又稱蠅量模式,就是運用共享技術郵箱的支援大量細粒度的物件。也就是共享一寸的物件來減少需要建立的物件數量,避免大量相似的開銷,從而提升資源的利用率
優點:
- 解決重複物件的記憶體浪費的問題
缺點:
- 因為物件需要共享,因此需要將一些無妨共享的狀態,在外部進行處理
適用場景:
- 系統中大量相同或者相似的物件,這些物件耗費大量的記憶體資源
- 大部分物件可以按照內部狀態進行分組
- 最經典的應用就是池技術,例如JVM的常量池,資料庫連線池,執行緒池
享元模式的UML:
各角色定義:
- 抽象享元角色:所有的享元類的基類,提供享元角色過飯。非享元的外部狀態以引數形式通過方法注入
- 具體享元角色:實現類
- 非享元角色:不可以共享的外部狀態,以引數的形式注入具體享元方法中
- 享元工廠:負責建立與管理享元角色。當客戶端請求享元物件,享元工廠檢查系統中是否存在符合要求的享元物件,如存在則提供給客戶端,否則進行建立
程式碼如下:
抽象享元角色:
public interface Flyweight { void operation(UnsharedConcreteFlyweight state); }
具體享元角色:
public class ConcreteFlyweight1 implements Flyweight { public String key; public ConcreteFlyweight1(String key) { System.out.println("create ConcreteFlyweight1: " + key); this.key = key; } @Override public void operation(UnsharedConcreteFlyweight state) { System.out.println("ConcreteFlyweight1 has been used: " + key); System.out.println("unShared is: " + (Objects.nonNull(state) ? state.getInfo() : "nothing")); } } public class ConcreteFlyweight2 implements Flyweight { public String key; public ConcreteFlyweight2(String key) { System.out.println("create ConcreteFlyweight2: " + key); this.key = key; } @Override public void operation(UnsharedConcreteFlyweight state) { System.out.println("ConcreteFlyweight2 has been used: " + key); System.out.println("unShared is: " + (Objects.nonNull(state) ? state.getInfo() : "nothing")); } }
非共享享元角色:
public class UnsharedConcreteFlyweight { private String info; public UnsharedConcreteFlyweight(String info) { this.info = info; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } }
享元工廠角色:
public class FlyweightFactory { private final Map<String, Flyweight> flyweights = new HashMap<>(); private int count = 0; public Flyweight getFlyweight(String key) { if (!flyweights.containsKey(key)) { synchronized (this) { if (!flyweights.containsKey(key)) { // 這裡取決於業務場景 flyweights.put(key, count % 2 == 1 ? new ConcreteFlyweight1(key) : new ConcreteFlyweight2(key)); count++; } } } return flyweights.get(key); } }
客戶端:
public class Client { public static void main(String[] args) { FlyweightFactory factory = new FlyweightFactory(); Flyweight a01 = factory.getFlyweight("a"); Flyweight a02 = factory.getFlyweight("a"); Flyweight b01 = factory.getFlyweight("b"); Flyweight b02 = factory.getFlyweight("b"); Flyweight c01 = factory.getFlyweight("c"); Flyweight c02 = factory.getFlyweight("c"); a01.operation(new UnsharedConcreteFlyweight("first")); a02.operation(new UnsharedConcreteFlyweight("second")); b01.operation(new UnsharedConcreteFlyweight("first")); b02.operation(new UnsharedConcreteFlyweight("second")); c01.operation(new UnsharedConcreteFlyweight("first")); c02.operation(new UnsharedConcreteFlyweight("second")); /** * create ConcreteFlyweight2: a * create ConcreteFlyweight1: b * create ConcreteFlyweight2: c * ConcreteFlyweight2 has been used: a * unShared is: first * ConcreteFlyweight2 has been used: a * unShared is: second * ConcreteFlyweight1 has been used: b * unShared is: first * ConcreteFlyweight1 has been used: b * unShared is: second * ConcreteFlyweight2 has been used: c * unShared is: first * ConcreteFlyweight2 has been used: c * unShared is: second */ } }
小結:
享元工廠也可以認為是工廠模式的擴充套件類,將相當於在中間加了一層緩衝。使用享元模式,最好是系統中存在大量的相似物件或頻繁使用物件。否則沒有必要。
享元模式提高了系統的複雜度,需要分離內部狀態以及外部狀態,外部狀態具有固化屬性,不應歲內部章臺變化而變化
12. 代理模式(Proxy)
為一個物件提供一個替身,以便控制這個物件的訪問。通過代理物件訪問目標物件。
優點:
- 可以在客戶端與目標物件之間起到一箇中介作用以及保護目標物件的作用
- 擴充套件目標物件的功能
- 將客戶端與目標物件分離,一定程度上降低了耦合性,增加了程式的可擴充套件性
缺點:
- 造成系統中類的增加
- 請求速度變慢,因為中間加了一層代理物件
- 增加系統的複雜度
應用場景:
- 遠端代理
- 防火牆代理
- 快取代理
- 安全代理
- 同步代理
代理模式的UML:
程式碼如下,代理模式比較簡單:
public interface Subject { void request(); } public class RealSubject implements Subject { @Override public void request() { System.out.println("real request"); } } public class Proxy implements Subject { private final RealSubject realSubject; public Proxy(RealSubject realSubject) { this.realSubject = realSubject; } @Override public void request() { this.preRequest(); this.realSubject.request(); this.postRequest(); } private void preRequest() { System.out.println("pre request"); } private void postRequest() { System.out.println("post request"); } } public class Client { public static void main(String[] args) { Proxy proxy = new Proxy(new RealSubject()); proxy.request(); /** * pre request * real request * post request */ } }
上述屬於靜態代理,靜態代理有一個缺點:
代理物件需要跟目標物件實現一樣的介面,並且一旦介面增加方法,目標物件與代理物件都要維護
學過java都知道還有一個動態代理(介面代理)以及cglib代理,這個代理物件不需要實現介面,維護以及擴充套件都比較方便
看一下動態代理的反射以及客戶端呼叫
public class ProxyFactory { private Object target; public ProxyFactory(Object target) { this.target = target; } public Object getInstant(){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) -> { System.out.println("pre request"); Object invoke = method.invoke(target, args); System.out.println("post request"); return invoke; }); } } public class Client { public static void main(String[] args) { DynamicSubject dynamicRealSubject = new DynamicRealSubject(); ProxyFactory proxyFactory = new ProxyFactory(dynamicRealSubject); DynamicSubject dynamicSubject = (DynamicSubject) proxyFactory.getInstant(); dynamicSubject.request(); /** * pre request * real request * post request */ } }
使用動態代理必須實現介面,對於沒有介面的需要使用cglib進行代理
cglib代理也叫做子類代理,他是在記憶體中構建一個子類物件從事實現對目標物件的動態代理,注意類不能是final,否則會報錯,因為無法繼承,目標方法不能是final或者static,否則不會執行目標獨享額外的方法
被代理物件
public class CglibObject { public void request() { System.out.println("i am real object"); } }
代理類
public class ProxyFactory implements MethodInterceptor { private final Object target; public ProxyFactory(Object target) { this.target = target; } public Object getInstance() { // 建立一個工具類 Enhancer enhancer = new Enhancer(); // 設定父類 enhancer.setSuperclass(target.getClass()); // 設定回撥函式 enhancer.setCallback(this); // 返回子類物件,即代理物件 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("pre request"); Object invoke = method.invoke(target, args); System.out.println("post request"); return invoke; } }
客戶端
public class Client { public static void main(String[] args) { CglibObject cglibObject = new CglibObject(); ProxyFactory proxyFactory = new ProxyFactory(cglibObject); CglibObject instance = (CglibObject) proxyFactory.getInstance(); instance.request(); /** * pre request * i am real object * post request */ } }
小結:
通過代理模式訪問目標物件,這樣足以哦可以再目標物件實現的基礎上,額外增強功能,並且實現一定程度的介面,但是也會造成額外的開銷。