It is called the Dependency Inversion Principle, and should be followed in almost all systems.
Systems should (almost) never need to know about the concrete class involved. All components should be written against the abstract base class that defines the interface.
As an example from another similar thread recently, there was a bit of confusion regarding base classes.
// Start with a pointer to a base class
shared_ptr<AvatarBase> avatar = ...
// BAD, you are relying on concrete type details. Breaks if you add a new subclass.
if( Wizard* wizard = dynamic_cast<Wizard*>(avatar.get()) ) { /* do Wizard stuff */ }
else if( Warrior* warrior = dynamic_cast<Warrior*>(avatar.get()) { /* do Warrior stuff */ }
// BAD, you are relying on concrete implementations details.
// In addition to breaking on new subclasses, also breaks if you derive from any specified class.
if( avatar->subclassKey() == Wizard::subclassKey) { /* do Wizard stuff */ }
else if( avatar->subclassKey() == Warrior::subclassKey) { /* do Warrior stuff */ }
// GOOD. This is how inheritance is supposed to work.
avatar->DoTheThing();
Every time you write code that depends on knowledge of the concrete class, you are almost certainly writing a bug.
Specifying a concrete type means you cannot later extend the class to do something different without going back to all that code and adding the new extended type.
Specifying a concrete type means that if you need to make a new subclass you need to revisit all those areas of code and add your new subclass.
If other systems use your code, then you need to modify those systems as well. If your code is shared with your main engine and also between ten or fifteen different tools, you need to modify and rebuild and test all of those tools in addition to modifying your code base.
Specify the interface and write all code against that. In C++ that is an abstract base class. In Java that is an interface.
Letter* Alphabet::CreateLetter(const int* type) {
C++ factories should return either a shared_ptr or unique_ptr based on ownership mechanics in the code.
Otherwise it is easy to have code accidentally create an object and leak it.
Java's memory model does that automatically due to garbage collection.