Last active
May 30, 2022 04:29
-
-
Save pongo-pygmaeus/2187be52c335a3ddc6a3d95c5f8753d5 to your computer and use it in GitHub Desktop.
An example of subclassing in Ruby using instance singleton class module extension instead of inheritance.
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 characters
# An example of subclassing in Ruby using instance singleton class module extension instead of inheritance. | |
# Can be thought of a more "rubyesque" version of the common OOP "factory" design pattern. | |
# This work is based on Fred Heath's article "Solving Design Anti-Patterns in Ruby: Fix the Factory" | |
# located here: https://www.sitepoint.com/solving-design-anti-patterns-in-ruby-fix-the-factory | |
class BaseClass | |
attr_reader :text | |
# Since our "inherited" instances won't actually be subclasses | |
# (child classes via inheritance) we should have some way of | |
# describing the "type" to which an instance belongs. | |
# This array gets filled in when specific instances of BaseClass | |
# are instantiated and extended with our custom modules | |
attr_accessor :type | |
def initialize(input_text) | |
@text = input_text | |
@type = [] | |
end | |
def instance_method | |
puts "'#{@text}' in the base class" | |
end | |
end | |
module ChildA | |
def instance_method | |
puts "'#{@text}' in the child_a module" | |
end | |
# Overridden callback defined in Module class | |
# See https://ruby-doc.org/core-2.2.0/Module.html#method-i-extended | |
def self.extended(thing_extending_this_module) | |
# We define the behavior of what the module does | |
# when it's extended by something. This is completely | |
# arbitrary, but we're using it to hold onto "types" | |
# since our pattern isn't using inheritance and we | |
# want some way of identifying our objects class or type | |
# In this case, 'thing_extending_this_module' is the | |
# singleton class of the BaseClass instance. | |
thing_extending_this_module.type << :ChildA | |
end | |
end | |
module ChildB | |
def instance_method | |
puts "'#{@text}' in the child_b module" | |
end | |
# Overridden callback defined in Module class | |
# See https://ruby-doc.org/core-2.2.0/Module.html#method-i-extended | |
def self.extended(thing_extending_this_module) | |
thing_extending_this_module.type << :ChildB | |
end | |
end | |
module SpecialABModule | |
def self.extended(thing_extending_this_module) | |
thing_extending_this_module.type << :SpecialABModule | |
# Change behavior based on the last | |
# module extended before this one (because we want | |
# to honor the proper inheritance chain) | |
case thing_extending_this_module.type[-2] | |
when :ChildA | |
thing_extending_this_module.instance_eval do | |
def special_method | |
p "ChildA is off the chain" | |
end | |
end | |
when :ChildB | |
thing_extending_this_module.instance_eval do | |
def special_method | |
p "ChildB is the bomb" | |
end | |
end | |
else | |
thing_extending_this_module.instance_eval do | |
def special_method | |
p "Invalid extended thing!" | |
end | |
end | |
end | |
end | |
end | |
# 0: Basic instance, no modules | |
instance0 = BaseClass.new("What up?") | |
instance0.instance_method #puts "'What up' in the base class" | |
p instance0.class #BaseClass | |
p instance0.type #[] | |
# 1: Instance with single extend | |
puts | |
instance1 = BaseClass.new("Yo").extend(ChildA) | |
instance1.instance_method #puts "'Yo' in the child_a class" | |
p instance1.class #BaseClass | |
p instance1.type #[:ChildA] | |
# 2: Instance with multiple extends, so ChildB stuff overrides ChildA | |
puts | |
instance2 = BaseClass.new("Hey!").extend(ChildA).extend(ChildB) | |
instance2.instance_method #puts "'Hey!' in the child_b class" | |
p instance2.class #BaseClass | |
p instance2.type #[:ChildA, :ChildB] | |
# 3: Instance with multiple extends, so SpecialABModule adds behavior | |
# based on other extended types | |
puts | |
instance3 = BaseClass.new("Wooo!").extend(ChildA).extend(SpecialABModule) | |
instance3.special_method | |
p instance3.class #BaseClass | |
p instance3.type #[:ChildA, :SpecialABModule] | |
# 4: Instance with multiple extends, so SpecialABModule adds behavior | |
# based on other extended types | |
puts | |
instance4 = BaseClass.new("Wooo!").extend(ChildB).extend(SpecialABModule) | |
instance4.special_method | |
p instance4.class #BaseClass | |
p instance4.type #[:ChildB, :SpecialABModule] | |
# 5: Instance with multiple extends, so SpecialABModule adds behavior | |
# but prints an error because of improper extension | |
puts | |
instance5 = BaseClass.new("Wooo!").extend(SpecialABModule) | |
instance5.special_method | |
p instance5.class #BaseClass | |
p instance5.type #[:SpecialABModule] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment