オブジェクト指向プログラミングの最も重要な原則の 1 つは、内部インターフェースと外部インターフェースを区別することです。
これは、「Hello World」アプリよりも複雑なものを開発する場合に「必須」の練習です。
これを理解するために、開発から離れて現実の世界に目を向けてみましょう。
通常、私たちが使用しているデバイスは非常に複雑です。ただし、内部インターフェースと外部インターフェースを区別することで、問題なく使用できるようになります。
たとえば、コーヒーマシン。外観はシンプルです: ボタン、ディスプレイ、いくつかの穴…そして、その結果、間違いなく、おいしいコーヒーが生まれます。 :)
でも中身は…(修理書より写真)
詳細がたくさんあります。しかし、何も知らなくても使えます。
コーヒーマシンって結構信頼できるんですよね。何年も使用できますが、何か問題が発生した場合にのみ、修理に持ち込んでください。
コーヒーマシンの信頼性とシンプルさの秘密 – すべての細部がうまく調整され、内部に隠されています。
コーヒーマシンから保護カバーを取り外すと、使用がはるかに複雑になり(どこを押すか)、危険になります(感電する可能性があります)。
これから説明するように、プログラミングにおいてオブジェクトはコーヒーマシンのようなものです。
ただし、内部の詳細を隠すために、保護カバーではなく、言語の特別な構文と規則を使用します。
オブジェクト指向プログラミングでは、プロパティとメソッドは 2 つのグループに分割されます。
内部インターフェイス– メソッドとプロパティ。クラスの他のメソッドからアクセスできますが、外部からはアクセスできません。
外部インターフェイス– クラスの外部からもアクセスできるメソッドとプロパティ。
コーヒーマシンの例えを続けると、ボイラーチューブや発熱体などの内部に隠されているのは、内部インターフェースです。
オブジェクトが動作するために内部インターフェイスが使用され、その詳細は相互に使用されます。たとえば、ボイラーチューブが発熱体に取り付けられます。
しかし、コーヒーマシンは外側から見ると保護カバーで閉ざされており、誰もそこに手が届かないようになっています。詳細は隠されており、アクセスできません。外部インターフェースを介してその機能を使用できます。
したがって、オブジェクトを使用するために必要なのは、その外部インターフェイスを知ることだけです。私たちはそれが内部でどのように機能するか全く知らないかもしれませんが、それは素晴らしいことです。
以上、一般的な紹介でした。
JavaScript には、次の 2 種類のオブジェクト フィールド (プロパティとメソッド) があります。
パブリック: どこからでもアクセス可能。これらは外部インターフェイスを構成します。これまでは、パブリック プロパティとメソッドのみを使用していました。
プライベート: クラス内からのみアクセス可能。これらは内部インターフェイス用です。
他の多くの言語にも「保護された」フィールドが存在します。クラス内およびそれを拡張するフィールドからのみアクセスできます (プライベートと同様ですが、継承クラスからのアクセスも追加されます)。これらは内部インターフェイスにも役立ちます。これらは、通常、継承クラスがそれらにアクセスできるようにする必要があるため、ある意味、プライベートなものよりも広く普及しています。
Protected フィールドは JavaScript では言語レベルでは実装されていませんが、実際には非常に便利なのでエミュレートされています。
次に、これらすべてのタイプのプロパティを備えたコーヒーマシンを JavaScript で作成します。コーヒーマシンには多くの詳細が含まれているため、単純にするためにモデル化することはできません (ただし、モデル化することは可能です)。
まずは簡単なコーヒーマシンクラスを作成してみましょう。
クラスコーヒーマシン { 水量 = 0; // 中の水の量 コンストラクター(パワー) { this.power = パワー; alert( `コーヒーマシンを作成しました、電源: ${power}` ); } } // コーヒーマシンを作成する コーヒーマシン = 新しいコーヒーマシン(100); // 水を追加します CoffeeMachine.waterAmount = 200;
現在、プロパティwaterAmount
とpower
公開されています。外部から簡単に任意の値を取得/設定できます。
もっと細かく制御できるように、 waterAmount
プロパティを protected に変更しましょう。たとえば、誰にもゼロ未満に設定してほしくないのです。
保護されたプロパティには通常、アンダースコア_
が接頭辞として付けられます。
これは言語レベルでは強制されていませんが、そのようなプロパティやメソッドには外部からアクセスすべきではないというプログラマ間の周知の慣例があります。
したがって、プロパティは_waterAmount
という名前になります。
クラスコーヒーマシン { _水量 = 0; setwaterAmount(value) { if (値 < 0) { 値 = 0; } this._waterAmount = 値; } getwaterAmount() { this._waterAmount を返します。 } コンストラクター(パワー) { this._power = パワー; } } // コーヒーマシンを作成する コーヒーマシン = 新しいコーヒーマシン(100); // 水を追加します CoffeeMachine.waterAmount = -10; // _waterAmount は -10 ではなく 0 になります
現在はアクセスが規制されており、水量をゼロ以下に設定することは不可能となっている。
power
プロパティについては、読み取り専用にしましょう。プロパティは作成時にのみ設定し、その後は変更しない必要がある場合があります。
まさにコーヒーマシンの場合に当てはまります。電力は決して変わりません。
そのためには、ゲッターを作成するだけでよく、セッターは作成しません。
クラスコーヒーマシン { // ... コンストラクター(パワー) { this._power = パワー; } パワーを取得() { これを返します。_power; } } // コーヒーマシンを作成する コーヒーマシン = 新しいコーヒーマシン(100); alert(`パワーは: ${coffeeMachine.power}W`); // 電力: 100W コーヒーマシン.パワー = 25; // エラー (セッターなし)
ゲッター/セッター関数
ここではゲッター/セッター構文を使用しました。
ただし、ほとんどの場合、次のようなget.../set...
関数が優先されます。
クラスコーヒーマシン { _水量 = 0; setWaterAmount(値) { if (値 < 0) 値 = 0; this._waterAmount = 値; } getWaterAmount() { this._waterAmount を返します。 } } new CoffeeMachine().setWaterAmount(100);
少し長く見えますが、関数はより柔軟です。それらは複数の引数を受け入れることができます (現時点ではそれらが必要ない場合でも)。
一方、get/set 構文は短いため、最終的には厳密なルールはなく、決定はユーザー次第です。
保護されたフィールドは継承されます
class MegaMachine extends CoffeeMachine
継承すると、新しいクラスのメソッドからthis._waterAmount
またはthis._power
にアクセスすることを妨げるものは何もありません。
したがって、保護されたフィールドは当然継承可能です。以下で説明するプライベートなものとは異なります。
最近の追加
これは言語に最近追加されたものです。 JavaScript エンジンでサポートされていない、またはまだ部分的にサポートされている場合は、ポリフィルが必要です。
プライベート プロパティとメソッドに対する言語レベルのサポートを提供する、ほぼ標準に近い完成した JavaScript 提案があります。
プライベートは#
で始める必要があります。これらはクラス内からのみアクセスできます。
たとえば、プライベート#waterLimit
プロパティとウォーターチェックのプライベート メソッド#fixWaterAmount
次に示します。
クラスコーヒーマシン { #水制限 = 200; #fixWaterAmount(値) { (値 < 0) の場合は 0 を返します。 if (値 > this.#waterLimit) this.#waterLimit を返します。 } setWaterAmount(値) { this.#waterLimit = this.#fixWaterAmount(値); } } let CoffeeMachine = new CoffeeMachine(); // クラス外からプライベートにはアクセスできません コーヒーマシン.#fixWaterAmount(123); // エラー CoffeeMachine.#waterLimit = 1000; // エラー
言語レベルでは、 #
はフィールドがプライベートであることを示す特別な記号です。外部から、または継承クラスからアクセスすることはできません。
プライベートフィールドはパブリックフィールドと競合しません。プライベート#waterAmount
フィールドとパブリックwaterAmount
フィールドの両方を同時に持つことができます。
たとえば、 waterAmount
#waterAmount
のアクセサーにしましょう。
クラスコーヒーマシン { #水量 = 0; getwaterAmount() { これを返します。#waterAmount; } setwaterAmount(value) { if (値 < 0) 値 = 0; this.#waterAmount = 値; } } let machine = new CoffeeMachine(); machine.waterAmount = 100; アラート(machine.#waterAmount); // エラー
保護されたフィールドとは異なり、プライベート フィールドは言語自体によって適用されます。それは良いことだ。
ただし、 CoffeeMachine
から継承すると、 #waterAmount
に直接アクセスできなくなります。 waterAmount
ゲッター/セッターに依存する必要があります。
class MegaCoffeeMachine extends CoffeeMachine { 方法() { アラート( this.#waterAmount ); // エラー: CoffeeMachine からのみアクセスできます } }
多くのシナリオでは、このような制限は厳しすぎます。 CoffeeMachine
拡張する場合、その内部にアクセスする正当な理由がある可能性があります。そのため、言語構文でサポートされていない場合でも、保護されたフィールドがより頻繁に使用されます。
プライベートフィールドはこの[名前]として使用できません
プライベートフィールドは特別です。
ご存知のとおり、通常はthis[name]
使用してフィールドにアクセスできます。
クラス ユーザー { ... SayHi() { フィールド名 = "名前"; alert(`こんにちは、${this[フィールド名]}`); } }
プライベートフィールドではそれは不可能です。this this['#name']
は機能しません。これはプライバシーを確保するための構文制限です。
OOP の観点からは、内部インターフェイスと外部インターフェイスを区切ることをカプセル化と呼びます。
これにより、次のような利点が得られます。
ユーザーが足を撃たれないように保護する
コーヒーマシンを使用している開発者チームがいると想像してください。 「Best CoffeeMachine」社製で正常に動作しますが、保護カバーが外れていました。したがって、内部インターフェイスが公開されます。
すべての開発者は文明的であり、意図どおりにコーヒー マシンを使用しています。しかしそのうちの一人、ジョンは自分が一番賢いと判断し、コーヒーマシンの内部にいくつかの調整を加えました。そのため、2日後にコーヒーマシンが故障しました。
それはきっとジョンのせいではなく、むしろ保護カバーを外してジョンに操作をさせた人物のせいだ。
プログラミングでも同様です。クラスのユーザーが、外部から変更する予定のないものを変更した場合、その結果は予測できません。
サポート可能
コーヒーマシンは一度購入すれば終わりではないため、プログラミングの状況は実際のコーヒーマシンよりも複雑です。コードは常に開発と改善が行われます。
内部インターフェイスを厳密に区切ると、クラスの開発者は、ユーザーに通知しなくても、その内部プロパティとメソッドを自由に変更できます。
このようなクラスの開発者であれば、プライベート メソッドに依存する外部コードがないため、プライベート メソッドの名前を安全に変更したり、パラメータを変更したり、さらには削除したりできることを知っておくと便利です。
ユーザーにとって、新しいバージョンがリリースされると、内部的には全面的な見直しになる可能性がありますが、外部インターフェイスが同じであれば、アップグレードは簡単です。
複雑さを隠す
人々はシンプルなものを使うことに憧れます。少なくとも外からは。中に入っているものは別物です。
プログラマーも例外ではありません。
実装の詳細が非表示であり、シンプルで十分に文書化された外部インターフェイスが利用できる場合には、常に便利です。
内部インターフェイスを非表示にするには、保護されたプロパティまたはプライベート プロパティを使用します。
保護されたフィールドは_
で始まります。これはよく知られた慣例であり、言語レベルで強制されるものではありません。プログラマは、 _
で始まるフィールドには、そのクラスおよびそれを継承するクラスからのみアクセスする必要があります。
プライベートフィールドは#
で始まります。 JavaScript は、クラス内からのみアクセスできるようにします。
現時点では、プライベート フィールドはブラウザ間で十分にサポートされていませんが、ポリフィルは可能です。