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


訪問者模式

定義

訪問者模式(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實現訪問者模式/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM