-
Notifications
You must be signed in to change notification settings - Fork 1
Classes and Modules
#About classes Ruby classes are broadly similar to classes in object-oriented languages such as Java, however there are important differences. Key characteristics of Ruby classes include:
- They are Objects themselves (and therefore they have a class, which is Class (and Class has a class, which is itself))
- They are not a static specification processed at compile time as in e.g. Java - they are defined at runtime
- They are almost completely mutable - you can add, modify and delete methods, constants and instance/class variables at runtime, however you can't change what its superclass is (at least not directly)
- They don't have a namespace of themselves - class objects are typically assigned to a Ruby constant and accessed/instantiated by accessing this constant
- Ruby constants are always attached to a module or class, and a namespace hierarchy emerges - top-level classes are objects in constants typically attached to the Object class, other classes can be stored in constants attached to top-level classes, etc.
- Methods are always attached to a class (or a module) and can be called on (and only on) instances of that class or its subclasses - there is no analog for the "static method" in other languages, i.e. methods which can be called on the class itself (but see singleton classes below)
- There is almost no Ruby syntax for defining classes - classes are defined by creating a class object with a specified superclass (typically using the
classstatement) and then executing Ruby code which adds components to the class object such as methods, constants and class variables. This code typically includes (but is not limited to): - the
defstatement, which adds a method to the in-scope class object - calls to methods defined on the Class or Module classes (and therefore callable on class objects), such as
attr_accessor, which sets up a publically read-only property - nested
classstatements which create classes within the outer class's namespace - Even classes in the Ruby core library are mutable - changing methods in core objects as part of a Ruby program is called "monkey patching"
#About modules Ruby modules are a bit like abstract classes - they are typically used in these scenarios:
- As a container for methods which can be "mixed in" to another module or class - mixing in a module is similar to inheriting all the methods in the module
- As an element in the namespace hierarchy for constants, containing only other constants (such as classes and modules)
Modules are very similar to Ruby classes with the following key differences:
- They can't be instantiated
- They don't have a superclass
- They can be "mixed in" to another module/class, whereas classes can't
- The Module class can be subclassed - Ruby disallows subclassing of the Class class
#About singleton classes and metaclasses If you have some knowledge of Ruby, you may already know that at face value Ruby appears to support some methods that don't fit in with the characteristics described above. In Ruby, you can:
- Define a method on a regular object, which can only be called on that object
- Define a method on a class or module which can be called on that class or module, as opposed to on instances of it
It's important to understand that behind the scenes, there is nothing special about these methods. What is special is that Ruby is defining these methods on the "singleton class" of the object, class or module.
All objects in Ruby have a singleton class (at least in concept, although in implementation they may not be created until they are needed). A singleton class is a class whose only instance is the one object it's attached to - methods defined on an object's singleton class are specifically callable on that object, and not on other objects which are also instances of the the object's "real" class. Therefore, Ruby can:
- Define a method specific to a regular object, by defining it on the object's singleton class
- Define a "static method" on a class/module which can be called directly on the class/module, by defining it on that class/module's singleton class
The singleton class of a class is called a "metaclass" - this distinction is made only because metaclasses have more complicated inheritance relationships than singleton classes, and the reason for this is that metaclasses are attached to classes which themselves have superclasses and can have mixed-in modules, whereas singleton classes are attached to objects which don't have any superclass or mixins.
#How all this works It is confusing to try to understand the relationships and interactions between objects, classes, modules, mixins, superclasses, singleton classes and metaclasses - objects can be related to classes and modules through all of these mechanisms, so how exactly does Ruby decide what set of methods can be called on an object, and what class or module's method implementation will be used?
The key to understanding this is to look at Ruby's internal model for these relationships, which is quite elegant and simple because it distills all of this into a single class for each object and a single-inheritance tree for each class.
A review of the fundamental classes in Ruby and the internal state maintained for them
BasicObject:
- Is the fundamental root class of the whole Ruby class inheritance tree - all classes ultimately inherit from it
- All Ruby objects are instances of this class
- This class is not usually used directly - it exists because if classes don't want to have their method and constant namespaces populated by everything available in the Object class, they can avoid it by explicitly inheriting from this class
- State maintained for instances of this class and its subclasses:
- The instance's class (i.e. BasicObject or one of its subclasses)
- Instance variables
Object:
- Superclass is BasicObject
- Is in general the fundamental root class for all Ruby classes, unless classes explicitly decide to inherit from BasicObject instead
- Is an ancestor superclass of every class in the Ruby core library
- Is effectively the root constant namespace in Ruby
- By default, newly created classes inherit from Object
Module
- Superclass is Object
- State maintained for instances of this class and its subclasses:
- Methods attached
- Constants
- Class variables
- Superclass (this is nil to begin with in Modules, and you can't directly set this on Modules as you can with Classes)
Class
- Superclass is Module
- Ruby disallows subclassing Class
- State maintained for instances of this class:
- A flag indicating whether it is a singleton class or not
- Superclass is mandatory (except for BasicObject)
Every object has, in concept, a singleton class - however in practice they are only created when they are needed, otherwise there would be an infinite proliferation of singleton classes to create because singleton classes themselves also have singleton classes.
Lets say, for example, we have:
- a class
Personwhose superclass is Object - a class
Developerwhose superclass is Person - an object
devof classDeveloper
Singleton classes on non-class objects are simply inserted as a hidden element in the object's inheritance tree. At face value, the inheritance tree appears to be this:
dev <instanceof> Developer <subclassof> Person <subclassof> Object <subclassof> BasicObject
Internally, dev's singleton class is inserted into the mix - it is internally the class of dev, and its superclass is the "real" class of dev
dev <instanceof> <singletonclass:dev> <subclassof> Developer <subclassof> Person <subclassof> Object <subclassof> BasicObject
Note that the singleton classes of dev's class and it's superclasses (e.g. Developer, Person) are not included in dev's inheritance tree, and therefore dev can't access methods defined thereon. This is why an instance of a class can't access the class's "static methods", i.e. it's class's singleton class's methods.
Singleton classes defined on classes (including other singleton classes) are called metaclasses and are implemented slightly differently. Lets start with looking at what would happen if they are implemented the same way, with the Developer class as an example:
Developer <instanceof> <metaclass:Developer> <subclassof> Class <subclassof> Module <subclassof> Object <subclassof> BasicObject # if the world was simple...
There's nothing wrong with this, however Ruby has decided to add a bit extra. In the "static method" paradigm of other languages, a subclass inherits the static methods of its superclass - in Ruby, can subclasses be made to inherit singleton methods from their superclasses, so that e.g. the Developer class inherits "static methods" from the Person class?
Yes, they can, and do - a subclass's singleton class inherits from its parent's singleton class:
Developer <instanceof> <metaclass:Developer> <subclassof> <metaclass:Person> <subclassof> <metaclass:Object> <subclassof> <metaclass:BasicObject> <subclassof> ??? # Almost...
OK, that almost worked, but there's 2 unresolved problems with the scenario above:
- What is the superclass of the metaclass of BasicObject?
- Less obviously - developer's metaclass is no longer inheriting from Class, as it was in the basic scenario above. This means that you can't call Class methods on the Developer class, which would be wrong because Developer is obviously an instance of Class.
How could Ruby resolve this? Well, it sets the superclass of BasicObject's metaclass to Class.
Developer <instanceof> <metaclass:Developer> <subclassof> <metaclass:Person> <subclassof> <metaclass:Object> <subclassof> <metaclass:BasicObject> <subclassof> Class <subclassof> Module <subclassof> Object <subclassof> BasicObject # Ruby's internal reality
It turns out that these rules are sufficient to describe all of Ruby's singleton classes and metaclasses, and the result is a consistent and correct model. Even for metaclasses of metaclasses (meta-metaclasses), etc. - a diagram showing the network of singleton and superclasses looks quite complicated and bewildering, but it's really just an extrapolation of the rules and principles described here. Here's a diagram from the Ruby Class API documentation:
+---------+ +-...
| | |
BasicObject-----|-->(BasicObject)-------|-...
^ | ^ |
| | | |
Object---------|----->(Object)---------|-...
^ | ^ |
| | | |
+-------+ | +--------+ |
| | | | | |
| Module-|---------|--->(Module)-|-...
| ^ | | ^ |
| | | | | |
| Class-|---------|---->(Class)-|-...
| ^ | | ^ |
| +---+ | +----+
| |
obj--->OtherClass---------->(OtherClass)-----------...
A Ruby Language Reference
Copyright © by Michael Hore, 2016.
Introduction to This Document
Ruby Elements
- Classes and Modules
- Methods
- Blocks, Procs and Lambdas
- Execution Context and Closures
- Variables, Constants and Namespaces
- Types and Literals
- Ruby Expressions
- Operators
Syntax Grammar
Exceptions and Throw
Ruby Sourcefiles and Libraries
Multi Threading
Execution and Lifecycle