03. Въведение в обектно-ориентирано Ruby

03. Въведение в обектно-ориентирано Ruby

03. Въведение в обектно-ориентирано Ruby

17 октомври 2012

Днес

Ruby и ООП

Класове

прост пример

Дефинират се с class. Методите, дефинирани в тялото на класа, стават методи на инстанциите му. Инстанцират се се с ИмеНаКласа.new.

class Bacon
  def chunky?
    'yes, of course!'
  end
end

bacon = Bacon.new
bacon.chunky?      # "yes, of course!"

Класове

полета

Полетата (още: instance variables) имат представка @.

class Vector
  def initialize(x, y)
    @x = x
    @y = y
  end

  def length
    (@x * @x + @y * @y) ** 0.5
  end
end

vector = Vector.new 2.0, 3.0
vector.length()     # 3.605551275463989
vector.length       # 3.605551275463989

Класове

полета (2)

По подразбиране имат стойност nil.

class Person
  def heh
    @something
  end
end

person = Person.new
person.heh      # nil

Класове

викане на методи

В метод на може да извикате друг със self.име_на_метод или просто име_на_метод:

class Person
  def initialize(name) @name = name                end
  def say_hi()         puts "My name is #{@name}!" end
  def sound_smart()    puts "1101000 1101001"      end

  def talk
    self.say_hi
    sound_smart
  end
end

mel = Person.new 'Mel'
mel.talk

Такова подравняване на методи е гадно, но пък се събира в слайд

Класове

self

В методите на класа, self е референция към обекта, на който е извикан методът. Като this в Java.

class Person
  def me
    self
  end
end

person = Person.new
person           # #<Person:0x9db4008>
person.me        # #<Person:0x9db4008>
person.me.me     # #<Person:0x9db4008>

Атрибути

Полетата не са публично достъпни. Може да ги достигнете само чрез метод.

class Person
  def initialize(age)
    @age = age
  end

  def age
    @age
  end

  def set_age(age)
    @age = age
  end
end

person = Person.new(33)
person.age          # 33
person.set_age 20
person.age          # 20

Атрибути

setter-и

Разбира се, set_age е гадно име на метод. Може и по-добре:

class Person
  def age
    @age
  end

  def age=(value)
    @age = value
  end
end

person = Person.new
person.age = 33  # Същото като person.age=(33)

person.age       # 33

Атрибути

attr_accessor

Последното е досадно за писане. Затова:

class Person
  attr_accessor :age
end

person = Person.new
person.age = 33

person.age # 33

Атрибути

другите макроси

Ако ви трябва само getter или setter, може така:

class Person
  attr_reader :name
  attr_writer :grade
  attr_accessor :age, :height
end

Атрибути

какво е attr_accessor?

attr_accessor е метод, който генерира два метода — #foo и #foo=. Достъпен е в дефинициите на класове. Неформален термин за такива методи е "class macro".

Има ги в изобилие.

Атрибути

Meyer's Uniform Access Principle

Обърнете внимание, че следните два реда правят едно и също:

person.age()
person.age

Няма разлика между достъпване на атрибут и извикване на метод, който го изчислява. Това се нарича Uniform Access Principle и като цяло е хубаво нещо.

Тялото на класа

където става странно

Тялото на класа е напълно изпълним код:

class Something
  a = 1
  b = 2
  a + b # 3
end

Тялото на класа (2)

Понякога дори е полезно:

class Object
  if RUBY_VERSION <= '1.8.6'
    def tap
      yield self
      self
    end
  end
end

Конвенции

В Ruby важат следните конвенции.

"Отваряне" на класове

Във всеки момент може да "отворите" клас и да му добавите методи.

class Person
  def name
    'River'
  end
end

class Person
  def say_hi
    "Hi, I am #{name}."
  end
end

Person.new.say_hi # "Hi, I am River."
Person.new.name   # "River"

Повторно дефиниране на метод

Ако дефинирате един метод два пъти, втората дефиниция измества първата.

class Something
  def name
    'Tom Baker'
  end

  def name
    'Colin Baker'
  end
end

Something.new.name # => 'Colin Baker'

alias

Въпреки името си, alias прави копие на метод.

class Array
  alias old_inject inject

  def inject(*args, &block)
    puts "I see you are using #inject. Let me help!"
    old_inject(*args, &block) * 0.01
  end
end

[1, 2, 3, 4, 5, 6].inject { |a, b| a + b } # 0.21

Object#methods

Ако викнете #methods на нещо, ще получите масив от символи с имената на методите му.

Помните ли Array#-?

class Person
  def foo() end
  def bar() end
end

Person.new.methods - Object.new.methods # [:foo, :bar]

Предефиниране на оператори

Много интуитивно.

class Vector
  attr_accessor :x, :y

  def initialize(x, y)
    @x, @y = x, y
  end

  def +(other)
    Vector.new(x + other.x, y + other.y)
  end

  def inspect
    "Vector.new(#@x, #@y)"
  end
end

Vector.new(1, 5) + Vector.new(3, 10) # Vector.new(4, 15)

private

class Person
  def say_hi
    "Hello! I am #{name}"
  end

  private

  def name
    'the Doctor'
  end
end

person = Person.new
person.say_hi     # "Hello! I am the Doctor"
person.name       # error: NoMethodError

private (2)

Ако един метод е private, не може да го викате с явен получател. Дори със self.

class Person
  def say_hi
    "Hello! I am #{self.name}"
  end

  private

  def name
    'the Doctor'
  end
end

person = Person.new
person.say_hi     # error: NoMethodError

Решения на първа задача

Ще разгледаме:

Integer#prime_divisors

class Integer
  def prime_divisors
    2.upto(abs).select { |n| remainder(n).zero? and n.prime? }
  end

  def prime?
    2.upto(pred).all? { |n| remainder(n).nonzero? }
  end
end

Range#fizzbuzz

class Range
  def fizzbuzz
    map do |n|
      if    n % 15 == 0 then :fizzbuzz
      elsif n % 3  == 0 then :fizz
      elsif n % 5  == 0 then :buzz
      else                   n
      end
    end
  end
end

Hash#group_values

class Hash
  def group_values
    each_with_object({}) do |(key, value), result|
      result[value] ||= []
      result[value] << key
    end
  end
end

Array#densities

class Array
  def densities
    map { |item| count item }
  end
end

#1 Глупави имена

...двете трудни неща в компютърните науки

#2 Неспазване на конвенции

...където не ни взехте насериозно

#3 Whitespace

...където дори не знам как да започна

#4 Литерали

class Range
  def fizzbuzz
    fizzbuzzed = Array.new
    # ...
  end
end

#5 Не използвате map и select

Редовно виждаме:

output = []
input.each do |item|
  output << do_something_with(item)
end
output

#6 Цикли

while и until са зли

current = 2
divisors = []
while current <= self.abs / 2
  divisors << current if self.abs % current == 0 and prime? current
  current += 1
end
divisors << self.abs if divisors.empty?
divisors

#7 Сложен/умен код

def fizzbuzz
  to_a.tap do |result|
    each_with_index.select { |n, i| (n % 3).zero? or (n % 5).zero? }.map do |n, i|
      result[i] = :"#{ "fizz" if (n % 3).zero? }#{ "buzz" if (n % 5).zero? }"
    end
  end
end

#7 Сложен/умен код

def fizzbuzz
  map do |n|
    if    n % 15 == 0 then :fizzbuzz
    elsif n % 5 == 0  then :fizz
    elsif n % 3 == 0  then :buzz
    else                   n
    end
  end
end

#8 Твърде много код

Това е твърде много код за Ruby. Ако вашия изглежда така, правите нещо грешно. Отделно, имената пак са гадни.

def prime_divisors
  div = []
  num = self
  num = num < 0 ? num * (-1) : num
  2.upto(num) do |cur|
    isprime = true
    2.upto(cur - 1) do |check|
      if (cur % check) == 0
        isprime = false
        break
      end
    end
    if (num % cur) == 0 and isprime
      div << cur
    end
    isprime = true
  end
  div
end

#9 Много гъст код

(n % 3 == 0) ? ((n % 5 == 0) ? :fizzbuzz : :fizz) : ((n % 5 == 0) ? :buzz : n)

#9 Много гъст код

each_with_object({}) do |(key, val), memo|
  memo[val] ? memo[val] << key : memo[val] = [key]
end

Въпроси