クラスで「最終」を使用することが本当に悪いのはなぜですか?


34

PHP OOPレガシーWebサイトをリファクタリングしています。

クラスの「最終」を「make it explicit that the class is currently not extended by anything」に使い始めたいと思います。クラスに行くと、これにより多くの時間を節約でき、protectedプロパティまたはメソッドの名前を変更/削除/変更できるかどうか疑問に思っています。クラスを本当に拡張したい場合は、最後のキーワードを削除し、拡張のためにロック解除します。

すなわち、もし子クラスのないクラスに来たら、そのクラスを決勝にマークすることでその知識を記録することができます。次回、コードベースを再検索して、子があるかどうかを確認する必要はありません。したがって、リファクタリング中の時間を節約できます。

それはすべて賢明な時間節約のアイデアのように思えます...しかし、クラスはまれ/特別な場合にのみ「最終」にすべきであると読んでいます。

Mockオブジェクトの作成を台無しにしたり、私が考えていない他の副作用があるかもしれません。

私は何が欠けていますか?


3
コードを完全に制御できるのであれば、ひどいことではないと思いますが、OCD側に少しあります。クラスを見逃した場合はどうしますか(たとえば、子供がいないが最終ではない場合)ツールでコードをスキャンし、一部のクラスに子があるかどうかを確認する方が良いでしょう。これをライブラリとしてパッケージ化し、他の人に渡すとすぐに、「最終」に対処するのが苦痛になります。
ジョブ

たとえば、MbUnitは.Net単体テスト用のオープンフレームワークであり、MsTestはそうではありません(驚きです)。その結果、MSFTの方法でいくつかのテストを実行したいという理由だけで、5kをMSFTにシェルアウトする必要があります。他のテストランナーは、MSFTが使用する最終クラスのために、MsTestでビルドされたテストdllを実行できません。
ジョブ

3
トピックに関するブログ投稿:最終クラス:拡張用にオープン、継承用にクローズ(2014年5月; Mathias Verraesによる)
hakre

素敵な記事。これについての私の考えを語っています。私はそれらの注釈について知らなかったので、それは便利でした。乾杯。
JW01

「PHP 5ではfinalキーワードが導入され、定義の前にfinalを付けることで子クラスがメソッドをオーバーライドすることを防ぎます。クラス自体がfinalとして定義されている場合、拡張できません。」php.net/manual/en/language.oop5.final.phpまったく悪くありません。

回答:


54

クラスはまれ/特別な場合にのみ「最終」にすべきだとよく読んでいます。

それを書いた人は誰でも間違っています。final自由に使用して、それは何も悪いことはありません。クラスは継承を念頭に置いて設計されたものではないことを文書化しており、これは通常、デフォルトですべてのクラスに当てはまりfinalます。多くの注意が必要です。

したがってfinal、デフォルトで使用することは決して悪いことではありません。実際、多くの人々は、これがデフォルトであると提案します、例えば、ジョン・スキート

たぶんそれはモックオブジェクトの作成を台無しにします…

これは確かに警告ですが、クラスをモックする必要がある場合はいつでもインターフェイスに頼ることができます。これは、モックの目的のためだけにすべてのクラスを継承に対してオープンにするよりも確かに優れています。


1
1+:モッキングを台無しにするため。「最終」クラスごとにインターフェースまたは抽象クラスを作成することを検討してください。
スポイケ

まあ、それは作品にスパナを置きます。この遅い到着は、他のすべての答えと矛盾します。今、私は何を信じるべきかわかりません:o。(私の受け入れられた答えを解き、再試行中に決定を保留します)
-JW01

1
Java API自体と、「final」キーワードの使用を見つける頻度を検討できます。実際、あまり使用されていません。これは、「仮想」メソッドが呼び出されたときにJavaで動的バインディングが発生してパフォーマンスが低下する可能性がある場合(Javaで「最終」または「プライベート」として宣言されていない場合、すべてのメソッドが仮想)、またはカスタムを導入する場合に使用されますJava APIクラスのサブクラスでは、「java.lang.String」のサブクラス化などの一貫性の問題が発生する場合があります。Java文字列は定義ごとの値オブジェクトであり、後でその値を変更してはなりません。サブクラスはこの規則を破る可能性があります。
ジョニーディー

5
@JonnyほとんどのJava APIの設計は不適切です。その大部分は10年以上前のものであり、その間に多くのベストプラクティスが開発されていることを忘れないでください。しかし、私の言葉を受け入れないでください。Javaエンジニア自身がそう言っています。何よりもまず、これについて詳しく説明したJosh Blochが、例えばEffective Javaで。Java APIが今日開発された場合、外観が大きく異なり、finalはるかに大きな役割を果たしますのでご安心ください。
コンラッドルドルフ

1
「最終」をより頻繁に使用することがベストプラクティスになっているとはまだ確信していません。私の意見では、「最終」は、パフォーマンス要件が指示する場合、またはサブクラスによって一貫性を壊すことができないことを保証する必要がある場合にのみ、実際に使用する必要があります。他のすべての場合、必要な文書は、文書(とにかく記述する必要があります)に入るべきであり、特定の使用パターンを強制するキーワードとしてソースコードに入るべきではありません。私にとっては、それは一種の神です。注釈を使用することは、より良い解決策です。「@final」注釈を追加すると、コンパイラが警告を発行できます。
ジョニーディー

8

クラスにサブクラスがないという注意を自分に残したい場合は、必ずサブクラスを使用してコメントを使用してください。「最終」キーワードはコメントではありません。言語キーワードを使用して何かを伝えるだけで(そしてそれが何を意味するかを知っているのはあなただけです)、悪い考えです。


17
これは完全に間違っています!「あなたに何かを伝えるためだけに言語キーワードを使用することは悪い考えです。」–それどころか、まさにそれが言語キーワードの目的です。あなたの暫定的な警告、「そしてあなただけがそれが何を意味するか知っているだろう」はもちろん正しいですが、ここでは当てはまりません。OPはfinal期待どおりに使用しています。それには何の問題もありません。また、言語機能を使用して制約を強制することは、コメントを使用するよりも常に優れています。
コンラッドルドルフ

8
@Konrad:例外としてfinal、「このクラスのサブクラスは作成しないでください」(法律上の理由など)を示す必要があります。「このクラスには現在子供がいないため、保護されたメンバーをいじっても安全です」。の意図finalは、「自由に編集可能」という非常にアンチテーゼであり、finalクラスにはprotectedメンバーさえいるべきではありません!
SF。

3
@Konrad Rudolphそれはまったく些細なことではありません。他の人が後でクラスを拡張したい場合、「拡張されない」というコメントと「拡張されない可能性がある」というキーワードには大きな違いがあります。
ジェレミー

2
@Konrad Rudolphそれは、OOP言語設計についての質問に持ち込む素晴らしい意見のように聞こえます。しかし、PHPがどのように機能するかはわかっています。また、封印されていない限り、拡張を許可する別のアプローチを取ります。「それがどうあるべきか」という理由でキーワードを統合しても大丈夫だと偽装することは単なる防御です。
ジェレミー

3
@Jeremyキーワードを混同していません。キーワードfinalは、「このクラスは(今のところ)拡張されない」ことを意味します。それ以上でもそれ以下でもありません。かどうかはPHPを念頭に置いて、この理念に設計された無関係です:それは持っているfinalすべての後に、キーワードを。第二に、PHPの設計からの議論は、パッチワークと全体的なひどいPHPがどのように設計されているかを考えると、失敗するはずです。
コンラッドルドルフ

5

「いつfinalクラスを宣言するか」についての素晴らしい記事があります。それからいくつかの引用:

TL; DR:finalインターフェイスを実装し、他のパブリックメソッドが定義されていない場合は、常にクラスを作成します

なぜ使用しなければならないのfinalですか?

  1. 破滅の大規模な継承チェーンの防止
  2. 有望な組成
  3. 開発者にユーザーのパブリックAPIについて考えるように強制する
  4. 開発者にオブジェクトのパブリックAPIを縮小させる
  5. finalクラスは常に拡張可能行うことができます
  6. extends カプセル化を破る
  7. あなたはその柔軟性を必要としません
  8. コードを自由に変更できます

回避する場合final

最終クラスは、次の前提の下でのみ効果的に機能します。

  1. finalクラスが実装する抽象化(インターフェース)があります
  2. 最終クラスのパブリックAPIはすべて、そのインターフェイスの一部です

これらの2つの前提条件のいずれかが欠落している場合、コードが実際に抽象化に依存していないため、クラスを拡張可能にする時点に達する可能性があります。

PS素晴らしい読書をしてくれた@ocramiusに感謝します!


5

クラスの「最終」とは、サブクラスが必要ですか?先に進み、「最終」サブクラスを必要なだけ削除しますが、機能しない場合は文句を言わないでください。あなたは自分でしています。

クラスをサブクラス化できる場合、他のクラスが依存する動作であるため、サブクラスが従う抽象的な用語で説明する必要があります。呼び出し元は、ある程度のばらつきを想定して作成する必要があります。ドキュメントは慎重に作成する必要があります。ソースコードがまだないので、人々に「ソースコードを見ている」と言うことはできません。それはすべての努力です。クラスがサブクラス化されることを期待しない場合、それは不必要な努力です。「最終」は、この努力がなされていないことを明確に示し、公正な警告を与えます。


-1

あなたが考えていない可能性があります一つのことは事実であるANYクラス手段の変化は、それが新しいQAテストを受けることがあること。

あなたが本当に、本当にそれを意味しない限り、物事を最終としてマークしないでください。


ありがとう。さらに説明してもらえますか。クラスをfinal(変更)としてマークすると、再テストする必要があるということですか?
JW01

4
これをもっと強く言うでしょう。クラスの設計のために拡張機能を機能させることができない場合を除いて、物事を最終としてマークしないでください。
S.Lott

ありがとう、S.Lott-finalの不適切な使用がコード/プロジェクトに与える影響を理解しようとしています。あなたはの温存使用についてとても独断的なことにつながるしている任意の悪いホラーストーリーを経験していますfinal。これは直接体験ですか?
JW01

1
@ JW01:finalクラスには1つの主要なユースケースがあります。サブクラスがポリモーフィズムを破壊する可能性があるため、拡張したくないポリモーフィッククラスがあります。サブクラスの作成を防止finalする必要がある場合を除き、使用しないでください。それ以外は役に立たない。
S.Lott

@ JW01、本番環境では、ファイナルを削除することは許可されていない場合があります。これは、これが顧客がテストおよび承認したコードではないためです。ただし、テスト用にコードを追加することは許可されますが、クラスを拡張できないため、目的のコードを実行できない場合があります。

-1

「最終」を使用すると、コードを使用する他の人の自由が奪われます。

あなたが書いたコードがあなただけのためのものであり、決して一般公開や顧客に公開されないなら、あなたはもちろんあなたが望むコードでそれを行うことができます。そうしないと、他の人がコードを作成するのを防ぐことができます。必要に応じて簡単に拡張できるAPIを使用しなければならないことが多かったのですが、「最終」に妨げられました。

また、そこに多くの場合、より良いはずですコードではない行うことがprivate、しかしprotected。確かに、private「カプセル化」を意味し、実装の詳細と見なされるものを隠します。しかし、APIプログラマーとして、メソッドxyzが実装の詳細であると見なされているため、将来のバージョンで変更/削除される可能性があるという事実を文書化することもできます。したがって、警告にもかかわらずこのようなコードに依存するすべての人は、自分の責任でそれを行っています。しかし、彼は実際にそれを実行し、(できればテスト済みの)コードを再利用し、ソリューションをより迅速に作成できます。

もちろん、API実装がオープンソースの場合、「最終」を削除するか、メソッドを「保護」するだけでかまいませんが、コードを変更してパッチの形で変更を追跡する必要があります。

ただし、実装がクローズドソースの場合、回避策を見つけることができず、最悪の場合、カスタマイズ/拡張の可能性に関する制限が少ない別のAPIに切り替えることになります。

「最終」または「プライベート」は悪ではないことに注意してください。ただし、プログラマはコードの再利用と拡張の観点から自分のコードについて考えなかったため、あまりにも頻繁に使用されると思います。


2
「継承はコードの再利用ではありません」。
quant_dev

2
わかりました、完全な定義を主張すれば正しいです。継承は「is-a」関係を表し、この事実がポリモーフィズムを可能にし、APIを拡張する方法を提供します。しかし実際には、コードの再利用には継承が非常に頻繁に使用されます。これは、特に多重継承の場合です。そして、スーパークラスのコードを呼び出すとすぐに、実際にコードを再利用しています。
ジョニーディー

@quant_dev:OOPでの再利用性とは、まず多態性を意味します。また、継承は、ポリモーフィックな動作(インターフェイスの共有)にとって重要なポイントです。
ファルコン

@Falcon共有インターフェース!=共有コード。少なくとも通常の意味ではそうではありません。
quant_dev

3
@quant_dev:OOPの意味で再利用可能性が間違っています。これは、インターフェイスの共有により、1つのメソッドで多くのデータ型を操作できることを意味します。これは、OOPコードの再利用の大部分です。また、継承によりインターフェイスを自動的に共有できるため、コードの再利用性が大幅に向上します。それが、OOPの再利用性について話す理由です。ルーチンを使用してライブラリを作成することは、プログラミング自体とほぼ同じくらい古く、ほぼすべての言語で実行できます。OOPでのコードの再利用はそれ以上です。
ファルコン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.