web-dev-qa-db-ja.com

「親x = new Child();」です「Child x = new Child();」の代わりに後者を使用できる場合は悪い習慣ですか?

たとえば、次のようなフラグメントを作成するコードをいくつか見ました。

Fragment myFragment=new MyFragment();

これは、変数をMyFragmentではなくFragmentとして宣言します。MyFragmentはFragmentの子クラスです。このコードは次のようになるはずなので、このコード行は満足していません。

MyFragment myFragment=new MyFragment();

より具体的には、それは本当ですか?

または質問の一般化で、使用するのは悪い習慣ですか?

Parent x=new Child();

の代わりに

Child x=new Child();

コンパイルエラーなしで前者を後者に変更できるとしたら?

32
ggrr

それはコンテキストに依存しますが、可能な限りmost abstractタイプを宣言する必要があると私は主張します。そうすれば、コードはできるだけ一般的になり、無関係な詳細に依存しなくなります。

例として、LinkedListArrayListがあり、どちらもListから派生しているとします。コードがどのような種類のリストでも同じように機能する場合、サブクラスの1つに任意に制限する理由はありません。

69
JacquesB

JacquesBの答えは正しいですが、少し抽象的です。いくつかの側面をより明確に強調しましょう:

コードスニペットでは、明示的にnewキーワードを使用します。具体的な型に固有である可能性が高い追加の初期化手順を続行したい場合があります。次に、その特定の具体的な型で変数を宣言する必要があります。

あなたがそのアイテムをどこか別の場所から入手した場合、状況は異なります。 IFragment fragment = SomeFactory.GiveMeFragments(...);。ここで、ファクトリは通常、可能な限り最も抽象的な型を返す必要があり、下位のコードは実装の詳細に依存するべきではありません。

11
Bernhard Hiller

パフォーマンスが異なる可能性があります。

派生クラスがシールされている場合、コンパイラーはインライン化して最適化するか、そうでなければ仮想呼び出しになるものを排除できます。したがって、パフォーマンスに大きな違いが生じる可能性があります。

例:ToStringは仮想呼び出しですが、Stringはシールされています。 Stringでは、ToStringは何もしないので、objectとして宣言した場合、これは仮想呼び出しです。Stringを宣言した場合、コンパイラはクラスがシールされているため、派生クラスがメソッドをオーバーライドしているため、何もできません。 ArrayListLinkedListにも同様の考慮事項が適用されます。

したがって、オブジェクトの具体的なタイプがわかっていて、それを隠すカプセル化の理由がない場合は、そのタイプとして宣言する必要があります。オブジェクトを作成したばかりなので、具象タイプがわかります。

6
Ben

主な違いは、必要なアクセスのレベルです。そして、抽象化についてのすべての良い答えは動物を含むので、私は言われています。

動物が数頭いるとしましょう-CatBirdDog。現在、これらの動物にはいくつかの一般的な動作があります-move()eat()、およびspeak()。彼らは皆、食べ方や話し方が異なりますが、動物が食べたり話したりする必要がある場合、私は彼らがどうやってそれをするのか本当に気にしません。

しかし、時々私はそうします。私は番犬や番鳥を固めたことはありませんが、番犬は飼っています。誰かが私の家に侵入したとき、私は侵入者を怖がらせるために話すことに頼ることはできません。私は本当に吠えるのに私の犬が必要です-これはただの横糸である彼の話すこととは異なります。

したがって、侵入者を必要とするコードでは、実際にDog fido = getDog(); fido.barkMenancingly();を実行する必要があります

しかし、ほとんどの場合、私は喜んでどんな動物にもおやつを鳴らしたり、鳴いたり、おやつにつぶやいたりするトリックをさせることができます。 Animal pet = getAnimal(); pet.speak();

より具体的に言うと、ArrayListにはListにはないsomeメソッドがあります。特に、trimToSize()。現在、99%のケースでは、これは問題ではありません。 Listは、私たちが気にするほど大きくなることはほとんどありません。しかし、そうなることもあります。したがって、時々、trimToSize()を実行してバッキング配列を小さくするために、ArrayListを明確に要求する必要があります。

これは構築時に作成する必要がないことに注意してください。絶対に確実な場合は、returnメソッドまたはキャストを使用して行うこともできます。

4
corsiKa

一般的に言って、あなたは使うべきです

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

または

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

初期化されたものとは異なる型を変数が持つ正当な理由がない限り、ローカル変数の場合。

(ここでは、C++コードがスマートポインターを使用している可能性が高いという事実を無視しています。実際に知ることができない行が1行もない場合もあります。)

ここでの一般的な考え方は、変数の自動型検出をサポートする言語であり、それを使用するとコードが読みやすくなり、正しいことを実行します(つまり、コンパイラーの型システムは可能な限り最適化し、初期化を新しいものに変更できますタイプは、ほとんどの抽象が使用されていた場合にも機能します)。

2
Joshua

このコードは理解しやすく、変更も簡単なので、良いです。

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

意図を伝えるので良い:

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

わかりました。一般的で理解しやすいからです。

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

本当に悪い1つのオプションは、ParentだけにメソッドDoSomething()がある場合にChildを使用することです。それは意図を間違って伝えているので悪いです:

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

ここで、質問が具体的に尋ねるケースについてもう少し詳しく説明しましょう。

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

後半を関数呼び出しにして、これを別の方法でフォーマットしてみましょう。

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

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

これは次のように短縮できます。

_FunctionCall(new Child());

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

ここでは、WhichTypeが関数の動作を可能にする最も基本的な/抽象型であることが広く受け入れられていると想定しています(パフォーマンスの懸念がない限り)。この場合、適切なタイプはParentまたはParentから派生したものになります。

この推論の行は、タイプParentの使用が適切な選択である理由を説明していますが、天候に恵まれず、他の選択は不適切です(そうではありません)。

1
Peter

tl; dr-ChildよりもParentを使用することをお勧めしますローカルスコープ内。読みやすさだけでなく、オーバーロードされたメソッド解決が適切に機能し、効率的なコンパイルを可能にすることも必要です。


ローカルスコープでは、

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

概念的には、可能な限り多くのタイプ情報を維持することです。 Parentにダウングレードする場合、基本的には有用である可能性のある型情報を取り除いているだけです。

完全な型情報を保持することには、4つの主な利点があります。

  1. コンパイラーに詳細情報を提供します。
  2. 読者に詳細情報を提供します。
  3. よりクリーンで標準化されたコード。
  4. プログラムのロジックをより変更可能にします。

利点1:コンパイラーへの詳細情報

見かけの型は、オーバーロードされたメソッドの解決と最適化で使用されます。

例:オーバーロードされたメソッドの解決

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

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

上記の例では、両方のfoo()workを呼び出していますが、1つのケースでは、オーバーロードされたメソッドの解像度が向上しています。

例:コンパイラの最適化

_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; }
}
_

上記の例では、両方の.Foo()呼び出しは、最終的には__2_を返す同じoverrideメソッドを呼び出します。最初のケースでは、正しいメソッドを見つけるための仮想メソッドルックアップがあります。 2番目のケースでは、メソッドのsealedであるため、この仮想メソッドルックアップは必要ありません。

彼の回答 で同様の例を提供した@Benの功績です。

利点2:読者への詳細情報

正確なタイプ、つまりChildを知ると、コードを読んでいる人に詳細情報が提供され、プログラムの動作を簡単に確認できます。

確かに、parent.Foo();child.Foo();はどちらも理にかなっているので、実際のコードには関係ないかもしれませんが、コードを初めて見る人にとっては、より多くの情報が役に立ちます。

さらに、開発環境によっては、IDEがChildよりもParentに関するより役立つツールチップとメタデータを提供できる場合があります。

利点3:よりクリーンで標準化されたコード

最近見たほとんどのC#コード例ではvarを使用しています。これは基本的に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
_

var以外の宣言ステートメントを見ると、見当違いです。状況に理由がある場合は素晴らしいですが、それ以外の場合はアンチパターンに見えます。

_// 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();   
_

メリット4:プロトタイピングのための変更可能なプログラムロジック

最初の3つの利点は大きなものでした。これははるかに状況に応じたものです。

それでも、他の人と同じようにコードを使用するユーザーのために、Excelを常に変更しています。 thisバージョンのコードでChildに固有のメソッドを呼び出す必要はないかもしれませんが、後でコードを再利用または再処理する場合があります。

強力な型システムの利点は、プログラムロジックに関する特定のメタデータを提供し、可能性をより明確にすることです。これはプロトタイピングに非常に役立つので、可能な限りそれを維持することをお勧めします。

概要

Parentを使用すると、オーバーロードされたメソッド解決が混乱し、一部のコンパイラの最適化が禁止され、リーダーから情報が削除され、コードが見苦しくなります。

varを使用するのは、実際の方法です。それは素早く、きれいで、パターン化されており、コンパイラとIDEが適切に機能するのを助けます。


重要:この答えはParentChildについてですメソッドのローカルスコープ内。 ParentChildの問題は、戻り値の型、引数、およびクラスフィールドで大きく異なります。

1
Nat

parentType foo = new childType1();を指定すると、コードはfooでの親タイプのメソッドの使用に制限されますが、childType1のインスタンスだけでなく、fooから派生したタイプのオブジェクトへの参照をparentに格納できます。新しいchildType1インスタンスが、fooが参照を保持する唯一のものである場合、fooの型をchildType1として宣言することをお勧めします。一方、fooが他の型への参照を保持する必要がある場合、それをparentTypeとして宣言すると、それが可能になります。

0
supercat

私は使用をお勧めします

_Child x = new Child();
_

の代わりに

_Parent x = new Child();
_

前者は情報を失いませんが、後者は情報を失います。

前者を使用すると、後者を使用してできるすべてのことを実行できますが、その逆はできません。


ポインタをさらに詳しく説明するために、複数のレベルの継承があるとします。

Base-> _DerivedL1_-> _DerivedL2_

そして、new DerivedL2()の結果を変数に初期化したいとします。基本タイプを使用すると、Baseまたは_DerivedL1_を使用するオプションが残ります。

使用できます

_Base x = new DerivedL2();
_

または

_DerivedL1 x = new DerivedL2();
_

どちらか一方を優先する論理的な方法は見当たらない。

使用する場合

_DerivedL2 x = new DerivedL2();
_

議論することは何もありません。

0
R Sahu

常に変数を最も具体的な型として返すか格納することをお勧めしますが、パラメーターは最も広い型として受け入れます。

例えば。

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

パラメータは、非常に一般的なタイプであるList<T>です。 ArrayList<T>LinkedList<T>などは、すべてこのメソッドで受け入れられます。

重要なことに、戻り値の型はLinkedHashMap<K, V>ではなくMap<K, V>です。誰かがこのメソッドの結果をMap<K, V>に割り当てたい場合、それを行うことができます。これは、この結果が単なるマップではなく、順序付けが定義されたマップであることを明確に伝えています。

このメソッドがMap<K, V>を返したとします。呼び出し元がLinkedHashMap<K, V>を必要とする場合、型チェック、キャスト、およびエラー処理を行う必要があり、これは本当に面倒です。