0

According to Why define a Java object using interface (e.g. Map) rather than implementation (HashMap), I know I should declare the most abstract type as possible. However, I found that a situation that declare the exact type instead of the most abstract type may help me to write more maintainable code, for example, I'm using a game engine, cocos2d-x, which has Sprite and Label, both of them are the child class of Node. First start with code that written with most concrete type, which is a UI scene that shows an image and a welcome text:

Non most abstract type version:

Sprite* sprite = new Sprite("welcome.png");
this->addChild(sprite);
// ...

Label* label = new Label("Welcome, "+playerInfo.username,"",32);
this->addChild(label);
LabelAnimationHelper::showFirstAppearAnimation(label);
...
    
class LabelAnimationHelper{
public:
    static void showFirstAppearAnimation(Label* label){
        label->setOpacity(0);
        label->setScale(2);
        label->runAction(FadeIn::create(1));
        label->runAction(ScaleTo::create(1,1));
    }

    static void showMarqueeAnimation(Label* label){
        // ...
    }
}

which LabelAnimationHelper is a class that suppose to set different animations for Label only, but not for Sprite or other UI types. However, according to "declare most abstract type" rule, I should modify the code above to:

Most abstract type version:

Node* sprite = new Sprite("welcome.png");
this->addChild(sprite);
// ...

Node* label = new Label("Welcome, "+playerInfo.username,"",32);
this->addChild(label);
LabelAnimationHelper::showFirstAppearAnimation(label);
...
    
class LabelAnimationHelper{
public:
    static void showFirstAppearAnimation(Node* label) {
        label->setOpacity(0);
        label->setScale(2);
        label->runAction(FadeIn::create(1));
        label->runAction(ScaleTo::create(1,1));
    }

    static void showMarqueeAnimation(Node* label) {
        // ...
    }
}

I found the "Most abstract type version" may have a problem that the "Non most abstract type version" hasn't: I may call

LabelAnimationHelper::showFirstAppearAnimation(sprite);

wrongly, which LabelAnimationHelper is supposed to work with Label only. So I would rather keep the exact type to avoid this situation. Also I found declaring the most concrete type version is more readable: In game development, I usually need to find specific UI (eg: welcome Label) in a method to change. Showing the exact type in left hand side helps me to filter other irrelevant UI (eg: Sprite) quickly and hence faster to find the part of code that I need to change.

So my question is, is the reason above rationale to declare the exact type instead of most abstract type?

4
  • 5
    "the most abstract type as possible" not "the most abstract type", which is always Object
    – Caleth
    Feb 19 at 9:21
  • I'm not familiar with cocos2d-x. What would be the harm of passing a non-Label Node to showFirstAppearAnimation? Feb 19 at 9:31
  • 3
    If your requirements explicitly state that those animations should only be done with labels, then that is an important point in your consideration of the the "most abstract type possible" should be. That requirement then effectively states that it cannot be a parent type of 'Label'. Feb 19 at 9:35
  • "Showing the exact type in left hand side helps me to filter other irrelevant UI (eg: Sprite) quickly and hence faster to find the part of code that I need to change." This feels like an issue you have created yourself by using "helper" classes. If you wish to extend label with new functionality, you should either extend the class itself or compose it into a larger object. As a very good rule of thumb, a class called Helper is generally a code smell. Feb 19 at 9:37

3 Answers 3

7

The rule-of-thumb to write code which deals with

"the most abstract type as possible"*

does not mean

the most abstract type which is syntactically possible

but

the most abstract type for which the code is semantically correct.

So if showFirstAppearAnimation will only work correctly for Label objects, but not for Sprite objects, then it makes no sense to use the Node type, even if by chance the code might compile for Nodes.

3

I think you can argue this one either way.

Why you should change to Node

You may discover that you need to bring other node types into view in the same way as you bring labels into view today. By having a ready-made function which does this for all node types, you reduce the chance that the next developer decides to write their own separate function for doing this, causing both duplicated effort and possibly a mismatch in the UI if they for some reason use slightly different parameters to all the individual functions that are called.

Why you shouldn't change to Node

YAGNI. Refactor it when you actually find that second use case, not for some hypothetical second use case which might never occur.

How do you work out which of these paths to take?

Only you/your team have the full context of your system in order to weigh up the benefits and risks. Every decision in software engineering is a trade-off, good engineers are the ones that make good decisions more often.

2

You need to use the most abstract type that actually works.

If you're using Java, then everything inherits from Object. But that doesn't mean that every method in your code should take parameters of type Object. You would be forever checking that you've been passed the right sort of thing, before doing what you want to do.

So if your method requires specific properties/behaviour of the objects passed to it, then use the type system to limit it to those classes only. That way errors are caught at compile time, not runtime.

But use the most abstract class that has those properties/behaviours, otherwise you'll create several methods that do the same things, but each only does it on one specific class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.