M
Michael T. Richter
[Note: parts of this message were removed to make it a legal post.]
I've been trying to crack this nut on my own for the past week or so,
but can't get a solution that doesn't suck. I'd appreciate any
assistance that could be tossed my way by some of the more expert Ruby
users.
The situation is this: I have a stream of bytes in a particular format
(I have no control over this format). The stream corresponds to types
in another programming language. The structure of the stream is
basically a boilerplate wrapper around a variety of different types
internally (some simple types, some complex types which can contain
other types both simple and complex).
I'm treating the external wrapper as one type which:
1. sanity checks (makes sure it has the right wrapper tag, makes
sure the internal object's tag is a real one, etc.);
2. decompresses the stream if necessary since the data internal to
it can often be very large so is frequently compressed;
3. instantiates the actual objects based on the internal object
tags.
Now the internal objects themselves have tags which reference them but
which must go with the objects because once extracted they need to be
manipulated, moved around, possibly embedded in other wrapped types,
etc. So the external wrapper, when parsing, needs to read the tag and
dispatch object creation to the appropriate instance.
I've got all this working nicely, I should mention. It's all functional
and glorious and such. It's just fugly. REALLY fugly. It breaks DRY
in so many ways it's frightening. Here's an example of what I mean:
class Bar
@@registry = {}
def External.register tag, klass
@@registry[tag] = klass unless @@registry.has_key? tag
end
# other stuff
end
class Foo1
[TAG1 = 1, TAG2 = 2, TAG3 = 3].each { |tag| Bar.register(tag, Foo1) }
# other stuff
end
class Foo2
[TAG4 = 4, TAG5 = 5, TAG6 = 6].each { |tag| Bar.register(tag, Foo1) }
# other stuff
end
(Note that this is a simplified representation of what I'm doing. I
have much better error checking, etc. in the registry in real life.)
What I like about this setup is that classes are registered
automagically upon the file being loaded. There's no chance for error
if someone forgets to call the registration function. Further, the
External class is instantly usable because by the time you can use it it
already has every tag and associated class registered. That being said,
I've shut down one avenue of bugs by introducing a whole new set of
them. Take a close look at class Foo2 to see if you can spot the bug,
for example.
Basically I'm relying on several non-DRY chunks of code, each of which
can cause me problems in the future. First, in each of the Foo* classes
I'm repeating ".each { |tag| Bar.register(tag, Foo1) }". This means
that if I change the way I choose to register classes for parsing, I
have to change each and every client class because I RYed. Given that
there's currently about two dozen such classes to implement, you can see
that this would make refactoring a real bitch. The second, more subtle,
problem is exemplified in Foo2. Because the code is mostly boilerplate
-- I'm changing the tags and the instantiating class only -- it's very
easy to make a cut-and-paste bug like telling the registry to
instantiate Foo1 instead of Foo2 for tags 4, 5 and 6. (How do I know
it's easy? I did it to myself three times THIS MORNING.) Now catching
this is easy because I have each instantiating class sanity-check to
make sure the right tag is being passed in, but I'd prefer a more robust
solution.
Were the registration to take place in an instance method, this wouldn't
be a problem. I'd pass self.class in and likely make the method that
does the registration something in the parent class so that all the
child classes would have to do is call super in initialize to get the
glorious benefits of knowing its own class. Unfortunately, because of
the requirement that the registration happen out of any actual instance
executing, I have no self to get the class from.
The only way I've seen so far to get around this problem is fuglier than
the problem: have boilerplate code evaled to create a class instance.
I'm hoping there's something better out there that I've overlooked.
Any suggestions?
I've been trying to crack this nut on my own for the past week or so,
but can't get a solution that doesn't suck. I'd appreciate any
assistance that could be tossed my way by some of the more expert Ruby
users.
The situation is this: I have a stream of bytes in a particular format
(I have no control over this format). The stream corresponds to types
in another programming language. The structure of the stream is
basically a boilerplate wrapper around a variety of different types
internally (some simple types, some complex types which can contain
other types both simple and complex).
I'm treating the external wrapper as one type which:
1. sanity checks (makes sure it has the right wrapper tag, makes
sure the internal object's tag is a real one, etc.);
2. decompresses the stream if necessary since the data internal to
it can often be very large so is frequently compressed;
3. instantiates the actual objects based on the internal object
tags.
Now the internal objects themselves have tags which reference them but
which must go with the objects because once extracted they need to be
manipulated, moved around, possibly embedded in other wrapped types,
etc. So the external wrapper, when parsing, needs to read the tag and
dispatch object creation to the appropriate instance.
I've got all this working nicely, I should mention. It's all functional
and glorious and such. It's just fugly. REALLY fugly. It breaks DRY
in so many ways it's frightening. Here's an example of what I mean:
class Bar
@@registry = {}
def External.register tag, klass
@@registry[tag] = klass unless @@registry.has_key? tag
end
# other stuff
end
class Foo1
[TAG1 = 1, TAG2 = 2, TAG3 = 3].each { |tag| Bar.register(tag, Foo1) }
# other stuff
end
class Foo2
[TAG4 = 4, TAG5 = 5, TAG6 = 6].each { |tag| Bar.register(tag, Foo1) }
# other stuff
end
(Note that this is a simplified representation of what I'm doing. I
have much better error checking, etc. in the registry in real life.)
What I like about this setup is that classes are registered
automagically upon the file being loaded. There's no chance for error
if someone forgets to call the registration function. Further, the
External class is instantly usable because by the time you can use it it
already has every tag and associated class registered. That being said,
I've shut down one avenue of bugs by introducing a whole new set of
them. Take a close look at class Foo2 to see if you can spot the bug,
for example.
Basically I'm relying on several non-DRY chunks of code, each of which
can cause me problems in the future. First, in each of the Foo* classes
I'm repeating ".each { |tag| Bar.register(tag, Foo1) }". This means
that if I change the way I choose to register classes for parsing, I
have to change each and every client class because I RYed. Given that
there's currently about two dozen such classes to implement, you can see
that this would make refactoring a real bitch. The second, more subtle,
problem is exemplified in Foo2. Because the code is mostly boilerplate
-- I'm changing the tags and the instantiating class only -- it's very
easy to make a cut-and-paste bug like telling the registry to
instantiate Foo1 instead of Foo2 for tags 4, 5 and 6. (How do I know
it's easy? I did it to myself three times THIS MORNING.) Now catching
this is easy because I have each instantiating class sanity-check to
make sure the right tag is being passed in, but I'd prefer a more robust
solution.
Were the registration to take place in an instance method, this wouldn't
be a problem. I'd pass self.class in and likely make the method that
does the registration something in the parent class so that all the
child classes would have to do is call super in initialize to get the
glorious benefits of knowing its own class. Unfortunately, because of
the requirement that the registration happen out of any actual instance
executing, I have no self to get the class from.
The only way I've seen so far to get around this problem is fuglier than
the problem: have boilerplate code evaled to create a class instance.
I'm hoping there's something better out there that I've overlooked.
Any suggestions?