Forked from davidvandusen/ruby_classical_inheritance_review.md
Created
July 26, 2016 15:26
-
-
Save spacemonkeyvt/e555bd9ae209a061ef57b41bf48745e7 to your computer and use it in GitHub Desktop.
Revisions
-
David VanDusen revised this gist
Jan 14, 2016 . 1 changed file with 4 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -593,9 +593,10 @@ instead of directly accessing the instance variables `@front_gear` and `@rear_ge follow because your class behaves in a predictable way to users (programmers using your class.) Some code that has an instance of `MountainBike` can access all the `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are linked to one another in a predetermined way. This keeps the class' internal code and external, or public, API consistent. By using existing instance methods inside your new instance methods, the code is more resilient against future changes, too. The way instance variables are updated can change or the instance variables can be removed an replaced by computed values. If that happens, then you don't have to fix any code that was using the instance variables directly. Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object -
David VanDusen revised this gist
Jan 14, 2016 . 1 changed file with 4 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -592,10 +592,10 @@ Did you notice that inside the `#gear` method the `attr_reader` methods `#front_ instead of directly accessing the instance variables `@front_gear` and `@rear_gear`? This is a useful convention to follow because your class behaves in a predictable way to users (programmers using your class.) Some code that has an instance of `MountainBike` can access all the `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are linked to one another in a predetermined way. This keeps the class' internal code and external, or public, API consistent. By using existing instance methods inside your new instance methods, the code is more robust against future changes, too. The way instance variables are updated can change or the instance variables can be removed an replaced by computed values. If that happens, then you don't have to fix any code that was using the instance variables directly. Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object -
David VanDusen revised this gist
Jan 14, 2016 . 1 changed file with 43 additions and 43 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -416,49 +416,49 @@ the console. So far all the attributes of these classes been initialized to values specified as parameters to the `#initialize` method. Sometimes, each new instance of a class will have a starting value for an attribute. For example, each new `MountainBike` might start with its brakes released. It's easy to add these kinds of attributes. You just set them to their starting value inside the `#initialize` method. Let's add a `braking` attribute to the `MountainBike` class that represents whether the brakes are being held, but let's give it a default value instead of taking its initial value as a parameter. ```ruby class MountainBike < Bicycle def initialize super('off road') @braking = false end end ``` Now every new `MountainBike` instance starts with the brakes released. Of course, we can't read whether an instance of `MountainBike` is braking, so we should add an `attr_reader` for it. ```ruby class MountainBike < Bicycle attr_reader :braking def initialize super('off road') @braking = false end end ``` Now we can access the value representing whether the `MountainBike` is braking. Let's try it out. ```ruby a_mountain_bike = MountainBike.new puts a_mountain_bike.terrain puts a_mountain_bike.propulsion puts a_mountain_bike.braking ``` Ok, its brakes are off, but how do we change that? We did the right thing by defining the braking attribute as read only first. All attributes should start as `attr_reader`s until you know for a fact that you want to expose the ability to update them. Exposing a "writer" method as well is as easy as using the `attr_accessor` method instead of `attr_reader`. This will also expose a method for updating the value of the instance variable using the equals sign @@ -467,11 +467,11 @@ to update them. Exposing a "writer" method as well is as easy as using the `attr ```ruby class MountainBike < Bicycle attr_accessor :braking def initialize super('off road') @braking = false end end @@ -484,17 +484,17 @@ class MountainBike < Bicycle def initialize super('off road') @braking = false end # Manually defined accessor methods. It's better to use `attr_accessor` for these. def braking @braking end def braking=(braking) @braking = braking end end @@ -505,8 +505,8 @@ special. You can then use the following syntax for updating the value of an attr ```ruby a_mountain_bike = MountainBike.new a_mountain_bike.braking = true puts a_mountain_bike.braking ``` Now instances of `MountainBike` can break. As mentioned above, not all attributes should have "writer" methods. @@ -521,11 +521,11 @@ let's add front and rear gears to the `MountainBike` class. ```ruby class MountainBike < Bicycle attr_accessor :braking def initialize super('off road') @braking = false @front_gear = 1 @rear_gear = 1 end @@ -541,19 +541,19 @@ class MountainBike < Bicycle attr_reader :front_gear, :rear_gear attr_accessor :braking def initialize super('off road') @braking = false @front_gear = 1 @rear_gear = 1 end end ``` Because they are only readers, we can't just add them to the `attr_accessor` call for the braking attribute, so we add a separate line for the `attr_reader`s. The full gear that the bike is in can be calculated using the gears on the front and rear. Let's add an instance method @@ -564,11 +564,11 @@ class MountainBike < Bicycle attr_reader :front_gear, :rear_gear attr_accessor :braking def initialize super('off road') @braking = false @front_gear = 1 @rear_gear = 1 end @@ -600,7 +600,7 @@ using the instance variables directly. Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object instance, and you can access its methods using dot-notation just like you can outside the method. For example, if you wanted to implement a `#stop` instance method on `MountainBike` that turns on the brakes, it would have to be written the following way. @@ -609,11 +609,11 @@ class MountainBike < Bicycle attr_reader :front_gear, :rear_gear attr_accessor :braking def initialize super('off road') @braking = false @front_gear = 1 @rear_gear = 1 end @@ -623,14 +623,14 @@ class MountainBike < Bicycle end def stop self.braking = true end end ``` Note the use of `self.` in the `#stop` method. If it was omitted, then Ruby would just think you are defining a new local variable named `braking` instead of invoking the `#braking=` instance method. There is still no way to change the gear that the `MountainBike` is in. `MountainBike` objects should only be able to increase or decrease their gears by one at a time. Let's implement an instance method that increases the rear gear by @@ -641,11 +641,11 @@ class MountainBike < Bicycle attr_reader :front_gear, :rear_gear attr_accessor :braking def initialize super('off road') @braking = false @front_gear = 1 @rear_gear = 1 end @@ -655,7 +655,7 @@ class MountainBike < Bicycle end def stop self.braking = true end def increase_rear_gear @@ -695,11 +695,11 @@ class MountainBike < Bicycle attr_reader :front_gear, :rear_gear attr_accessor :braking def initialize super('off road') @braking = false @front_gear = 1 @rear_gear = 1 end @@ -709,7 +709,7 @@ class MountainBike < Bicycle end def stop self.braking = true end def increase_rear_gear @@ -738,11 +738,11 @@ class MountainBike < Bicycle attr_reader :front_gear, :rear_gear attr_accessor :braking def initialize super('off road') @braking = false @front_gear = 1 @rear_gear = 1 end @@ -752,7 +752,7 @@ class MountainBike < Bicycle end def stop self.braking = true end def increase_rear_gear @@ -779,11 +779,11 @@ class MountainBike < Bicycle attr_reader :front_gear, :rear_gear attr_accessor :braking def initialize super('off road') @braking = false @front_gear = 1 @rear_gear = 1 end @@ -793,7 +793,7 @@ class MountainBike < Bicycle end def stop self.braking = true end def increase_rear_gear -
David VanDusen revised this gist
Jan 13, 2016 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -213,7 +213,7 @@ and `#propulsion` instance methods work as expected because they are inherited f inside of `Boat#initialize`, passing the parameter values, and the code inside of `Vehicle#initialize` set those values to instance variables. ## Querying inheritance and polymorphism With these two classes, there is now a new, interesting dynamic to the way you can write a program. If you have some code that only works when it has an instance of `Vehicle`, it will work for instances of `Vehicle` _and_ instances of -
David VanDusen revised this gist
Jan 13, 2016 . 1 changed file with 4 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -213,7 +213,7 @@ and `#propulsion` instance methods work as expected because they are inherited f inside of `Boat#initialize`, passing the parameter values, and the code inside of `Vehicle#initialize` set those values to instance variables. ## Querying inheritance (polymorphism) With these two classes, there is now a new, interesting dynamic to the way you can write a program. If you have some code that only works when it has an instance of `Vehicle`, it will work for instances of `Vehicle` _and_ instances of @@ -240,6 +240,9 @@ display_vehicle(steam_boat) In the above example, `steam_boat.is_a?(Vehicle)` returns `true`, because `#is_a?` returns `true` if the class object that is passed into it is the same class or any of the superclasses of the object. The ability to use `Boat` instances when `Vehicle` instances are needed is what's known as "polymorphism". A subclass can be used anywhere its parent classes are needed. ## New attributes Sometimes classes that inherit from other classes will have new properties that their superclasses don't have. For -
David VanDusen revised this gist
Jan 13, 2016 . 1 changed file with 6 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -587,11 +587,12 @@ puts a_mountain_bike.gear Did you notice that inside the `#gear` method the `attr_reader` methods `#front_gear` and `#rear_gear` are being used instead of directly accessing the instance variables `@front_gear` and `@rear_gear`? This is a useful convention to follow because your class behaves in a predictable way to users (programmers using your class.) Some code that has an instance of `MountainBike` can access all the `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are linked to one another in a predetermined way. By using existing instance methods inside your new instance methods, the code is more robust against future changes, too. The way instance variables are updated can change or the instance variables can be removed an replaced by computed values. If that happens, then you don't have to fix any code that was using the instance variables directly. Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object -
David VanDusen revised this gist
Jan 13, 2016 . 1 changed file with 15 additions and 15 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -10,10 +10,10 @@ read the output. Try writing your own driver code to try things out new concepts ## A simple class In Ruby, all objects are instances of classes. There are a lot of built-in classes, but you can define your own if none of the built-in ones are sufficient for the problem you're trying to solve. This is often the case when you are working in a very specific problem domain. For example, if you want to program a traffic simulation, you probably need objects representing vehicles, which don't come pre-packaged with Ruby. Let's define a `Vehicle` class to see how that would look. @@ -195,8 +195,8 @@ travel on water, so we don't need to take a parameter for it. The next thing is the use of the `super` keyword. The way the `super` keyword works is just like any other method call. It calls the method of the same name as the one that the program is currently inside of, but in the superclass. In this case, the keyword `super` is inside of the `#initialize` method in the `Boat` class, therefore it will call the method of the same name, `#initialize`, in the superclass, which is `Vehicle`. The `#initialize` method of `Vehicle` takes two parameters, `terrain` and `propulsion`, so they both need to be passed. This is the completed `Boat` class. All it does is extend the `Vehicle` class by defaulting the `terrain` attribute to `'water'`. All the other instance methods, including the ones defined by `attr_reader`, are inherited and can be used @@ -585,13 +585,13 @@ a_mountain_bike = MountainBike.new puts a_mountain_bike.gear ``` Did you notice that inside the `#gear` method the `attr_reader` methods `#front_gear` and `#rear_gear` are being used instead of directly accessing the instance variables `@front_gear` and `@rear_gear`? This is a useful convention to follow because your class behaves in a predictable way to users. Some code that has an instance of `MountainBike` can access all the `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are linked to one another in a predictable way. By using existing instance methods inside your new instance methods, the code is more robust against future changes, too, because the way instance variables are reported can change or the instance variables can be removed an replaced by computed values and none of the other code has to be changed. Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object @@ -679,7 +679,7 @@ The rear gear of a `MountainBike` as previously mentioned, can't go above `6`. W instance variable from going above this amount. The value `6` has special meaning in the context of a `MountainBike`. In cases like these, instead of hard-coding the value `6` into multiple methods in a class, it should be defined as a constant. Constants are defined inside a class declaration, are named in ALL_CAPS, and can't be changed. They can be referred to inside instance methods of the class they're defined in by name, but from outside the class they must be prefixed with the scope resolution operator, which is two colons, (`::`). For example: @@ -823,4 +823,4 @@ To practice working with classes and inheritance in Ruby, try some of the follow - Think of another kind of vehicle that wasn't discussed and come up with ways to define classes that take advantage of inheritance to simplify working with the subclasses. Good luck! -
David VanDusen created this gist
Jan 13, 2016 .There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,826 @@ # Classical Inheritance in Ruby For programmers who are new to Object Oriented Programming, the ideas behind classical inheritance can take some getting used to. This article walks you through the syntax of defining class inheritance in Ruby with explanations of each OOP feature along the way. You will have created many inheriting classes and used them in Ruby code by the end of this exercise. Create a new Ruby code file and copy the code examples into it as you go. Run the file with the provided driver code and read the output. Try writing your own driver code to try things out new concepts as they're introduced. ## A simple class In Ruby, all objects are instances of classes. There are a lot of built in classes, but you can define your own if none of the built in ones are sufficient for solving the problem you're trying to solve. This is often the case when you are working in a very specific problem domain. For example, if you want to program a traffic simulation, you probably need objects representing vehicles, which don't come pre-packaged with Ruby. Let's define a `Vehicle` class to see how that would look. ```ruby class Vehicle end ``` Every class in Ruby starts off like this. The keyword `class` is followed by the name of the kind of object in UpperCamelCase, and the keyword `end` on a separate line. The specifics of the class come between those lines. To use this class in a program, you invoke the `.new` class method. Class methods are invoked by writing the class name followed by a dot and then the name of the method. In many cases, you can tell when documentation is referring to a class method because it precedes the method name with a dot (`.`) character. Let's try it. ```ruby my_vehicle = Vehicle.new puts my_vehicle ``` If you run the above code after defining the `Vehicle` class, you should see a representation of a `Vehicle` object printed to the console. Presently `Vehicle` objects aren't very useful, but we can improve that by adding **attributes**. ## Instance attributes Custom objects become more useful when they are given attributes. Attributes are a combination of values that are unique to each instance of a class and methods for reading or writing those values. For example, every vehicle instance might have a value representing the kind of terrain that it can travel on, or it might have a value representing how it's propelled. Let's add those to our `Vehicle` class. The first step is to initialize the data. It doesn't make sense for a `Vehicle` to not have values for those two attributes, so we should force the programmer to specify values for each when they are creating a new instance. We do this by creating an instance method named `#initialize`, which is a special method name. It gets called automatically on new instances of our class when the class' `.new` method is called. The number sign (`#`) is just notation to indicate that it is an instance method, the same way the dot indicates a class method. ```ruby class Vehicle def initialize(terrain, propulsion) @terrain = terrain @propulsion = propulsion end end ``` Adding an `#initialize` method is often the second thing you do when defining a new class. The parameters that the method takes represent the values that vary from one instance of the class to another. In this case, each `Vehicle` can travel on different terrain and have a different method of propulsion. The values of the passed parameters are stored inside of instance variables so that they can be accessed later on from within the class. Accessing the values from within the class is useful, but it is also useful to be able to ask an instance of `Vehicle` what its values for these attributes are, so we need to expose them. This is done by using the `attr_reader` method. When `attr_reader` is called from inside a class declaration, it creates "reader" methods (methods that just return the value of an instance variable) for each instance variable that matches the names of the symbols that are passed in as parameters. For example: ```ruby class Vehicle attr_reader :terrain, :propulsion def initialize(terrain, propulsion) @terrain = terrain @propulsion = propulsion end end ``` Adding the `attr_reader` statement has generated two methods, one for each parameter. This is the same as: ```ruby class Vehicle def initialize(terrain, propulsion) @terrain = terrain @propulsion = propulsion end # Manually defined reader methods. It's better to use `attr_reader` for these. def terrain @terrain end def propulsion @propulsion end end ``` You can define reader methods manually like this, but it's far more concise to use `attr_reader`. Now you can create `Vehicle` objects that can report their values for these attributes later on, like this: ```ruby steam_train = Vehicle.new('rails', 'steam') puts steam_train.terrain puts steam_train.propulsion skateboard = Vehicle.new('road', 'manual') puts skateboard.terrain puts skateboard.propulsion ``` Notice that the `.new` class method now requires the two `#initialize` parameters to be passed into it. Under the hood, `.new` passes each parameter it receives along to the `#initialize` method of the new instance. Now we're getting to a point where we have object that are meaningful to a real problem domain. Often you have to create many instances of a class to solve a problem, and several of those instances will likely have the same values for some of their attributes. We can use inheritance to simplify this process. ## Class inheritance Let's say we need a bunch of `Vehicle` objects that represent boats. We could define each one separately and specify that the terrain they travel on is `'water'`, but it would be much simpler to define a `Boat` class that inherits from `Vehicle` that automatically specifies that for us. We can start the new `Boat` class the same way we start every class. ```ruby class Boat end ``` The next step is to define the class that we want to inherit functionality from. The notation for that is to put a less-than sign (`<`) and then the name of the other class after the name of the new class. ```ruby class Boat < Vehicle end ``` Now, the `Boat` class has a reference to the `Vehicle` class as its "superclass". This can even be seen by asking the `Boat` class object what its superclass is. ```ruby # Should print out `Vehicle`. puts Boat.superclass ``` Notice the capital "B" on `Boat` in that snippet. Each class can be referred to as an object and it has its own attributes, such as its superclass. Take a look at the superclass of the `Vehicle` class. ```ruby # Should print out `Object`. puts Vehicle.superclass ``` If you don't specify another class to inherit from, the default superclass for a new class in Ruby is the `Object` class. So, the new `Boat` class inherits from the `Vehicle` class, and therefore can be used just like a `Vehicle`, but when we try to initialize a new `Boat`, it still requires two parameters. This is because there is no `#initialize` method defined in the `Boat` class, so when creating a new `Boat` it goes to the superclass to find an `#initialize` method to call. Ruby will keep going from one superclass to another to find the `#initialize` method until it gets to a class where one is defined, and then it will call that one. This is the same for every instance method that you call on an object. To specify all boats use the `'water'` terrain, we can hard-code that into the `#initialize` method of `Boat`. ```ruby class Boat < Vehicle def initialize(propulsion) super('water', propulsion) end end ``` There are a few things going on in the above class definition. The first is that we defined an instance method named `#initialize` that takes one argument: `propulsion`. This is the method that will be invoked when we call `Boat.new`. It takes this argument because different `Boat` objects can have different methods of propulsion. However, all boats travel on water, so we don't need to take a parameter for it. The next thing is the use of the `super` keyword. The way the `super` keyword works is just like any other method call. It calls the method of the same name as the one that the program is currently inside of, but in the superclass. In this case, the keyword `super` is inside of the `#initialize` method in the `Boat` class, therefore it will call the method of the same name — `#initialize` — in the superclass, which is `Vehicle`. The `#initialize` method of `Vehicle` takes two parameters, `terrain` and `propulsion`, so they both need to be passed. This is the completed `Boat` class. All it does is extend the `Vehicle` class by defaulting the `terrain` attribute to `'water'`. All the other instance methods, including the ones defined by `attr_reader`, are inherited and can be used on instances of `Boat`. ```ruby speed_boat = Boat.new('internal combustion') puts speed_boat.terrain puts speed_boat.propulsion ``` `Boat.new` takes one parameter because the `#initialize` method in the `Boat` class takes one parameter. The `#terrain` and `#propulsion` instance methods work as expected because they are inherited from `Vehicle` and `super` was called inside of `Boat#initialize`, passing the parameter values, and the code inside of `Vehicle#initialize` set those values to instance variables. ## Querying inheritance With these two classes, there is now a new, interesting dynamic to the way you can write a program. If you have some code that only works when it has an instance of `Vehicle`, it will work for instances of `Vehicle` _and_ instances of `Boat`. Boats _are_ vehicles, after all. You can prove it by using the `#is_a?` method, which is inherited from `Object` by all the classes you define. ```ruby # Prints out details about the passed `vehicle` parameter. # Raises an ArgumentError if `vehicle` is not a `Vehicle`. def display_vehicle(vehicle) # Raises an error if `vehicle.is_a?(Vehicle)` returns false. # Note the use of capital "V" `Vehicle` here - the class object. raise ArgumentError, 'vehicle parameter must be a Vehicle' unless vehicle.is_a?(Vehicle) # Displays information about the passed `Vehicle`. puts "The vehicle can travel on #{vehicle.terrain} and is propelled by #{vehicle.propulsion}." end steam_boat = Boat.new('steam') display_vehicle(steam_boat) ``` In the above example, `steam_boat.is_a?(Vehicle)` returns `true`, because `#is_a?` returns `true` if the class object that is passed into it is the same class or any of the superclasses of the object. ## New attributes Sometimes classes that inherit from other classes will have new properties that their superclasses don't have. For example, a sail boat is always propelled by wind, so it makes sense to define a `SailBoat` class, but it may also have an attribute that holds the number of sails that the boat has. Let's define this as a new class. The first step, as usual, is the basic class declaration. ```ruby class SailBoat end ``` After that, specify the superclass. ```ruby class SailBoat < Boat end ``` In this case, we're inheriting from `Boat` because a sail boat _is a_ boat. However, we need to specify that all `SailBoat` objects have `'wind'` as their propulsion. ```ruby class SailBoat < Boat def initialize super('wind') end end ``` The superclass `#initialize` method in the `Boat` class only takes one parameter, the propulsion, so we only pass it one. Now we're ready to add a new attribute to this class, the number of sails. ```ruby class SailBoat < Boat def initialize(number_of_sails) super('wind') @number_of_sails = number_of_sails end end ``` Just like the attributes we defined earlier, we add a parameter to the `#initialize` method because the number of sails can vary from one `SailBoat` to another. Then we save the value that was passed in to an instance variable so that it can be used later. Right now, there's no way to ask a `SailBoat` how many sails it has after it's instantiated, so we can add an `attr_reader` method for that instance variable. ```ruby class SailBoat < Boat attr_reader :number_of_sails def initialize(number_of_sails) super('wind') @number_of_sails = number_of_sails end end ``` Now we have a useful `SailBoat` class that can be initialized with a number of sails. Let's give it a try. ```ruby schooner = SailBoat.new(7) puts schooner.terrain puts schooner.propulsion puts schooner.number_of_sails ``` Instances of `SailBoat` have inherited methods as well as methods of its own, such as `#number_of_sails`. When they are invoked on an instance, Ruby looks up which one to use by first looking at the class of the object itself, then the superclass if it couldn't find the method, then the superclass of the superclass, and so on. ## Class inheritance practice Let practice declaring classes using inheritance. This time we'll define a `Bicycle` class that is propelled manually, and a `Fixie` class that can travel on `'road'` and a `MountainBike` class that can travel `'off road'`. First, declare the `Bicycle` class. ```ruby class Bicycle end ``` Then, inherit from `Vehicle`. ```ruby class Bicycle < Vehicle end ``` Then, default the `propulsion` parameter to `'manual'`. ```ruby class Bicycle < Vehicle def initialize(terrain) super(terrain, 'manual') end end ``` The `terrain` parameter is still necessary and is passed along to the `Vehicle#initialize` method to be saved as an instance variable. Next, define the `Fixie` class. ```ruby class Fixie end ``` Then, inherit from `Bicycle`. ```ruby class Fixie < Bicycle end ``` Then, default the `terrain` parameter to `'road'`. ```ruby class Fixie < Bicycle def initialize super('road') end end ``` Only one parameter has to be passed to `super` because the superclass `Bicycle` only takes one parameter to its `#initialize` method. Next, define the `MountainBike` class. ```ruby class MountainBike end ``` Then, inherit from `Bicycle`. ```ruby class MountainBike < Bicycle end ``` Finally, default the `terrain` parameter to `'off road'`. ```ruby class MountainBike < Bicycle def initialize super('off road') end end ``` Now you can make two different kinds of bikes and neither of the `Fixie` or `MountainBike` `#initialize` methods take any parameters, so they're super easy to create! Try them out by writing your own driver code that produces output to the console. ## Default and writable attributes So far all the attributes of these classes been initialized to values specified as parameters to the `#initialize` method. Sometimes, each new instance of a class will have a starting value for an attribute. For example, each new `MountainBike` might start with its breaks released. It's easy to add these kinds of attributes. You just set them to their starting value inside the `#initialize` method. Let's add a `breaking` attribute to the `MountainBike` class that represents whether the breaks are being held, but let's give it a default value instead of taking its initial value as a parameter. ```ruby class MountainBike < Bicycle def initialize super('off road') @breaking = false end end ``` Now every new `MountainBike` instance starts with the breaks released. Of course, we can't read whether an instance of `MountainBike` is breaking, so we should add an `attr_reader` for it. ```ruby class MountainBike < Bicycle attr_reader :breaking def initialize super('off road') @breaking = false end end ``` Now we can access the value representing whether the `MountainBike` is breaking. Let's try it out. ```ruby a_mountain_bike = MountainBike.new puts a_mountain_bike.terrain puts a_mountain_bike.propulsion puts a_mountain_bike.breaking ``` Ok, its breaks are off, but how do we change that? We did the right thing by defining the breaking attribute as read only first. All attributes should start as `attr_reader`s until you know for a fact that you want to expose the ability to update them. Exposing a "writer" method as well is as easy as using the `attr_accessor` method instead of `attr_reader`. This will also expose a method for updating the value of the instance variable using the equals sign (`=`). This is how it looks. ```ruby class MountainBike < Bicycle attr_accessor :breaking def initialize super('off road') @breaking = false end end ``` It's a very simple change. The `attr_accessor` method creates two instance methods that are equivalent to the following. ```ruby class MountainBike < Bicycle def initialize super('off road') @breaking = false end # Manually defined accessor methods. It's better to use `attr_accessor` for these. def breaking @breaking end def breaking=(breaking) @breaking = breaking end end ``` In Ruby, defining a method whose name ends with the equals sign, including automatically generated "writer" methods, is special. You can then use the following syntax for updating the value of an attribute on an object. ```ruby a_mountain_bike = MountainBike.new a_mountain_bike.breaking = true puts a_mountain_bike.breaking ``` Now instances of `MountainBike` can break. As mentioned above, not all attributes should have "writer" methods. Generally you will define your own instance methods on a class that update the values of instance variables. ## Custom instance methods Most of the functionality of the classes you create will be inside of instance methods. The instance methods you define will typically change the values of instance variables that were initialized when the object was created. To see this, let's add front and rear gears to the `MountainBike` class. ```ruby class MountainBike < Bicycle attr_accessor :breaking def initialize super('off road') @breaking = false @front_gear = 1 @rear_gear = 1 end end ``` When a new `MountainBike` is created, its front and rear gears are both in position `1`. Let's expose these using `attr_reader`. ```ruby class MountainBike < Bicycle attr_reader :front_gear, :rear_gear attr_accessor :breaking def initialize super('off road') @breaking = false @front_gear = 1 @rear_gear = 1 end end ``` Because they are only readers, we can't just add them to the `attr_accessor` call for the breaking attribute, so we add a separate line for the `attr_reader`s. The full gear that the bike is in can be calculated using the gears on the front and rear. Let's add an instance method that gets the actual gear that the bike is in. ```ruby class MountainBike < Bicycle attr_reader :front_gear, :rear_gear attr_accessor :breaking def initialize super('off road') @breaking = false @front_gear = 1 @rear_gear = 1 end def gear (front_gear - 1) * 6 + rear_gear end end ``` The maximum rear gear is `6`, so the full gear number can be calculated as in the example above. The `#gear` instance method should return `1` for each new `MountainBike` until the gears are changed. ```ruby a_mountain_bike = MountainBike.new puts a_mountain_bike.gear ``` Notice that inside the `#gear` method the `attr_reader` methods `#front_gear` and `#rear_gear` are being used instead of directly accessing the instance variables `@front_gear` and `@rear_gear`? This is a useful convention to follow because your class behaves in a predictable way to users. Some code that has an instance of `MountainBike` can access all the `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are linked to one another in a deterministic way. By using existing instance methods inside your new instance methods, the code is more robust against future changes, too, because the way instance variables are reported can change or the instance variables can be removed an replaced by computed values and none of the other code has to be changed. Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object instance, and you can access its methods using dot-notation just like you can outside the method. For example, if you wanted to implement a `#stop` instance method on `MountainBike` that turns on the breaks, it would have to be written the following way. ```ruby class MountainBike < Bicycle attr_reader :front_gear, :rear_gear attr_accessor :breaking def initialize super('off road') @breaking = false @front_gear = 1 @rear_gear = 1 end def gear (front_gear - 1) * 6 + rear_gear end def stop self.breaking = true end end ``` Note the use of `self.` in the `#stop` method. If it was omitted, then Ruby would just think you are defining a new local variable named `breaking` instead of invoking the `#breaking=` instance method. There is still no way to change the gear that the `MountainBike` is in. `MountainBike` objects should only be able to increase or decrease their gears by one at a time. Let's implement an instance method that increases the rear gear by `1`. ```ruby class MountainBike < Bicycle attr_reader :front_gear, :rear_gear attr_accessor :breaking def initialize super('off road') @breaking = false @front_gear = 1 @rear_gear = 1 end def gear (front_gear - 1) * 6 + rear_gear end def stop self.breaking = true end def increase_rear_gear @rear_gear += 1 end end ``` The new `#increase_rear_gear` method can't use a "writer" method like the `#stop` method did because there is no writer method for the `@rear_gear` instance variable. Let's try this method out. ```ruby a_mountain_bike = MountainBike.new puts a_mountain_bike.gear a_mountain_bike.increase_rear_gear puts a_mountain_bike.gear ``` Alright, now we can increase the rear gear and the total gear goes up. ## Class constants The rear gear of a `MountainBike` as previously mentioned, can't go above `6`. We need to prevent the `@rear_gear` instance variable from going above this amount. The value `6` has special meaning in the context of a `MountainBike`. In cases like these, instead of hard-coding the value `6` into multiple methods in a class, it is defined as a constant. Constants are defined inside a class declaration, are named in ALL_CAPS, and can't be changed. They can be referred to inside instance methods of the class they're defined in by name, but from outside the class they must be prefixed with the scope resolution operator, which is two colons, (`::`). For example: ```ruby class MountainBike < Bicycle MAX_REAR_GEAR = 6 attr_reader :front_gear, :rear_gear attr_accessor :breaking def initialize super('off road') @breaking = false @front_gear = 1 @rear_gear = 1 end def gear (front_gear - 1) * MAX_REAR_GEAR + rear_gear end def stop self.breaking = true end def increase_rear_gear @rear_gear += 1 end end ``` The `6` that was previously being used in `#gear` has been replaced by the new constant `MAX_REAR_GEAR`. You can get the value of this constant from outside the `MountainBike` class like so: ``` puts MountainBike::MAX_REAR_GEAR ``` Note the use of the scope resolution operator, `::`. This can be useful if you need to access the value of the constant from another class. Now we can prevent `@rear_gear` from going over `MAX_REAR_GEAR` like this: ```ruby class MountainBike < Bicycle MAX_REAR_GEAR = 6 attr_reader :front_gear, :rear_gear attr_accessor :breaking def initialize super('off road') @breaking = false @front_gear = 1 @rear_gear = 1 end def gear (front_gear - 1) * MAX_REAR_GEAR + rear_gear end def stop self.breaking = true end def increase_rear_gear @rear_gear += 1 if rear_gear < MAX_REAR_GEAR end end ``` Note the use of the `#rear_gear` method again. The instance variable is just being read here, so we can use the `attr_reader` that is defined. ## Class methods Sometimes there are actions that can be thought of as being done by the class of objects as opposed to individual instances. In these cases, class methods are used. For example, the `.new` method is an action of the `MountainBike` class. We could add a new action to the `MountainBike` class if we wanted to. Here's an example of adding a `.repair_kit` class method thar returns an array of tools for fixing `MountainBike`s. ```ruby class MountainBike < Bicycle MAX_REAR_GEAR = 6 attr_reader :front_gear, :rear_gear attr_accessor :breaking def initialize super('off road') @breaking = false @front_gear = 1 @rear_gear = 1 end def gear (front_gear - 1) * MAX_REAR_GEAR + rear_gear end def stop self.breaking = true end def increase_rear_gear @rear_gear += 1 if rear_gear < MAX_REAR_GEAR end def self.repair_kit ['wrench', 'pliers', 'pump'] end end ``` As you can see, the method name starts with `self.`. This is different from the `self.` earlier, which was _inside_ an instance method and therefore referred to the instance. When `self.` appears after `def` or inside a class method, it refers to the class object itself, in this case `MountainBike`. Now, the `MountainBike` class object has a new method that can be called on it. ```ruby puts MountainBike.repair_kit ``` As you can see, no instance of the class had to be created. The method is used on the class object itself. ## More practice To practice working with classes and inheritance in Ruby, try some of the following. - Finish the `MountainBike` methods for increasing and decreasing the front and rear gears, making sure that the gears can't go below `1` and the front gear can't go above `3`. - Think of another kind of vehicle that wasn't discussed and come up with ways to define classes that take advantage of inheritance to simplify working with the subclasses. Good luck!