paint-brush
コードを堅固に保つ方法@oxymoron_31
3,997 測定値
3,997 測定値

コードを堅固に保つ方法

Nagarakshitha Ramu6m2023/03/15
Read on Terminal Reader

長すぎる; 読むには

S.O.L.I.D の原則は、オブジェクト指向プログラミングでクリーンなコードを記述するための一般的なガイドラインです。それらには、単一責任の原則、オープン/クローズドの原則、インターフェイスの分離、依存関係の逆転、および代替の原則が含まれます。これらの原則は、さまざまなプログラミング言語に適用できます。
featured image - コードを堅固に保つ方法
Nagarakshitha Ramu HackerNoon profile picture
0-item

大まかに、すべてのプログラミング言語は 2 つのパラダイムに分類できます。

命令型/オブジェクト指向プログラミング- 行ごとに実行される一連の命令に従います。

宣言型/ 関数型プログラミング- シーケンシャルではありませんが、プログラムの目的により関連しています。プログラム全体は、それぞれが特定のタスクを実行するサブ関数をさらに持つ関数のようなものです。

ジュニア開発者として、機能的なコードを書くだけでなく、意味的に簡単で柔軟なコードを書くことも重要であることに苦労しています (読んでください: 何千行ものコードを神経質に見つめていました)。

両方のパラダイムでクリーンなコードを作成するためのベスト プラクティスは複数ありますが、ここではプログラミングのオブジェクト指向パラダイムに関する SOLID 設計原則について説明します。

ソリッドとは?

S — 単一の責任
O - 開閉原理
L — リスコフ置換原理
I — インターフェースの分離
D — 依存関係の逆転

これらの概念を理解するのが難しい主な理由は、技術的な深さが計り知れないからではなく、オブジェクト指向プログラミングでクリーンなコードを書くために一般化された抽象的なガイドラインだからです。これらの概念を理解するために、いくつかの高レベルのクラス図を見てみましょう。

これらは正確なクラス図ではありませんが、クラスに存在するメソッドを理解するのに役立つ基本的な青写真です。

この記事全体を通して、カフェの例を考えてみましょう。

単一責任の原則

クラスを変更する理由は 1 つだけにする必要があります

カフェが受け取ったオンライン注文を処理するこのクラスを考えてみましょう。

どうしたの?
この単一のクラスは、複数の機能を担当します。他の支払い方法を追加する必要がある場合はどうなりますか?確認を送信する方法が複数ある場合はどうなりますか?注文の処理を担当するクラスで支払いのロジックを変更することは、あまり良い設計ではありません。それは非常に柔軟性のないコードにつながります。

より良い方法は、以下の図に示すように、これらの特定の機能を具体的なクラスに分離し、それらのインスタンスを呼び出すことです。

開閉原理

エンティティは、拡張用に開いている必要がありますが、変更用に閉じている必要があります。

カフェでは、コーヒーの調味料をリストから選択する必要があり、これを処理するクラスがあります。

カフェは新しい調味料、バターを追加することにしました。選択した調味料に応じて価格がどのように変化するかに注意してください。価格計算のロジックは Coffee クラスにあります。毎回新しい調味料クラスを追加する必要があるだけでなく、メイン クラスで可能なコード変更を作成するだけでなく、ロジックを毎回異なる方法で処理します。

より良い方法は、そのメソッドをオーバーライドする子クラスを持つことができる調味料インターフェイスを作成することです。また、メイン クラスは調味料インターフェイスを使用してパラメーターを渡し、各注文の数量と価格を取得するだけです。

これには 2 つの利点があります。

1. 注文を動的に変更して、さまざまな調味料や複数の調味料を用意することもできます (モカとチョコレートの入ったコーヒーは天国のように聞こえます)。

2. Condiments クラスは、Coffee クラスと is-a ではなく has-a 関係になります。したがって、あなたのコーヒーは、モカ/バター/ミルクの種類のコーヒーではなく、モカ/バター/ミルクを持つことができます.

リスコフ置換原理

すべてのサブクラスまたは派生クラスは、その基本クラスまたは親クラスに置き換え可能である必要があります。

これは、サブクラスが親クラスを直接置き換えることができる必要があることを意味します。同じ機能を持つ必要があります。複雑な数式のように聞こえるので、これを理解するのは難しいと思いました。しかし、私はこの記事でそれを明確にしようとします.

カフェのスタッフについて考えてみましょう。バリスタ、マネージャー、サーバーがいます。それらはすべて同様の機能を備えています。

したがって、name、position、getName、getPostion、takeOrder()、serve() を使用してベース スタッフ クラスを作成できます。

具体的なクラス 、Waiter、Barista、および Manager のそれぞれは、これから派生し、同じメソッドをオーバーライドして、ポジションの必要に応じてそれらを実装できます。

この例では、 Liskov Substitution Principle (LSP)を使用して、コードの正確性に影響を与えることなく、Staff の派生クラスをベース Staff クラスと交換可能に使用できるようにしています。

たとえば、Waiter クラスは Staff クラスを拡張し、takeOrder および serveOrder メソッドをオーバーライドして、ウェイターの役割に固有の追加機能を含めます。ただし、さらに重要なことは、機能の違いにもかかわらず、Staff クラスのオブジェクトを予期するすべてのコードが、Waiter クラスのオブジェクトでも正しく機能することです。

これを理解するために例を見てみましょう

 public class Cafe {    public void serveCustomer (Staff staff) { staff.takeOrder(); staff.serveOrder(); } } public class Main {    public static void main (String[] args) { Cafe cafe = new Cafe(); Staff staff1 = new Staff( "John" , "Staff" ); Waiter waiter1 = new Waiter( "Jane" ); restaurant.serveCustomer(staff1); // Works correctly with Staff object
 restaurant.serveCustomer(waiter1); // Works correctly with Waiter object
 } }

ここで、Cafe クラスの serveCustomer() メソッドは、Staff オブジェクトをパラメーターとして取得します。 serveCustomer() メソッドは、Staff オブジェクトの takeOrder() メソッドと serveOrder() メソッドを呼び出して、顧客にサービスを提供します。

Main クラスでは、Staff オブジェクトと Waiter オブジェクトを作成します。次に、Cafe クラスの serveCustomer() メソッドを 2 回呼び出します。1 回は Staff オブジェクトで、もう 1 回は Waiter オブジェクトで呼び出します。

Waiter クラスは Staff クラスから派生しているため、Staff クラスのオブジェクトを期待するコードは、Waiter クラスのオブジェクトでも正しく機能します。この場合、Cafe クラスの serveCustomer() メソッドは、Waiter オブジェクトにウェイターの役割に固有の追加機能があるにもかかわらず、Staff オブジェクトと Waiter オブジェクトの両方で正しく機能します。

インターフェースの分離

クラスは、使用しないメソッドに依存することを余儀なくされるべきではありません。

そのため、カフェには、コーヒー、紅茶、スナック、ソーダを分配できる非常に用途の広い自動販売機があります。

それの何が問題なのですか?技術的には何もありません。コーヒーの抽出などの関数のインターフェイスを実装する必要がある場合は、お茶、ソーダ、スナック用の他のメソッドも実装する必要があります。これは不要であり、これらの機能は相互に関連していません。これらの各機能は、それらの間の凝集性が非常に低くなります。

結束とは何ですか?これは、インターフェース内のメソッドが互いにどの程度強く関連しているかを決定する要因です。

また、自動販売機の場合、これらの方法はほとんど相互依存していません。凝集度が非常に低いため、メソッドを分離できます。

現在、1 つのことを実装することを意図したインターフェースは、すべての関数に共通の takeMoney() のみを実装する必要があります。これにより、インターフェース内の無関係な機能が分離されるため、インターフェース内の無関係な機能を強制的に実装することを回避できます。

依存関係の逆転

高レベル モジュールは低レベル モジュールに依存すべきではありません。詳細は抽象化に依存する必要があります。

カフェのエアコン(クーラー)について考えてみましょう。そして、あなたが私のようなら、そこはいつも凍っています。リモコンとACクラスを見てみましょう。

ここで、remoteControl は、低レベルのコンポーネントである AC に依存する高レベルのモジュールです。投票があれば、私もヒーターが欲しいです:P だから、クーラーではなく、一般的な温度調節を行うために、リモコンと温度制御を切り離しましょう.しかし、remoteControl クラスは、具体的な実装である AC と密接に結合されています。依存関係を切り離すために、たとえば 45 ~ 65 F の範囲内で increaseTemp() と reduceTemp() の関数を持つインターフェイスを作成できます。

最終的な考え

ご覧のとおり、高レベル モジュールと低レベル モジュールの両方が、温度の上昇または下降の機能を抽象化するインターフェイスに依存しています。

具象クラス AC は、適用可能な温度範囲でメソッドを実装します。

これで、ヒーターと呼ばれる別の具体的なクラスにさまざまな温度範囲を実装したいヒーターをおそらく手に入れることができます。

高レベル モジュールである remoteControl は、実行時に適切なメソッドを呼び出すことだけを気にする必要があります。