I'm wanting to create a pair of classes. The second of the pair sub-classes the first, is nested within the first, and only has the addition of being an Enumerator. Like so:
MyClass # this is the parent class
MyClass::Enumerator # it has an Enumerator which sub-classes MyClass
Further to this, when sub-classing MyClass, the subclass should get its own iterator which is also nested within it. Like so:
MySubClass = Class.new(MyClass) # This class sub-classes MyClass
MySubClass::Enumerator # It has an Enumerator which sub-classes MySubClass
I've actually created the code and it works, but I'm wondering if there is a better way or an existing design pattern to achieve this. The code I've written to make this work is the following:
module EnumerableCompanionClass
extend ActiveSupport::Concern
# this ensures the parent class gets it's companion class
included { create_enumerable_class }
module ClassMethods
# this ensures an descendants get their companion class
def inherited(sub)
# need to make sure this doesn't get called for the companion class.
# at this point it's still an anonymous class
# this is to stop infinite recursion
unless sub.name.blank? || sub < Enumerable
sub.create_enumerable_class
end
end
def create_enumerable_class
# only create the companion class once
@enumerable_class ||=
# create the companion class as a subclass of the current class
# but only if self is a class (not a module) and is not the companion class
if self.is_a?(Class) && !(self < Enumerable)
enumerable_class = Class.new(self) do
include ::Enumerable
# my custom each method
def each(&block); end
end
# define a constant for the companion class
const_set('Enumerable', enumerable_class)
end
end
end
end
And this is the code to check that it works as expected:
class A
include EnumerableCompanionClass
def test1; 'this should work!'; end
end
A::Enumerable.parent == A # true
A::Enumerable.new.test1 # 'this should work!'
class B < A
def test2; 'this should also work'; end
end
B::Enumerable.parent == B # true
B::Enumerable.new.test1 # 'this should work!'
B::Enumerable.new.test2 # 'this should also work'
class C < B
def test3; 'this should triple work'; end
end
C::Enumerable.parent == C # true
C::Enumerable.new.test1 # 'this should work!'
C::Enumerable.new.test2 # 'this should also work'
C::Enumerable.new.test3 # 'this should triple work'
So as you can see from the output, it all works as expected.
But it's messy and not very intuitive. For starters, the included method is only there for the parent. Likewise, the inherited method is only there for the sub-classes. The checks for anonymous classes and unnamed classes are there to prevent infinite recursion (we don't want the Enumerator classes to have it's own Enumerator).
I'm happy that it works but I'm sure there has to be a better way. I've tried other methods and this is the only one that I've gotten to work correctly so far.
Does any one know the canonical way to achieve this, or a better design pattern?
Thanks in advance!
Aucun commentaire:
Enregistrer un commentaire