Java設計模式之結構型模式(一)
在解決了物件的建立問題之後,物件的組成以及物件之間的依賴關係就成了開發人員關注的焦點,因為如何設計物件的結構、繼承和依賴關係會影響到後續程式的維護性、程式碼的健壯性、耦合性等。
一、介面卡模式(Adapter)
介面卡模式是指“將一個類的介面轉換成客戶希望的另一個介面。Adapter模式使得原本介面不相容而不能一起工作的那些類可以一起工作,其別名是包裝器(Wrapper)”。介面卡可以分為類的介面卡模式、物件的介面卡模式、介面的介面卡模式。
1.1 類的介面卡模式
類介面卡使用多重繼承對一個介面與另一個介面進行匹配。
有一個Adaptee類,擁有一個方法,待適配,目標介面是Target,通過Adapter類,將Adaptee的功能擴充套件到Target裡,看程式碼:
public class Adaptee {
public void request() {
System.out.println("this is original method!");
}
}
public interface Target {
/* 與原類中的方法相同 */
public void request();
/* 新類的方法 */
public void specificRequest();
}
public class Adapter extends Adaptee implements Target {
@Override
public void specificRequest() {
System.out.println("this is the Adaptee method!");
}
}
Adapter類繼承Adaptee類,實現Target介面,下面是測試類:
public class AdapterTest {
public static void main(String[] args) {
Target target = new Adapter();
target.request();
target.specificRequest();
}
}
1.2 物件的介面卡模式
物件匹配依賴於物件組合。
例子只需要修改Adapter類的原始碼即可
public class Adapter implements Target {
private Adaptee adaptee;
public Wrapper(Adaptee adaptee){
super();
this.adaptee = adaptee;
}
@Override
public void specificRequest() {
System.out.println("this is the Adaptee method!");
}
@Override
public void request() {
adaptee.request();
}
}
1.3 介面的介面卡模式
有時我們寫的一個介面中有多個抽象方法,當我們寫該介面的實現類時,必須實現該介面的所有方法,這明顯有時比較浪費,因為並不是所有的方法都是我們需要的,有時只需要某一些,此處為了解決這個問題,我們引入了介面的介面卡模式,藉助於一個抽象類,該抽象類實現了該介面,實現了所有的方法,而我們不和原始的介面打交道,只和該抽象類取得聯絡,所以我們寫一個類,繼承該抽象類,重寫我們需要的方法就行。看一下類圖:
public interface Target {
public void method1();
public void method2();
}
public abstract class Adapter implements Target {
public void method1(){}
public void method2(){}
}
public class Adaptee1 extends Adapter {
public void method1(){
System.out.println("the Target interface's first Sub1!");
}
}
public class Adaptee1 extends Adapter {
public void method2(){
System.out.println("the Target interface's second Sub2!");
}
}
三種介面卡模式的應用場景:
- 類的介面卡模式:當希望將一個類轉換成滿足另一個新介面的類時,可以使用類的介面卡模式,建立一個新類,繼承原有的類,實現新的介面即可。
- 物件的介面卡模式:當希望將一個物件轉換成滿足另一個新介面的物件時,可以建立一個Wrapper類,持有原類的一個例項,在Wrapper類的方法中,呼叫例項的方法就行。
- 介面的介面卡模式:當不希望實現一個介面中所有的方法時,可以建立一個抽象類Wrapper,實現所有方法,我們寫別的類的時候,繼承抽象類即可。
二、裝飾模式(Decorator)
裝飾模式別名也叫包裝器,GOF中定義為“動態的給物件新增一些額外的職責”。要求裝飾物件和被裝飾物件實現同一個介面,裝飾物件持有被裝飾物件的例項,關係圖如下:
public interface Sourceable {
public void method();
}
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
public class Decorator implements Sourceable {
private Sourceable source;
public Decorator(Sourceable source){
super();
this.source = source;
}
@Override
public void method() {
System.out.println("before decorator!");
source.method();
System.out.println("after decorator!");
}
}
測試類:
public class DecoratorTest {
public static void main(String[] args) {
Sourceable source = new Source();
Sourceable obj = new Decorator(source);
obj.method();
}
}
裝飾器模式的應用場景:
- 需要擴充套件一個類的功能。
- 動態的為一個物件增加功能,而且還能動態撤銷。(繼承不能做到這一點,繼承的功能是靜態的,不能動態增刪。)
三、門面模式(Facade)
門面模式又稱外觀模式,是指“外部與一個子系統的通訊必須通過一個統一的門面物件進行”。簡單來說,該模式就是把一些複雜的流程封裝成一個介面供給外部使用者更簡單的使用。
門面角色:外觀模式的核心。它被客戶角色呼叫,它熟悉子系統的功能。內部根據客戶角色的需求預定了幾種功能的組合。
子系統角色:可以同時又一個。它對客戶角色和Facade時未知的。它內部可以有系統內的相互互動,也可以由供外界呼叫的介面。
下面,我們就通過一個簡單的例子來實現該模式。
每個Computer都有CPU、Memory、Disk。在Computer開啟和關閉的時候,相應的部件也會開啟和關閉,所以,使用了該外觀模式後,會使使用者和部件之間解耦。如:
//cpu子系統類
public class CPU {
public void start() {
System.out.println("cpu is start...");
}
public void shutDown() {
System.out.println("CPU is shutDown...");
}
}
public class Memory { //Memory子系統類
public void startup(){
System.out.println("memory startup!");
}
public void shutdown(){
System.out.println("memory shutdown!");
}
}
public class Disk { //Disk子系統
public void startup(){
System.out.println("disk startup!");
}
public void shutdown(){
System.out.println("disk shutdown!");
}
}
public class Computer { //門面類Computer
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer(){
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void startup(){
System.out.println("start the computer!");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("start computer finished!");
}
public void shutdown(){
System.out.println("begin to close the computer!");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("computer closed!");
}
}
public class User {
public static void main(String[] args) {
Computer computer = new Computer();
computer.startup();
computer.shutdown();
}
}
如果我們沒有Computer類,那麼,CPU、Memory、Disk他們之間將會相互持有例項,產生關係,這樣會造成嚴重的依賴,修改一個類,可能會帶來其他類的修改,這不是我們想要看到的,有了Computer類,他們之間的關係被放在了Computer類裡,這樣就起到了解耦的作用。
使用場景:
- 為複雜的模組或子系統提供外界訪問的模組;
- 子系統相互獨立;
- 在層析結構中,可以使用外觀模式定義系統的每一層的入口。
四、橋接模式(Bridge)
橋接模式是指“將抽象部分與它的實現部分分離,是它們都可以獨立的變化。”
Abstraction:定義抽象類的介面,維護一個指向Implementor介面的指標,將使用者的請求轉發給它的Implementor。RefinedAbstraction寇衝由Abstraction定義的介面。
Implementor:定義實現類的介面,該介面不一定要和Abstraction的介面定義的一樣,事實上兩個介面可以完全不同。
ConcretelImplementor:實現Implementor介面並定義其具體實現。
我們這裡使用電視機遙控器做示例,多個電視機廠家想要遙控器廠家生產遙控器,並且遙控器的樣式可能會變化。
電視機廠家遙控介面
public interface Control {
public void On();
public void Off();
public void setChannel(int ch);
public void setVolume(int vol);
}
電視機廠家遙控介面實現
public class SharpControl implements Control {
@Override
public void On() {
System.out.println("***Open Sharp TV***");
}
@Override
public void Off() {
System.out.println("***Off Sharp TV***");
}
@Override
public void setChannel(int ch) {
System.out.println("***The Sharp TV Channel is setted "+ch+"***");
}
@Override
public void setVolume(int vol) {
System.out.println("***The Sharp TV Volume is setted "+vol+"***");
}
}
public class SonyControl implements Control {
@Override
public void On() {
System.out.println("*Open Sony TV*");
}
@Override
public void Off() {
System.out.println("*Off Sony TV*");
}
@Override
public void setChannel(int ch) {
System.out.println("*The Sony TV Channel is setted "+ch+"*");
}
@Override
public void setVolume(int vol) {
System.out.println("*The Sony TV Volume is setted "+vol+"*");
}
}
遙控器廠家抽象類介面
public abstract class TvControlabs {
Control mControl=null;
public TvControlabs(Control mControl) {
this.mControl=mControl;
}
public abstract void Onoff();
public abstract void nextChannel();
public abstract void preChannel();
}
遙控器為廠家生產各種型別的遙控器
public class TvControl extends TvControlabs {
private int ch = 0;
private boolean ison = false;
public TvControl(Control mControl) {
super(mControl);
}
@Override
public void Onoff() {
if(ison) {
ison=false;
mControl.Off();
} else {
ison=true;
mControl.On();
}
}
@Override
public void nextChannel() {
ch++;
mControl.setChannel(ch);
}
@Override
public void preChannel() {
ch--;
if(ch<0) {
ch=200;
}
mControl.setChannel(ch);
}
}
public class newTvControl extends TvControlabs {
private int ch=0;
private boolean ison=false;
private int prech=0;
public newTvControl(Control mControl) {
super(mControl);
}
@Override
public void Onoff() {
if(ison) {
ison=false;
mControl.Off();
} else {
ison=true;
mControl.On();
}
}
@Override
public void nextChannel() {
prech=ch;
ch++;
mControl.setChannel(ch);
}
@Override
public void preChannel() {
prech=ch;
ch--;
if(ch<0) {
ch=200;
}
mControl.setChannel(ch);
}
public void setChannel(int nch) {
prech=ch;
ch=nch;
mControl.setChannel(ch);
}
public void Back() {
mControl.setChannel(prech);
}
}
這裡各個電視機廠家的電視機都實現了Control介面,而遙控器廠家只需要設計出遙控器型別並呼叫各個電視機廠家的具體實現就可以了。
使用場景:
- 如果一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承聯絡,通過橋接模式可以使它們在抽象層建立一個關聯關係。
- 對於那些不希望使用繼承或因為多層次繼承導致系統類的個數急劇增加的系統,橋接模式尤為適用。
- 一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴充套件。
注意事項:對於兩個獨立變化的維度,使用橋接模式再適合不過了。
五、組合模式(Composite)
組合模式又稱合成模式,是用於把一組相似的物件當作一個單一的物件。將物件組合成樹形結構以表示“部分-整體”的層次機構。
Component 是組合中的物件宣告介面,在適當的情況下,實現所有類共有介面的預設行為。宣告一個介面用於訪問和管理Component
子部件。
Leaf 在組合中表示葉子結點物件,葉子結點沒有子結點。
Composite 定義有枝節點行為,用來儲存子部件,在Component介面中實現與子部件有關操作,如增加(add)和刪除。
public abstract class Component {
String name;
public Component(String s){
this.name=s;
}
public abstract void add(Component c);
public abstract void remove(Component c);
public abstract void foreach();
}
組合類
public class Composite extends Component {
private List<Component>child=new ArrayList<Component>
();
public Composite(String s) {
super(s);
}
@Override
public void add(Component c) {
child.add(c);
}
@Override
public void foreach() {
System.out.println("節點名:\t"+name);
for (Component c : child) {
c.foreach();
}
}
@Override
public void remove(Component c) {
child.remove(c);
}
}
葉子節點
public class Leaf extends Component {
public Leaf(String s) {
super(s);
}
@Override
public void add(Component c) {
}
@Override
public void foreach() {
System.out.println("tself name-->"+this.name);
}
@Override
public void remove(Component c) {
}
}
測試類
public class TestComponent {
public static void main(String[] args) {
Component component = new Composite("根節點");
Component child = new Composite("一級子節點child");
Component child_1 = new Leaf("一級子節點child之子節點一");
Component child_2 = new Leaf("一級子節點child之子節點二");
child.add(child_1);
child.add(child_2);
Component child2=new Composite("一級子節點child2");
component.add(child);
component.add(child2);
component.foreach();
}
}
使用場景:部分、整體場景,如樹形選單,檔案、資料夾的管理。
六、享元模式(Flyweight)
享元模式是指“運用共享技術有效的支援大量細粒度的物件”。主要目的是實現物件的共享,即共享池,當系統中物件多的時候可以減少記憶體的開銷,通常與工廠模式一起使用。
FlyWeight 享元介面或者(抽象享元類),定義共享介面
ConcreteFlyWeight 具體享元類,該類例項將實現共享
UnSharedConcreteFlyWeight 非共享享元實現類
FlyWeightFactory 享元工廠類,控制例項的建立和共享
內部狀態 vs. 外部狀態
內部狀態是儲存在享元物件內部,一般在構造時確定或通過setter設定,並且不會隨環境改變而改變的狀態,因此內部狀態可以共享。
外部狀態是隨環境改變而改變、不可以共享的狀態。外部狀態在需要使用時通過客戶端傳入享元物件。外部狀態必須由客戶端儲存。
public interface FlyWeight {
void action(String externalState);
}
public class ConcreteFlyWeight implements FlyWeight {
private String name;
public ConcreteFlyWeight(String name) {
this.name = name;
}
@Override
public void action(String externalState) {
System.out.pringln("name = {}, outerState = {}", this.name, externalState);
}
}
享元模式中,最關鍵的享元工廠。它將維護已建立的享元例項,並通過例項標記(一般用內部狀態)去索引對應的例項。當目標物件未建立時,享元工廠負責建立例項並將其加入標記-物件對映。當目標物件已建立時,享元工廠直接返回已有例項,實現物件的複用。
public class FlyWeightFactory {
private static ConcurrentHashMap<String, FlyWeight> allFlyWeight = new ConcurrentHashMap<String, FlyWeight>();
public static FlyWeight getFlyWeight(String name) {
if (allFlyWeight.get(name) == null) {
synchronized (allFlyWeight) {
if (allFlyWeight.get(name) == null) {
System.out.println("Instance of name = {} does not exist, creating it");
FlyWeight flyWeight = new ConcreteFlyWeight(name);
System.out.println("Instance of name = {} created");
allFlyWeight.put(name, flyWeight);
}
}
}
return allFlyWeight.get(name);
}
}
使用場景: 1、系統有大量相似物件。 2、需要緩衝池的場景。