1

I have a question about naming multiple classes that share similar functionality. I am working on a scientific API and have the following classes/interfaces:

public interface IRange<T>{
    T Minimum {get;}
    T Maximum {get;}
    // A few other methods that aren't important
}

public class Range<T> : IRange<T> where T: IComparable<T> {
    public T Minimum {get; protected set;}
    public T Maximum {get; protected set;}

    public Range(T minimum, T maximum) {
        Minimum = minimum;
        Maximum = maximum;
    }
}

For my API, I work with double ranges a lot (i.e., Range<double>), so I made an another class called MassRange. This class also has a few new constructors and properties shown below:

public class MassRange : Range<double>, IRange<double> {
    public double Width { get { return Maximum - Minimum;} }
    public double Mean { get { return (Maximum + Minimum) / 2.0;} }

    public MassRange(double mean, MassTolerance width) {
        Minimum = mean - width.Value;  // pseudo-code
        Maximum = mean + width.Value;
    }
}

Conceptually, I also have another type of Range<double> called a MzRange, that shares all the same structure and functionality as MassRange but I want to keep separate in the API. They act exactly the same and store the same types of data, but in terms of the science, they are different and distinct.

So I considered renaming the MassRange class to a more generic name of DoubleRange and then have both MassRange : DoubleRange and MzRange : DoubleRange designed like so:

public MzRange : DoubleRange, IRange<double> {}

public MassRange : DoubleRange, IRange<double> {}

But I don't really care for the name DoubleRange, and would rather not expose it publicly through my API. Is exposing two distinct types with the same functionality even appropriate? Should I just come up with a better name for DoubleRange and forgo MzRange and MassRange? Can I make DoubleRange internal or something so that it is not exposed through the API but can still be used?

This seems to be a case for Extension Properties but I know they don't currently exist.

Community
  • 1
  • 1
Moop
  • 3,414
  • 2
  • 23
  • 37

3 Answers3

1

There doesn't seem to be any reason you can't accomplish this with standard extension methods:

public static double GetWidth(this IRange<double> range) {
    return range.Maximum - range.Minimum;
}

public static double GetMean(this IRange<double> range) {
    return (range.Maximum + range.Minimum) / 2.0;
}

And then you can call it from any implementation of IRange:

var massRange = ...
var mean = massRange.GetMean();

I know it may not have the syntactic appeal of extension properties, but it's a clean solution that gets around having to create that DoubleRange class.

p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • I definitely agree with what you're saying here. They should implement their needs in the way that Linq is implemented. A lot of generic interfaces and a lot of extension methods. +1 – Michael J. Gray Mar 25 '14 at 17:26
  • This works for the properties, but does not help with the new constructor I added to DoubleRange. – Moop Mar 25 '14 at 17:32
  • @Moop It wouldn't provide you any constructors, but presumably you'd have different constructors on the `MassRange` and `MzRange` class that your `DoubleRange` wouldn't have anyway, right? Does `MzRange` take an `MzTolerance`? Of course you can create a static factory method depending on your exact needs, but it's not entirely clear from the question exactly what that would be. – p.s.w.g Mar 25 '14 at 17:39
0

This is absolutely appropriate. Besides the logical distinction, it gives you the possibility to evolve the two classes independently from each other in future.

Even if elephants and birds are animals (both inherit from animals) and both have the same set of properties (they don't add or override members), you might want to add a Fly method to birds in future. I assume that elephants named Dumbo do not exist in real world scenarios :-)

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • Why wouldn't you have an `Animal` class where `Elephant` and `Bird` inherit from it? The `Bird` and `Elephant` classes are then not identical, but rather they are both inheriting from `Animal` which contains base property like `Weight` or `Height` or some common property for all animals. You wouldn't want to make a `Bird` and `Elephant` with duplicate properties all over the place; that would be a poor design. – Michael J. Gray Mar 25 '14 at 17:23
  • 1
    @MichaelJ.Gray I can see what Oliver is saying, but I suppose `Elephant` and `Bird` may not be the best metaphor here. `Elephant` and `Car` may be better. Both have a certain weight and height, and the both even have a trunk. However, they are so different, it might warrant designing completely different hierarchies. Still, I'd probably design functional interfaces around them (e.g. `Car : IHasWheels`) to describe what you can actually *do* with them. – p.s.w.g Mar 25 '14 at 17:32
  • @MichaelJ.Gray: By "elephants and birds are animals" I mean that both inherit from `animal`. And by "both have the same set of properties" I mean that they do not provide additional members besides what they inherit from `animal`. – Olivier Jacot-Descombes Mar 25 '14 at 17:36
  • It is fine to create a Bird and Elephant classes, make them inherit from Animal, and keep it there. It isn't to have both classes inherit Animal, then have them separately implement the exact same functionality. – idmadj Mar 25 '14 at 17:44
  • @p.s.w.g I agree functional interfaces at that point would be beneficial. Thanks for giving that example, it made more sense. – Michael J. Gray Mar 25 '14 at 17:44
0

Having two types with the exact same functionality isn't appropriate as any change made to one type will need to be made to the other.

Unless you know that MassRange and MzRange will be differentiated at some point in the future, providing a better name for DoubleRange and forgoing MassRange/MzRange is the way to go. And even then, any shared functionality would need to be put in a parent type.

You can always provide some syntactic sugar to help users. For instance, you could create "dumb" classes called MassRange and MzRange, which simply extend DoubleRange. I'd keep the original, well named class exposed, to encourage users to use it, but if you really want to hide it, just use the protected accessor instead of public.

idmadj
  • 2,565
  • 2
  • 19
  • 23