Skip to content

Instantly share code, notes, and snippets.

@TomohikoSato
Last active July 13, 2019 14:42
Show Gist options
  • Save TomohikoSato/8c69f0650e5e84bb2a60be9a807ec9fc to your computer and use it in GitHub Desktop.
Save TomohikoSato/8c69f0650e5e84bb2a60be9a807ec9fc to your computer and use it in GitHub Desktop.
EffectiveRuby写経
# 項目13 "<=>"とComparableモジュールで比較を実装しよう
class Version
include(Comparable) # <=>が定義されていれば、 <, <=, =>, > といったComparableで定義されたメソッドが利用可能になる
attr_reader :major, :minor, :patch
alias_method(:eql?, :==) # Hashのキーの同一性にeql?とhashを使う
def initialize(version)
@major, @minor, @patch = version.split('.').map(&:to_i)
end
def <=> (other)
return nil unless other.is_a?(Version)
[ major <=> other.major,
minor <=> other.minor,
patch <=> other.patch].detect {|n| !n.zero?} || 0
end
def hash
[major, minor, patch].hash
end
end
# オブジェクトの順序は、"<=>"演算子を定義し、Comparableモジュールをインクルードして実装しよう。
hoge = %w(1.2.3 3.4.5 0.1.1).map {|v| Version.new(v) }
p hoge.sort # => [#<Version:0x00007ff49386fad0 @major=0, @minor=1, @patch=1>, #<Version:0x00007ff49386ffa8 @major=1, @minor=2, @patch=3>, #<Version:0x00007ff49386fd00 @major=3, @minor=4, @patch=5>]
p Version.new('1.2.3') < Version.new('2.3.4') # => true
# クラスのために"<=>"を実装した場合、特にインスタンスをハッシュキーとして使うつもりなら、eql?を"=="の別名にすることを検討しよう。別名にする場合には、hashメソッドもオーバーライドしなければならない。
v1 = Version.new '1.2.3'
v2 = Version.new '1.2.3'
h = {}
h[v1] = 'good'
h[v2] = 'nice'
p h # => {#<Version:0x00007ff7e18733f0 @major=1, @minor=2, @patch=3>=>"nice"}
# 項目14 protectedメソッドを使ってプライベートな状態を共有しよう
class Widget
def initialize(screen_x, screen_y)
@screen_x = screen_x
@screen_y = screen_y
end
# 他のobjectと座標が重なっているかチェックする場合など
def overlapping?(other)
x1, y1 = @screen_x, @screen_y
# レシーバであるotherが、selfと同じクラスまたは共通のスーパークラスからprotectedメソッドを継承している場合に、protectedだと呼べる
# privateだと呼べない。privateはレシーバーを指定すして呼べない。
x2, y2 = other.screen_coordinates
p "x1 #{x1}: y1 #{y1}" # => "x1 2: y1 3"
p "x2 #{x2}: y2 #{y2}" # => "x2 10: y2 20"
end
protected
def screen_coordinates
[@screen_x, @screen_y]
end
end
w1 = Widget.new(2, 3)
w2 = Widget.new(10, 20)
w1.overlapping? w2
# 項目15 クラス変数よりもクラスインスタンス変数を使うようにしよう
# クラス変数を利用
## グローバル変数みたいなもん
class Singleton
private_class_method(:new, :dup, :clone)
def self.instance
@@single ||= new
end
end
class Configuration < Singleton; end
class Database < Singleton; end
p Configuration.instance # => #<Configuration:0x00007f9e610e9230>
p Database.instance # => #<Configuration:0x00007f9e610e9230> NG!!
# クラスインスタンス変数を利用
## クラス定義ごとにユニークになるので、上述の継承の問題を解決できる
class Singleton
private_class_method(:new, :dup, :clone)
def self.instance
@single ||= new
end
end
class Configuration < Singleton; end
class Database < Singleton; end
p Configuration.instance # => #<Configuration:0x00007fe66681d400>
p Database.instance # => #<Database:0x00007fe66681d040>
# シングルトンパターンの実現にはSingletonモジュールを利用すれば十分
require "singleton"
class SingletonClass
include Singleton
end
class Configuration < SingletonClass; end
class Database < SingletonClass; end
p Configuration.instance # => #<Configuration:0x00007fb72584cba8>
p Database.instance # => #<Database:0x00007fb72584c9a0>
# 項目17 nil、スカラーオブジェクトを配列に変換するには、Arrayメソッドを使おう
class NGPizza
def initialize toppings # *で可変長引数
toppings.each do |topping|
p topping
end
end
end
class Pizza
def initialize toppings # *で可変長引数
Array(toppings).each do |topping|
p topping
end
end
end
ng_p1 = NGPizza.new [4, 8, 10, 13]
ng_p2 = NGPizza.new nil # undefined method `each' for nil:NilClass (NoMethodError)
p1 = Pizza.new [4, 8, 10, 13]
p2 = Pizza.new nil
# 項目18 要素が含まれているかどうかの処理を効率よく行うために集合を使うことを検討しよう
# Setは標準ライブラリ requireする必要がある
# Array, Hash, Range はコアライブラリ requireする必要はない。リテラルからオブジェクトを作るための専用構文もある
# [a, b, c]
# {p: q} keyがシンボルの場合JSONのように記述できる {:p => q}
# .. 終端含める ... 含めない
# 権限の例
# include? は Enumerableモジュールのメソッド どこで使用されているかはRubyMineでFindUsagesすれば一覧できる
# Hashの要素へのアクセスはO(logn) と書いてあるが、一般にハッシュテーブルはO(1)では?
# 項目19 reduceを使ってコレクションを畳み込む方法を身に付けよう
# map, select, reduceと言った重要なメソッドはEnumerableに属する
# reduceのレシーバが空の場合、アキュムレータの初期値がnilになる
## accumulate .. 蓄積 accumulaterにブロックの計算結果を繰り返しaccumulateしていくイメージ(?)
def sum enum
enum.reduce(0) do |acc, element|
acc + element
end
end
def ngsum enum
enum.reduce do |acc, element|
acc + element
end
end
p sum [1,2,3,4,5] # 15
p sum [] # 0
p ngsum [1,2,3,4,5] # 15
p ngsum [] # nil
# 項目6 Rubyが継承階層をどのように組み立てるかを頭に入れよう
module ThingsWithNames
def name
p 'things'
end
end
class Person
include(ThingsWithNames)
def name
p 'person'
end
end
Person.new.name #person
# 項目7 superのふるまいがひと通りではないことに注意しよう
# superはメソッドではなくキーワード
## 引数も括弧もつけずにsuperキーワードを呼ぶと、呼び出し元のメソッドと同じ引数をスーパークラスに渡してオーバーライドするメソッドを呼ぶ
class Base
def m1(x, y)
p 'base'
end
end
class Derived < Base
def m1 (x)
p 'derived'
super(x, 'b') # 'base'
super x, 'b' # 'base'
super # wrong number of arguments (given 1, expected 2) (ArgumentError)
super() # wrong number of arguments (given 0, expected 2) (ArgumentError)
end
end
Derived.new().m1('a')
Derived.new().m1('a', 'b') # wrong number of arguments (given 2, expected 1) (ArgumentError)
# moduleもsuperに入る
module CoolFeatures
def feature_a
p 'a'
end
end
class Vanila
include(CoolFeatures)
def feature_a
super # a
end
end
Vanila.new.feature_a
# 項目9 Rubyの最悪に紛らわしい構文に注意しよう
class SetMe
def initialize
@value = 0
end
def value
@value
end
def value= (x)
@value = x
end
end
s = SetMe.new
p s.value # 0
s.value = 3
p s.value # 3
class Counter
attr_accessor(:counter)
def initialize
counter = 0 #ローカル変数counterを作成し0に代入。インスタンス変数には代入されない。
end
end
c = Counter.new
p c.counter # nil
class Counter2
attr_accessor(:counter)
def initialize
self.counter = 0
end
end
c2 = Counter2.new
p c2.counter # 0
class Name
attr_accessor(:first, :last)
def initialize(first, last)
self.first = first
self.last = last
end
# Getterにselfは不要
def full
self.first + " " + self.last
end
def full2
first + " " + last
end
end
name = Name.new('Tomohiko', 'Sato')
p name.full # "Tomohiko Sato"
p name.full2 # "Tomohiko Sato"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment