Last active
August 29, 2015 14:19
-
-
Save PamBWillenz/dd555fdd1eec918fb50e to your computer and use it in GitHub Desktop.
Advanced Classes Checkpoint
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSTRUCTIONS | |
Create a Apple class. It should take a single argument on initialization -- a boolean value (true or false) asserting whether or not it is ripe. Apple should define a tasty? instance method that returns "Yes" when ripe and "Not yet" when not. | |
Below it, create a Fuji class which inherits from Apple and adds two methods: | |
flavor -- Returns "Sweet" if ripe and "Tart!" if not. | |
color -- Returns "Yellowish red" if ripe and "Green" if not. | |
Your class should work like this: | |
not_ready = Fuji.new(false) | |
not_ready.tasty? | |
#=> "Not yet" | |
not_ready.color | |
#=> "Green" | |
ready = Fuji.new(true) | |
ready.flavor | |
#=> "Sweet" | |
Bonus - use attr_accessor, the inherited initialize method, and the implied self to avoid directly using any instance (@) variables in the Fuji class. | |
SPECS | |
describe Apple do | |
describe '#tasty?' do | |
it "is based on ripeness" do | |
expect( Apple.new(false).tasty? ).to eq("Not yet") | |
expect( Apple.new(true).tasty? ).to eq("Yes") | |
end | |
end | |
end | |
describe Fuji do | |
describe '#tasty?' do | |
it "inherits from Apple" do | |
expect( Fuji.new(false).tasty? ).to eq("Not yet") | |
expect( Fuji.new(true).tasty? ).to eq("Yes") | |
end | |
end | |
CODE | |
class Apple | |
attr_accessor :ripe | |
def initialize(ripe) | |
@ripe = ripe | |
end | |
def tasty? | |
if ripe | |
"Yes" | |
else | |
"Not yet" | |
end | |
end | |
end | |
p Apple.new(true) | |
class Fuji < Apple | |
def flavor | |
if ripe | |
"Sweet" | |
else | |
"Tart!" | |
end | |
end | |
def color | |
if ripe | |
"Yellowish red" | |
else | |
"Green" | |
end | |
end | |
end | |
Results | |
Apple#tasty? is based on ripeness | |
Fuji#tasty? inherits from Apple | |
Fuji#flavor is based on ripeness | |
Fuji#color is based on ripeness | |
Output | |
#<Apple:0x007fe36ee98c58 @ripe=true> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSTRUCTIONS | |
Rewrite the FrancePresident and UnitedStatesPresident classes, but instead of inheriting from a President class, have them both include the following Presidential module: | |
module Presidential | |
attr_accessor :name, :age, :citizenship | |
def initialize(name, age) | |
@name, @age, @citizenship = name, age, self.class.citizenship | |
end | |
end | |
FrancePresident and UnitedStatesPresident should behave as they did in the last exercise. | |
Note: This will involve removing the calls to super in FrancePresident, because there is no longer a parent class. Instead, you'll want to directly access the instance variables. | |
It's important to access these variables directly, rather than calling the module's getter methods. When you try to call name (the module method) from within name (the instance method), you call the more locally defined instance method instead, which in turn calls the instance method, which again calls the instance method, and on and on, in a process known as infinite recursion. | |
Read up further on when to use modules ("mixins") or classes in Ruby. Discuss uses of both with your mentor. Which do you think makes more sense for our President exercises? What are some problems with the module solution? What are some problems with the class solution? | |
SPECS | |
describe FrancePresident do | |
describe "catchphrase" do | |
it "sounds just right" do | |
expect( FrancePresident.citizenship ).to eq("La France") | |
sarcozy = FrancePresident.new("Nicolas Sarkozy", 59) | |
expect( sarcozy.citizenship ).to eq("La France, bien sur") | |
expect( sarcozy.age ).to eq("59, bien sur") | |
expect( sarcozy.name ).to eq("Nicolas Sarkozy, bien sur") | |
end | |
end | |
describe "inheritance" do | |
it "should not inherit from President" do | |
expect( FrancePresident.superclass.to_s ).not_to eq('President') | |
end | |
end | |
end | |
CODE | |
module Presidential | |
attr_accessor :name, :age, :citizenship | |
def initialize(name, age) | |
@name, @age, @citizenship = name, age, self.class.citizenship | |
end | |
# def citizenship | |
# self.class.citizenship | |
# end | |
end | |
class FrancePresident | |
include Presidential | |
def self.citizenship | |
"La France" | |
end | |
def age | |
"#{@age}, #{catchphrase}" | |
end | |
def name | |
"#{@name}, #{catchphrase}" | |
end | |
def citizenship | |
"#{@citizenship}, #{catchphrase}" | |
end | |
def catchphrase | |
"bien sur" | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSTRUCTIONS | |
In this exercise we'll create classes to represent a square, rectangle and circle. Since all three classes are different types of shapes, we'll define a parent class because we assume that they'll share some similar properties. Let's start by defining a parent class and three children: | |
class Shape | |
end | |
class Rectangle < Shape | |
end | |
class Square < Shape | |
end | |
class Circle < Shape | |
end | |
We need to be able to get and set the color of each shape. Let's use our parent class to do this efficiently: | |
class Shape | |
attr_accessor :color | |
end | |
class Rectangle < Shape | |
end | |
class Square < Shape | |
end | |
class Circle < Shape | |
end | |
We also want to be able to calculate the area of each shape. Rectangles and squares share the same formula for area (width * height), but a circle's area is calculated as Pi * (radius*radius). Since our shapes share two different area formulas amongst them, we can't define an area method in the Shape parent class. We'll want to refactor our code, but let's start by defining area methods for each child: | |
class Shape | |
attr_accessor :color | |
end | |
class Rectangle < Shape | |
def area | |
# width * height | |
end | |
end | |
class Square < Shape | |
def area | |
# width * height | |
end | |
end | |
class Circle < Shape | |
def area | |
# Pi * (radius*radius) | |
end | |
end | |
The area methods we defined above will require attributes. Let's create getter and setter methods for the attributes needed for the area methods: | |
class Shape | |
attr_accessor :color | |
end | |
class Rectangle < Shape | |
attr_accessor :width, :height | |
def area | |
# width * height | |
end | |
end | |
class Square < Shape | |
attr_accessor :width, :height | |
def area | |
# width * height | |
end | |
end | |
class Circle < Shape | |
attr_accessor :radius | |
def area | |
# Pi * (radius*radius) | |
end | |
end | |
We'll also need to initialize each class. Remember that each shape must be initialized with a color, which is why we created a color getter and setter in the Shape class. Let's start with the initialize method for Shape: | |
class Shape | |
attr_accessor :color | |
def initialize(color = nil) | |
@color = color || 'Red' | |
end | |
end | |
This allows us to initialize an instance of shape with a defined color. If the color is not provided, we'll default the color to red with a conditional assignment. | |
Next we'll want to define initialize methods for the child classes. | |
Notice how we use super to keep our code DRY. | |
class Shape | |
attr_accessor :color | |
def initialize(color = nil) | |
@color = color || 'Red' | |
end | |
end | |
class Rectangle < Shape | |
attr_accessor :width, :height | |
def initialize(width, height, color = nil) | |
@width, @height = width, height | |
super(color) # this calls Shape#initialize | |
end | |
def area | |
# width * height | |
end | |
end | |
class Square < Shape | |
attr_accessor :width, :height | |
def initialize(width, height, color = nil) | |
@width, @height = width, height | |
super(color) # this calls Shape#initialize | |
end | |
def area | |
# width * height | |
end | |
end | |
class Circle < Shape | |
attr_accessor :radius | |
def initialize(radius, color = nil) | |
@radius = radius | |
super(color) # this calls Shape#initialize | |
end | |
def area | |
# Pi * (radius*radius) | |
end | |
end | |
Let's take a step back and regard our code. The Rectangle and Square class are looking awfully similar -- this is a bad "code smell." A code smell is something that just "feels" wrong in its implementation. Earlier we questioned the relationship between a square and a rectangle, so let's figure out a DRYer way to structure these classes. A square is really a more specific type of rectangle -- one with equal width and height -- and so it represents a prime candidate for classical inheritance. | |
Let's make Square inherit from Rectangle: | |
... | |
class Square < Rectangle | |
def initialize(side, color = nil) | |
super(side, side, color) # calls `Rectangle#initialize` | |
end | |
end | |
... | |
The width and height attr_accessors, as well as the area method, are all inherited from Rectangle now. | |
As you can see this is a much DRYer implementation of Square. | |
We've set up a solid class structure for our program, now here's a challenge for you. | |
We should be able to call a larger_than? method on each shape. This method should evaluate two shapes and return true or false depending on one shape's area being larger than the other. In other words, the larger_than? method should return true if the receiving object is larger than the argument object: | |
square.larger_than?(rectangle) | |
#=> true if the square is larger than the rectangle, false if not | |
Hint: To find the area of a circle, you can use Math::PI. Remember that the area of a circle is calculated as Pi multiplied by the square of the radius. Math::PI evaluates to Pi and can be used like any other number in a calculation. | |
Math is actually a module, with PI "namespaced" under it. We could include Math and reference PI directly, or simply call Math::PI to return the value of Pi. If you're curious, talk to your mentor about using modules for namespaced functionality. | |
SPECS | |
describe "Shape" do | |
describe "larger_than?" do | |
it "should tell if a shape is larger than another shape" do | |
class A < Shape | |
def area | |
5 | |
end | |
end | |
class B < Shape | |
def area | |
10 | |
end | |
end | |
a = A.new | |
b = B.new | |
expect( b.larger_than?(a) ).to eq(true) | |
expect( a.larger_than?(b) ).to eq(false) | |
end | |
end | |
describe "color" do | |
it "should be able to get and set color" do | |
s = Shape.new | |
expect( s.respond_to?(:color) ).to eq(true) | |
expect( s.respond_to?(:color=) ).to eq(true) | |
end | |
end | |
end | |
describe "Rectangle" do | |
describe "initialize" do | |
it "should take width, height" do | |
r = Rectangle.new(1, 2, "Blue") | |
expect( r.width ).to eq(1) | |
expect( r.height ).to eq(2) | |
r = Rectangle.new(5, 6, "Blue") | |
expect( r.width ).to eq(5) | |
expect( r.height ).to eq(6) | |
end | |
it "should be able to set a color if given" do | |
r = Rectangle.new(1, 2, "Blue") | |
expect( r.color ).to eq("Blue") | |
r = Rectangle.new(1, 2, "Green") | |
expect( r.color ).to eq("Green") | |
end | |
it "should be able to set the default color to Red" do | |
r = Rectangle.new(1, 2) | |
expect( r.color ).to eq("Red") | |
end | |
end | |
describe "area" do | |
it "should return correct area for large rectangle" do | |
r = Rectangle.new(100, 240) | |
expect( r.area ).to eq(24000) | |
end | |
it "should return correct area for a small rectangle" do | |
r = Rectangle.new(5, 2) | |
expect( r.area ).to eq(10) | |
end | |
end | |
end | |
describe "Square" do | |
describe "initialize" do | |
it "should only take a side and color" do | |
s = Square.new(5, "Green") | |
expect( s.width ).to eq(5) | |
expect( s.height ).to eq(5) | |
expect( s.color ).to eq("Green") | |
end | |
it "should set a default color" do | |
s = Square.new(13) | |
expect( s.width ).to eq(13) | |
expect( s.height ).to eq(13) | |
expect( s.color ).to eq("Red") | |
end | |
end | |
describe "area" do | |
it "should return right area" do | |
s = Square.new(15) | |
expect( s.area ).to eq(225) | |
end | |
end | |
end | |
describe "Circle" do | |
describe "initialize" do | |
it "should only take radius and color" do | |
c = Circle.new(7, "Brown") | |
expect( c.radius ).to eq(7) | |
expect( c.color ).to eq("Brown") | |
end | |
it "should set a default color" do | |
c = Circle.new(8) | |
expect( c.radius ).to eq(8) | |
expect( c.color ).to eq("Red") | |
end | |
it "should not respond to width or height" do | |
c = Circle.new(8) | |
expect( c.respond_to?(:width) ).to eq(false) | |
expect( c.respond_to?(:width=) ).to eq(false) | |
expect( c.respond_to?(:height) ).to eq(false) | |
expect( c.respond_to?(:height=) ).to eq(false) | |
end | |
end | |
describe "area" do | |
it "returns the area of a small circle" do | |
c = Circle.new(3) | |
expect( c.area ).to eq(28.274333882308138) | |
end | |
it "returns the area of a large circle" do | |
c = Circle.new(23) | |
expect( c.area ).to eq(1661.9025137490005) | |
end | |
end | |
end | |
CODE | |
class Shape | |
attr_accessor :color | |
def initialize(color = nil) | |
@color = color || 'Red' | |
end | |
def larger_than?(shapeType) | |
self.area >= shapeType.area ? true : false end #changed if/else statement to one line | |
end | |
# if self.area >= shapeType.area | |
# true | |
# else | |
# false | |
# end | |
# end | |
# end | |
class Rectangle < Shape | |
attr_accessor :width, :height | |
def initialize(width, height, color = nil) | |
@width, @height = width, height | |
super(color) | |
end | |
def area | |
p @width * @height | |
end | |
end | |
class Square < Rectangle | |
def initialize(side, color = nil) | |
@width, @height = width, height | |
super(side, side, color) | |
end | |
end | |
class Circle < Shape | |
attr_accessor :radius | |
def initialize(radius, color = nil) | |
@radius = radius | |
super(color) | |
end | |
def area | |
p Math::PI * (@radius * @radius) | |
end | |
end | |
RESULT | |
Results | |
Shape larger_than? should tell if a shape is larger than another shape | |
Shape color should be able to get and set color | |
Rectangle initialize should take width, height | |
Rectangle initialize should be able to set a color if given | |
Rectangle initialize should be able to set the default color to Red | |
Rectangle area should return correct area for large rectangle | |
Rectangle area should return correct area for a small rectangle | |
Square initialize should only take a side and color | |
Square initialize should set a default color | |
Square area should return right area | |
Circle initialize should only take radius and color | |
Circle initialize should set a default color | |
Circle initialize should not respond to width or height | |
Circle area returns the area of a small circle | |
Circle area returns the area of a large circle | |
OUTPUT | |
24000 | |
10 | |
225 | |
28.274333882308138 | |
1661.9025137490005 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSTRUCTIONS | |
Turn our compounding interest calculator from the Math exercises in the Ruby Syntax chapter into an InterestCalculator class. | |
InterestCalculator should take four arguments on initialization: | |
Principal | |
Rate of interest | |
Years compounded | |
Times compounded per year | |
And it should define the following functions | |
amount - A simple number representation, rounded to 2 decimals. | |
statement - A string of the format, "After 5 years I'll have 2000 dollars!" | |
Make sure to use self (implied or explicit) throughout the class. Instance variables should only be directly accessed in the initialize function, and amount should only be calculated in one place. | |
Hint - to use instance @ variables only in the initialize method, you'll need to use attr_accessor. | |
The compound interest formula: | |
amount = principal * (1 + rate / times_compounded) ** (times_compounded * years) | |
SPECS | |
describe InterestCalculator do | |
before { @calc = InterestCalculator.new(500, 0.05, 4, 5) } | |
describe "#amount" do | |
it "calculates correctly" do | |
expect( @calc.amount ).to eq(610.1) | |
end | |
end | |
describe "#statement" do | |
it "calls amount" do | |
@calc.stub(:amount).and_return(100) | |
expect( @calc.statement ).to eq("After 4 years I'll have 100 dollars!") | |
end | |
end | |
end | |
CODE | |
class InterestCalculator | |
attr_accessor :amount, :statement, :years | |
def initialize(principal, rate, years, times_compounded) | |
@principal = principal | |
@rate = rate | |
@years = years | |
@times_compounded = times_compounded | |
self.amount = principal * (1 + rate / times_compounded) ** (times_compounded * years) | |
self.amount = amount.round(1) | |
end | |
def statement | |
statement = "After #{years} years I'll have #{amount} dollars!" | |
end | |
end | |
SOLUTION | |
Results | |
InterestCalculator#amount calculates correctly | |
InterestCalculator#statement calls amount | |
STACKOVERFLOW - http://stackoverflow.com/questions/26332805/expectationnotmeterror-method-returns-nil/26334103#26334103 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSTRUCTIONS | |
Let's revisit our Presidents. Create three classes -- President, FrancePresident, and UnitedStatesPresident -- with the following specifications: | |
The President class should accept name and age on initialization, and define getters and setters for those two attributes. | |
FrancePresident and UnitedStatesPresident should inherit from President. | |
The two child classes of President should define a citizenship class method, which returns "La France" or "The United States of America", as appropriate. | |
All instances of President should also have citizenship methods, which return the classes' citizenships. To keep your code DRY, take a look at the class method every Ruby object inherits from the Object class, so you can call the class methods within the instance methods. | |
All instances of FrancePresident should append a ", bien sur" to their responses when asked for their name, age, or citizenship. This is a good case for super. | |
Note that the class method is one of the exceptions to the implicit calling of self in a Ruby method. To access an instance's parent class, you must call self.class within the instance method -- just class won't do it. | |
obama = UnitedStatesPresident.new("Barack Obama", 52) | |
obama.name | |
#=> "Barack Obama" | |
UnitedStatesPresident.citizenship | |
#=> "The United States of America" | |
obama.citizenship | |
#=> "The United States of America" | |
francois = FrancePresident.new("Francois Hollande", 59) | |
francois.age | |
#=> "59, bien sur" | |
FrancePresident.citizenship | |
#=> "La France" | |
francois.citizenship | |
#=> "La France, bien sur" | |
Bonus -- Using inheritance, keep all the above functionality while calling self.class in only one place. | |
This is a tough exercise. If you've been struggling with it for a while, take a look at our walkthrough of the sample code (https://www.bloc.io/resources/super-presidents-example-code). | |
When you've solved it, think through or talk to your mentor about the example code. What exactly is the FrancePresident#citizenship method doing? What knowledge does it rely on? What does the President#citizenship method need from President's children classes in order to work correctly? Do you think this sort of reliance of parent on child is a good idea? Why? | |
SPECS | |
describe President do | |
describe "initialization" do | |
it "takes a name and age, and delegates citizenship to the class default" do | |
prez = President.new("George Washington", 283) | |
expect( prez.name ).to eq("George Washington") | |
expect( prez.age ).to eq(283) | |
end | |
end | |
describe "children" do | |
it "is the parent of FrancePresident and UnitedStatesPresident" do | |
expect( FrancePresident.superclass ).to eq(President) | |
expect( UnitedStatesPresident.superclass ).to eq(President) | |
end | |
end | |
end | |
describe FrancePresident do | |
describe "catchphrase" do | |
it "sounds just right" do | |
expect( FrancePresident.citizenship ).to eq("La France") | |
sarcozy = FrancePresident.new("Nicolas Sarkozy", 59) | |
expect( sarcozy.citizenship ).to eq("La France, bien sur") | |
expect( sarcozy.age ).to eq("59, bien sur") | |
expect( sarcozy.name ).to eq("Nicolas Sarkozy, bien sur") | |
end | |
end | |
end | |
CODE | |
class President | |
attr_accessor :name, :age | |
def initialize(name, age) | |
@name = name | |
@age = age | |
end | |
def citizenship | |
self.class.citizenship | |
end | |
end | |
class UnitedStatesPresident < President | |
def self.citizenship | |
"The United States of America" | |
end | |
end | |
class FrancePresident < President | |
def self.citizenship | |
"La France" | |
end | |
def age | |
"#{super}, #{catchphrase}" | |
end | |
def name | |
"#{super}, #{catchphrase}" | |
end | |
def citizenship | |
"#{super}, #{catchphrase}" | |
end | |
def catchphrase | |
"bien sur" | |
end | |
end | |
Results | |
President initialization takes a name and age, and delegates citizenship to the class default | |
President children is the parent of FrancePresident and UnitedStatesPresident | |
FrancePresident catchphrase sounds just right |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment