oop
# Metadata
2022-06-06 01:22 | oop | Doriel Rivalet
# Content
lambdas are nameless methods/functions
another name for properties are attributes
classes have properties and objects have attributes?
the @ in Ruby classes is the same as private
in Java classes
In Ruby, we use @
before a variable to signify that it’s an instance variable. This means that the variable is attached to the instance of the class.
|
|
Another important aspect of Ruby classes is scope. The scope of a variable is the context in which it’s visible to the program.
When dealing with classes, you can have variables that are available everywhere (global variables), ones that are only available inside certain methods (local variables), others that are members of a certain class (class variables), and variables that are only available to particular instances of a class (instance variables).
global, local, class, instance
what is $
, @
, or @@
prepended to a variable name? This helps mark them as global, instance, and class variables (respectively).
|
|
|
|
Global variables can be declared in two ways. The first is one that’s already familiar to you: you just define the variable outside of any method or class, and voilà! It’s global. If you want to make a variable global from inside a method or class, just start it with a $
, like so: $matz
.
rule of thumb: modularise variables, limit their scope
limit the scope of everything
sandboxing
we can use a class variable to keep track of the number of instances of that class we’ve created. Let’s do that now!
|
|
|
|
you can override/overwrite methods/attributes you inherit from
|
|
When you call super
from inside a method, that tells Ruby to look in the superclass of the current class and find a method with the same name as the one from which super
is called. If it finds it, Ruby will use the superclass’ version of the method.
|
|
|
|
Any given Ruby class can have only one superclass. Some languages allow a class to have more than one parent, which is a model called multiple inheritance. This can get really ugly really fast, which is why Ruby disallows it.
However, there are instances where you want to incorporate data or behavior from several classes into a single class, and Ruby allows this through the use of mixins. We’ll learn about mixins in a later lesson! For now, we’ll demonstrate what happens if you try to do multiple inheritance in Ruby.
|
|
|
|
|
|
Ruby allows you to explicitly make some methods public
and others private
. Public methods allow for an interface with the rest of the program; they say, “Hey! Ask me if you need to know something about my class or its instances.”
private
methods are just that: they’re private to the object where they are defined. This means you can only call these methods from other code inside the object. Another way to say this is that the method cannot be called with an explicit receiver. You’ve been using receivers all along—these are the objects on which methods are called! Whenever you call object.method
, object
is the receiver of the method
.
In order to access private information, we have to create public methods that know how to get it. This separates the private implementation from the public interface, and we’ll go over this in more detail in the next two exercises.
|
|
If we want to both read and write a particular variable, there’s an even shorter shortcut than using attr_reader
and attr_writer
. We can use attr_accessor
to make a variable readable and writeable in one fell swoop.
|
|
|
|
One of the main purposes of modules is to separate methods and constants into named spaces. This is called (conveniently enough) namespacing, and it’s how Ruby doesn’t confuse Math::PI
and Circle::PI
.
|
|
See that double colon we just used? That’s called the scope resolution operator, which is a fancy way of saying it tells Ruby where you’re looking for a specific bit of code. If we say Math::PI
, Ruby knows to look inside the Math
module to get that PI
, not any other PI
(such as the one we created in Circle
).
|
|
We can do more than just require
a module, however. We can also include
it!
Any class that include
s a certain module can use those module’s methods!
A nice effect of this is that you no longer have to prepend your constants and methods with the module name. Since everything has been pulled in, you can simply write PI
instead of Math::PI
.
|
|
When a module is used to mix additional behavior and information into a class, it’s called a mixin. Mixins allow us to customize a class without having to rewrite code!
|
|
|
|
Whereas include
mixes a module’s methods in at the instance level (allowing instances of a particular class to use the methods), the extend
keyword mixes a module’s methods at the class level. This means that class itself can use the methods, as opposed to instances of the class.
You probably also noticed we used underscores in our 1_000_000
(one million). Ruby allows this, and it makes it easier to read big numbers! Cool, no?
|
|
Encapsulation is hiding pieces of functionality and making it unavailable to the rest of the code base. It is a form of data protection, so that data cannot be manipulated or changed without obvious intention. It is what defines the boundaries in your application and allows your code to achieve new levels of complexity. Ruby, like many other OO languages, accomplishes this task by creating objects, and exposing interfaces (i.e., methods) to interact with those objects.
Polymorphism is the ability for different types of data to respond to a common interface. For instance, if we have a method that expects argument objects that have a move
method, we can pass it any type of argument, provided it has a compatible move
method. The object might represent a human, a cat, a jellyfish, or, conceivably, even a car or train. That is, it lets objects of different types respond to the same method invocation.
https://launchschool.com/books/oo_ruby/read/the_object_model
Classes Define Objects
modules are another way to achieve polymorphism in Ruby. A module is a collection of behaviors that is usable in other classes via mixins. A module is “mixed in” to a class using the include
method invocation. Let’s say we wanted our GoodDog
class to have a speak
method but we have other classes that we want to use a speak method with too. Here’s how we’d do it.
|
|
We can use the ancestors
method on any class to find out the method lookup chain.
To disambiguate from creating a local variable, we need to use self.name=
to let Ruby know that we’re calling a method. So our change_info
code should be updated to this:
This tells Ruby that we’re calling a setter method, not creating a local variable. To be consistent, we could also adopt this syntax for the getter methods as well, though it is not required.
|
|
Note that prefixing self.
is not restricted to just the accessor methods; you can use it with any instance method. For example, the info
method is not a method given to us by attr_accessor
, but we can still call it using self.info
:
|
|
While this works, the general rule from the Ruby style guide is to “Avoid self where not required.”
|
|
|
|
Writing @age
directly accesses the instance variable @age
. Writing self.age
tells the object to send itself the message age
, which will usually return the instance variable @age
— but could do any number of other things depending on how the age
method is implemented in a given subclass. For example, you might have a MiddleAgedSocialite class that always reports its age 10 years younger than it actually is. Or more practically, a PersistentPerson class might lazily read that data from a persistent store, cache all its persistent data in a hash.
When you find that you want the same method to be run on a bunch of different objects without having to make a bunch of different if/else
or case
statements, you should start thinking about using a class.
|
|
There are two good times to use class methods: when you’re building new instances of a class that have a bunch of known or “preset” features, and when you have some sort of utility method that should be identical across all instances and won’t need to directly access instance variables.
The first case is called a factory method, and is designed to save you from having to keep passing a bunch of parameters manually to your #initialize
method:
|
|
If a hash (good data storage) and a module (good methods) had a love child, would it be a class (object with methods)?
|
|
Scope is the formal term that represents when you can access a variable or method and when you can’t. It’s nothing explicit in the code (you’re never calling a method named scope
or anything like that); it’s just a concept. If your variable is “in scope” then it’s available for use, otherwise it’s “out of scope”.
Since we don’t want #take_damage
to be visible to anyone on the command line but we DO want it to be visible to the methods inside other instances of Viking
, we call that protected
. protected
provides most of the privacy of private
but lets the methods inside other instances of the same class or its descendents also access it:
In Ruby, a protected method (or protected message handler) can only respond to a message with an implicit/explicit receiver (object) of the same family. It also cannot respond to a message sent from outside of the protected message handler context.
Both protected and private methods cannot be called from the outside of the defining class. Protected methods are accessible from the subclass and private methods are not. Private methods of the defining class can be invoked by any instance of that class. Public access is the default one.
protected
methods can be called by any instance of the defining class or its subclasses.
private
methods can be called only from within the calling object. You cannot access another instance’s private methods directly.
attr_accessor :name, :location
|
|
|
|
|
|
the ensure
branch will execute whether an error/exception was rescued or not. Here, we’ve decided to sleep for 3 seconds no matter the outcome of the open method. (
ruby-doc definition)
retry retries the begin block.
|
|
|
|
|
|
|
|
Modules cannot be used to create objects
Array.ancestors()
Array.ancestors() returns the following array: [Array, Enumerable, Object, Kernel, BasicObject]. In this list, Array, Object, and BasicObject are classes. Array inherits from Object and Object inherits from BasicObject. Enumerable and Kernel are modules. The Enumerable module is included in the Array class and the Kernel module is mixed-in to the Object class (mixed-in is another way of saying that a module is included in a class).
# Sources
Own notes
https://www.theodinproject.com/lessons/ruby-object-oriented-programming
https://launchschool.com/books/oo_ruby/read/the_object_model
https://launchschool.com/books/oo_ruby/read/classes_and_objects_part1#statesandbehaviors
https://rubystyle.guide/#no-self-unless-required
https://stackoverflow.com/questions/1693243/instance-variable-self-vs
https://www.eriktrautman.com/posts/ruby-explained-classes
# Content Lists
If you prefer browsing the contents of this site through a list instead of a graph, you can find content lists here too: