Reputation: 622
I keep trying to reason upon the functionality of a method within a subclass that inherits functionality of a parents class. But it seems that I keep getting into a mental loop of: one cannot behave without the other but the other cannot come before the one... My brain hurts...
Ok heres my relevant code in the parent class
class BankAccount
# method to initialize and other methods etc...
def withdraw(amount)
if (amount <= @balance)
@balance -= amount
else
'Insufficient funds'
end
end
end
And heres my relevant code in the subclass
class CheckingAccount < BankAccount
# methods to initialize and other methods etc...
def withdraw
super
end
end
According to the tutorial im learning from - what I am trying to accomplish is
So if I create a variable number_of_withdrawals
inside of my BankAccount
class (as tutorial examples hint towards) then how is it that when I call super
from the subclass version of withdraw
that it would know to increment number_of_withdrawals
based on the if
else
statement executing a withdraw or not.
Shouldn't a variable number_of_withdrawals
be declared in the BankAccount
class, not the CheckingAccount
class (even though tutorial examples hint towards putting it in the CheckingAccount
class). For a full picture of this here is a gist with the test specs() below of my current code state:
If someone can provide a working example of
With modified code I have provided in the GIST - I would really appreciate it. Im very new to ruby.(1 week)
Upvotes: 1
Views: 119
Reputation: 26768
With inheritance, the parent can be thought of as a 'template' for the child. That is to say, you can instead of using a parent at all simply write everything into the child class (not that you should). The point is that everything from the parent class can be thought of as getting copied on to the child class, so if you make an instance variable on the parent and change it from the child, there is only one instance variable defined since there is only one object instantiated. In other words, when you say CheckingAccount.new
there is no separate BankAccount
getting instantiated - it's all the same object.
So, for every method that is defined in both the parent and child, you need to call super
or else the parent method won't be called. Here's an example with your code:
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
I just skimmed the requirement document, so make sure to double check (e.g. don't just take my code and hand it is as homework :D)
Upvotes: 1
Reputation: 211590
Your method does way too much and internalizes too many assumptions. A better approach to this is to break things up a little:
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
Then you can make the subclass specialize very specific methods instead of having to lean on super
so hard:
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
Upvotes: 2
Reputation: 10349
The behavior you're seeing -- or expected to provide in order to complete the exercise -- is due to Ruby's dynamic nature. Since your program is "interpreted" as it's run (and is subject to change), there's no way for Ruby to know that the instance variable in question won't exist until the method is actually executed.
Here's a contrived example which (hopefully) demonstrates why you're seeing/hoping to see this behavior:
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!
You should have a look at this question and its answers for a more detailed discussion about the distinction between static/dynamic languages and the early/late binding of values.
Upvotes: 1
Reputation: 944
With the code as it's currently written, CheckingAccount#withdraw
could check the return value of super
to determine whether the withdrawal was successful or not.
For example:
def withdraw(n)
result = super
if result != 'Insufficient funds'
@number_of_withdrawals += 1
end
result
end
Upvotes: 2