06. Класове, именовани пространства, наследяване. Синоними на методи. Класови методи

06. Класове, именовани пространства, наследяване. Синоними на методи. Класови методи

06. Класове, именовани пространства, наследяване. Синоними на методи. Класови методи

27 октомври 2014

Днес

Втора задача

Гореща тема:

Днес изтече крайният срок за второто домашно в изборен курс, който се води във Факултета по математика и информатика към Софийкия университет.

Студент от курса се оплака, че втората задача е била неочаквано по-трудна от първата. Негови колеги изразиха недоволство от проблемите със системата за ограничения, въведена от преподавателите в курса.

Преди редакционното приключване на новинарската емисия, не успяхме да се свържем с представител на преподавателския екип за коментар.

We're sorry, but something went wrong

Втора задача

Трето предизвикателство

OpenFest

"Да споделим свободата!"

Въпрос 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.

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

#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 

#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"

Синоними на методи в 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

Именувани пространства

Класовете и модулите могат да служат като именувани пространства.

module Useless
  class Thing
  end
end

class Grandfather
  class StraightRazor
  end
end

Useless::Thing.new             # #<Useless::Thing:0x40f67f30>
Grandfather::StraightRazor.new # #<Grandfather::StraightRazor:0x40f67da0>

Именувани пространства (2)

Ако се намирате в модул, няма нужда да ползвате пълния път до константите:

module Useless
  class Thing
  end

  Thing.new         # #<Useless::Thing:0x40f9f278>
end

Useless::Thing.new  # #<Useless::Thing:0x40f9f0c0>
Thing.new           # error: NameError

Търсене на променливи в Ruby

bacon = 2

def foo
  chunky = 10

  1.times do
    chunky       # 10
    chunky = 44
  end

  chunky         # 44
  bacon          # error: NameError
end

foo()

Правила за търсене на константи

Малък пример

PLACE = 'root'
module Outer
  PLACE = 'intermediate'
  module Inner
    PLACE = 'deep'
  end
end

PLACE               # "root"
Outer::Inner::PLACE # "deep"
module Outer
  module Inner
    PLACE           # "deep"
    ::PLACE         # "root"
  end
  PLACE             # "intermediate"
  Inner::PLACE      # "deep"
end

Класови методи

Може да дефинирате класови методи така:

class Something
  def Something.answer
    42
  end
end

Something.answer   # 42

Класови методи (2)

Не може да ги викате неквалифицирано от инстанцията:

class Something
  def Something.answer
    42
  end

  def do_stuff
    answer             # error: NameError
    Something.answer   # 42
  end
end

thing = Something.new
thing.answer           # error: NoMethodError
Something.answer       # 42

thing.do_stuff

Класови методи (3)

Достъпни са в наследниците:

class Base
  def Base.answer() 42 end
end

class Derived < Base
  def Derived.say_answer
    answer         # 42
    Base.answer    # 42
  end
end

Derived.answer     # 42
Base.answer        # 42

Derived.say_answer

Класови методи (4)

Има и други начини за дефиниция на класови методи:

Класови методи (5)

Лоши примери 1

Лошо:

def each(&block)
  @numbers.each do |number|
    if block_given?
      block.call number
    else
      yield number
    end
  end
end

Добро:

def each(&block)
  @numbers.each &block
end

Лоши примери 2

Лошо:

def empty?
  size == 0 ? true : false
end

def non_positive?
  return true if self <= 0
  false
end

Добро:

def empty?
  size == 0
end

def non_positive?
  self <= 0
end

Лоши примери 3

Лошо:

number.class.to_s == 'Complex'

Добро 1:

number.class == Complex

Добро 2:

number.is_a? Complex

Лоши примери 4

Лошо:

def size
  (map { |item| item = 1 }).reduce { |sum, number| sum + number }
end

Добро:

def size
  @numbers.size
end

Лоши примери 5

Лошо:

def << (number)
      i = 0
      while i < @numbers.length
        if (@numbers[i] == number)
              return
        end
      i += 1
      end
  @numbers << number
end

Добро:

def <<(number)
  @numbers << number unless @numbers.include? number
end

Лоши примери 6

Лошо:

TYPES = {
  integer: [Integer],
  real: [Float, Rational],
  complex: [Complex]
}

def initialize(type)
  @block = Proc.new do |number|
    TYPES[type].reduce(false) { |memo, type| memo || number.is_a?(type) }
  end
end

Добро:

def initialize(type)
  case type
    when :integer then super() { |number| number.is_a?(Integer)  }
    when :real    then super() { |number| number.is_a?(Float) or number.is_a?(Rational) }
    when :complex then super() { |number| number.is_a?(Complex)  }
  end
end

Добро решение на задачата

class NumberSet
  include Enumerable

  def initialize(numbers = [])
    @numbers = numbers
  end

  def each(&block)
    @numbers.each(&block)
  end

  def size
    @numbers.size
  end

  def empty?
    @numbers.empty?
  end

  def <<(number)
    @numbers << number unless @numbers.include? number
  end

  def [](filter)
    NumberSet.new @numbers.select { |number| filter.matches? number }
  end
end

Добро решение на задачата

class Filter
  def initialize(&block)
    @predicate = block
  end

  def matches?(number)
    @predicate.(number)
  end

  def |(other)
    Filter.new { |number| matches?(number) || other.matches?(number) }
  end

  def &(other)
    Filter.new { |number| matches?(number) && other.matches?(number) }
  end
end

Добро решение на задачата

class TypeFilter < Filter
  def initialize(type)
    case type
    when :integer then super() { |number| number.kind_of? Integer }
    when :real    then super() { |number| number.is_a?(Float) ||
                                          number.is_a?(Rational) }
    when :complex then super() { |number| number.is_a? Complex }
    end
  end
end

class SignFilter < Filter
  def initialize(sign)
    case sign
    when :positive     then super() { |number| number > 0  }
    when :negative     then super() { |number| number < 0  }
    when :non_positive then super() { |number| number <= 0 }
    when :non_negative then super() { |number| number >= 0 }
    end
  end
end

Въпроси