Smalltalkベストプラクティス・パターン
ケント・ベックのあれをRubyでメモしたメモ。
このメモについて
ケント・ベックのSmalltalkベストプラクティス・パターン―シンプル・デザインへの宝石集
ケント・ベックのSmalltalkベストプラクティス・パターンを読む時になんとなく メモしたメモなので、内容の要約であるわけではありません。 元の本はこのメモの1024倍以上の内容だよう。
「はじめに」の「この本で扱わない事柄」がなぜか大好き。
振る舞い
メソッド
Composed Method
メソッドを一つのことのみをするメソッドに分割しよう
class Controller
def control_activity
control_initialize
control_loop
control_terminate
end
end
Constructor Method
- インスタンスの生成を表現するには?
適切な値のインスタンスを生成できるようなメソッドを用意しよう。 必要なパラメータをそのメソッドに与えよう
class Point
def initialize(x, y)
@x = x
@y = y
end
def self.polar(r, theta)
self.new(r * Math.cos(theta), r * Math.sin(theta))
end
end
Constructor Parameter Method
- Constructor Methodのパラメータをインスタンス変数にセットするには?
# before
class Point
def initialize(x_number, y_number)
x = x_number
y = y_number
end
attr_accessor :x, :y
end
インスタンス変数を一度に設定するメソッドを作ろう
# after
class Point
def initialize(x_number, y_number)
set_x_y(x_number, y_number)
end
def set_x_y(x_number, y_number)
@x = x_number
@y = y_number
end
end
Shortcut Constructor Method
Constructor Methodの呼び出しが面倒な時、さらにインターフェイスを 追加するには?
Conversion
- あるオブジェクトの形式から別のオブジェクトの形式へ変換するには?
あるオブジェクトのプロトコルを次々と増やすより、 別のオブジェクトに変換しよう
Converter Method
- 内部の構造は異なるものの、同じプロトコルを持つオブジェクトに変換 するには
class Collection def to_set end end class Number def to_float end end
Converter Constructor Method
- 異なるプロトコルのオブジェクトへ変換するには?
変換元のオブジェクト引数として受け取るConstructor Methodを作ろう
Query Method
- オブジェクトのプロパティを調べるには?
# before
class Switch
def make_on
@status = :on
end
def make_off
@status = :off
end
attr_reader :status
end
class WallPlate
def update
light.make_on if @switch.status == :on
light.make_off if @switch.status == :off
end
end
真偽値を返すメソッドを用意しよう
# after
class Switch
def on?
# レシーバがonならtrue、offならfalseを返す
end
end
Comparing Method
- オブジェクト同士を並び替えるには?
class Event
def <=>(other)
time_stamp <=> other.time_stamp
end
end
Reversing Method
- メッセージの流れをスムーズに書くには?
# before
class Point
def print_on(stream)
@x.print_on(stream)
stream.puts(' @ ')
@y.print_on(stream)
end
end
引数に対してメソッドを書こう
# after
class Stream
def puts(obj)
obj.print_on(self)
end
end
class Point
def print_on(stream)
stream.puts(@x)
stream.puts(" @ ")
stream.puts(@y)
end
end
FIXME。Rubyだとどうかな?
Method Object
- たくさんの引数や一時変数であふれているメソッドを整理するには?
元のメソッドにちなんだ名前を持つクラスを定義しよう 元のメソッドのレシーバ、引数、一時変数を保持するインスタンス変数を 用意して、元のレシーバと引数を受け取るConstructor Method
# before
class Obligation
def send_task(task, job)
# 長いコード
end
def prepare_task(task, job, ...)
end
end
# after
class TaskSender
def initialize(obligation, task, job)
@obligation = obligation
@task = task
@job = job
end
def compute
# 長いコード
end
end
class Obligation
def send_task(task, job)
TaskSender.new(self, task, job).compute
end
end
Execute Around Method
- 続けて実行しなければならないアクションのペアを表現するには?
ブロックを受け取るメソッドを書こう。
class Cursor
def show_while
old = current_cursor
show
yield
old show
end
end
File.openなど
class PgUtil
def transaction
aborted = false
begin_transaction
yield
rescue
aborted = true
abort
ensure
end_transaction unless aborted
end
end
Debug Printing Method
- デバッグ用のプリントメソッドを書くには?
inspectをオーバーライドしよう
Method Comment
- メソッドのコメントを書くには?
メソッドの先頭にコメントを書くことで、コードから読み取れない 重要な情報を伝えるようにしよう
メッセージ
Message
- 処理を起動するには?
よい名前のついたメッセージを送り、何を行うかはメッセージを 受け取るメッセージに任せよう
Choosing Message
- さまざまな選択肢のうちのひとつを実行させるには?
# before
def responsible
if Film === entry
entry.producer
else
entry.author
end
end
# after
class Film
def responsible
return producer
end
end
class Entry
def responsible
return author
end
end
def responsible
entry.responsible
end
いくつかのオブジェクトを用意して、そのうちの一つにメッセージを送り 各オブジェクトが一つの選択肢を実行するようにしよう
Decomposing Message
- 分割した処理の一部を呼び出すには?
selfに対して複数のメッセージを送ろう
class Controller
def controy_activity
control_initialize
control_loop
control_terminate
end
end
Intention Revealing Message
- 単純な実装の意図を伝えるには?
class ParagraphEditor
def highlight(rect)
reverse(rect)
end
end
selfにメッセージを送ろう。そのメッセージには処理の方法ではなく 何を行うか伝えられる名前にしよう。メッセージの実装メソッドは単純に しよう
class Collection
def empty?
size == 0
end
end
class Number
def reciprocal
1 / self
end
end
Intention Revealing Selector
- メソッドにどんな名前をつける?
HowではなくWhat
メソッドには何を達成するかにちなんだ名前をつけよう
# before
class Array
def linear_search_for(x)
...
end
end
# after
class Array
def include?(x)
...
end
end
Dispatched Interpretation
- 二つのオブジェクトがあり、片方のオブジェクトの表現形式を 隠ぺいしたい時、どうやって協調させる?
# before
class PostScriptPrinter
def display(shape)
shape.size.times do |n|
command = shape.command_at(n)
arg = shape.argument_at(n)
case command
when :line
print_point(arg[0])
space
print_point(arg[1])
space
....
when ...
...
end
end
end
end
クライアントからはエンコードオブジェクトにメッセージを送ろう。 エンコードオブジェクトにパラメータを渡し、デコード用のメッセージを 更に送らせるようにしよう。
# after-1
class PostScriptPrinter
def line(from_point, to_point)
print_point(from_point)
space
pint_point(to_point)
space
puts('line')
end
end
class PostScriptPrinter
def display(shape)
shape.size.times do |n|
shape.send_command_at(n, self)
end
end
end
Shape自身に繰り返しを行う責務を与えると…
# after-2
class Shape
def send_command_to(printer)
size.times do |n|
send_command_at(n, printer)
end
end
end
class PostScriptPrinter
def display(shape)
shape.send_command_to(n, self)
end
end
Double Dispatch
- ふたつの継承木のオブジェクトが相互作用し、多くの場合分けが 起こる処理をコーディングするには?
引数にメッセージを送ろう。 セレクタはレシーバのクラス名を後ろに連結したものに。 そのメッセージを元のレシーバを引数に渡そう。
class Int
def +(num)
num.add_int(self)
end
end
class Float
def +(num)
num.add_float(self)
end
end
class Int
def add_int(num)
self + num # primitive
end
end
class Float
def add_float(num)
self + num # primitive
end
end
Mediating Protocol
- お互いに独立させておきたいオブジェクトの相互作用をコーディングするには?
オブジェクト間のプロトコルで使われている言葉が一貫したものに なるように推敲しよう
Super
- スーパークラスの振る舞いを呼び出すには?
superだよーん。
Extending Super
- あるメソッドのスーパークラスの実装に機能を追加するには?
class Super
def initialize
@a = default_a
end
end
class Sub < Super
def initialize
super
@b = default_b
end
end
メソッドをオーバーライドし、そのメソッドの中でsuperを送ろう
Modifying Super
- スーパークラスのメソッドを変更せずに振る舞いの一部を変えるには?
# before
class IntegerAdder
def initialize
@sum = 0
@count = 0
end
end
class FloatAdder
def initialize
super
@sum = 0.0
end
end
# after-1
class IntegerAdder
def initialize
@sum = default_sum
@count = default_count
end
def default_sum
0
end
def default_count
0
end
end
class FloatAdder < IntegerAdder
def default_sum
0.0
end
end
スーパークラスを修正できないときは…
メソッドをオーバーライドして"super"を送り、その後で結果を修正する コードを実行しよう
class SuperFigure
def initialize
@color = Color.white
@size = Point.new(0, 0)
end
end
class SubFigure < SuperFigure
def initialize
super
@color = Color.beige
end
end
Delegation
- 継承なしに実装を共有するには?
仕事の一部を他のオブジェクトにまかせよう
Simple Delegation
- 呼び出し元に依存しない委譲を行うには?
メッセージをそのまま委譲しよう
class Vector
def initialize(num)
set_elements(Array.new(num))
end
def set_elements(ary)
@elements = ary
end
def each(&block)
@elements.each(&block)
end
end
Self Delegation
- 呼び出し元への参照が必要な委譲を実装するには?
呼び出し元のオブジェクト(self)をfor:というキーワードを 追加してパラメータで渡そう
うーん。うまくサンプルが作れない… FIXME
Pluggable Behavior
- オブジェクトの振る舞いをパラメータ化するには?
変数を追加してそこから異なる振る舞いを実行できるように。
Pluggable Selector
- インスタンス固有の振る舞いを簡単にコーディングするには?
# before
class ListPane
def print_element(obj)
obj.to_s
end
end
class DollerListPane
def print_element(obj)
obj.doller_format_string
end
end
class DescriptionListPane
def print_element(obj)
obj.inspect
end
end
そこだけオーバーライドしたクラスがたくさんになっちゃった。
# after
class ListPane
def print_element(obj)
obj.send(@print_message)
end
end
class ListPane
def initialize
@print_message = :to_s
end
end
実行されるセレクタを保持する変数を追加しよう Role Sugessting Instance Variable Nameに「Message」をつけて 変数名にしよう。 そのセレクタを実行するためのComposed Methodを作ろう
class RelativePoint
def self.centered(fig)
self.new(fig, :center)
end
def initialize(fig, symbol)
@figure = fig
@message = symbol
end
def to_point
@figure.send(@message)
end
def x
to_point.x
end
end
Pluggable Block
- そのクラス自身ではまったく価値のない複雑なPluggable Behaviorを コーディングするには?
Blockを保持するインスタンス変数を追加しよう
class PluggableAdaptor
def initialize(set_blk, get_blk)
@set_block = set_blk
@get_block = get_blk
end
def value
@get_block.call
end
def value=(obj)
@set_block.call(obj)
end
end
class Car
def speed_adaptor
get_blk = Proc.new { self.speed }
set_blk = Proc.new { |new_speed| self.speed=(new_speed) }
PluggableAdaptor.new(get_blk, set_blk)
end
end
Collecting Parameter
- いくつものメソッドの協調作業の結果としてコレクションを返すには?
すべてのサブメソッドの結果を集めるためのパラメータを追加しよう。
# before
def married_men_and_unmarried_women
result = Array.new
prople.each do |person|
result.push(person) if person.married? && person.man?
result.push(person) if person.unmarried? && person.woman?
end
result
end
Composed Methodを使って繰り返しを別々のメソッドにしよう。
# after
def married_men
result = Array.new
prople.each do |person|
result.push(person) if person.unmarried? && person.woman?
end
result
end
def unmarried_women
result = Array.new
prople.each do |person|
result.push(person) if person.married? && person.man?
end
result
end
def married_men_and_unmarried_women
married_men + unmarried_women
end
Collectionを返す代わりに各々がオブジェクトをCollectionに追加しよう。
# after-2
def add_married_men_to(ary)
prople.each do |person|
ary.push(person) if person.unmarried? && person.woman?
end
end
def add_unmarried_women_to(ary)
prople.each do |person|
ary.push(person) if person.married? && person.man?
end
end
def married_men_and_unmarried_women
result = Array.new
add_married_men_to(result)
add_unmarried_women_to(result)
result
end
状態
インスタンス変数
Variable State
- インスタンスごとに表現の仕方が異なるような状態を表わすには?
いくつかのインスタンスでしか使わない変数はHashに入れちゃえ。
でもRubyでは実行時にインスタンス変数を作れるから、 そういう手もあります。
Explicit Initialization
- インスタンス変数をデフォルト値で初期化するには?
変数の値を初期化するメソッドを実装しよう。 Rubyではinitializeがnewの最後(?)に呼ばれます。
class Timer
def initialize
@count = 0
@period = 1000
end
end
さらにマジックナンバーを説明するようにすると…
class Timer
def default_msec_period
return 1000
end
def initialize
@count = 0
@period = default_msec_priod
end
end
Lazy Initialization
- インスタンス変数をデフォルト値で初期化するには?
アクセサ(attr_reader相当)を書いて、必要に応じてDefaultMethodを 使って初期化しよう。
class Timer
def count
@count = default_count if @count.nil?
@count
end
def default_count
return 0
end
def period
@period = default_msec_period if @period.nil?
@count
end
def default_msec_period
return 1000
end
end
Default Value Method
- 変数のデフォルト値をどのように表現する?
値を返すメソッドを作ろう。メソッドの名前には変数名の前にdefaultを つけたものに。
class Book
def default_synopsis
return ""
end
end
Constant Method
- 定数をどのようにコーディングする?
うーん。Rubyの良い例はないかなあ。
定数を返すためのメソッドを書こう。
Direct Variable Access
- インスタンス変数の値を取り出したり設定するには?
アクセサ経由で…
class Foo
attr_accessor :x
..
def foo
... x ...
end
end
直接アクセスしよう
def foo .. @x .. end
読みやすい
Indirect Variable Access
- インスタンス変数の値を取り出したり設定するには?
アクセサ経由でアクセスしよう
拡張しやすい
Getting Method
- インスタンス変数にどうやってアクセスする?
変数の値を返すメソッドを書こう。名前は変数と同じにしよう。
Rubyではattr_readerで簡単にかけるよ。
class Foo attr_reader :foo end
Setting Method
- インスタンス変数にどうやって変更する?
変数の値を変更するメソッドを書こう。名前は変数名=にしよう。
Rubyではattr_writerで簡単にかけるよ。
class Foo attr_writer :foo end
Collection Accessor Method
- コレクションを持つインスタンス変数にアクセスするには?
コレクションを直接見せちゃうとちょっと困るよ。
コレクションにdelegateするメソッドを作ろう。 名前は変数名+メッセージにしよう
class Department
def total_salary
return @total_salary if @total_salary
compute_total_salary
end
def compute total_salary
sum = 0
@employees.each do |x|
sum += x.salary
end
sum
end
def add_employee(employee)
clear_total_salary
@employees.push(employee)
end
def remove_employee(employee)
clear_total_salary
@emplyoees.remove(employee)
end
end
機械的に名前を付けないで、もっと意味の名前を付けよう
#before def include_employee(employee) @employees.include?(employee) end #after def employs(employee) @employees.include?(employee) end
Enumeration Method
- コレクションの要素に、安全、汎用的にアクセスするには?
コレクションの各要素にblockを実行するメソッドを書こう。
class Department
def employees_each(&block)
@employees.each(&block)
end
end
Boolean Property Setting Method
- 真偽値の属性を設定するには?
# before
class Switch
def on=(bool)
@on = bool
end
end
# after
class Switch
def on
@on = true
end
def off
@on = false
end
end
beではじまるメソッドを書こう
- be_visible, be_invisible, toggle_visible
- be_drity, be_clean
Role Suggesting Instance Variable Name
- インスタンス変数の名前はどうする?
役割を示す名前にしよう。コレクションのときは複数形にしよう。
一時変数
Temporary Variable
- メソッド内で後で使用する値を保存するには?
スコープ、エクステントを1メソッドに絞った一時変数を作ろう。
Collecting Temporary Variable
- 後で使う値を段階的に集めたいときは?
def foo
sum = 0
@children.each do |child|
sum += child.size
end
sum
end
inject (ruby-1.8) を使うとsumが不要
def foo
@children.inject(0) do |sum, child|
sum + child.size
end
end
複雑なenumerationでマージや収集を行う場合に、中間結果の コレクションを作ろう
Caching Temporary Variable
Explaining Temporary Variable
Reusing Temporary Variable
Role Suggestin Temporary Variable
- 一時変数にはどんな名前をつける?
役割を表わす名前を付けよう
コレクション
クラス
Collection
- 1対多の関係を表現するには?
コレクションクラスを使いましょう
Array
- 作成時にサイズが決められないようなコレクションを使うには?
とりあえずArrayを使おう
RunArray
なし
Set
- 要素が重複しないコレクションを扱いたいときには?
SetがないのでHashを使う?
class Set
def push(obj)
@hash[obj] = true
end
def each(&block)
@hash.keys.each(&block)
end
end
Equality Method
- 新たに作ったオブジェクトの同値性を保証するには?
同値を判定するには==を定義しよう
class Book
def ==(other)
return (@author == other.author) && (@title == other.title)
end
end
コレクションから探せるようになる
class Library
def has_book(author_str, title_str)
book = Book.new(author_str, title_str)
@books.include?(book)
end
end
さらにクラス判定を含めるのが一般的
class Book
def ==(other)
return false unless self.class === other
return (@author == other.author) && (@title == other.title)
end
end
Hashing Method
- Hashに対応させるには?
「=」をオーバーライドしたオブジェクトをHashに入れるときは、 hashもオーバーライドしよう。
class Book
def hash
@author.hash ^ @title.hash
end
end
Hash
- オブジェクトを他のオブジェクトに対応付けるには?
Hashを使おう
Sorted Collection
- コレクションをソートするには?
sortを使おう。「<=>」メソッドを定義しよう
Range
- 順番に並んだ数値のコレクションを扱うには?




