Нелогично: круг логики, в рубине используется супер

Я продолжаю пытаться рассуждать о функциональности метода в подклассе, который наследует функциональность родительского класса. Но, похоже, я продолжаю зацикливаться на мысли: один не может вести себя без другого, а другой не может прийти раньше, чем один... Мой мозг болит...

Хорошо, вот мой соответствующий код в родительском классе

class BankAccount
  # method to initialize and other methods etc...

  def withdraw(amount)
    if (amount <= @balance)
      @balance -= amount
    else
      'Insufficient funds'
    end
  end

end

И вот мой соответствующий код в подклассе

class CheckingAccount < BankAccount
  # methods to initialize and other methods etc...

  def withdraw
    super
  end 

end

Согласно учебному пособию, из которого я учусь - я пытаюсь достичь

  • "Методы CheckingAccount #withdraw увеличивают число" number_of_withdrawals "на единицу после успешного снятия"

Так что, если я создам переменную number_of_withdrawals внутри моего BankAccount класс (как подсказывают учебные примеры), то как получается, что когда я звоню super из версии подкласса withdraw что он будет знать, чтобы увеличить number_of_withdrawals на основе ifelse Заявление о выполнении отзыва или нет.

Не должна переменная number_of_withdrawals быть объявленным в BankAccount класс, а не CheckingAccount класс (хотя учебные примеры намекают на то, чтобы поместить его в CheckingAccount учебный класс). Для полного представления об этом ниже приведена суть тестовых спецификаций () ниже моего текущего состояния кода:

Спецификации теста / Попытка кода

Если кто-то может предоставить рабочий пример

  • "Методы CheckingAccount #withdraw увеличивают число" number_of_withdrawals "на единицу после успешного снятия"

С измененным кодом, который я предоставил в GIST - я был бы очень признателен. Я очень новичок в рубине.(1 неделя)

4 ответа

Решение

С наследованием родитель может считаться "шаблоном" для ребенка. То есть вы можете вместо того, чтобы вообще использовать родителя, просто записать все в дочерний класс (не то, что вы должны). Дело в том, что все из родительского класса может рассматриваться как копирование в дочерний класс, поэтому, если вы создадите переменную экземпляра для родителя и измените ее из дочернего, будет определена только одна переменная экземпляра, поскольку существует только один объект создан. Другими словами, когда вы говорите CheckingAccount.new нет отдельного BankAccount получить экземпляр - это все тот же объект.

Итак, для каждого метода, определенного как в родительском, так и в дочернем методах, необходимо вызвать super иначе родительский метод не будет вызван. Вот пример с вашим кодом:

class BankAccount
  def initialize
    @balance = 0
    @number_of_withdrawals = 0
  end

  def withdraw(amount)
    if amount <= @balance
      @balance -= amount
      @number_of_withdrawals += 1
    else
      'Insufficient funds'
    end
  end
end

class CheckingAccount < BankAccount
  MAX_FREE_WITHDRAWALS = 3

  def withdraw(amount)
    if @number_of_withdrawals >= self.class::MAX_FREE_WITHDRAWALS
    amount += 5
    super(amount)
  end 

end

Я только что просмотрел документ с требованиями, поэтому не забудьте проверить его еще раз (например, не просто взять мой код и передать его как домашнее задание:D)

С кодом, как он написан в настоящее время, CheckingAccount#withdraw может проверить возвращаемое значение super определить, был ли вывод успешным или нет.

Например:

def withdraw(n)
  result = super
  if result != 'Insufficient funds'
    @number_of_withdrawals += 1
  end
  result
end

Ваш метод делает слишком много и усваивает слишком много предположений. Лучший подход к этому - немного разбить вещи:

class BankAccount
  attr_reader :balance

  def initialize
    @balance = 0
  end

  def withdraw(amount)
    if (can_withdraw?(amount))
      credit(amount)

      after_withdraw
    else
      false
    end
  end

  def can_withdraw?(amount)
    amount <= balance
  end

  def after_withdraw
    # No default behaviour
  end

  def debit(amount)
    @balance += amount
  end

  def credit(amount)
    @balance -= amount
  end
end

Тогда вы можете заставить подкласс специализироваться на очень специфических методах вместо того, чтобы опираться на super так трудно:

class CheckingAccount < BankAccount
  attr_reader :overdraft

  def initialize
    super
    @overdraft = 0
    @withdrawals = 0
  end

  def can_withdraw?(amount)
    amount <= balance + overdraft
  end

  def after_withdraw
    @withdrawals += 1
  end
end

Поведение, которое вы видите или ожидаете, чтобы выполнить упражнение, связано с динамичной природой Руби. Поскольку ваша программа "интерпретируется" во время ее выполнения (и может быть изменена), Ruby не может знать, что рассматриваемая переменная экземпляра не будет существовать до тех пор, пока метод не будет фактически выполнен.

Вот надуманный пример, который (надеюсь) демонстрирует, почему вы видите / надеетесь увидеть это поведение:

class Foo    
  def say_something_that_doesnt_exist
    # Foo is free to try to make use of @nothing, 
    # in case it's been provided by a child class' 
    # instance, but if it's not present, its value 
    # will just be nil
    puts "say_something_that_doesnt_exist, like #{@nothing}!"
  end

  def say_something_that_does_exist
    puts "say_something_that_does_exist, like #{@bar}!"
  end
end

class Bar < Foo
  attr_reader :bar

  def initialize
    super
    @bar = "bar"
  end
end

bar = Bar.new
bar.say_something_that_doesnt_exist # say_something_that_doesnt_exist, like !
bar.say_something_that_does_exist # say_something_that_does_exist, like bar!

Вам следует взглянуть на этот вопрос и его ответы для более подробного обсуждения различия между статическими / динамическими языками и ранним / поздним связыванием значений.

Другие вопросы по тегам