ためすう
第14章 オブジェクトを組み立てやすくする: Builder
2019-06-30「Rubyによるデザインパターン」の読書メモです。
ビルダ (Builder) クラスが担当するのは、ある複雑なオブジェクトのコンポーネントの組み立てです。各ビルダには新しいオブジェクトに対してステップごとに構成を指示するインターフェースを持っています。
test.rb
class Computer
attr_accessor :display
attr_accessor :motherboard
attr_reader :drives
def initialize(display=:crt, motherboard=Motherboard.new, drives=[])
@motherboard = motherboard
@drives = drives
@display = display
end
end
class CPU
end
class BasicCPU < CPU
end
class TurboCPU < CPU
end
class Motherboard
attr_accessor :cpu
attr_accessor :memory_size
def initialize(cpu=BasicCPU.new, memory_size=10000)
@cpu = cpu
@memroy_size = memory_size
end
end
class Drive
attr_reader :type # :hard_disk, :cd, :dvd
attr_reader :size # 単位は MB
attr_reader :writable
def initialize(type, size, writable)
@type = type
@size = size
@writable = writable
end
end
class ComputerBuilder
attr_reader :computer
def initialize
@computer = Computer.new
end
def turbo(has_turbo_cpu=true)
@computer.motherboard.cpu = TurboCPU.new
end
def display=(display)
@computer.display = display
end
def memory_size=(size_in_mb)
@computer.motherboard.memory_size = size_in_mb
end
def add_cd(writer=false)
@computer.drives << Drive.new(:cd, 760, writer)
end
def add_dvd(writer=false)
@computer.drives << Drive.new(:cd, 760, writer)
end
def add_hard_disk(writer=false)
@computer.drives << Drive.new(:dvd, 4700, writer)
end
end
builder = ComputerBuilder.new
builder.turbo
builder.add_cd(true)
builder.add_dvd
builder.add_hard_disk(true)
p builder.computer
出力結果
$ ruby test.rb
#<Computer:0x00007fd3da8b3c78 @motherboard=#<Motherboard:0x00007fd3da8b3c50 @cpu=#<TurboCPU:0x00007fd3da8b3bd8>, @memroy_size=10000>, @drives=[#<Drive:0x00007fd3da8b3bb0 @type=:cd, @size=760, @writable=true>, #<Drive:0x00007fd3da8b3b88 @type=:cd, @size=760, @writable=false>, #<Drive:0x00007fd3da8b3b60 @type=:dvd, @size=4700, @writable=true>], @display=:crt>
まとめ
Builderパターンの考え方は、オブジェクトを作り出すのが難しい場合や、オブジェクトを構成するのに大量のコードを書かなければいけない場合に、それらの生成のためのコードを別のクラス、すなわちビルダに分離するというものです。
第13章 正しいクラスを選び出す: Factory part2
2019-06-30「Rubyによるデザインパターン」の読書メモです。
Abstract Factory
Factory Method パターンがTemplate Method パターンをオブジェクトの作成に適用したのと同じように、Abstract Factory パターンは同じ問題にStrategyパターンを適用したのです。
アブストラクトファクトリで大事なことは、どの製品の型が一緒になるのかという知識をカプセル化することなのです。
class Duck
def initialize(name)
@name = name
end
def eat
puts "アヒル #{@name} は食事中です。"
end
def speak
puts "アヒル #{@name} はガーガー鳴いています。"
end
def sleep
puts "アヒル #{@name} は静かに眠っています。"
end
end
class Frog
def initialize(name)
@name = name
end
def eat
puts "カエル #{@name} は食事中です。"
end
def speak
puts "カエル #{@name} はゲロゲロっと鳴いています。"
end
def sleep
puts "カエル #{@name} は眠りません。一晩中ゲロゲロ鳴いています。"
end
end
class Algae
def initialize(name)
@name = name
end
def grow
p "藻 #{@name} は日光を浴びて育ちます。"
end
end
class WaterLily
def initialize(name)
@name = name
end
def grow
p "睡蓮 #{@name} は浮きながら日光を浴びて育ちます。"
end
end
class Tree
def initialize(name)
@name = name
end
def grow
p "樹木 #{@name} が高く育っています。"
end
end
class Tiger
def initialize(name)
@name = name
end
def eat
puts "トラ #{@name} は食べたいものを何でも食べます。"
end
def speak
puts "トラ #{@name} はガオーとほえています。"
end
def sleep
puts "トラ #{@name} は眠くなったら眠ります。"
end
end
class PondOrganismFactory
def new_animal(name)
Frog.new(name)
end
def new_plant(name)
Algae.new(name)
end
end
class JungleOrganismFactory
def new_animal(name)
Tiger.new(name)
end
def new_plant(name)
Tree.new(name)
end
end
class Habitat
def initialize(number_animals, number_plants, organism_factory)
@organism_factory = organism_factory
@animals = []
number_animals.times do |i|
animal = @organism_factory.new_animal("動物#{i}")
@animals << animal
end
@plants = []
number_plants.times do |i|
plant = @organism_factory.new_plant("植物#{i}")
@plants << plant
end
end
def simulate_one_day
@plants.each {|plant| plant.grow}
@animals.each {|animal| animal.speak}
@animals.each {|animal| animal.eat}
@animals.each {|animal| animal.sleep}
end
end
jungle = Habitat.new(1, 4, JungleOrganismFactory.new)
jungle.simulate_one_day
pond = Habitat.new(2, 4, PondOrganismFactory.new)
pond.simulate_one_day
Factory の一段上のレイヤーで、組み合わせを担っています。
まとめ
「どのクラスを選ぶのか?」という問いに答えるための技術です。
pandas の DataFrame に変換する
2019-06-30やったこと
pandas で 配列データ、辞書データを DataFrame に変換してみます。
確認環境
$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import pandas as pd
print(pd.__version__)
0.20.3
調査
from sklearn import datasets
iris = datasets.load_iris()
train = iris.data
print(type(train))
df = pd.DataFrame(train)
print(type(df))
data : ndarray (structured or homogeneous), Iterable, dict, or DataFrame Dict can contain Series, arrays, constants, or list-like objects
引数に渡すデータは ndarray だけでなくても良いようです。
リストを渡す
import pandas as pd
train = [2, 3]
print(type(train))
df = pd.DataFrame(train)
print(type(df))
<class 'list'>
<class 'pandas.core.frame.DataFrame'>
辞書を渡す
import pandas as pd
train = {'col1': [1, 2], 'col2': [3, 4]}
print(type(train))
df = pd.DataFrame(train)
print(type(df))
<class 'dict'>
<class 'pandas.core.frame.DataFrame'>
参考
pandas で任意の列を抽出する
2019-06-29やったこと
pandas で任意の列を抽出してみます。
確認環境
$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import pandas
print(pd.__version__)
0.20.3
調査
アイリスデータの1 ~ 2列目を取得します
import pandas
import pandas as pd
from sklearn import datasets
iris = datasets.load_iris()
train = iris.data
df = pd.DataFrame(train)
df[[0, 1]]
また、下記でも同じものが取得できます。
df.loc[:, [0, 1]]
参考
NumPy の astype を使ってみる
2019-06-29やったこと
NumPy で astype を使いデータをキャストしてみます。
確認環境
$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import numpy as np
np.__version__
'1.13.1'
調査
float -> int へ変換してみたいと思います。
x = np.array([1, 2, 2.5])
print(x)
print(x.dtype)
[ 1. 2. 2.5]
float64
x = x.astype(int)
print(x)
print(x.dtype)
[1 2 2]
int64
参考
第13章 正しいクラスを選び出す: Factory part1
2019-06-29「Rubyによるデザインパターン」の読書メモです。
パラメータ化された Factory Method パターン
class Duck
def initialize(name)
@name = name
end
def eat
puts "アヒル #{@name} は食事中です。"
end
def speak
puts "アヒル #{@name} はガーガー鳴いています。"
end
def sleep
puts "アヒル #{@name} は静かに眠っています。"
end
end
class Frog
def initialize(name)
@name = name
end
def eat
puts "カエル #{@name} は食事中です。"
end
def speak
puts "カエル #{@name} はゲロゲロっと鳴いています。"
end
def sleep
puts "カエル #{@name} は眠りません。一晩中ゲロゲロ鳴いています。"
end
end
class Algae
def initialize(name)
@name = name
end
def grow
p "藻 #{@name} は日光を浴びて育ちます。"
end
end
class WaterLity
def initialize(name)
@name = name
end
def grow
p "睡蓮 #{@name} は浮きながら日光を浴びて育ちます。"
end
end
class Pond
def initialize(number_animals, number_plants)
@animals = []
number_animals.times do |i|
animal = new_organism(:animal, "動物#{i}")
@animals << animal
end
@plants = []
number_plants.times do |i|
plant = new_organism(:plant, "植物#{i}")
@plants << plant
end
end
def simulate_one_day
@plants.each {|plant| plant.grow}
@animals.each {|animal| animal.speak}
@animals.each {|animal| animal.eat}
@animals.each {|animal| animal.sleep}
end
end
class DuckWaterLilyPond < Pond
def new_organism(type, name)
if type == :animal
Duck.new(name)
elsif type == :plant
WaterLily.new(name)
else
raise "Unknown"
end
end
end
class FrogAlgaePond < Pond
def new_organism(type, name)
if type == :animal
Frog.new(name)
elsif type == :plant
Algae.new(name)
else
raise "Unknown"
end
end
end
pond = FrogAlgaePond.new(3, 2)
pond.simulate_one_day
具象クラスも外から渡すパターン
class Duck
def initialize(name)
@name = name
end
def eat
puts "アヒル #{@name} は食事中です。"
end
def speak
puts "アヒル #{@name} はガーガー鳴いています。"
end
def sleep
puts "アヒル #{@name} は静かに眠っています。"
end
end
class Frog
def initialize(name)
@name = name
end
def eat
puts "カエル #{@name} は食事中です。"
end
def speak
puts "カエル #{@name} はゲロゲロっと鳴いています。"
end
def sleep
puts "カエル #{@name} は眠りません。一晩中ゲロゲロ鳴いています。"
end
end
class Algae
def initialize(name)
@name = name
end
def grow
p "藻 #{@name} は日光を浴びて育ちます。"
end
end
class WaterLily
def initialize(name)
@name = name
end
def grow
p "睡蓮 #{@name} は浮きながら日光を浴びて育ちます。"
end
end
class Pond
def initialize(number_animals, animal_class,
number_plants, plant_class)
@animal_class = animal_class
@plant_class = plant_class
@animals = []
number_animals.times do |i|
animal = new_organism(:animal, "動物#{i}")
@animals << animal
end
@plants = []
number_plants.times do |i|
plant = new_organism(:plant, "植物#{i}")
@plants << plant
end
end
def simulate_one_day
@plants.each {|plant| plant.grow}
@animals.each {|animal| animal.speak}
@animals.each {|animal| animal.eat}
@animals.each {|animal| animal.sleep}
end
def new_organism(type, name)
if type == :animal
@animal_class.new(name)
elsif type == :plant
@plant_class.new(name)
else
raise "Unknown"
end
end
end
pond = Pond.new(3, Duck, 2, WaterLily)
pond.simulate_one_day
animals と plants の組み合わせを呼び出し元から決めることができます。
この組み合わせも構造化するのは次の part2 で実施してみます。
pandas で データ型を確認する
2019-06-29やったこと
pandas のデータ構造である
- Series
- DataFrame
について調べたことを書きます。
確認環境
$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
print(pd.__version__)
0.20.3
調査
DataFrame の各列の型を確認してみます。
data = {'state': ['Ohio', 'Nevada'],
'year': [2000, 2001]}
df = DataFrame(data)
df.dtypes
出力結果
state object
year int64
dtype: object
参考
第12章 唯一を保証する: Singleton
2019-06-29「Rubyによるデザインパターン」の読書メモです。
ただ1つのインスタンスしか持てないクラスで、その1つのインスタンスへのアクセスをグローバルに提供します。
シングルトンを使わないロガーの例
test.rb
class SimpleLogger
attr_accessor :level
ERROR = 1
WARNING = 2
INFO = 3
def initialize
@level = WARNING
end
def error(msg)
p "#{self.class}##{__method__}"
p msg
end
def warning(msg)
p "#{self.class}##{__method__}" if @level >= WARNING
p msg
end
def info(msg)
p "#{self.class}##{__method__}" if @level >= INFO
p msg
end
end
logger = SimpleLogger.new
logger.level = SimpleLogger::INFO
logger.info('1つめ')
logger.info('2つめ')
logger2 = SimpleLogger.new
p '---'
p logger.object_id
p logger2.object_id
出力結果
$ ruby test.rb
"SimpleLogger#info"
"1つめ"
"SimpleLogger#info"
"2つめ"
"---"
70311664013160
70311664012640
シングルトンを適用したロガーの例
test.rb
class SimpleLogger
attr_accessor :level
@@instance = SimpleLogger.new
ERROR = 1
WARNING = 2
INFO = 3
def initialize
@level = WARNING
end
def error(msg)
p "#{self.class}##{__method__}"
p msg
end
def warning(msg)
p "#{self.class}##{__method__}" if @level >= WARNING
p msg
end
def info(msg)
p "#{self.class}##{__method__}" if @level >= INFO
p msg
end
def self.instance
return @@instance
end
private_class_method :new
end
logger = SimpleLogger.instance
logger.level = SimpleLogger::INFO
logger.info('1つめ')
logger.info('2つめ')
logger2 = SimpleLogger.instance
p '---'
p logger.object_id
p logger2.object_id
出力結果
$ ruby test.rb
"SimpleLogger#info"
"1つめ"
"SimpleLogger#info"
"2つめ"
"---"
70234052873820
70234052873820
シングルトンモジュール
Singleton モジュールを使って、実装することができます。
include Singleton
シングルトンとしてのクラス
class ClassBasedLogger
ERROR = 1
WARNING = 2
INFO = 3
def self.error(msg)
p "#{self.class}##{__method__}"
p msg
end
end
シングルトンとしてのモジュール (自作)
module ClassBasedLogger
ERROR = 1
WARNING = 2
INFO = 3
def self.error(msg)
p "#{self.class}##{__method__}"
p msg
end
end
モジュールは、インスタンス化するためにあるのではないと少しは示すことができます。
Need-to-Know原則に基づくシングルトン
Need-to-Know原則とは > 「必要な人にだけ情報を開示する」という情報セキュリティにおける基本的な考え方です。
require 'singleton'
class DatabaseConnectionManager
include Singleton
def get_connection
p "#{self.class}##{__method__}"
end
end
class PreferenceManager
def initialize
@reader = PrefReader.new
@writer = PrefWriter.new
@preferences = {
:display_splash => false,
:background_color => :blue
}
end
def save_preferences
@writer.write(DatabaseConnectionManager.instance, @preferences)
end
def get_preferences
@preferences = @reader.read(DatabaseConnectionManager.instance)
end
end
class PrefWriter
def write(manager, preferences)
p "#{self.class}##{__method__}"
p preferences
manager.get_connection
end
end
class PrefReader
def read(manager)
p "#{self.class}##{__method__}"
manager.get_connection
p '読み取ったもの'
end
end
PreferenceManager.new.get_preferences
PreferenceManager.new.save_preferences
シングルトンは実装詳細なので、シングルトンであると知っているクラスを少なくした方が良いようです。
第11章 オブジェクトを改良する: Decorator
2019-06-27「Rubyによるデザインパターン」の読書メモです。
Decoratorパターンについて
既存のオブジェクトに対して簡単に機能を追加するためのパターンです。Decoratorパターンを使うとレイヤ状に機能を積み重ねていくことができ、それぞれの状況でまさに必要なだけの機能をもつオブジェクトを作ることができます。
今回、ファイルに下記の形式で書き出すことができるクラスを作ります。
- プレーンテキスト
- チェックサム付き
- タイムスタンプ付き
- 行番号付き
継承ベースのアプローチ
継承ベースのアプローチでは、あり得るすべての機能の組み合わせを前もって設計時に考えなければならない,ということです。
クラスの数が増えすぎるので、不採用とします。
Decoratorパターンで実装した例
class SimpleWriter
def initialize(path)
@file = File.open(path, 'w')
end
def write_line(line)
@file.print(line)
@file.print("\n")
end
def pos
@file.pos
end
def rewind
@file.rewind
end
def close
@file.close
end
end
class WriterDecorator
def initialize(real_writer)
@real_writer = real_writer
end
def write_line(line)
@real_writer.write_line(line)
end
def pos
@real_writer.pos
end
def rewind
@real_writer.rewind
end
def close
@real_writer.close
end
end
class NumberingWriter < WriterDecorator
def initialize(real_writer)
super(real_writer)
@line_number = 1
end
def write_line(line)
@real_writer.write_line("#{@line_number}: #{line}")
@line_number += 1
end
end
class CheckSummingWriter < WriterDecorator
attr_reader :check_sum
def initialize(real_writer)
@real_writer = real_writer
@check_sum = 0
end
def write_line(line)
line.each_byte {|byte| @check_sum = (@check_sum + byte) % 256}
# check_sum の計算...
@real_writer.write_line("#{line} (checksum: #{@check_sum})")
end
end
class TimeStampingWriter < WriterDecorator
def write_line(line)
@real_writer.write_line("#{Time.new}: #{line}")
end
end
writer = CheckSummingWriter.new(
TimeStampingWriter.new(
NumberingWriter.new(SimpleWriter.new('final.txt'))
)
)
writer.write_line('Hello out there')
出力された final.txt
1: 2019-06-23 13:28:36 +0900: Hello out there (checksum: 164)
Forwardable を使った例
class SimpleWriter
def initialize(path)
@file = File.open(path, 'w')
end
def write_line(line)
@file.print(line)
@file.print("\n")
end
def pos
@file.pos
end
def rewind
@file.rewind
end
def close
@file.close
end
end
出力された final2.txt
2019-06-23 13:34:25 +0900: Hello out there
ラッピングする例
class SimpleWriter
def initialize(path)
@file = File.open(path, 'w')
end
def write_line(line)
@file.print(line)
@file.print("\n")
end
def pos
@file.pos
end
def rewind
@file.rewind
end
def close
@file.close
end
end
w = SimpleWriter.new('final3.txt')
class << w
alias old_write_line write_line
def write_line(line)
old_write_line("#{Time.new}: #{line}")
end
end
w.write_line('write_line')
w.old_write_line('old_write_line')
出力された final3.txt
2019-06-23 13:42:53 +0900: write_line
old_write_line
モジュールを使った例
class SimpleWriter
def initialize(path)
@file = File.open(path, 'w')
end
def write_line(line)
@file.print(line)
@file.print("\n")
end
def pos
@file.pos
end
def rewind
@file.rewind
end
def close
@file.close
end
end
module TimeStampingWriter
def write_line(line)
super("#{Time.new}: #{line}")
end
end
w = SimpleWriter.new('final4.txt')
w.extend(TimeStampingWriter)
w.write_line('hello')
出力された final4.txt
2019-06-23 13:45:02 +0900: hello
第10章 オブジェクトに代理を立てる: Proxy part2
2019-06-27「Rubyによるデザインパターン」の読書メモです。
Proxy で重複を取り除いたパターン
test.rb
class BankAccount
attr_reader :balance
def initialize(starting_balance=0)
@balance = starting_balance
end
def deposit(amount)
@balance += amount
end
def withdraw(amount)
@balance -= amount
end
end
class AccountProxy
def initialize(real_account)
@subject = real_account
end
def method_missing(name, *args)
p "Delegating #{name} message to subject."
@subject.send(name, *args)
end
end
ap = AccountProxy.new(BankAccount.new(100))
ap.deposit(25)
ap.withdraw(50)
p ap.balance
出力結果
$ ruby test.rb
"Delegating deposit message to subject."
"Delegating withdraw message to subject."
"Delegating balance message to subject."
75
method_missing をオーバーライドして、委譲メソッドを別々に定義しなくても良くなりました。
Proxy パターンの注意
method_missingの使いすぎは、継承の使いすぎと同様、あなたのコードをわかりにくくしてしまう代表的な原因になるということです。
次に来る実装者には、実装により意図が伝わるようにしてください。