32

For example,I had seen some codes that create a fragment like this:

Fragment myFragment=new MyFragment();

which declares a variable as Fragment instead of MyFragment , which MyFragment is a child class of Fragment. I'm not satisified this line of codes because I think this code should be:

MyFragment myFragment=new MyFragment();

which is more specific, is that true?

Or in generalization of the question, is it bad practice to use:

Parent x=new Child();

instead of

Child x=new Child();

if we can change the former one into latter one without compile error?

ggrr
  • 5,873

10 Answers10

69

It depends on the context, but I would argue you should declare the most abstract type possible. That way your code will be as general as possible and not depend on irrelevant details.

An example would be having a LinkedList and ArrayList which both descend from List. If the code would work equally well with any kind of list then there is no reason to arbitrary restrict it to one of the subclasses.

JacquesB
  • 61,955
  • 21
  • 135
  • 189
11

JacquesB's answer is correct, though a little abstract. Let me emphasize some aspects more clearly:

In your code snippet, you explicitly use the new keyword, and perhaps you would like to continue with further initialization steps which are likely specific for the concrete type: then you need to declare the variable with that specific concrete type.

The situation is different when you get that item from somewhere else, e.g. IFragment fragment = SomeFactory.GiveMeFragments(...);. Here, the factory should normally return the most abstract type possible, and the downward code should not depend on implementation details.

Quentin 2
  • 103
6

There are potentially performance differences.

If the derived class is sealed, then the compiler can inline and optimise or eliminate what would otherwise be virtual calls. So there can be a significant performance difference.

Example: ToString is a virtual call, but String is sealed. On String, ToString is a no-op, so if you declare as object that's a virtual call, if you declare a String the compiler knows no derived class has overridden the method because the class is sealed, so that's a no-op. Similar considerations apply to ArrayList vs LinkedList.

Therefore, if you know the concrete type of the object, (and there is no encapsulation reason to conceal it), you should declare as that type. Since you have just created the object, you know the concrete type.

Ben
  • 863
4

The key difference is the level of access required. And every good answer about abstraction involves animals, or so I'm told.

Let us suppose you have a few animals - Cat Bird and Dog. Now, these animals have a few common behaviors - move(), eat(), and speak(). They all eat differently and speak differently, but if I need my animal to eat or speak I don't really care how they do it.

But sometimes I do. I've never hard a guard cat or a guard bird, but I do have a guard dog. So when someone breaks into my house, I can't just rely on speak to scare away the intruder. I really need my dog to bark - this is different than his speak, which is just a woof.

So in code that requires an intruder I really need to do Dog fido = getDog(); fido.barkMenancingly();

But most of the time, I can happily have any animals do tricks where they woof, meow or tweet for treats. Animal pet = getAnimal(); pet.speak();

So to be more concrete, ArrayList has some methods that List doesn't. Notably, trimToSize(). Now, in 99% of cases, we don't care about this. The List is almost never big enough for us to care. But there are times when it is. So occasionally, we must specifically ask for an ArrayList to execute trimToSize() and make its backing array smaller.

Note that this does not have to be made at construction - it could be done via a return method or even a cast if we're absolutely sure.

corsiKa
  • 1,084
2

Generally speaking, you should use

var x = new Child(); // C#

or

auto x = new Child(); // C++

for local variables unless there is a good reason for the variable to have a different type as what it is initialized with.

(Here I'm ignoring the fact that the C++ code should most likely be using a smart pointer. There are cases not to and without more than one line I can't actually know.)

The general idea here is with languages that support automatic type detection of variables, using it makes the code easier to read and does the right thing (that is, the compiler's type system can optimize as much as possible and changing the initialization to be a new type works as well if most abstract had been used).

Joshua
  • 1,674
1

Good because it's easy to understand and easy to modify this code:

var x = new Child(); 
x.DoSomething();

Good because it communicates intent:

Parent x = new Child(); 
x.DoSomething(); 

Ok, because it's common and easy to understand:

Child x = new Child(); 
x.DoSomething(); 

The one option that is truly bad is to use Parent if only Child has a method DoSomething(). It's bad because it communicates intent wrongly:

Parent x = new Child(); 
(x as Child).DoSomething(); // DON'T DO THIS! IF YOU WANT x AS CHILD, STORE x AS CHILD

Now let's elaborate a bit more on the case the question specifically asks about:

Parent x = new Child(); 
x.DoSomething(); 

Let's format this differently, by making the second half a function call:

WhichType x = new Child(); 
FunctionCall(x);

void FunctionCall(WhichType x)
    x.DoSomething(); 

This can be shortened to:

FunctionCall(new Child());

void FunctionCall(WhichType x)
    x.DoSomething();

Here, I assume it's widely accepted that WhichType should be the most basic/abstract type that allows the function to work (unless there are performance concerns). In this case the proper type would be either Parent, or something Parent derives from.

This line of reasoning explains why using the type Parent is a good choice, but it doesn't go into weather the other choices are bad (they are not).

Peter
  • 3,778
1

tl;dr- Using Child over Parent is preferable in the local scope. It not only helps with readability, but it's also necessary to ensure that overloaded method resolution works properly and helps enable efficient compilation.


In the local scope,

Parent obj = new Child();  // Works
Child  obj = new Child();  // Better
var    obj = new Child();  // Best

Conceptually, it's about maintaining the most type information possible. If we downgrade to Parent, we're essentially just stripping out type information that could've been useful.

Retaining the complete type information has four main advantages:

  1. Provides more information to the compiler.
  2. Provides more information to the reader.
  3. Cleaner, more standardized code.
  4. Makes the program logic more mutable.

Advantage 1: More information to the compiler

The apparent type is used in overloaded method resolution and in optimization.

Example: Overloaded method resolution

main()
{
    Parent parent = new Child();
    foo(parent);

    Child  child  = new Child();
    foo(child);
}
foo(Parent arg) { /* ... */ }  // More general
foo(Child  arg) { /* ... */ }  // Case-specific optimizations

In the above example, both foo() calls work, but in one case, we get the better overloaded method resolution.

Example: Compiler optimization

main()
{
    Parent parent = new Child();
    var x = parent.Foo();

    Child  child  = new Child();
    var y = child .Foo();
}
class Parent
{
    virtual         int Foo() { return 1; }
}
class Child : Parent
{
    sealed override int Foo() { return 2; }
}

In the above example, both .Foo() calls ultimately call the same override method that returns 2. Just, in the first case, there's a virtual method lookup to find the correct method; this virtual method lookup isn't needed in the second case since that method's sealed.

Credit to @Ben who provided a similar example in his answer.

Advantage 2: More information to the reader

Knowing the exact type, i.e. Child, provides more information to whoever's reading the code, making it easier to see what the program's doing.

Sure, maybe it doesn't matter to the actual code since both parent.Foo(); and child.Foo(); make sense, but for someone seeing the code for the first time, more information's just plain helpful.

Additionally, depending on your development environment, the IDE may be able to provide more helpful tooltips and metadata about Child than Parent.

Advantage 3: Cleaner, more standardized code

Most of the C# code examples that I've seen lately use var, which is basically shorthand for Child.

Parent obj = new Child();  // Sub-optimal
Child  obj = new Child();  // Optimal, but anti-pattern syntax
var    obj = new Child();  // Optimal, clean, patterned syntax "everyone" uses now

Seeing a non-var declaration statement just looks off; if there's a situational reason for it, awesome, but otherwise it looks anti-pattern.

// Clean:
var foo1 = new Person();
var foo2 = new Job();
var foo3 = new Residence();

// Staggered:
Person foo1 = new Person();
Job foo2 = new Job();
Residence foo3 = new Residence();   

Advantage 4: More mutable program logic for prototyping

The first three advantages were the big ones; this one's far more situational.

Still, for people who use code like others use Excel, we're constantly changing our code. Maybe we don't need to call a method unique to Child in this version of the code, but we might repurpose or rework the code later.

The advantage of a strong type system is that it provides us with certain meta-data about our program logic, making the possibilities more readily apparent. This is incredibly useful in prototyping, so it's best to keep it where possible.

Summary

Using Parent messes up overloaded method resolution, inhibits some compiler optimizations, removes information from the reader, and makes the code uglier.

Using var is really the way to go. It's quick, clean, patterned, and helps the compiler and IDE to do their job properly.


Important: This answer is about Parent vs. Child in a method's local scope. The issue of Parent vs. Child is very different for return types, arguments, and class fields.

Nat
  • 1,101
  • 1
  • 8
  • 12
0

Given parentType foo = new childType1();, code will be limited to using parent-type methods on foo, but will be able to store into foo a reference to any object whose type derives from parent--not just instances of childType1. If the new childType1 instance is the only thing to which foo will ever hold a reference, then it may be better to declare foo's type as childType1. On the other hand, if foo might need to hold references to other types, having it declared as parentType will make that possible.

supercat
  • 8,629
0

I suggest using

Child x = new Child();

instead of

Parent x = new Child();

The latter loses information while former does not.

You can do everything with the former that you can do with the latter but not vice versa.


To elaborate the pointer further, let's you have multiple levels of inheritance.

Base -> DerivedL1 -> DerivedL2

And you want to initialize the result of new DerivedL2() to a variable. Using a base type leaves you the option of using Base or DerivedL1.

You can use

Base x = new DerivedL2();

or

DerivedL1 x = new DerivedL2();

I don't see any logical way to prefer one over the other.

If you use

DerivedL2 x = new DerivedL2();

there is nothing to argue about.

R Sahu
  • 2,016
0

I would suggest always returning or storing variables as the most specific type, but accepting parameters as the broadest types.

E.g.

<K, V> LinkedHashMap<K, V> keyValuePairsToMap(List<K> keys, List<V> values) {
   //...
}

The parameters are List<T>, a very general type. ArrayList<T>, LinkedList<T> and others could all be accepted by this method.

Importantly, the return type is LinkedHashMap<K, V>, not Map<K, V>. If somebody wants to assign the result of this method to a Map<K, V>, they can do that. It clearly communicates that this result is more than just a map, but a map with a defined ordering.

Suppose this method returned Map<K, V>. If the caller wanted a LinkedHashMap<K, V>, they would have to do a type check, cast and error handling, which is really cumbersome.

Alexander
  • 5,185