設計模式學習-使用go實現訪問者模式

Rick.lz發表於2021-11-26

訪問者模式

定義

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

使用訪問者模式,元素的執行演算法可以隨著訪問者改變而改變。主要意圖是將資料結構與資料操作分離。

不過作為比較難理解的設計模式之一,因為它難理解、難實現,應用它會導致程式碼的可讀性、可維護性變差,所以,訪問者模式在實際的軟體開發中很少被用到,在沒有特別必要的情況下,訪問者模式是不建議使用的。

優點

1、開閉原則。 你可以引入在不同類物件上執行的新行為, 且無需對這些類做出修改。

2、單一職責原則。 可將同一行為的不同版本移到同一個類中。

3、靈活性更好。

缺點

1、具體元素變更比較困難。每次在元素層次結構中新增或移除一個類時,都要更新所有的訪問者。

2、比較難理解,應用它會導致程式碼的可讀性、可維護性變差。

適用範圍

1、物件結構中物件對應的類很少改變,但經常需要在此物件結構上定義新的操作。

2、需要對一個物件結構中的物件進行很多不同的並且不相關的操作,而需要避免讓這些操作"汙染"這些物件的類,也不希望在增加新操作時修改這些類。

程式碼實現

程式碼實現:

type Visitor interface {
	VisitConcreteElementA(cea *ConcreteElementA)
	VisitConcreteElementB(ceb *ConcreteElementB)
}

type ConcreteVisitor1 struct {
}

func (cea *ConcreteVisitor1) VisitConcreteElementA(concreteElementA *ConcreteElementA) {
	fmt.Println("concreteVisitor1 visitConcreteElementA")
}

func (*ConcreteVisitor1) VisitConcreteElementB(concreteElementB *ConcreteElementB) {
	fmt.Println("concreteVisitor1 visitConcreteElementB")
}

type ConcreteVisitor2 struct {
}

func (*ConcreteVisitor2) VisitConcreteElementA(concreteElementA *ConcreteElementA) {
	fmt.Println("concreteVisitor2 visitConcreteElementA")
}

func (*ConcreteVisitor2) VisitConcreteElementB(concreteElementB *ConcreteElementB) {
	fmt.Println("concreteVisitor2 visitConcreteElementB")
}

type Element interface {
	Accept(visitor Visitor)
}

type ConcreteElementA struct {
}

func (cea *ConcreteElementA) Accept(visitor Visitor) {
	visitor.VisitConcreteElementA(cea)
}

type ConcreteElementB struct {
}

func (ceb *ConcreteElementB) Accept(visitor Visitor) {
	visitor.VisitConcreteElementB(ceb)
}

測試程式碼:

func TestVisitor(t *testing.T) {
	var elements []Element
	elements = append(elements, &ConcreteElementA{})
	elements = append(elements, &ConcreteElementB{})

	for _, item := range elements {
		cv1 := &ConcreteVisitor1{}
		cv2 := &ConcreteVisitor2{}
		item.Accept(cv1)
		item.Accept(cv2)
	}
}

結構圖:

visitor

什麼是 Double Dispatch

什麼是分派?

分派即 Dispatch,在物件導向程式語言中,我們可以把方法呼叫理解為一種訊息傳遞(Dispatch)。一個物件呼叫另一個物件的方法,相當於給被呼叫物件傳送一個訊息,這個訊息包括物件名、方法名、方法引數等資訊。

什麼是單分派?

單分派,即執行哪個物件的方法,根據物件的執行時型別決定;執行物件的哪個方法,根據方法引數的編譯時型別決定。

什麼是雙分派?

雙分派,即執行哪個物件的方法,根據物件的執行時型別來決定;執行物件的哪個方法,根據方法引數的執行時的型別來決定。

具體到程式語言的語法機制,Single Dispatch 和 Double Dispatch 跟多型和函式過載直接相關。所以 go 是不支援雙分派的。

當前主流的物件導向程式語言(比如,Java、C++、C#)都只支援Single Dispatch,不支援Double Dispatch。

使用 java 舉栗子更容易理解:

import java.util.ArrayList;
import java.util.List;

abstract class ResourceFile {
    protected String filePath;

    public ResourceFile(String filePath) {
        this.filePath = filePath;
    }
}

class PdfFile extends ResourceFile {
    public PdfFile(String filePath) {
        super(filePath);
    }
}

class PPTFile extends ResourceFile {
    public PPTFile(String filePath) {
        super(filePath);
    }
}

//...PPTFile、WordFile程式碼省略...
class Extractor {

    public void extract2txt(PdfFile pdfFile) {
        System.out.println("Extract PDF.");
    }

    public void extract2txt(PPTFile ppTFile) {
        System.out.println("Extract PPT.");
    }
}

public class Test {
    public static void main(String[] args) {
        Extractor extractor = new Extractor();
        List<ResourceFile> resourceFiles = listAllResourceFiles();

        for (ResourceFile resourceFile : resourceFiles) {
            extractor.extract2txt(resourceFile);
        }
    }

    private static List<ResourceFile> listAllResourceFiles() {
        List<ResourceFile> resourceFiles = new ArrayList<>();
        //...根據字尾(pdf/ppt/word)由工廠方法建立不同的類物件(PdfFile/PPTFile/WordFile)
        resourceFiles.add(new PPTFile("a.ppt"));
        resourceFiles.add(new PdfFile("a.pdf"));

        return resourceFiles;
    }
}

比如這段程式碼,就會在extractor.extract2txt(resourceFile);,程式碼會在執行時,根據引數(resourceFile)的實際型別(PdfFile、PPTFile、WordFile),來決定使用extract2txt的三個過載函式中的哪一個。那下面的程式碼實現就能正常執行了。

報錯資訊

java: 對於extract2txt(ResourceFile), 找不到合適的方法
    方法 Extractor.extract2txt(PdfFile)不適用
      (引數不匹配; ResourceFile無法轉換為PdfFile)
    方法 Extractor.extract2txt(PPTFile)不適用
      (引數不匹配; ResourceFile無法轉換為PPTFile)

參考

【文中程式碼】https://github.com/boilingfrog/design-pattern-learning/tree/master/訪問者模式
【大話設計模式】https://book.douban.com/subject/2334288/
【極客時間】https://time.geekbang.org/column/intro/100039001
【雙分派-訪問者模式的前世今生】https://www.codenong.com/cs110749395/
【訪問者模式】https://boilingfrog.github.io/2021/11/25/使用go實現訪問者模式/

相關文章