主流の強力な静的OOP言語がプリミティブの継承を妨げるのはなぜですか?


53

なぜこれが大丈夫であり、ほとんど期待されているのですか:

abstract type Shape
{
   abstract number Area();
}

concrete type Triangle : Shape
{
   concrete number Area()
   {
      //...
   }
}

...これはOKではなく、誰も文句を言いません:

concrete type Name : string
{
}

concrete type Index : int
{
}

concrete type Quantity : int
{
}

私の動機は、コンパイル時の正当性検証のために型システムの使用を最大化することです。

PS:はい、私はこれを読んでおり、ラッピングはハッキングの回避策です。


1
コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
maple_shaft

この質問にも同じような動機がありました。あなたはそれを面白いと思うかもしれません。
default.kramer

「継承を望まない」というアイデアを確認する回答を追加しました。特にラッピング、暗黙的または明示的なキャスト(または失敗)のいずれかを提供することを含め、特にJIT最適化ではとにかくほぼ同じパフォーマンスを得ることができますが、あなたはその答えにリンクしています:-)私は追加するだけです、言語がプロパティ/メソッドを転送するために必要な定型的なコードを減らすための機能を追加した場合、特に単一の値がある場合はいいでしょう
マークハード

回答:


82

JavaやC#のような言語を考えていると思いますか?

これらの言語でintは、基本的に(など)プリミティブはパフォーマンスの妥協点です。オブジェクトのすべての機能をサポートしているわけではありませんが、より高速でオーバーヘッドが少ないです。

オブジェクトが継承をサポートするためには、各インスタンスが実行時にどのクラスのインスタンスであるかを「知る」必要があります。それ以外の場合、オーバーライドされたメソッドは実行時に解決できません。オブジェクトの場合、これはインスタンスデータがクラスオブジェクトへのポインタとともにメモリに格納されることを意味します。そのような情報もプリミティブ値とともに保存する必要がある場合、メモリ要件が膨れ上がります。16ビット整数値では、値に16ビットが必要であり、さらにクラスへのポインターに32または64ビットのメモリが必要です。

メモリのオーバーヘッドとは別に、算術演算子などのプリミティブの一般的な操作をオーバーライドできることも期待できます。サブタイプなしで、のような演算子を+コンパイルして、単純なマシンコード命令にできます。オーバーライドできる場合は、実行時にメソッドを解決する必要があります。これは、はるかにコストのかかる操作です。(C#は演算子のオーバーロードをサポートしていることをご存知かもしれませんが、これは同じではありません。演算子のオーバーロードはコンパイル時に解決されるため、デフォルトのランタイムペナルティはありません。)

文字列はプリミティブではありませんが、メモリ内での表現方法は「特別」です。たとえば、それらは「抑留」されています。つまり、等しい2つの文字列リテラルを同じ参照に最適化できます。文字列インスタンスもクラスを追跡する必要がある場合、これは不可能です(または、少なくともそれほど効果的ではありません)。

あなたが説明することは確かに有用ですが、それをサポートするには、継承を利用しない場合でも、プリミティブと文字列を使用するたびにパフォーマンスのオーバーヘッドが必要になります。

Smalltalk 、整数のサブクラス化を許可しています(私は信じています)。しかし、Javaが設計されたとき、Smalltalkは遅すぎると見なされ、すべてをオブジェクトにするオーバーヘッドが主な理由の1つと見なされました。Javaは、パフォーマンスを向上させるために、優雅さと概念的な純度を犠牲にしました。


12
@Den:string不変に動作するように設計されているため、シールされています。文字列から継承できる場合、可変文字列を作成することが可能になり、エラーが発生しやすくなります。.NETフレームワーク自体を含む大量のコードは、副作用のない文字列に依存しています。こちらもご覧ください。同じことが言え
Doc Brown

5
@DocBrownこれも、Java Stringでマークさfinalれている理由です。
開発

47
「Javaが設計されたとき、Smalltalkは遅すぎると考えられていました[…]。Javaは、パフォーマンスを向上させるために優雅さと概念的な純度を犠牲にしました。」–皮肉なことに、SunがSmalltalk VMテクノロジーにアクセスするためにSunがSmalltalk社を買収し、わずかに変更されたSmalltalk VMであるHotSpot JVMをリリースするまで、Javaは実際にそのパフォーマンスを獲得しませんでした。
ヨルグWミッターグ

3
@underscore_d:あなたが非常に明確にリンクした答えは、C♯にはプリミティブ型がないことを示しています。もちろん、C#の実装が存在するプラットフォームにはプリミティブ型がある場合とない場合がありますが、C#にプリミティブ型があることを意味するわけではありません。たとえば、CLI用のRubyの実装があり、CLIにはプリミティブ型がありますが、それはRubyにプリミティブ型があるという意味ではありません。実装は、値型をプラットフォームのプリミティブ型にマッピングすることで値型を実装することを選択する場合としない場合がありますが、それは仕様の一部ではなく、内部の実装の詳細です。
ヨルグWミットタグ

10
それはすべて抽象化についてです。私たちは頭をはっきりさせなければなりません。さもないと、ナンセンスになってしまいます。たとえば、C♯は.NETに実装されています。.NETはWindows NTに実装されています。Windows NTはx86に実装されています。x86は二酸化シリコンに実装されています。SiO₂は単なる砂です。それで、stringC#のa は単なる砂ですか?いいえ、もちろんそうではありませんが、stringC♯のa は、C♯の仕様にあるとおりです。実装方法は関係ありません。C♯のネイティブ実装はECMAScriptの実装はECMAScriptのにそれらをマップする、バイト配列として文字列を実装しString、S、など
イェルクWミッターク

20

一部の言語が提案しているのは、サブクラス化ではなく、サブタイプ化です。たとえば、Adaでは派生型またはサブタイプを作成できます。エイダプログラミング/型システムのセクションでは、すべての詳細を理解するために読む価値あり。ほとんどの場合、値の範囲を制限できます。

 type Angle is range -10 .. 10;
 type Hours is range 0 .. 23; 

明示的に変換する場合、両方のタイプを整数として使用できます。また、範囲が構造的に同等であっても(タイプは名前でチェックされます)、別のものの代わりに使用することはできません

 type Reference is Integer;
 type Count is Integer;

上記のタイプは、同じ範囲の値を表していますが、互換性がありません。

(ただし、Unchecked_Conversionを使用できます。そのことを他の人に伝えないでください)


2
実際には、セマンティクスに関するものだと思います。そして、うまくいけば、コンパイル時エラーを引き起こすインデックスが期待されている数量を使用して
マージャンVenema氏

@MarjanVenema実行しますが、これは論理エラーをキャッチする目的で行われます。
コアダンプ

私のポイントは、セマンティクスが必要なすべてのケースではなく、範囲が必要だということです。その後、必要がありますtype Index is -MAXINT..MAXINT;すべての整数が有効になるよう何とか私のために何もしませんか?それで、チェックされているものがすべて範囲である場合、どのような種類のエラーをインデックスに渡すのでしょうか?
マルジャンヴェネマ16

1
@MarjanVenema彼女の2番目の例では、両方のタイプがIntegerのサブタイプです。ただし、Countを受け入れる関数を宣言する場合、型チェックは名前の等価性に基づいているため、参照を渡すことはできません。これは、「チェックされるのは範囲のみ」の反対です。これは整数に限定されず、列挙型またはレコードを使用できます。(archive.adaic.com/standards/83rat/html/ratl-04-03.html
コアダンプ

1
@Marjanタグ付けタイプが非常に強力になる理由の良い例の1つは、OCamlでのZorgの実装に関する Eric Lippertのシリーズにあります。これを行うと、コンパイラは多くのバグをキャッチできます-一方、暗黙的に型を変換できるようにすると、この機能は役に立たないように見えます。それらは両方とも同じ基本型を持っているからです。
Voo

16

これはX / Yの質問になると思います。質問からの顕著な点...

私の動機は、コンパイル時の正当性検証のために型システムの使用を最大化することです。

...そして詳細なコメントから:

暗黙的に別のものに置き換えることはできません。

何かが足りない場合はすみませんが、...これらがあなたの目的であれば、なぜ地球上で相続について話しているのですか?暗黙の代替可能性は...のように...その全体です。知っていますか、リスコフ代替原理ですか?

あなたがしたいように見える何、実際に、「強い型定義」の概念である-それによって何かがたとえば、「は」int範囲と表現の点ではなく、期待してコンテキストに代入することはできませんintし、その逆を。この用語に関する情報と、選択した言語で呼ばれているものを検索することをお勧めします。繰り返しになりますが、文字通り、継承の反対です。

そして、X / Yの回答が気に入らない人にとっては、LSPを参照してタイトルがまだ答えられる可能性があると思います。プリミティブ型は非常に単純なことを行うためプリミティブ型であり、それがすべてです。それらを継承することを可能にし、その結果として可能な効果を無限にすることは、最高の状態では大きな驚きに、最悪の場合は致命的なLSP違反につながります。タレス・ペレイラこの驚異的なコメントを引用し気にしないと楽観的に思うかもしれません:

誰かがIntから継承できた場合、データベースにログを書き込み、URLを開き、どういうわけかエルビスを復活させます。プリミティブ型は安全であり、多かれ少なかれ保証された、明確に定義された動作を持つはずです。

誰かが正気の言語で原始的な型を見た場合、彼らは当然ながら、驚くことなく、常にその小さなことを1つだけ行うと推測しています。プリミティブ型には、継承できるかどうかを示すクラス宣言がなく、メソッドがオーバーライドされます。もしそうなら、それは非常に驚くべきことです(そして完全に後方互換性を壊しますが、それは「なぜXはYで設計されていないのか」に対する後方の答えだと思います)。

...ただし、Mooing Duckがそれに応じて指摘したように、オペレーターのオーバーロードを許可する言語は、ユーザーが本当に必要な場合にユーザーが同じ程度または等しい程度に混乱することを可能にするため、この最後の引数が成り立つかどうかは疑わしいです。そして、私は今、他の人のコメントを要約するのをやめるでしょう。


4

多くの場合、アプリケーション設計で非常に望ましいと考えられている仮想ディスパッチ8での継承を許可するには、ランタイムタイプ情報が必要です。すべてのオブジェクトについて、オブジェクトのタイプに関するデータを保存する必要があります。定義によると、プリミティブにはこの情報がありません。

プリミティブを特徴とする2つの(管理され、VMで実行される)メインストリームOOP言語があります:C#とJava。他の多くの言語、そもそもプリミティブを持たないか、それらを許可/使用するために同様の推論を使用します。

プリミティブはパフォーマンスの妥協点です。オブジェクトごとに、オブジェクトヘッダー(Javaでは64ビットVMの場合は通常2 * 8バイト)に加えて、フィールド、および最終的なパディング(ホットスポットでは、すべてのオブジェクトは、 8)。そのため、intasオブジェクトでは、4バイト(Javaの場合)ではなく、少なくとも24バイトのメモリを保持する必要があります。

したがって、パフォーマンスを改善するためにプリミティブ型が追加されました。それらは非常に多くのことを簡単にします。何んa + b平均の両方の場合のサブタイプがありますかint?正しい追加を選択するには、何らかの嫌悪感を追加する必要があります。これは、仮想ディスパッチを意味します。追加に非常に単純なオペコードを使用する機能があると、はるかに高速になり、コンパイル時の最適化が可能になります。

String別のケースです。JavaとC#の両方がStringオブジェクトです。しかし、C#では封印され、Javaでは最終版です。これは、JavaとC#の両方の標準ライブラリがStringsを不変にする必要があり、それらをサブクラス化すると、この不変性が壊れるからです。

Javaの場合、VMは文字列をインターンして「プール」し、パフォーマンスを向上させることができます(実行します)。これは、文字列が本当に不変の場合にのみ機能します。

さらに、プリミティブ型をサブクラス化する必要はほとんどありません。プリミティブがサブクラス化できない限り、数学がそれらについて教えてくれるすばらしいことがたくさんあります。たとえば、加算が可換および結合であることを確認できます。それは整数の数学的な定義が私たちに伝えるものです。さらに、多くの場合、誘導を介してループ上の不変式を簡単にprrofできます。のサブクラス化を許可intすると、特定のプロパティが保持されることが保証されなくなるため、数学が提供するツールを失います。したがって、プリミティブ型をサブクラス化できないことは実際には良いことだと思います。誰かが壊すことの少ないものに加えて、コンパイラーは特定の最適化を許可されていることをしばしば証明できます。


1
この答えは深byです...狭いです。to allow inheritance, one needs runtime type information.偽。For every object, some data regarding the type of the object has to be stored.偽。There are two mainstream OOP languages that feature primitives: C# and Java.C ++は現在主流ではありませんか?実行時の型情報 C ++の用語なので、反論として使用します。dynamic_castまたはを使用しない限り、絶対に必要ではありませんtypeid。そして、たとえ RTTIのクラスがある場合、継承はスペースのみを消費上のvirtualメソッドのクラスごとのテーブルにはインスタンスごとに指摘しなければならないためのメソッド
underscore_d

1
C ++の継承は、VM上で実行される言語とはまったく異なる動作をします。仮想ディスパッチにはRTTIが必要ですが、これは元々C ++の一部ではありませんでした。仮想ディスパッチを使用しない継承は非常に限定されており、仮想ディスパッチを使用した継承と比較する必要があるかどうかはわかりません。さらに、「オブジェクト」の概念はC ++とC#またはJavaで大きく異なります。あなたは正しい、私はより良い言葉ができるいくつかのことがありますが、すべての非常に複雑なポイントに入るとすぐに言語設計に関する本を書かなければならないことにつながります。
ポリノーム

3
また、C ++で「仮想ディスパッチにはRTTIが必要」というわけではありません。ここでも、のみdynamic_casttypeinfoのことを必要としています。仮想ディスパッチは、オブジェクトの具象クラスのvtableへのポインタを使用して実際に実装されているため、適切な関数を呼び出すことができますが、RTTIに固有のタイプと関係の詳細は必要ありません。コンパイラが知る必要があるのは、オブジェクトのクラスが多相かどうか、もしそうであれば、インスタンスのvptrが何であるかだけです。仮想ディスパッチされたクラスをで簡単にコンパイルできます-fno-rtti
underscore_d

2
事実、RTTIには仮想ディスパッチが必要です。文字通り-C ++は、dynamic_cast仮想ディスパッチのないクラスでは許可しません。実装の理由は、RTTIが一般的にvtableの隠されたメンバーとして実装されるためです。
-MSalters

1
@MilesRout C ++には、言語がOOPに必要なすべてのもの、少なくともやや新しい標準があります。古いC ++標準には、OOP言語に必要なものが欠けていると主張する人もいるかもしれませんが、それでもストレッチです。C ++は、高レベルの OOP言語ではありません。いくつかのものをより直接的に低レベルで制御できますが、それでもOOPは可能です。(ここでは抽象化の観点で高レベル/低レベル、マネージド言語のような他の言語はC ++よりもシステムを抽象化するため、それらの抽象化はより高くなります)。
ポリグノーム

4

主流の強力な静的OOP言語では、サブタイピングは主に型を拡張し、型の現在のメソッドをオーバーライドする方法と見なされます。

そのために、「オブジェクト」にはそのタイプへのポインターが含まれています。これはオーバーヘッドです。Shapeインスタンスを使用するメソッドのコードは、Area()呼び出すべき正しいメソッドを知る前に、まずそのインスタンスの型情報にアクセスする必要があります。

プリミティブは、単一の機械語命令に変換できる操作のみを許可する傾向があり、タイプ情報を持ちません。整数を遅くして誰かがサブクラス化できるようにすることは、それが主流になる言語を止めるのに十分魅力的ではなかった。

答えは:

主流の強力な静的OOP言語がプリミティブの継承を妨げるのはなぜですか?

は:

  • 需要はほとんどなかった
  • そして、それは言語を遅すぎたでしょう
  • サブタイピングは、(ユーザー定義の)静的型チェックを改善する方法ではなく、主に型を拡張する方法と見なされていました。

ただし、「type」以外の変数のプロパティに基づいて静的チェックを許可する言語を取得し始めています。たとえば、F#には「dimension」と「unit」があるため、エリアに長さを追加することはできません。

また、型の動作を変更(または交換)せず、静的型チェックのみに役立つ「ユーザー定義型」を許可する言語もあります。coredumpの回答をご覧ください。


F#の測定単位は便利な機能ですが、残念ながら名前が間違っています。また、コンパイル時のみであるため、たとえばコンパイル済みのNuGetパッケージを使用する場合など、あまり便利ではありません。しかし、正しい方向。
デン

「ディメンション」は「タイプ」以外のプロパティではなく、あなたが慣れているよりも豊富なタイプであることに注意するのはおそらく興味深いでしょう。
-porglezomp

3

ここで何かを見落としているかどうかはわかりませんが、答えはかなり簡単です:

  1. プリミティブの定義は、プリミティブ値はオブジェクトではなく、プリミティブ型はオブジェクト型ではなく、プリミティブはオブジェクトシステムの一部ではありません。
  2. 継承はオブジェクトシステムの機能です。
  3. エルゴ、プリミティブ継承に参加できません

でも2つだけ強い静のOOP言語実際にそこにあることに注意してください持っている JavaとC ++:プリミティブ、私の知る限りは。(実際には、後者についてはよくわかりません。C++についてはあまり知りませんし、検索時に見つけたものは混乱を招きました。)

C ++では、プリミティブは基本的にCから継承された(しゃれた)レガシーです。したがって、Cにはオブジェクトシステムも継承もないため、オブジェクトシステムに参加しません(したがって、継承)。

Javaでは、プリミティブはパフォーマンスを改善しようとする誤った試みの結果です。プリミティブもシステム内の唯一の値型であり、実際、Javaで値型を記述することは不可能であり、オブジェクトが値型になることは不可能です。だから、別にしても、プリミティブはオブジェクトシステムに参加していないので、「相続」のアイデアも意味がないという事実から、場合、あなたは彼らから継承することができ、あなたは」を維持することはできないであろう価値」。これは、例えばC♯と異なるない値の型(持っているstructにもかかわらずオブジェクトである複数可)を、。

もう1つは、継承できないことは、実際にはプリミティブに固有ではないことです。C♯では、structsはsを暗黙的に継承しSystem.Objectて実装できますinterfaceが、classesまたはstructs から継承も継承もできません。また、sealed classesは継承できません。Javaでは、final classesは継承できません。

tl; dr

主流の強力な静的OOP言語がプリミティブの継承を妨げるのはなぜですか?

  1. プリミティブはオブジェクトシステムの一部ではありません(定義により、存在する場合、プリミティブではありません)、継承の概念はオブジェクトシステムに結び付けられ、エルゴプリミティブの継承は用語の矛盾です
  2. プリミティブは一意ではなく、他の多くの型も継承できません(finalまたはsealedJavaまたはC#で、C#でstructs case class、Scala でes)

3
Ehm ...「C Sharp」と発音していることは知っていますが、えーと
リスター氏

私はあなたがC ++側でかなり間違っていると思います。純粋なオブジェクト指向言語ではありません。クラスメソッドはデフォルトではnotですvirtual。つまり、LSPに従わないということです。例えばstd::string、プリミティブではありませんが、単なる別の値として動作します。このような値のセマンティクスは非常に一般的であり、C ++のSTL部分全体がそれを想定しています。
–MSalters

2
「Javaでは、プリミティブはパフォーマンスを改善しようとする誤った試みの結果です。」プリミティブをユーザー拡張可能なオブジェクトタイプとして実装することで、パフォーマンスがどの程度低下するかについて、あなたはまったく知らないと思います。Javaでのその決定は、慎重かつ十分に根拠のあるものです。int使用するたびにメモリを割り当てる必要があることを想像してください。各割り当てには、100nsのオーダーとガベージコレクションのオーバーヘッドがかかります。2つのプリミティブを追加することで消費される単一のCPUサイクルと比較してくださいint。言語の設計者が別の方法で決定した場合、Javaコードはクロールします。
cmaster

1
@cmaster:Scalaにはプリミティブがなく、その数値パフォーマンスはJavaのパフォーマンスとまったく同じです。それは、整数をJVMプリミティブにコンパイルするため、intまったく同じように実行されるためです。(Scalaネイティブはそれらをプリミティブマシンレジスタにコンパイルし、Scala.jsはプリミティブECMAScriptにコンパイルしますNumber。)Rubyにはプリミティブはありませんが、YARVとRubiniusは整数をプリミティブマシン整数にコンパイルし、JRubyはそれらをJVMプリミティブにコンパイルしますlong。ほとんどすべてのLisp、Smalltalk、またはRuby実装はVMでプリミティブ使用します。それはですどこパフォーマンスの最適化...
イェルクWミッターク

1
…所属:言語ではなく、コンパイラ内。
ヨルグWミットタグ

2

「Effective Java」のJoshua Blochは、継承を明示的に設計するか、継承を禁止することを推奨しています。彼らは不変になるように設計されており、許可の継承は、このように、サブクラスでそれを変更壊す可能性があるため、プリミティブクラスは、継承のために設計されていないリスコフの原理を、それは多くのバグの原因になります。

とにかく、なぜこれがハッキーな回避策なのですか?継承よりも構成を優先する必要があります。理由がパフォーマンスよりも高い場合、質問に対する答えは、機能を追加するさまざまな側面をすべて分析するのに時間がかかるため、すべての機能をJavaに配置することはできないということです。たとえば、Javaには1.5より前のGenericsがありませんでした。

多くの忍耐がある場合は、Javaに値クラスを追加する計画があるので幸運です。値クラスを作成して、パフォーマンスを向上させ、同時に柔軟性を高めることができます。


2

抽象レベルでは、設計している言語に必要なものを含めることができます。

実装レベルでは、それらのいくつかは実装がより簡単になり、一部は複雑になり、一部は高速になり、一部は低速になります。これを説明するために、設計者はしばしば厳しい決定と妥協をしなければなりません。

実装レベルでは、変数にアクセスするための最も速い方法の1つは、アドレスを見つけてそのアドレスの内容をロードすることです。ほとんどのCPUにはアドレスからデータをロードするための特定の命令があり、それらの命令は通常、ロードする必要があるバイト数(1、2、4、8など)とロードするデータを置く場所(単一レジスタ、レジスタ)を知る必要がありますペア、拡張レジスタ、その他のメモリなど)。変数のサイズを知ることにより、コンパイラはその変数の使用のためにどの命令を発行するかを正確に知ることができます。変数のサイズがわからない場合、コンパイラーはより複雑で恐らくより遅いものに頼る必要があります。

抽象レベルでは、サブタイピングのポイントは、同等またはより一般的なタイプが期待される1つのタイプのインスタンスを使用できるようにすることです。つまり、特定の型のオブジェクトまたはそれ以上の派生物を予期するコードを、事前に正確にこれが何であるかを知ることなく記述できます。そして明らかに、派生型が増えるとデータメンバが追加されるため、派生型のメモリ要件は必ずしも基本型と同じになるとは限りません。

実装レベルでは、事前に定義されたサイズの変数が未知のサイズのインスタンスを保持し、通常は効率的と呼ぶ方法でアクセスされる簡単な方法はありません。しかし、物事を少し動かし、変数を使用してオブジェクトを保存するのではなく、オブジェクトを識別し、そのオブジェクトを別の場所に保存する方法があります。その方法は参照(メモリアドレスなど)です。その情報を介してオブジェクトを見つけることができる限り、変数が何らかの種類の固定サイズの情報のみを保持する必要があることを保証する間接レベルです。そのためには、アドレス(固定サイズ)を読み込むだけで、有効なオブジェクトのオフセットを使用して、そのオブジェクトに不明なオフセットのデータがさらにある場合でも、通常どおり作業できます。私たちはそれを行うことができます

抽象レベルでは、このメソッドを使用すると、aにする情報を失うことなくstringobject変数に(a への参照)を格納できますstring。すべてのタイプがこのように動作することは問題ありませんが、多くの点でエレガントであると言うこともできます。

それでも、実装レベルでは、間接レベルの追加レベルはより多くの命令を必要とし、ほとんどのアーキテクチャでは、オブジェクトへの各アクセスが多少遅くなります。余分なレベルの間接参照(参照)を持たない一般的に使用される型を言語に含めると、コンパイラーがプログラムからより多くのパフォーマンスを引き出すことができます。しかし、そのレベルの間接参照を削除することにより、コンパイラはメモリの安全な方法でサブタイプを許可できなくなります。これは、型にさらにデータメンバーを追加し、より一般的な型に割り当てると、ターゲット変数に割り当てられたスペースに収まらない余分なデータメンバーが切り捨てられるためです。


1

一般に

クラスが抽象(メタファー:穴のあるボックス)である場合、「穴を埋める」ことは問題ありません(使用可能なものが必要です!)、それが抽象クラスをサブクラス化する理由です。

クラスが具象(メタファー:ボックスがいっぱい)の場合、既存のクラスを変更しても大丈夫ではありません。ボックスの中に何かを追加する余地はありません。そのため、具象クラスをサブクラス化するべきではありません。

プリミティブを使用

プリミティブは、設計上、具体的なクラスです。それらは、よく知られ、完全に明確なもの(何か抽象的なものを持つプリミティブ型を見たことがない、そうでなければプリミティブではない)、システム全体で広く使用されているものを表します。プリミティブ型をサブクラス化し、プリミティブの設計された動作に依存する他のユーザーに独自の実装を提供できるようにすると、多くの副作用と大きな損害を引き起こす可能性があります!



このリンクは興味深いデザイン意見です。もっと考える必要があります。
デン

1

通常、継承は必要なセマンティクスではありません。なぜなら、プリミティブが期待される場所ならどこでも特別な型に置き換えることができないからです。あなたの例から借用するにQuantity + Indexは、a は意味的に意味をなさないため、継承関係は間違った関係です。

ただし、いくつかの言語には、説明している種類の関係を表す値型の概念がありますScalaはその一例です。値型は、基本表現としてプリミティブを使用しますが、外部では異なるクラスIDと操作を持ちます。これにはプリミティブ型を拡張する効果がありますが、継承関係ではなく構成になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.