オープン/クローズと単一責任の原則

単一責任
  SOLID オブジェクト指向設計の原則は、変更が容易なコードを記述するための優れたガイドラインを提供しますが、原則によっては、動機と価値を理解するのが難しい場合があります。 オープン/クローズ 特に厄介なのは、 ソフトウェア エンティティ (クラス、モジュール、関数など) は拡張に対してオープンである必要がありますが、変更に対してはクローズされている必要があります 拡張されているが変更されていないとはどういう意味ですか? この原則は本質的に深く複雑なクラス階層を生み出すのでしょうか? があった 一部 議論 & 批判 そこで、Rescale 開発チームが Open/Closed を理解し、それを使用して動作を変更しやすいクラスを作成した経験の一部を共有したいと思いました。 私たちは、オープン/クローズドを単一責任原則を補完するものとみなしています。これは、開発者が最小限のコード変更で動作を簡単に変更できる、焦点を絞った責任を持つクラスを作成することを奨励するためです。

説明

まず最初に、最初の質問のいくつかを検討してみましょう。 コードが拡張されるとはどういう意味ですか? それが変更されるとはどういう意味ですか? コードは 修正されました コード自体を変更するときは、メソッドに条件を追加したり、追加の引数を取得したり、引数のプロパティに基づいて動作を切り替えたりすることによって行われます。 これは多くの場合、既存のコードに新しいタスクを実行させる方法について、開発者が最初に考えることです。つまり、別の条件を追加するだけです。 コードは コード自体を変更せずに動作を変更する場合、 さまざまな協力者を注入する。 私たちのコードがそのような拡張に対応できるようにするには、他の多くの SOLID 原則に従う必要があります。つまり、達成したいタスクの側面ごとに異なるオブジェクトを挿入する必要があります。 そのため、タスクの各側面を異なるオブジェクトによって実行する必要があります。これが単一責任原則の核心であり、オブジェクトは XNUMX つのことだけを実行する必要があります。 Open/Closed は、開発者が責任を分散するコードを書くことを奨励し、長いメソッドや大規模なクラスを成長させることなく動作を簡単に変更できるという利点を提供します。

この理論すべてを例を挙げて説明しましょう。 Rescale では最近、 大量のコードをリファクタリングした REST API クライアントを使用します。 API クライアントは、呼び出し元が行うリクエストの種類ごとに異なるメソッドを使用して作成されることが多く、それが私たちの開発の始まりです。 次のようなインターフェイスがありました。

パブリック インターフェイス APIClient { 分析 getAnalysis(long jobId); Hardwaresummary getHardwaresummary(long jobId); ... }

各メソッドの実装は次のようになります。

public Analysis getAnalysis(long jobId) { 文字列パス = “/api/jobs/” + jobId + “/analysis/”; 文字列応答 = makeHttpRequest(新しい URL(this.baseApiUrl, パス)); return parseJson(response, Analysis.class); }

これは作業するのが苦痛でした。 API から新しいデータを取得するたびに、新しい冗長メソッドを追加する必要がありました。 このクライアントはそうではありませんでした 延長に向けてオープン、その動作を変更するには、クラスを変更する必要がありました。 それは責任の曖昧さから生じた。 上記の方法は単純に見えますが、実際にはいくつかの異なる責任とそれに対応する知識があります。 リクエストの種類ごとにパスをフォーマットする方法を認識しています。 API に対して http リクエストを行う方法と、各リクエストの json を解析する方法を知っています。 私たちはこれをリファクタリングして、API クライアントにリクエストの作成という XNUMX つの責任だけを与えることにしました。 代わりに、他の役割を引き継ぐ Request オブジェクトを渡すことでそれを拡張します。 これで、メソッドが XNUMX つだけ含まれた API クライアント インターフェイスが完成しました。

パブリックインターフェイス APIClient { public T get(リクエストリクエスト); }

各タイプのリクエストは比較的静的であるため、リクエスト オブジェクトを作成するための静的なファクトリ メソッドがあります。

パブリック クラス リクエスト { public static void 分析用リクエスト(ジョブ ジョブ) { ... } }

また、Java ジェネリックのおかげで、呼び出し元は正しいタイプのオブジェクトを返します。 これで、次のような自然なコードが完成しました。

分析分析 = client.get(Request.forAnalysis(this.job));

API から新しい情報を取得するのも簡単です。 API クライアントを変更する必要はなく、別のリクエストを渡すことで拡張するだけです。

まとめ

Open/Closed が何を意味するかについては多くの混乱がありますが、単にオブジェクトが集中した責任を持ち、注入される他のオブジェクトと対話することを必要とします。そのため、開発者はさまざまなコラボレーターを自由に注入して動作を変更できます。 大規模なコードベースで作業したことがある人なら誰でも、オープン/クローズを考慮せずに開発されたセクションを見たことがあるでしょう。 このコードは一見シンプルで、「仕事を完了させるだけ」です。 問題は、ビジネスの要望は必然的に変化するため、仕事を変える唯一の方法はコードを変更することであるということです。 メソッドには条件文や switch ステートメントが蓄積され、クラスにはフィールドが蓄積され、コードは一見すると理解しにくくなります。 タスクを達成する最も簡単な方法はメソッドにさらに数行を追加することであるため、貧弱な設計はそれ自体に影響を及ぼします。 開発者がオープン/クローズを念頭に置いている場合、拡張可能な動作を持つオブジェクトを作成する機会を見つけて、開発コストが制御不能に増大するのを防ぐことができます。

著者

  • Adam McKenzie

    アダムは CTO として、HPC チームとカスタマー サクセス チームの管理を担当しています。 アダムはボーイングでキャリアをスタートし、787 年間 XNUMX に取り組み、主翼の設計、分析、最適化を行う構造およびソフトウェア エンジニアリング プロジェクトを管理しました。 アダムはオレゴン州立大学で機械工学の学士号を優秀な成績で取得しています。

類似の投稿