JavaのSolidPrinciplesについて知っておくべきことすべて



この記事では、例を使用してJavaのSolid原則とは何か、実際の例を使用してそれらの重要性について詳しく学習します。

の世界で (OOP)、多くの設計ガイドライン、パターン、または原則があります。これらの原則のうち5つは通常グループ化されており、頭字語SOLIDで知られています。これらの5つの原則はそれぞれ特定のことを説明していますが、それらの1つを採用することは、別の原則を採用することを意味するか、それにつながるように重複しています。この記事では、JavaのSOLID原則を理解します。

JavaにおけるSOLID原則の歴史

ロバートC.マーチンは、5つのオブジェクト指向設計原則を示し、頭字語「S.O.L.I.D」が使用されています。 S.O.L.I.Dのすべての原則を組み合わせて使用​​すると、簡単に管理できるソフトウェアの開発が容易になります。 S.O.L.I.Dを使用するその他の機能は次のとおりです。





  • コードの臭いを防ぎます。
  • すばやく屈折コード。
  • 適応型またはアジャイルなソフトウェア開発を行うことができます。

コーディングでS.O.L.I.Dの原則を使用すると、効率的かつ効果的なコードの記述を開始できます。



S.O.L.I.Dの意味は何ですか?

Solidは、Javaの5つの原則を表しています。

  • S :単一責任の原則
  • または :オープンクローズ原則
  • L :リスコフの置換原則
  • :インターフェイス分離の原則
  • D :依存性逆転の原則

このブログでは、Javaの5つのSOLID原則すべてについて詳しく説明します。



Javaにおける単一責任の原則

それは何と言っていますか?

ロバートC.マーチンは、1つのクラスが唯一の責任を持つべきであると説明しています。

単一責任の原則によれば、クラスを変更しなければならない理由は1つだけです。これは、クラスが実行するタスクが1つある必要があることを意味します。この原則はしばしば主観的と呼ばれます。

原理は例でよく理解できます。次の操作を実行するクラスがあると想像してください。

  • データベースに接続されています

  • データベーステーブルからいくつかのデータを読み取る

  • 最後に、それをファイルに書き込みます。

シナリオを想像しましたか?ここで、クラスには複数の変更理由があり、そのうちのいくつかはファイル出力の変更、新しいデータベースの採用です。単一責任の原則について話しているとき、クラスを変更する理由が多すぎるため、単一責任の原則に適切に適合していません。

たとえば、Automobileクラスはそれ自体を開始または停止できますが、それを洗浄するタスクはCarWashクラスに属します。別の例では、Bookクラスに独自の名前とテキストを格納するプロパティがあります。ただし、本を印刷するタスクは、BookPrinterクラスに属している必要があります。 Book Printerクラスはコンソールまたは別のメディアに印刷する場合がありますが、そのような依存関係はBookクラスから削除されます

この原則が必要なのはなぜですか?

単一責任の原則に従うと、テストが容易になります。単一責任で、クラスのテストケースは少なくなります。機能が少ないということは、他のクラスへの依存関係も少ないということです。小さくて目的の広いクラスは検索が簡単なので、コード編成が改善されます。

この原則を明確にする例:

ユーザーが設定を変更できるUserSettingサービスを実装するように求められたが、その前にユーザーを認証する必要があるとします。これを実装する1つの方法は次のとおりです。

public class UserSettingService {public void changeEmail(User user){if(checkAccess(user)){//変更するオプションを付与する}} public boolean checkAccess(User user){//ユーザーが有効かどうかを確認します。 }}

checkAccessコードを他の場所で再利用するか、checkAccessの実行方法を変更するまでは、すべて問題ないようです。 2つのケースすべてで、同じクラスを変更することになり、最初のケースでは、UserSettingServiceを使用してアクセスも確認する必要があります。
これを修正する1つの方法は、UserSettingServiceをUserSettingServiceとSecurityServiceに分解することです。そして、checkAccessコードをSecurityServiceに移動します。

public class UserSettingService {public void changeEmail(User user){if(SecurityService.checkAccess(user)){//変更するオプションを付与する}}} public class SecurityService {public static boolean checkAccess(User user){//アクセスを確認します。 }}

Javaでのオープンクローズド原則

Robert C. Martinは、ソフトウェアコンポーネントは拡張のために開いている必要がありますが、変更のために閉じている必要があると説明しています。

正確には、この原則に従って、クラスは、将来の人々が単に来てそれを変更することを想定せずに、完璧にその仕事を実行するように書かれるべきです。したがって、クラスは変更のために閉じたままにする必要がありますが、拡張するオプションが必要です。クラスを拡張する方法は次のとおりです。

オープンクローズ原理の優れた例は、ブラウザの助けを借りて理解することができます。 Chromeブラウザに拡張機能をインストールしたことを覚えていますか?

Chromeブラウザの基本的な機能は、さまざまなサイトを閲覧することです。 Chromeブラウザを使用してメールを書いているときに文法をチェックしますか?はいの場合、Grammarly拡張機能を使用するだけで、コンテンツの文法チェックが可能になります。

ブラウザの機能を向上させるためのものを追加するこのメカニズムは拡張機能です。したがって、ブラウザは、拡張のために開いているが、変更のために閉じている機能の完璧な例です。簡単に言うと、ブラウザにプラグインを追加/インストールすることで機能を強化できますが、新しいものを構築することはできません。

この原則が必要なのはなぜですか?

クラスはサードパーティのライブラリを介して提供される可能性があるため、OCPは重要です。これらの基本クラスが拡張をサポートできるかどうかを心配することなく、これらのクラスを拡張できるはずです。ただし、継承により、基本クラスの実装に依存するサブクラスが生じる可能性があります。これを回避するには、インターフェイスの使用をお勧めします。この追加の抽象化により、疎結合が発生します。

さまざまな形状の面積を計算する必要があるとしましょう。最初の形状の長方形のクラスを作成することから始めます2つの属性の長さを持っています&幅。

public class Rectangle {public double length public double width}

次に、この長方形の面積を計算するクラスを作成しますメソッドcalculateRectangleAreaがあります長方形を取ります入力パラメータとして、その面積を計算します。

public class AreaCalculator {public doublecalculateRectangleArea(Rectanglerectangle){returnrectangle.length * rectangle.width}}

ここまでは順調ですね。次に、2番目の形状の円を取得するとします。そこで、すぐに新しいクラスサークルを作成します単一の属性半径を使用します。

パブリッククラスCircle {パブリックダブル半径}

次に、Areacalculatorを変更します新しいメソッドcalculateCircleaArea()を介して円の計算を追加するクラス

public class AreaCalculator {public doublecalculateRectangleArea(Rectanglerectangle){returnrectangle.length * rectangle.width} public doublecalculateCircleArea(Circle circle){return(22/7)* circle.radius * circle.radius}}

ただし、上記のソリューションの設計方法には欠陥があったことに注意してください。

新しい形の五角形があるとしましょう。その場合、AreaCalculatorクラスを変更することになります。形状のタイプが大きくなると、AreaCalculatorが変化し続けるため、これは厄介になり、このクラスのコンシューマーは、AreaCalculatorを含むライブラリを更新し続ける必要があります。その結果、AreaCalculatorクラスは、新しいシェイプが来るたびに変更されるため、確実にベースライン化(​​ファイナライズ)されません。したがって、この設計は変更のために閉じられていません。

AreaCalculatorは、新しいメソッドに計算ロジックを追加し続ける必要があります。シェイプの範囲を実際に拡大しているのではなく、追加されたすべてのシェイプに対してピースミール(ビットごと)のソリューションを実行しているだけです。

開放/閉鎖原則に準拠するための上記の設計の変更:

ここで、開放/閉鎖原則を遵守することにより、上記の設計の欠陥を解決する、よりエレガントな設計を見てみましょう。まず、デザインを拡張可能にします。このためには、最初に基本タイプShapeを定義し、Circle&RectangleにShapeインターフェイスを実装させる必要があります。

public interface Shape {public doublecalculateArea()} public class Rectangleimplements Shape {double length double width public doublecalculateArea(){return length * width}} public class Circleimplements Shape {public double radius public doublecalculateArea(){return(22 / 7)* radius * radius}}

基本インターフェースShapeがあります。すべてのシェイプは、ベースインターフェイスShapeを実装するようになりました。シェイプインターフェイスには、抽象メソッドcalculateArea()があります。円と長方形はどちらも、独自の属性を使用して、calculateArea()メソッドの独自のオーバーライドされた実装を提供します。
シェイプはシェイプインターフェイスのインスタンスになっているため、ある程度の拡張性をもたらしました。これにより、個々のクラスの代わりにShapeを使用できます
上記の最後のポイントは、これらの形状の消費者です。この場合、コンシューマーはAreaCalculatorクラスになり、次のようになります。

public class AreaCalculator {public doublecalculateShapeArea(Shape shape){return shape.calculateArea()}}

このAreaCalculatorクラスは、上記の設計上の欠陥を完全に取り除き、オープンクローズ原則に準拠したクリーンなソリューションを提供します。 Javaの他のSOLID原則に進みましょう

Javaにおけるリスコフの置換原則

Robert C. Martinは、派生型は基本型を完全に置き換える必要があると説明しています。

リスコフの置換原則は、q(x)がプロパティであり、タイプTに属するxのエンティティについて証明可能であると想定しています。これで、この原則に従って、q(y)はタイプSに属するオブジェクトyに対して証明可能になります。 Sは実際にはTのサブタイプです。混乱していて、リスコフの置換原則が実際に何を意味するのかわかりませんか?定義は少し複雑かもしれませんが、実際は非常に簡単です。唯一のことは、すべてのサブクラスまたは派生クラスは、それらの親クラスまたは基本クラスの代わりに使用できる必要があるということです。

それはユニークなオブジェクト指向の原則であると言えます。この原則は、特定の親タイプの子タイプによって、複雑にすることなく、または物事を爆破することなく、その親の代わりになる能力を持つ必要があるため、さらに単純化できます。この原則は、リスコフの置換原則と密接に関連しています。

この原則が必要なのはなぜですか?

これにより、継承の誤用を回避できます。これは、「is-a」関係に準拠するのに役立ちます。 サブクラスは、基本クラスによって定義されたコントラクトを満たす必要があるとも言えます。この意味で、契約による設計それはバートランドメイヤーによって最初に記述されました。たとえば、円は楕円の一種であると言いたくなりますが、円には2つの焦点または長軸/短軸がありません。

LSPは、正方形と長方形の例を使用して一般的に説明されます。 SquareとRectangleの間にISA関係があると仮定した場合。したがって、「正方形は長方形です」と呼びます。以下のコードは、関係を表しています。

public class Rectangle {private int length private int width public int getLength(){return length} public void setLength(int length){this.length = length} public int getBreadth(){return width} public void setBreadth(int width){ this.breadth = width} public int getArea(){return this.length * this.breadth}}

以下はSquareのコードです。 SquareはRectangleを拡張することに注意してください。

Javaアルゴリズムとデータ構造
public class Square extends Rectangle {public void setBreadth(int width){super.setBreadth(breadth)super.setLength(breadth)} public void setLength(int length){super.setLength(length)super.setBreadth(length)}}

この場合、Squareのインスタンスが渡されると、以下のコードで「Square is a Rectangle」を呼び出すと予期しない動作が開始されるように、SquareとRectangleの間にISA関係を確立しようとします。 「Area」チェックと「Breadth」チェックの場合、アサーションエラーがスローされますが、エリアチェックの失敗によりアサーションエラーがスローされるとプログラムは終了します。

public class LSPDemo {public voidcalculateArea(Rectangle r){r.setBreadth(2)r.setLength(3)assert r.getArea()== 6:printError( 'area'、r)assert r.getLength()== 3:printError( 'length'、r)assert r.getBreadth()== 2:printError( 'breadth'、r)} private String printError(String errorIdentifer、Rectangle r){return '予期しない値' + errorIdentifer + 'たとえば、 '+ r.getClass()。getName()} public static void main(String [] args){LSPDemo lsp = new LSPDemo()// Rectangleのインスタンスが渡されますlsp.calculateArea(new Rectangle()) // Squareのインスタンスが渡されますlsp.calculateArea(new Square())}}

このクラスは、リスコフの置換原則(LSP)を示しています 原則として、基本クラスへの参照を使用する関数は、派生クラスのオブジェクトを知らなくても使用できる必要があります。

したがって、以下に示す例では、「Rectangle」の参照を使用する関数calculateAreaは、Squareなどの派生クラスのオブジェクトを使用でき、Rectangle定義によって提示される要件を満たすことができるはずです。 Rectangleの定義に従って、以下のデータを考慮すると、次のことが常に当てはまる必要があることに注意してください。

  1. 長さは、メソッドsetLengthへの入力として渡される長さと常に等しくなければなりません。
  2. 幅は、メソッドsetBreadthへの入力として渡される幅と常に等しくなければなりません。
  3. 面積は常に長さと幅の積に等しくなければなりません

「Squareisa Rectangle」と呼ばれるように、SquareとRectangleの間にISA関係を確立しようとすると、Squareのインスタンスが渡されると、上記のコードが予期せず動作し始めます。領域のチェックとチェックの場合、アサーションエラーがスローされます。エリアチェックの失敗によりアサーションエラーがスローされるとプログラムは終了しますが、幅は広くなります。

Squareクラスには、setBreadthやsetLengthなどのメソッドは必要ありません。 LSPDemoクラスは、エラーのスローを回避するために適切にコーディングするために、Rectangleの派生クラス(Squareなど)の詳細を知る必要があります。既存のコードの変更は、そもそもオープンクローズの原則を破ります。

インターフェイス分離の原則

Robert C. Martinは、クライアントが使用しない不要なメソッドを実装することを強制されるべきではないと説明しています。

によるインターフェイス分離の原則クライアントは、使用しないインターフェースの実装を強制されるべきではなく、クライアントが使用されていないメソッドに依存する義務を負わされるべきではありません。したがって、基本的に、あなたが好むインターフェース分離の原則はインターフェースは小さいですが、モノリシックで大きなインターフェースではなく、クライアント固有です。要するに、クライアントに、必要のない特定のものに依存させるのは悪いことです。

たとえば、ログの書き込みと読み取りのための単一のロギングインターフェイスは、データベースには役立ちますが、コンソールには役立ちません。ログを読み取ることは、コンソールロガーにとって意味がありません。このSOLIDPrinciples inJavaの記事に進みます。

この原則が必要なのはなぜですか?

オンラインの顧客、ダイヤルインまたは電話の顧客、および持ち込みの顧客からの注文を受け入れるためのメソッドを含むレストランのインターフェースがあるとしましょう。また、オンライン支払い(オンライン顧客の場合)および対面支払い(持ち込み顧客および注文が自宅で配達される場合の電話顧客の場合)を処理する方法も含まれています。

次に、RestaurantのJavaインターフェースを作成し、RestaurantInterface.javaという名前を付けます。

パブリックインターフェイスRestaurantInterface {public void acceptOnlineOrder()public void takeTelephoneOrder()public void payOnline()public void walkInCustomerOrder()public void payInPerson()}

RestaurantInterfaceには、オンライン注文の受け付け、電話による注文、持ち込み顧客からの注文の受け付け、オンライン支払いの受け取り、および直接の支払いの受け取りの5つのメソッドが定義されています。

OnlineClientImpl.javaとしてオンライン顧客向けのRestaurantInterfaceを実装することから始めましょう

public classOnlineClientImplはRestaurantInterfaceを実装します{publicvoid acceptOnlineOrder(){//オンライン注文のロジック} public void takeTelephoneOrder(){//オンライン注文には適用されませんthrow new UnsupportedOperationException()} public void payOnline(){//支払いのロジックonline} public void walkInCustomerOrder(){//オンライン注文には適用されませんthrow new UnsupportedOperationException()} public void payInPerson(){//オンライン注文には適用されませんthrow new UnsupportedOperationException()}}
  • 上記のコード(OnlineClientImpl.java)はオンライン注文用であるため、UnsupportedOperationExceptionをスローします。

  • オンライン、電話、および持ち込みのクライアントは、それぞれに固有のRestaurantInterface実装を使用します。

  • TelephonicクライアントとWalk-inクライアントの実装クラスには、サポートされていないメソッドがあります。

  • 5つのメソッドはRestaurantInterfaceの一部であるため、実装クラスは5つすべてを実装する必要があります。

  • 各実装クラスがUnsupportedOperationExceptionをスローするメソッド。ご覧のとおり、すべてのメソッドの実装は非効率的です。

  • RestaurantInterfaceのメソッドの変更は、すべての実装クラスに伝播されます。その後、コードの保守は非常に面倒になり始め、変更による回帰効果は増加し続けます。

  • RestaurantInterface.javaは、支払いのロジックと注文のロジックが1つのインターフェースにグループ化されているため、単一責任の原則に違反しています。

上記の問題を克服するために、インターフェイス分離の原則を適用して、上記の設計をリファクタリングします。

SQLサーバー統合サービスのチュートリアル
  1. 支払いと注文の配置機能を、PaymentInterface.javaとOrderInterface.javaの2つの別々の無駄のないインターフェースに分離します。

  2. 各クライアントは、PaymentInterfaceとOrderInterfaceのそれぞれに1つの実装を使用します。たとえば、OnlineClient.javaはOnlinePaymentImplやOnlineOrderImplなどを使用します。

  3. 単一責任原則が、支払いインターフェース(PaymentInterface.java)および注文インターフェース(OrderInterface)として添付されるようになりました。

  4. 注文または支払いインターフェースのいずれかを変更しても、もう一方には影響しません。これらは現在独立しています。各インターフェイスには常に使用するメソッドしかないため、ダミーの実装を行ったり、UnsupportedOperationExceptionをスローしたりする必要はありません。

ISPを適用した後

依存性逆転の原則

ロバートC.マーチンは、それが具体化ではなく抽象化に依存していると説明しています。それによると、高レベルのモジュールは決して低レベルのモジュールに依存してはなりません。例えば

あなたは何かを買うために地元の店に行き、あなたはあなたのデビットカードを使ってそれを支払うことに決めます。そのため、支払いのために店員にカードを渡すとき、店員はあなたがどのような種類のカードを渡したかをわざわざ確認する必要はありません。

あなたがVisaカードを渡したとしても、彼はあなたのカードをスワイプするためのVisaマシンを出しません。あなたが支払うために持っているクレジットカードやデビットカードの種類は、彼らが単にそれをスワイプすることさえ問題ではありません。したがって、この例では、あなたと店員の両方がクレジットカードの抽象化に依存しており、カードの詳細について心配していないことがわかります。これが依存性逆転の原則です。

この原則が必要なのはなぜですか?

これにより、プログラマーはハードコードされた依存関係を削除できるため、アプリケーションは疎結合で拡張可能になります。

パブリッククラスStudent {プライベートアドレスアドレスpublicStudent(){アドレス=新しいアドレス()}}

上記の例では、StudentクラスにはAddressオブジェクトが必要であり、Addressオブジェクトの初期化と使用を担当します。将来、住所クラスが変更された場合は、学生クラスも変更する必要があります。これにより、StudentオブジェクトとAddressオブジェクトが緊密に結合されます。この問題は、依存性逆転のデザインパターンを使用して解決できます。つまり、Addressオブジェクトは独立して実装され、コンストラクターベースまたはセッターベースの依存性逆転を使用してStudentがインスタンス化されたときにStudentに提供されます。

これで、JavaでのこのSOLID原則は終わりです。

チェックしてください 25万人以上の満足した学習者のネットワークを持つ信頼できるオンライン学習会社であるEdurekaが世界中に広がっています。 EdurekaのJavaJ2EEおよびSOAトレーニングおよび認定コースは、Java開発者になりたい学生および専門家向けに設計されています。このコースは、Javaプログラミングをすぐに開始できるように設計されており、HibernateやSpringなどのさまざまなJavaフレームワークに加えて、コアと高度なJavaの両方の概念についてトレーニングします。

質問がありますか?この「JavaのSOLIDPrinciples」ブログのコメントセクションでそれについて言及してください。できるだけ早くご連絡いたします。