I wonder what is the best way in terms of strict OOP to add functionality to built-in types like Strings or integers or more complex objects (in my case the BitSet class).
To be more specific - I got two scenarios:
Now I wonder what the best practices for implementing this would be.
I could e.g. create a new Class "BitSetEx" extending from BitSet and add my methods. But I don't like the idea since this new class would need describing name and "BitSetWithConversionMethods" sound really silly.
Now I could write a class consisting only of static methods doing the conversions.
Well I got a lot of ideas but I wan't to know what would be the "best" in sense of OOP.
So could someone answer me this question?
There are a few approaches here:
Firstly, you could come up with a better name for the extends BitSet
class. No, BitsetWithConversionMethods
isn't a good name, but maybe something like ConvertibleBitSet
is. Does that convey the intent and usage of the class? If so, it's a good name. Likewise you might have a HashableString
(bearing in mind that you can't extend String
, as Anthony points out in another answer). This approach of naming child classes with XableY
(or XingY
, like BufferingPort
or SigningEmailSender
) can sometimes be a useful one to describe the addition of new behaviour.
That said, I think there's a fair hint in your problem (not being able to find a name) that maybe this isn't a good design decision, and it's trying to do too much. It is generally a good design principle that a class should "do one thing". Obviously, depending on the level of abstraction, that can be stretched to include anything, but it's worth thinking about: do 'manipulating the set/unset state of a number of bits' and 'convert a bit pattern to another format' count as one thing? I'd argue that (especially with the hint that you're having a hard time coming up with a name) they're probably two different responsibilities. If so, having two classes will end up being cleaner, easier to maintain (another rule is that 'a class should have one reason to change'; one class to both manipulate + convert has at least 2 reasons to change), easier to test in isolation, etc.
So without knowing your design, I would suggest maybe two classes; in the BitSet
example, have both a BitSet
and (say) a BitSetConverter
which is responsible for the conversion. If you wanted to get really fancy, perhaps even:
interface BitSetConverter<T> {
T convert(BitSet in);
BitSet parse(T in);
}
then you might have:
BitSetConverter<Integer> intConverter = ...;
Integer i = intConverter.convert(myBitSet);
BitSet new = intConverter.parse(12345);
which really isolates your changes, makes each different converter testable, etc.
(Of course, once you do that, you might like to look at guava and consider using a Function, e.g. a Function<BitSet, Integer>
for one case, and Function<Integer, BitSet>
for the other. Then you gain a whole ecosystem of Function
-supporting code which may be useful)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With