冒險村20 - Design Pattern(1) - Decorator

20 - Design Pattern(1) - Decorator

Decorator pattern 的原理是,增加一個修飾類包裹原來的 class,包裹的方式是在修飾類的構造函數中將原來的類以參數的形式傳入。裝飾類實現新的功能,但是,在不需要用到新功能的地方,它可以直接調用原來的類中的方法。修飾類必須和原來的類有相同的接口。

直接來看如何使用:

Create app/decorators folder

並在 folder 下可以先建立以下兩個檔案,稍後會來修改內容

  • base_decorator.rb
  • user_decorator.rb

Add decorate to ApplicationHelper

新增一個共用的方法取名為 decorate,可以方便之後在 controller 直接 helpers.decorate
來將重複的 code 整理起來,當然也會有不符合規則的時候,所以允許帶入 args。

1
2
3
4
5
6
# frozen_string_literal: true
module ApplicationHelper
def decorate(model, decorate_class = nil)
(decorate_class || "#{model.class}Decorator".constantize).new(model)
end
end

Add app > decorators > base_decorator.rb

這邊將為 decorator 的父層。主要重點是繼承在 SimpleDelegator 下,這個看文件其實滿好理解的。

1
2
3
4
5
6
# frozen_string_literal: true
class BaseDecorator < SimpleDelegator
def decorate(model, decorate_class = nil)
ApplicationController.helpers.decorate(model, decorate_class)
end
end

Add app > decorators > user_decorator.rb

可以將 view 的邏輯丟到 decorator 內,簡單以 User 常有的 first_name, last_name 來說,在 DB 內存成兩個欄位,不過畫面上通常都會是想要直接顯示兩個欄位加起來作為使用者的名稱,這時候只要在 view 直接 @xxx.full_name 即可。

1
2
3
4
5
6
# frozen_string_literal: true
class UserDecorator < BaseDecorator
def full_name
"#{first_name} #{last_name}"
end
end

Init instance variable

1
2
3
4
5
6
7
8
9
10
11
12
# frozen_string_literal: true
class UsersController < ApplicationController
def show
@user_decorator = helpers.decorate(current_user)
end

private

def current_user
User.find(params[:id])
end
end

View

1
2
3
# show.html.erb
<%= @user_decorator.full_name %>
<%= @user_decorator.email %>

參考資料