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 possible, but the question is about template class or containers, how about the non container class? For example, suppose I'm using a UI framework, cocos2d-x, which Label and Sprite are extended from Node, and the code to create the screen of UI:

Sprite* titleSprite=Sprite::create("welcome.png");
this->addChild(titleSprite);
titleSprite->setPosition(300,100);

Label* titleLabel=Label::create("Welcome,"+userInfo.name,"customFontType",32);
this->addChild(titleLabel);
//titleLabel->setFontColor(Color.RED);
titleLabel->setPosition(360,100);

It is not declaring most abstract type, so modified as follows:

Node* title1Node=Sprite::create("welcome.png");
this->addChild(title1Node);
title1Node->setPosition(300,100);

Node* title2Node=Label::create("Welcome,"+userInfo.name,"customFontType",32);
this->addChild(title2Node);
//title2Node->setFontColor(Color.RED);
title2Node->setPosition(360,100);

However, I found some disadvantages after declaring most abstract type:

  1. It becomes harder to find a specific label from the variable name, eg: if I want to change a specific label, I may try to search for suffix of variables first, declaring most abstract type erases the extra information about the variables from suffix. Note: I don't want to declare with Node but name with suffix Sprite,eg:
Node* titleSprite=Sprite::create("welcome.png");

because I think it is misleading to have a variable name suffix different from the type actually.

  1. There may some test code, which depends on the child type, eg:
//titleLabel->setFontColor(Color.RED);

which is necessary for testing (eg: let a new teammate to see the actual Label in the screen easily), but not required in production. Declaring with most abstract type voids the comment code, and becomes less convenient to run the test code.

  1. If there are many UI components with same suffix, it may increase the chance of typing the desired UI variable name incorrectly, eg:

non most abstract type:

Sprite* brickSprite=...
Label* blockLabel=...
//brickLabel->setVisible(false);
//wrongly typing "brickLabel" would not compile because brickLabel does not exist

most abstract type:

Node* brickNode=...
Node* blockNode=...
//brickNode->setVisible(false);
//wrongly typing "brickNode" would compile...
  1. I may accidentally using the wrong UI because of less strict typing, similar to problem of "primitive obsession", eg: I want to show fade in effect on Sprite but not Label (Which the fade in function is on parent class Node):

non most abstract type version:

class MyUIPageNode : Node{
    private:
        Sprite* sprite=NULL;
        Label* label=NULL;

    virtual void init(){
        .
        .
        .
        //interchanges 2 UI accidentally
        this->sprite=titleLabel;
        this->label=titleSprite;
        this->sprite->runAction(FadeIn::create(0.5));
        //not compile because titleLabel is not Sprite
    }
}

most abstract type version:

class MyUIPageNode : Node{
    private:
        Node* sprite=NULL;
        Node* label=NULL;

    virtual void init(){
        .
        .
        .
        //interchanges 2 UI accidentally
        this->sprite=titleLabel;
        this->label=titleSprite;
        this->sprite->runAction(FadeIn::create(0.5));
        //can compile, and fadeIn the Label actually instead of Sprite
    }
}

I think the code (especially for UI framework) declaring the exact type is more readable , more natural and less error prone. So my question is, are the problems above reasons to not declaring most abstract type?

3
  • C++ and Java are wildly different languages with very little in common. The "best practice" idioms in one are often considered poor practice in the other; in part because C++ compiles down to native/un-managed code without all the run-time safety and protection provided by the JVM, thus leading to completely different constraints and considerations. Commented Sep 29, 2023 at 7:46
  • For a private field created by the class itself I see little reason to use the base type. But the more "public" something is the more you should think about what type it should have.
    – JonasH
    Commented Sep 29, 2023 at 14:09
  • 1
    It's not about using the most abstract type. If that were the case, then why stop at Node, why not go all the way up to Ref (that Node inherits from)? The idea is to figure out what kinds of things your client code requires to operate, and then use the most abstract type that has those characteristics (or create one). Going more abstract than that would not be appropriate, and going more concrete would bring in assumptions (like extra features or peculiarities of some data structure) that your client code might rely on, but that will make it harder to change the implementation later. Commented Sep 29, 2023 at 23:52

2 Answers 2

2

Type strategy has nothing to do with what kind of class you're dealing with, it applies to Containers in the same way as to non-containers. If all you do is drive(), then you should deal with a Car rather than an ICECar, so that it can be more easily substituted with an ElectricCar later. This is no different from declaring a List rather than a LinkedList so that you can replace it with a better TurboList later.

However, if you do need type-specific information, e.g. you're building a car charger network, then you must declare ElectricCar (but still not Zoe or Tesla!), because Car is not, in fact, the most abstract type possible. That seems to be the case in your example.

1

Your problems boil down to two things:

  1. The "most abstract type" depends on what you do with the object
  2. Variable names should tell you what the reader needs to know, not what the compiler needs to know

Point 1 is the simplest: if you have code that needs to run a Label-specific operation on an object, then Label is the most abstract type. You have to choose the most abstract that is useful in your context, otherwise everything could just be declared as Object.

Point 2 is a bit more subtle: you (or your team) have decided on a naming convention that includes type information as a mnemonic for the reader. Your convention is equivalent to "Hungarian notation", and specifically what is known as "Systems Hungarian notation"

However, the same convention can be used with a different piece of information, equivalent to "Apps Hungarian notation". In that convention, the naming is based on the semantic type of the variable, not the system type.

For instance, the UI framework might have unrelated types for ImageNode and TextNode, either of which could be used for labelling something, so you would call the variable itemLabel, even though there is no type in the library called Label. This is not "misleading", as long as you have a specific convention of which "types" you are going to use in this way.

1
  • Your "Apps Hungarian" example uses a single tag across two different types, but I think the justification for it is the reverse, multiple tags across a single type. Then, when you assign from a variable with one tag to a variable with another tag, it's obvious that you're doing something wrong, even though the compiler won't stop you (because they have the same underlying type). e.g. an integer might be an index ('i') or a count of bytes ("c" or "cb").
    – Craig
    Commented Sep 29, 2023 at 17:28

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.