Папярэдні занятак

Стварэньне мэтадаў аб’ектаў

Зараз, калі мы ўжо ведаем пра класы, мы можам стварыць яшчэ больш цікавы мэтадаў. Дагэтуль мы стваралі толькі глабальныя мэтады, але большасьці мэтадаў, якімі мы карысталіся былі мэтадамі аб’ектаў (акрамя puts і rand). Гэтыя мэтады завуцца мэтадамі аб’ектаў таму што яны выкліклікаюцца ў асобніка класа.

Вы ўжо рабілі так, але нагадваю:

  • Float.superclass
  • Integer.superclass

Уле лікі належаць класу Numeric.

Ці памятаеце Вы як мы зрабілі глабальны мэтад add? Давайце зробім тое ж самае толькі для аб’екта.

  • class Numeric
  • def add(num)
  • self + num
  • end
  • end
  • 5.add(3)

self – гэта спасылка на аб’ект, у якога выклікаецца мэтад.

Зараз стварыце мэтады subtract, multiply і divide ў класе Numeric.

Рэкурсія

Перад тым як мы пачнем пісаць свае ўласныя мэтады аб’ектаў, давайце разглядзім што такое рэкурсія. Рэкурсія, гэта калі мэтад выклікае сам сабе. Клячычны прыклад – гэта падлік фактарыялаў, які мы рабілі раней, карыстаючыся дыяпазонамі. Давайце яшчэ раз падлічым фактарыял карыстаючыся рэкурсіяй.

Алгарытм будзе выглядаць вось так:

  • Калі лік роўны 0, вяртаем 1.
  • Інакш:
    • бярэм лік;
    • памнажаем яго на фактарыял ад яго самаго мінус 1.

Вось мой код для мэтада падліка фактарыяла:

class Fixnum
  def factorial
    if self < 0
      "invalid"
    elsif self == 0
      1
    else
      self * (self - 1).factorial
    end
  end
end

Пасьлядоўнасьць Фібаначчы

Окей, час кодзіць :)

Вось пачатак пасьлядоўнасьці Фібаначчы: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …

Пасьлядоўнасьць Фібаначчы пачынаецца з 0 і 1 (па вызначэньню). Кожны наступны лік – сума двух папярэдніх.

Стварыце мэтад fibonacci у класе Integer, які вяртае значэньне пасьлядоўнасьці Фібаначчы, адпавядаючае значэньню цэлага, у якога ён быў выкліканы. Напрыклад, 8.fibonacci мусіць вяртаць 21 (памятайце, што першы індэкс пасьлядоўнасьці – 0). Рэалізуйце гэтый мэтад з дапамогай рэкурсіі.

Падумайце чаму будзе патрэбна шмат часу каб падлічыць 100.fibonacci. Калі Вы паспрабуеце зрабіць гэта – спатрабуецца насамрэч шмат часу каб падлічыць 100.fibonacci; таму, каб спыніць выкананьне праграммы можна ціснуць Ctrl + C.

Памятайце што Вы павінны пісаць юніт-тэсты.

Эфэктыўнасьць Фібаначчы

З мінулага практыкаваньня Вы зразумелі, што падлік пасьлядоўнасьці Фібаначчы выкарыстоўваючы рэкурсію неэфэктыўны. Вось што адбываецца калі Вы выконваеце код 5.fibonacci:

  • Вы выконваеце 5.fibonacci, які выконвае:
    • 4.fibonacci, які выконвае:
      • 3.fibonacci, які выконвае:
        • 2.fibonacci, які выконвае:
          • 1.fibonacci
          • 0.fibonacci
        • 1.fibonacci
      • 2.fibonacci, які выконвае:
        • 1.fibonacci
        • 0.fibonacci
    • 3.fibonacci, які выконвае:
      • 2.fibonacci, які выконвае:
        • 1.fibonacci
        • 0.fibonacci
      • 1.fibonacci

У той час, як рэкурсіўны код вельмі элегантны, прадукцыйнасьць жудасная. На кожнай стадыі падліку колькасьць выкананых мэтадаў падвойваецца, і кожны раз, калі выклікаецца мэтад, яго копія загружаецца ў памяць. Таму 100.fibonacci загрузіць мэтад fibonacci у памяць каля 2^100 разоў. Гэта завецца “складанасьць 2^n”, таму што яна падвойваецца кожны раз.

Больш эфэктыўны спосаб падлічваць Фібаначчы гэта з дапамогай дыяразонаў. Гэтак жа, як мы першапачаткова падлічвалі фактарыял.

Вам спатрэбіцца мэтад each у дыяпазонаў:

size = 5
(1..size).each do |counter|
  # do something here
end

Адрэфактарыце Ваш код падліку пасьлядоўнасьці Фібаначчы, каб ён выкарыстоўваў такі падыход.

Лікі словамі

Стварыце мэтад in_words для класа Fixnum. Калі Вы выклікаеце in_words на цэлам меншы чым 1000, ён мусіць вяртаць лік напісана словамі.

Выкарыстоўвайце хэшы.

Пачніце з юніт тэстаў для лікаў менш 10, потым для лікаў менш 100, і ў рэшце, для лікаў менш 1000.

Гэта ня будзе складана :)

Замест таго, каб рабіць рэфактарынг, пасьля таго, як Вы скончыце кодзіць, рабіце рэфактарынг адруз пасьля таго, як скончыце пісаць код для асобнага тэста. Таму Ваш цыкл мусіць выглядаць test-code-refactor, test-code-refactor.

Лікі словамі, рэкурсіўна

Давайце дапрацуем ‘Лікі словамі’ й зробім каб ён працаваў для лікаў менш за адзін трыльён (10^12). Выкарыстоўвайце рэкурсію.

Test-code-refactor, test-code-refactor.

Дзьве гадзіны на гэта заданьне – гэта выдатны вынік! Гугліце, і самае галоўнае – разбівайце ў галаве заданьне на часткі! Вам у дапамогу Вікіпедыя, Дакумэнтацыя рубі й г.д.

Стварэньне класа

Вы ўжо так шмат чаго ведаеце! Вы ведаеце як працаваць з некаторымі класамі, выклікаць іх мэтады, працаваць з цыкламі, разгаліноўвацца, і нават ствараць глабальныя й аб’ектныя мэтады. Мы ўрэшце рэшт гатовы каб спазнаць самае сэрца праграмаваньня: стварэньне сваіх уласных класаў.

Давайце стварым клас Rectangle:

  • class Rectangle
  • end

Маючы толькі дзьве лініі коду Вы ўжо можаце стварыць новы аб’ект. Выклікайце Rectangle.new і атрымайце прастакутнік.

Мэтад new належыць класу BasicObject. Памятайце, калі мы праглядалі продкаў класаў, мы бачылі што кожны клас па-дэфолту ўспадкоўваецца ад класа Object, як, у сваю чаргу, ад класа BasicObject. Вы можаце бачыць тое ж самае й для Rectangle. Надрукуйце Rectangle.ancestors. Таму кожны новы клас аўтаматычна мае мэтад new.

Прастакутнік бессэнсоўны калі ён ня мае даўжыні ды шырыні. Выправім гэта.

  • class Rectangle
  • def initialize(length, width)
  • end
  • end

Зараз мы можам атрымаць прастакутнік 3×5 выклікаючы Rectangle.new(3, 5).

Калі Вы выклікаеце мэтад new, ён потым выклікае аб’ектны мэтад initialize у аб’екта, які быў толькі што створаны. BasicObject мае мэтад initialize, таму, калі Вы ня вызначыце свой уласны initialize, мэтад з BasicObject будзе выкліканы. У гэтым кодзе мы перапісваем мэтад initialize сваім уласным, які прымае два аргумэнта

Але ніколі ня трэба выкліка мэтад initialize прама.

Стварыце клас Square.

Стварыце клас Triangle з мэтадам initialize які прымае тры аргумэнта – бакі трыкутніка.

Зьменныя аб’ектаў

Вось як выгладае наш клас Rectangle:

class Rectangle
  def initialize(length, width)
  end
end

Давайце зараз адчынім Rectangle і створым мэтад, які вяртае перыметр прастакутніка:

class Rectangle
  def initialize(length, width)
  end

  def perimeter
    2 * (width + length)
  end
end

Нічога не атрымаліваецца… Калі мы спрабуем выканаць Rectangle.new(5,3).perimeter, мы атрымліваем памылку NameError: undefined local variable or method `width' for #<Rectangle:0x007fe08227caa8>.

Такія зьменныя як length і width у нашым мэтадзе initialize завуцца лакальнымі зьменнымі. Лакальныя зьменныя жывуць да таго моманту, пакуль ня скончыць выкананьне мэтад, дзе яны былі створаны, і яны даступны толькі ў гэтым мэтадзе. Так як яны былі вызначаны як аргумэнты ў мэтадзе initialize, яны не даступны ў мэтадзе perimeter.

Існуе іншы тым зьменных які завецца зьменныя асобнікаў. Ён выгладае так:

class Rectangle
  def initialize(length, width)
    @length = length
    @width = width
  end

  def perimeter
    2 * (@width + @length)
  end
end

Зьменныя асобнікаў(аб’ектаў) пачынаюцца з @ і існуюць пакуль існуе аб’ект (памятайце, што аб’ект – гэта асобнік класа, таму й назва зьменныя асобніка). Яны даступны ў кожным мэтадзе, які ёсьць у аб’екта. Звычайна зьменныя аб’ектаў выкарыстоўваюцца калі патрэбна захоўваць нейкі стан аб’екта. Каб гэтая зьменная была даступна ва ўсіх мэтадах.

Таму зараз, калі мы карыстаемся зьменным асобніка мы можам выканаць Rectangle.new(5,3).perimeter пасьпяхова.

my_rectangle = Rectangle.new(3, 5)
puts my_rectangle.perimeter

Зараз стварыце мэтад, які вяртае перымтр для класа Square. Таксама й для Triangle.

Чытачы

Давайце створым прастакутнік зноў:

class Rectangle
  def initialize(length, width)
    @length = length
    @width = width
  end
end
my_rectangle = Rectangle.new(4,5)

А што калі на патрэбна даведацца пра даўжыню прастакутніка ўжо потым, пасьля таго, як мы яго стварылі? Як Вы гэта зробіце?

Не атрымліваецца? Давайце створым мэтад length каб зрабіць гэта:

class Rectangle
  def length
    @length
  end
end

Калі мы паклічам my_rectangle.length, мы атрымаем даўжыню прастакутніка.

Стварыце мэтад width, які вяртае шырыню.

Тое ж самае зрабіце й для Square і Triangle.

“Пісачы”

Мы можам чытаць зьменныя з прастакутніка, а што калі мы хочам іх зьмяніць?

class Rectangle
  def length=(new_length)
    @length = new_length
  end
end

Зараз мы можам выклікаць my_rectangle.length=(7) каб зьмяніць даўжыню.

Таксама можна выкарыстоўваць сінтаксічны цукар – my_rectangle.length = 7 каб гэта выглядала нібыта Вы зьмяняеце зьменную. Гэта традыцыйны сінтаксіс Ruby.

Вось як выгладае Rectangle зараз:

class Rectangle
  def initialize(length, width)
    @length = length
    @width = width
  end

  def length
    @length
  end

  def length=(new_length)
    @length = new_length
  end
end

Зрабіце тое ж самае для шырыні й для класаў Square і Triangle.

Accessors

Ці ня выгладае гэта трошкі дзіўна што мы павінны пісаць мэтады каб зьвярнуцца да зьменных? Ёсьць больш просты спосаб.

Давайце пачнем з таго, што зробім зьменную даступнай:

class Rectangle
  attr_reader :length

  def initialize(length, width)
    @length = length
    @width = width
  end
end

Гэта штука :length завецца сымбал (symbol). Сымбалы, гэта вельмі простыя аб’екты, якія выкарыстоўваюцца, каб перадаваць дадзеныя. У гэтым прыкладзе мэтад attr_reader карыстаецца сымбалам каб ведаць якую зьменную зрабіць даступнай.

Зараз стварыце прастакутнік і выклікайце мэтад length каб даведацца пра шырыню й даўжыню

Калі Вы меркавалі што існуе мэтад attr_writer Вы здагадаліся:

class Rectangle
  attr_reader :length
  attr_writer :length

  def initialize(length, width)
    @length = length
    @width = width
  end
end

Стварыце прастакутнік і зьмяніце яго даўжыню. Працуе!

І зараз усё гэта разам:

class Rectangle
  attr_accessor :length

  def initialize(length, width)
    @length = length
    @width = width
  end
end

Паспрабуйце мэтад attr_accessor для width.

Для класаў Square і Triangle зрабіце даўжыню бакоў даступнай.

attr_reader гэта тое што завецца getter або reader (чытач); attr_writer – гэта setter або writer (пісач) ; attr_accessor – гэта accessor.

Наступны занятак