Skip to content

Instantly share code, notes, and snippets.

@pongo-pygmaeus
Last active May 30, 2022 04:29
Show Gist options
  • Save pongo-pygmaeus/2187be52c335a3ddc6a3d95c5f8753d5 to your computer and use it in GitHub Desktop.
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.
# 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