iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 18
0

PicCollage 的員工大概可以分成兩類:Regular Memnber(正職)與 Intern(實習生)。

每個員工會輪流當 weekie(週值日生),負責處理當週的一些大小事。Regular Memebr weekie 一個禮拜會有兩個,負責澆花、主持 team meeting、推薦午餐地點等等;而 Intern weekie 則主要負責補充 PicCafe(我們的零食區 ?)。

PicCafe
(PicCafe)

現在我們想要讓員工根據所屬類別來做出相應的 weekie 職責。如果這個人是 Regular Memeber 那他就會澆花;如果是實習生,那他就會補充零食。聽起來很直觀,畫成圖大概就長這樣:

employees without visitor

也就是在員工本來在做的事(doFunWorkalwaysBeLearning)之外,增加一個新的 beWeekie 行為。

好景不常

很快的,我們有了一個新的任務:在 PicCollage 團隊中,我們除了 Weekie,還有一個 Mentor 的角色:每一個員工都會有一個 Mentor,是個可以讓員工在工作上解惑或是傾訴的對象,負責照顧這個人的心理健康(?)。現在,我們想要能夠找出每個員工的 Mentor 是誰。

那就跟 beWeekie() 一樣,分別在 RegularMemberIntern 中加入 displayMentor() 方法就好啦?

是可以的,但是有幾個不太 OK 的點:

  1. beWeekie()displayMentor() 其實都不是跟 Employee 最直接相關的行為,比較像是附屬功能,直接放在 Employee 類別中並不符合單一功能原則(Single Responsibility Principle)
  2. 每當要新增一個角色功能(我們還有 Manager, Lead 等等還沒說呢!)就要改動一次所有實作 Employee 的類別,現在只有 RegularMemberIntern,所以只需要改兩個地方,但假設我們今天還有一個 Contractor 類別,那就要改三個地方,如果 Employee 底下再加上 RemoteWorker,那就要改四個地方......

既然這些行為放在類別本身不妥,那該怎麼安置他們呢?答案就在標題上:Visitor Pattern

訪問者模式 Visitor Pattern

Visitor 是一種行為型設計模式,讓你將演算法從物件中抽離出來。

登登!基本上,Visitor Pattern 讓我們將與該類別比較不相關的行為抽出來,集中到 Visitor 裡面管理。這些 Visitor 們定義好功能,當一個物件(這裡稱作 Element)需要有這些附屬行為時,就呼叫 Visitor。如此一來每個 Element 可以留下最相關的核心行為,並且只要實作一個接受 Visitor 的接口,就算之後還有新的附屬功能需要添加,也不再需要改到 Element 本身的程式碼。

Visitor 結構圖

visitor UML diagram

Visitor

首先,Visitor Interface 定義了一系列 visiting methods,每個 visit() 都接收一個 Concrete Element 作為參數。如果我們使用的語言有支援 overloading(接受同名的方法,呼叫時依照參數種類/數量來判斷要用哪一個,像是 C++ 或是 Java),那麼這些 visiting methods 的名字可以是一樣的。

每個實作 Visitor 的類別都會實現一個特定的(附屬)行為,並根據它訪問的類別做出該行為的不同版本。

Element

Element 開了一個介面(這邊是 accept() 方法)給 Visitor。這個介面有一個參數來接受 Visitor,並在裡面呼叫對應的行為。這邊使用一個叫做 Double Dispatch 的技巧—在下面的程式碼範例中,我們可以看到每個 Element 的 accept() 方法中會將自己丟進它呼叫的 Visitor 方法,讓該 Visitor 可以做相應的行為。

Client

Client 通常代表一個集合或是一個組合物件(complex object)(比如說一個 Composite)。

回到我們的 Employee

這是把 weekie 任務變成 WeekieVisitor 的樣子

with WeekiesVisitor

這是加入 MentorVisitor 的樣子

adding MentorVisitor

這裡原本分別在 RegularMemberIntern 中的 beWeekie() 邏輯都被放到了 WeekieVisitor 裡面統一管理。而在這兩個員工類別中,都新增了 accept() 來接受 Visitor 的「拜訪」。這個介面除了可以接受 WeekieVisitor 來做出 Weekie 相關的邏輯,也可以接受 MentorVisitor 來做出 Mentor 相關的邏輯。

看一下 Ruby 程式碼範例吧

class Visitor
  def visit_rm(regular_member)
    raise NotImplementedError
  end

  def visit_intern(intern)
    raise NotImplementedError
  end
end

class WeekieVisitor < Visitor
  def visit_rm(regular_member)
    "#{regular_member.name} is watering flowers!"
  end

  def visit_intern(intern)
    # do weekie duties for interns
    "#{intern.name} is adding snacks to PicCafe!"
  end
end

class MentorVisitor < Visitor
  def visit_rm(regular_member)
    mentor  = mentor_of(regular_member)
    mentees = mentees_of(regular_member)

    puts "#{regular_member.name}'s mentor is #{mentor}."
    puts "#{regular_member.name}'s mentees are #{mentees}."
  end

  # intern 不會當 mentor,所以只有 mentor 資訊
  def visit_intern(intern)
    mentor = mentor_of(intern)

    puts "#{intern.name}'s mentor is #{mentor}."
  end

  private

  def mentor_of(employee)
    # return the name of employee's mentor
  end

  def mentees_of(employee)
    # return a list of mentees' names
  end
end


# Element 
class Employee
  attr_reader: name

  def do_fun_work
    # good times
  end

  def always_be_learning
    # learn learn learn
  end

  # for visitor
  def accept(visitor)
  end
end

class RegularMember < Employee
  def accept(visitor)
    visitor.visit_rm(self)
  end
end

class Intern < Employee
  def accept(visitor)
    visitor.visit_intern(self)
  end
end

Visitor Pattern 的優點與限制

  • [+] Visitor 符合 SOLID 原則中的開閉原則(Open/Closed Principle)。我們可以為物件新增方法而不用改變物件原有的行為。
  • [+] Visitor 也符合單一責任原則(Single Responsibility Principle)。所有相同功能、不同版本的行為都會被放在同一個地方,集中管理所有的操作(centralized operations)。
  • [+] Visitor 讓我們可以輕易地對一個樹狀/組合架構進行一系列操作,而又不改變架構本身。
  • [-] 物件的 encapsulation 被破壞了—它們必須要能夠讓 Visitor 拿到自己的狀態,並且讓 Visitor 進行操作。
  • [-] 我們比較難改變 Element 架構。想像當我們要增加一個新的 Element 時,我們同時也要在 Visitor Interface 與其下的所有 Concrete Visitor 們定義新的方法。

總而言之

Visitor 有時候可以被看作是高級版的 Command Pattern。它們都是將部分行為/邏輯從原本類別中抽出來,不過 Command 中獨立出來的行為只限制於單一類別,而 Visitor 則可以使用在不同的類別。

Visitor 就到這,下一篇是 Iterator!

參考資料

作者:Jenny


上一篇
[Design Pattern] Bridge 橋樑模式
下一篇
[Design Pattern] Iterator 迭代器模式
系列文
什麼?又是/不只是 Design Patterns!?32

尚未有邦友留言

立即登入留言