Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort using static method defined in child classes

I apologize in advance if a similar question has been asked before, it's quite complicated to describe correctly what I am looking for, and I will use an example to explain.

We will work with a base class named Shape with the following child classes : Triangle, Square, Pentagon and Hexagon. The 4 last classes represent a shape with 3, 4, 5 and 6 sides respectively. This information "belongs" to the class itself and not to the instance of those classes.

We will also suppose that each class has a static method returning a given color. Every instance of a given class will share the same color.

What I want to do is to call the static function getColor() of my shapes in the ascending order of their number of sides. Meaning, I want to call :

  1. Triangle.getColor()
  2. Square.getColor()
  3. Pentagon.getColor()
  4. Hexagon.getColor()

Unfortunately, I have the following problems (shared by many programming languages)

  • I can't use Interfaces because the information does not belong to the instances but the classes

  • I can't define the getSideCount() static function inside my Shape class because I would not be able to "override" it in my child classes to get the correct number of sides

child classes

I am not asking for a complete code, only for design advices to manage this problem. Maybe I am totally wrong and I should not go this way. Don't hesitate to criticize and suggest a new way to do this.


If you want a more "concrete" example :

I have a string myString.

I have multiple classes A, B, C defining the toString method.

  • a.toString() returns a string of 1 character
  • b.toString() returns a string of 2 characters
  • c.toString() returns a string of 3 characters

myString is the concatenation of a.toString(), b.toString() and c.toString() : ABBCCC.

Tomorrow, I may want that myString is the concatenation of c.toString(), a.toString() and b.toString() : CCCABB. Thus, I defined a static method in the classes A, B and C returning the position of the representation of the instance in myString. What I want to do is to extract in the correct order the representation of my instances.

The "long" way to do this would be :

index ← 0
if( A.position == 1 )
    aStr ← extract( myString, index, 1 )
    index ← index + 1
elif ( B.position == 1 )
    bStr ← extract( myString, index, 2 )
    index ← index + 2
elif ( C.position == 1 )
    cStr ← extract( myString, index, 3 )
    index ← index + 3
endif

if( A.position == 2 )
    aStr  ← extract( myString, index, 1 )
    index ← index + 1
elif ( B.position == 2 )
    bStr ← extract( myString, index, 2 )
    index ← index + 2
elif ( C.position == 2 )
    cStr ← extract( myString, index, 3 )
    index ← index + 3
endif

if( A.position == 3 )
    aStr  ← extract( myString, index, 1 )
    index ← index + 1
elif ( B.position == 3 )
    bStr ← extract( myString, index, 2 )
    index ← index + 2
elif ( C.position == 3 )
    cStr ← extract( myString, index, 3 )
    index ← index + 3
endif

Thank you in advance for your time and your help.

like image 723
Hellium Avatar asked Feb 23 '26 11:02

Hellium


1 Answers

There are several ways you can approach this.

  1. Probably the simplest one would be pattern matching, if your language supports it (Scala, F#, Haskell). With support for (exchaustive) pattern matching, you would differentiate on the object type:

    // syntax will differ from language to language,
    // but you should get the idea
    
    def getColor(s: Shape): Color = s match {
      case Triangle => Color.Blue
      case Square   => Color.Yellow
      ...
    }
    

    This effectively moves the getColor method outside of the class, but the cool thing that you won't get with a plain "switch-case" is that compiler will warn you if you don't cover all the cases, so you effectively get the same compile-time guarantee as with abstract methods.

  2. In general, what you need is a way to map from a type to a value, so you might as well simply use a map. In C#, you would use a Dictionary<Key, Value>:

    static readonly Dictionary<Type, Color> _shapeToColor;
    
    // use this to register a color for each type somewhere at the beginning
    public static Register<T>(Color color) where T : Shape
    {
        _shapeToColor[typeof(T)] = color;
    }
    
    // generic version for compile-time read
    public static Color GetColor<T>() where T : Shape => _shapeToColor[typeof(T)];
    
    // or a parameterized version for run-time read
    // (but might fail if incorrect type passed)
    public static Color GetColor(Type t) => _shapeToColor[t];
    
    // or generally
    public static Color GetColor(object x) => _shapeToColor[x.GetType()];
    

    This is allows you greater flexibility, but you can easily forget to register a color for a newly added type. Also, you would probably register a ShapeInfo class for each color, and put there more than just a Color, so that you don't have several dictionaries.

  3. Another option (again a C# example, sorry if you're using Java) is to actually combine instance methods with the previous idea (static dictionary) and some reflection. The idea would then be something like:

    interface IShape
    {
        Color GetColor();
    }
    
    class Triangle : IShape
    {
        public Color GetColor() => Color.Blue;
    }
    
    class Square : IShape
    {
        public Color GetColor() => Color.Red;
    }
    
    static void InitializeValues()
    {
        // use reflection to iterate through all types
        var asm = Assembly.GetAssembly(typeof(IShape));
        foreach (var t in asm.GetTypes())
        {
            // find all Shapes 
            if (t.IsInterface ||
                t.IsAbstract ||
                !typeof(IConfigTokenizer).IsAssignableFrom(t))
                continue;
    
            // instantiate a temporary shape
            var inst = (IShape)Activator.CreateInstance(t);
    
            // but we are only interested in creating
            // a mapping to the result of GetColor
            _shapeToColor[t] = inst.GetColor();
        }
    }
    

But now that you've updated the question with that toString stuff, I am no longer sure what your actual goal is. :)

like image 183
Groo Avatar answered Feb 25 '26 05:02

Groo