05. Класове и наследяване. Синоними на методи. protected

05. Класове и наследяване. Синоними на методи. protected

05. Класове и наследяване. Синоними на методи. protected

26 октомври 2015

Днес

Въпрос 1

Кой (и как) може да вика private методи?

class Something
  private

  def foo
  end
end
  • Могат да се викат от други методи на обекта
  • Могат да се викат от методи в наследници
  • Не могат да се викат с явен target (self.foo)

Въпрос 2

Какви са конвенциите за имена на методи, променливи, константи и имена на класове?

  • UpperCamelCase - константи (включва имена на класове)
  • normal_snake_case - променливи, методи
  • SCREAMING_SNAKE_CASE - константи, които не са имена на класове или модули

Въпрос 3

На какво могат да завършват методите в Ruby?

  • На ?, ако са предикати
  • На !, ако имат две версии
  • На =, ако са setter-и
  • Технически погледнато, и на някои "оператори", напр. %

Въпрос 4

Как можем да направим наш клас-колекция, която да има всички методи на Enumerable?

В класа трябва:

  • Да имаме include Enumerable.
  • Да дефинираме метод each.

Въпрос 5

Избройте поне две разлики в поведението на lambda и Proc.new.

  • Ламбдите са стриктни по отношение на броя подадени аргументи, Proc.new обектите не са
  • return в тяло на lambda прекратява работата на lambda-та, а в Proc.new – на обгръщащата функция
  • Проковете правят автоматично разпадане на аргументи, подадени като списък, подобно на блокове, ламбдите – не

Enumerable (преговор)

#all? и #any?

#all?/#any? връщат истина, ако всички/един елемент(и) от колекцията отговарят на някакво условие.

[1, 2, 3, 4].all? { |x| x.even? } # => false
[1, 2, 3, 4].any? { |x| x.even? } # => true

[2, 4, 6, 8].all? { |x| x.even? } # => true
[2, 4, 6, 8].any? { |x| x.odd? }  # => false

# И разбира се:
[1, 2, 3, 4].any?(&:even?)        # => true

#all? и #any? без блок

#all? и #any? могат да работят и без блок:

[1, 2, 3, nil].all?     # => false
[1, 2, 3, :nil].all?    # => true
[1, 2, 3, false].any?   # => true

#one? и #none?

Аналогични на #all? и #any?. Също могат да работят без блок.

%w(foo bar larodi).one? { |word| word.length == 6 }  # => true
%w(foo bar larodi).one? { |word| word.length == 3 }  # => false

[1, 5, 3].none? { |number| number.even? }   # => true
[1, 2, 3].none? { |number| number.even? }   # => false

[1, 2, 3].one?     # => false
[1, nil, nil].one? # => true

#none? в действие

class Integer
  def prime?
    return false if self == 1
    2.upto(self ** 0.5).none? { |n| self % n == 0 }
  end
end

7.prime? # => true
8.prime? # => false

#each_with_index

#each_with_index yield-ва всеки елемент с индекса му в масива:

%w[foo bar baz].each_with_index do |word, index|
  puts "#{index}. #{word}"
end

Горното извежда:

0. foo
1. bar
2. baz 

#map with index?

Няма #map_with_index.

Затова пък има нещо по-общо:

%w[foo bar baz].map.with_index do |word, index|
  [index, word]
end

# => [[0, "foo"], [1, "bar"], [2, "baz"]]

#group_by

Името казва всичко, което ви е нужно да знаете.

words  = %w(foo bar plugh larodi)
groups = words.group_by { |word| word.length }

groups # => {3=>["foo", "bar"], 5=>["plugh"], 6=>["larodi"]}

#each_slice

#each_slice(n) yield-ва елементите на части по n:

%w(a b c d e f g h).each_slice(3) do |slice|
  p slice
end

Извежда

["a", "b", "c"]
["d", "e", "f"]
["g", "h"] 

#each_cons

#each_cons(n) yield "подмасиви" с n елемента:

[1, 2, 3, 4, 5].each_cons(3) do |cons|
  p cons
end

Извежда

[1, 2, 3]
[2, 3, 4]
[3, 4, 5] 

#include? и #member?

Вече знаете какво прави:

[1, 2, 3, 4].include? 3   # => true
[1, 2, 3, 4].member? 5    # => false

Двете са синоними.

To infinity and beyond!

лирическо отклонение

(0...1.0 / 0.0).include? -1 # => false
1 / 0     # => error: ZeroDivisionError
1.0 / 0.0 # => Infinity

#zip

[1, 2, 3].zip([4, 5, 6])    # => [[1, 4], [2, 5], [3, 6]]
[1, 2].zip([3, 4], [5, 6])  # => [[1, 3, 5], [2, 4, 6]]

#take, #drop, #take_while и #drop_while

[1, 2, 3, 4, 5].take(2)  # => [1, 2]
[1, 2, 3, 4, 5].drop(2)  # => [3, 4, 5]

[1, 3, 5, 6, 7, 9].take_while(&:odd?)  # => [1, 3, 5]
[1, 3, 5, 6, 7, 9].drop_while(&:odd?) # => [6, 7, 9]

Как генерирахме таблицата с методите?

all?        any?          chunk       collect          collect_concat
count       cycle         detect      drop             drop_while
each_cons   each_entry    each_slice  each_with_index  each_with_object
entries     find          find_all    find_index       first
flat_map    grep          group_by    include?         inject
lazy        map           max         max_by           member?
min         min_by        minmax      minmax_by        none?
one?        partition     reduce      reject           reverse_each
select      slice_before  sort        sort_by          take
take_while  to_a          zip 

Как генерирахме таблицата с методите?

кодът

Enumerable.instance_methods.
  sort.
  map { |name| name.to_s.ljust(16) }.
  each_slice(5) { |row| puts row.join '' }

Disclaimer: Леко е редактиран whitespace-а, за да се събере в слайд.

Въпроси по Enumerable?

След това ще продължим с наследяване.

Наследяване

Наследяването в Ruby става така:

class Person
  def name() 'The Doctor' end
end

class PolitePerson < Person
  def introduction
    "Hi, I am #{name}"
  end
end

PolitePerson.new.introduction # => "Hi, I am The Doctor"

Наследяване

ограничения

private методи

Имате достъп до private методите:

class Person
  private
  def name() 'The Doctor' end
end

class PolitePerson < Person
  def introduction() "Hi, I am #{name}" end
end

PolitePerson.new.introduction # => "Hi, I am The Doctor"

Наследяване

#is_a? и #instance_of?

class Base; end
class SpaceStation < Base; end

base    = Base.new
station = SpaceStation.new

base.is_a? Base            # => true
station.is_a? SpaceStation # => true
station.is_a? Base         # => true

base.instance_of? Base            # => true
station.instance_of? SpaceStation # => true
station.instance_of? Base         # => false

Наследяване

#is_a? и #instance_of? (2)

#is_a? има синоним #kind_of?

class Base; end
class SpaceStation < Base; end

base    = Base.new
station = SpaceStation.new

base.kind_of? Base            # => true
station.kind_of? SpaceStation # => true
station.kind_of? Base         # => true

Наследяване

#is_a? и #instance_of? (3)

super

Може да предефинирате метод и да извикате версията на родителя със super.

class Person
  def introduction_to(other)
    "Hello #{other}."
  end
end

class PolitePerson < Person
  def introduction_to(other)
    super("Mr. #{other}") + " How do you do?"
  end
end

queen = PolitePerson.new
queen.introduction_to('Smith') # => "Hello Mr. Smith. How do you do?"

super (2)

Ако извикате super без скоби, родителският метод получава същите аргументи.

class Person
  def introduction_to(other)
    "Hello #{other}."
  end
end

class PolitePerson < Person
  def introduction_to(other)
    super + " How do you do?"
  end
end

queen = PolitePerson.new
queen.introduction_to('Smith') # => "Hello Smith. How do you do?"

super (3)

super и super() са различни:

class Person
  def introduction_to(other)
    "Hello #{other}."
  end
end

class PolitePerson < Person
  def introduction_to(other)
    super() + " How do you do?"
  end
end

queen = PolitePerson.new
queen.introduction_to('Smith') # => error: ArgumentError

Ancestor chain

Ancestor chain (2)

module Foo; end
module Bar; end
module Qux; end

class Base
  include Foo
end

class Derived < Base
  include Bar
  include Qux
end

Derived.ancestors # => [Derived, Qux, Bar, Base, Foo, Object, Kernel, BasicObject]

Ancestor chain (3)

модули, миксирани в други модули

module Foo; end
module Bar; end

module Qux
  include Foo
  include Bar
end

class Thing
  include Qux
end

Thing.ancestors # => [Thing, Qux, Bar, Foo, Object, Kernel, BasicObject]

Ancestor chain (4)

Има само една версия на метода:

module Talking
  def greeting() "Hello, #{name}" end
end

class Base
  include Talking
  def name()        'Base'   end
  def say_hi_base() greeting end
end

class Derived < Base
  include Talking
  def name()           'Derived' end
  def say_hi_derived() greeting  end
end

derived = Derived.new
derived.say_hi_base    # => "Hello, Derived"
derived.say_hi_derived # => "Hello, Derived"
derived.ancestors      # => # ~> -:20:in `<main>': undefined method `ancestors' for #<Derived:0x002b152b3da2f0> (NoMethodError)

Синоними на методи в Ruby

като говорим за код в тялото на методи...

Синтаксис за синоними на методи

Семантика на синоними на методи

Пример с 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

Синоними на методи

Пример за реална употреба:

def to_s
  to_html
end

По-добре да се запише така:

alias_method :to_s, :to_html

Или така:

alias to_s to_html

Разлики между alias и alias_method

Разлики между alias и alias_method

създаване на синоними по време на изпълнение

class Array
  [:size, :count, :length].each do |method_name|
    alias_method "old_#{method_name}", :size
  end

  def size
    0
  end
end

[1, 2, 3].size     # => 0
[1, 2, 3].old_size # => 3

Забележки относно синоними на методи

methodfinder

$ gem install methodfinder

Интересен gem:

Основните класове в Ruby

"Всичко наследява от Object"

Основните класове в Ruby

protected

Само обекти от същия клас могат да викат protected методи

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

  def +(other)
    Vector.new(*coords.zip(other.coords).map { |a, b| a + b })
  end

  protected
  def coords() [@x, @y] end
end

vector = Vector.new(1, 2) + Vector.new(3, 4)
vector        # => Vector.new(4, 6)
vector.coords # => error: NoMethodError

private и protected

още известни като Private, Public и General Specific

Клас-макросите private и protected

Понеже private и protected са методи:

class Person
  def name() end
  def age()  end

  private :name, :age
end

Или дори:

class String
  private :upcase!, :downcase!
end

"Foo".upcase! # => error: NoMethodError

Клас-макросите private и protected (2)

Помните ли, че дефиницията на метод връща името на метод като символ?

def foo() end # :foo

Значи може да направите така:

class Person
  private def name
    "My name is a secret."
  end

  private def age
    "I'm feeling feminine, so my age is a secret, too."
  end
end

Въпроси