Android理解設計模式之組合模式、迭代器模式、訪問者模式

markriver發表於2021-09-09

迭代器模式

提供一種方法順序訪問一個聚合物件中各個元素, 而又不需暴露該物件的內部表示。

如果要對所有集合提供統一的for迴圈操作,首先要抽象出Iterator介面:

1
2
3
4
public interface Iterator<E> {
    boolean hasNext();
    E next();
}

集合類實現迭代介面,有兩種方式:繼承和組合。那是繼承Iterator好還是組合一個Iterator的實現類好呢?這裡很難分清isA和hasA的關係,也就是說都可以。

但是考慮到Iterator的實現可以是不同的,組合不同的Iterator比繼承Iterator產生不同的子類從分離變化的角度考慮更好(當然,這裡我覺得完全可以採用繼承),迭代器模式採用的是組合方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract class Aggregate {
    Iterator createIterator() {
        return null;
    }
}
public class ConcreteAggregate extends Aggregate {
    @Override
    Iterator createIterator() {
        // 建立一個對外的具體迭代器
        return new ConcreteIterator(this);
    }
}
public class ConcreteIterator implements Iterator {
    // 注入集合,讓迭代器提供資料支撐
    private ConcreteAggregate concreteAggregate;
    public ConcreteIterator(ConcreteAggregate concreteAggregate) {
        this.concreteAggregate = concreteAggregate;
    }
    @Override
    public boolean hasNext() {
        return false;
    }
    @Override
    public Object next() {
        return null;
    }
}

迭代器的實現模式幾乎是通用的

隨著語言的發展,集合迭代的需求逐漸朝著更真實更優雅的方向發展,從手動迭代器,到內建迭代器,到stream api,幾乎越來越看不到迭代器的實現,越是如此越說明迭代器模式的應用。
迭代器模式應該給了我們很大啟發,它只是集合操作的需求之一,集合操作的更多需求比如filter,map等是否都可以借鑑這種類似的思想呢?顯然可以,這裡不作展開。

組合模式

將物件組合成樹形結構以表示“部分-整體”的層次結構。Composite使得使用者對單個物件和組合物件的使用具有一致性。

組合物件包括一系列單個物件,如何保證對組合物件和單個物件操作的一致性?
這個問題再簡單不過,只要他們具備該操作的統一抽象行為即可,把這個行為抽象成介面Component:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IComponent {
    void operation();
}
public class Leaf implements IComponent {
    @Override
    public void operation() {
    }
}
public class Composite implements IComponent {
    @Override
    public void operation() {
    }
}

但是Composite畢竟是組合物件,它包括一系列單個物件,當你操作組合物件的時候,應該要把這種操作傳遞給它的“子民”們,所以充實一下Composite物件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Composite implements IComponent {
    private List<IComponent> components;
    @Override
    public void operation() {
        for (IComponent component : components) {
            component.operation();
        }
    }
    public void add(IComponent component) {
        components.add(component);
    }
    public void remove(IComponent component) {
        components.remove(component);
    }
    public IComponent getChild(int index) {
        return components.get(index);
    }
}

在傳遞operation給子物件的時候,得益於迭代器模式,不用關心List集合具體迭代的過程就能做到迭代(List換成其它集合,迭代的程式碼基本不變)。
現在當對IComponent物件傳送operation不用的時候,不用考慮是組合物件還是單個物件,Leaf和Composite都能很好的執行了。
我特意定義IComponent為介面,讓大家思考一下這個地方如果用抽象類會怎麼樣?
事實上,為了讓IComponent能最大化重用Leaf和Composite(作為具備統一行為的物件它們應該有很多相同的實現)的實現,有必要使用抽象類代替IComponent幷包含具體程式碼:

1
2
3
4
5
6
7
8
9
// 取代IComponent,為Leaf和Composite提供預設操作
public abstract class Component {
    public void operation() {
        // default impl code
    }
    // ...
    // more common code
}

在上述程式碼結構中,它們在add,remove,getChild這些行為表現出不一致性,我們是否有必要進一步改進?
如果把add,remove,getChild定義在抽象Component裡,那麼無疑表現出高度一致性,client就能透明的使用這些元件,此種模式稱為透明式組合模式。透明式帶來的隱患是,Leaf的add,remove,getChild這些操作是無意義的,這種無意義的後果是不可預測的,是不安全的。安全式組合模式不理財這些不一致性,當然這些行為的不一致性將導致Leaf和Component的強制轉換,這是安全式的缺點。
兩種方式各有優劣,根據軟體具體情況做出取捨決定

訪問者模式

表示一個作用於某物件結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。

訪問者模式並沒有傳說中的那麼複雜,也不需要先理解什麼雙分派(當然如果你懂的話自然更好),就當成一個小技巧會更簡單。

1
2
3
4
5
6
7
8
public class Element {
    public void a() {}
    public void b() {}
}
// Client呼叫a,b方法:
Element element = new Element();
element.a(); // 或者element.b()

如果想封裝對a,b方法的呼叫,讓客戶不關心呼叫的方法,怎麼實現?
增加一個間接層類Visitor,Element把自己交給Visitor,讓Visitor來控制呼叫哪個方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Visitor {
    public void visitElement(Element element) {
        // 把具體元素操作的呼叫延遲到這裡
        element.a();
    }
}
public class Element {
    public void a() {}
    public void b() {}
    public void accept(Visitor visitor) {
        visitor.visitElement(this);
    }
}
// Client不知道a,b方法,實際上呼叫了a方法
Element element = new Element();
element.accept(new Visitor());

這樣做的好處是,解耦了元素和操作之間的呼叫(對於client而言)。這個特性適合統一處理具有不同資料結構的集合的各元素上的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 我們為各不同元素定義統一的訪問方法
public interface IElement {
    void accept(Visitor visitor);
}
public class ElementA {
    public void a() {
        System.out.println("AAAAAAAA");
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visitElementA(this);
    }
}
public class ElementB {
    public void b() {
        System.out.println("BBBBBBBBB");
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visitElementB(this);
    }
}
// Visitor封裝具體各元素的操作的呼叫
public class Visitor {
    public void visitElementA(ElementA elementA) {
        elementA.a();
    }
    public void visitElementB(ElementB elementB) {
        elementB.b();
    }
}
// Client code
IElement elementA = new ElementA();
IElement elementB = new ElementB();
List<IElement> elements = new ArrayList<>();
elements.add(elementA);
elements.add(elementB);
// 使用迭代器模式統一處理這些不同的元素,實現呼叫不同方法,amazing!
for (IElement element : elements) {
    element.accept(new Visitor());
}

從client的程式碼也可以看的出來,透過IElement硬是把這些不同資料結構擰在了一起(ObjectStructure),儘管有些彆扭,但是這顯然比下面的程式碼要好的多:

1
2
3
4
5
6
7
8
9
// 訪問者模式就是為了最佳化這樣的程式碼結構
for (IElement element : elements) {
    element.accept(new Visitor());
    if (element instanceof ElementA) {
        ((ElementA) element).a();
    } else if (element instanceof  ElementB) {
        ((ElementB) element).b();
    }
}


小結

迭代器模式隨著發展,已經成為各大語言的標準語法糖。
組合模式內部遞迴常常要用到迭代器模式。

原文連結:http://www.apkbus.com/blog-705730-61032.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/818/viewspace-2815063/,如需轉載,請註明出處,否則將追究法律責任。

相關文章