RubyのSOLID設計原則
すべてのソフトウェアアプリケーションは時間とともに変化します。ソフトウェアに加えられた変更は、予期しないカスケードの問題を引き起こす可能性があります。ただし、変更しないソフトウェアは構築できないため、変更は避けられません。ソフトウェア要件は、ソフトウェアの成長とともに変化し続けます。私たちにできることは、変更に対して回復力のある方法でソフトウェアを設計することです。ソフトウェアを適切に設計することは、最初は時間と労力を要する可能性がありますが、長期的には時間と労力を節約します。緊密に結合されたソフトウェアは壊れやすく、変更によって何が起こるかを予測することはできません。設計が不十分なソフトウェアの影響の一部を次に示します。
- それは不動を引き起こします。
- コードの変更には費用がかかります。
- ソフトウェアを単純化するよりも、複雑さを追加する方が簡単です。
- コードは管理できません。
- 開発者がその機能がどのように機能するかを理解するには、多くの時間がかかります。
- ソフトウェアの一部を変更すると、通常、他の部分が破損します。変更によってどのような問題が発生するかを予測することはできません。
論文のDesignPrinciplesand Design Patternsには、腐敗したソフトウェアの次の症状が記載されています。
- 剛性:ある部分で変更を加えると、コードの他の部分で変更を加える必要があるため、問題を発生させずにコードを変更することは非常に困難です。
- 脆弱性:コードを変更すると、通常、ソフトウェアの動作が損なわれます。変更に直接関係のない部分を壊すことさえあります。
- 不動性:ソフトウェアアプリケーションの一部は同様の動作をする可能性がありますが、コードを再利用することはできず、それらを複製する必要があります。
- 粘度:ソフトウェアの変更が難しい場合は、ソフトウェアを改善するのではなく、複雑さを増し続けます。
変更を制御および予測できるようにソフトウェアを設計する必要があります。
SOLIDの設計原則は、ソフトウェアプログラムを分離することにより、これらの問題を解決するのに役立ちます。 Robert C. Martinは、Design Principles and Design Patternsというタイトルの論文でこれらの概念を紹介し、MichaelFeathersは後で頭字語を思いつきました。
SOLIDの設計原則には、次の5つの原則が含まれます。
- S イングル責任の原則
- O ペン/クローズドプリンシパル
- L iskov置換原則
- 私 インターフェイス分離の原則
- D 依存性逆転の原則
これらの原則がRubyで適切に設計されたソフトウェアを構築するのにどのように役立つかを理解するために、それぞれを調査します。
人事管理ソフトウェアの場合、ユーザーを作成し、従業員の給与を追加し、従業員の給与明細を生成する機能が必要であるとしましょう。構築中に、これらの機能を単一のクラスに追加することもできますが、このアプローチでは、これらの機能間に不要な依存関係が発生します。開始するのは簡単ですが、状況が変化して新しい要件が発生すると、その変更によってどの機能が機能しなくなるかを予測できなくなります。
クラスには、変更する理由が1つだけある必要があります-Robert C Martin
すべての機能が単一のクラスに含まれるサンプルコードは次のとおりです。
class User
def initialize(employee, month)
@employee = employee
@month = month
end
def generate_payslip
# Code to read from database,
# generate payslip
# and write it to a file
self.send_email
end
def send_email
# code to send email
employee.email
month
end
end
給与明細を生成してユーザーに送信するには、クラスを初期化し、給与明細の生成メソッドを呼び出します。
month = 11
user = User.new(employee, month)
user.generate_payslip
今、新しい要件があります。給与明細を作成したいのですが、メールを送信したくありません。内部提案の場合と同様に、既存の機能をそのまま維持し、電子メールを送信せずに内部レポート用の新しい給与明細ジェネレーターを追加する必要があります。このフェーズでは、従業員に送信された既存の給与明細が引き続き機能するようにします。
この要件では、既存のコードを再利用することはできません。 generate_payslipメソッドにフラグを追加して、trueの場合はメールを送信し、そうでない場合は送信しないようにする必要があります。これは可能ですが、既存のコードが変更されるため、既存の機能が破損する可能性があります。
物事を壊さないようにするには、これらのロジックを別々のクラスに分離する必要があります。
class PayslipGenerator
def initialize(employee, month)
@employee = employee
@month = month
end
def generate_payslip
# Code to read from database,
# generate payslip
# and write it to a file
end
end
class PayslipMailer
def initialize(employee)
@employee = employee
end
def send_mail
# code to send email
employee.email
month
end
end
次に、これら2つのクラスを初期化し、それらのメソッドを呼び出すことができます。
month = 11
# General Payslip
generator = PayslipGenerator.new(employee, month)
generator.generate_payslip
# Send Email
mailer = PayslipMailer.new(employee, month)
mailer.send_mail
このアプローチは、責任を切り離し、予測可能な変化を保証するのに役立ちます。メーラー機能のみを変更する必要がある場合は、レポートの生成を変更せずに変更できます。また、機能の変更を予測するのにも役立ちます。
メールの月フィールドの形式をNov
に変更する必要があるとします。 11
の代わりに 。この場合、PayslipMailerクラスを変更します。これにより、PayslipGeneratorの機能が変更されたり壊れたりすることはありません。
コードを書くたびに、後で質問してください。このクラスの責任は何ですか?答えに「and」が付いている場合は、クラスを複数のクラスにくちばしにします。小さいクラスは、大きいジェネリッククラスよりも常に優れています。
Bertrand Meyerは、彼の著書「Object-OrientedSoftwareConstruction」でオープン/クローズド原則を考案しました。
原則として、「ソフトウェアエンティティ(クラス、モジュール、関数など)は、拡張のために開いている必要がありますが、変更のために閉じている必要があります "。これが意味するのは、エンティティを変更せずに動作を変更できる必要があるということです。
上記の例では、従業員に給与明細を送信する機能がありますが、これはすべての従業員にとって非常に一般的です。ただし、新しい要件が発生します。従業員のタイプに基づいて給与明細を生成します。フルタイムの従業員と請負業者には、異なる給与生成ロジックが必要です。この場合、既存のPayrollGeneratorを変更して、次の機能を追加できます。
class PayslipGenerator
def initialize(employee, month)
@employee = employee
@month = month
end
def generate_payslip
# Code to read from database,
# generate payslip
if employee.contractor?
# generate payslip for contractor
else
# generate a normal payslip
end
# and write it to a file
end
end
しかし、これは悪いパターンです。その際、既存のクラスを変更します。従業員の契約に基づいて生成ロジックを追加する必要がある場合は、既存のクラスを変更する必要がありますが、そうすると、オープン/クローズの原則に違反します。クラスを変更することにより、意図しない変更を行うリスクがあります。何かが変更または追加されると、既存のコードに不明な問題が発生する可能性があります。これらのif-elseは、同じクラス内のより多くの場所に配置できます。したがって、新しい従業員タイプを追加すると、これらのif-elseが存在する場所を見逃す可能性があります。それらをすべて見つけて変更することはリスクが高く、問題を引き起こす可能性があります。
機能を拡張することで機能を追加できるが、エンティティの変更を回避できるように、このコードをリファクタリングできます。したがって、これらごとに個別のクラスを作成し、同じgenerate
を使用してみましょう。 それぞれの方法:
class ContractorPayslipGenerator
def initialize(employee, month)
@employee = employee
@month = month
end
def generate
# Code to read from the database,
# generate payslip
# and write it to a file
end
end
class FullTimePayslipGenerator
def initialize(employee, month)
@employee = employee
@month = month
end
def generate
# Code to read from the database,
# generate payslip
# and write it to a file
end
end
これらのメソッド名が同じであることを確認してください。次に、PayslipGeneratorクラスを変更して、次のクラスを使用します。
GENERATORS = {
'full_time' => FullTimePayslipGenerator,
'contractor' => ContractorPayslipGenerator
}
class PayslipGenerator
def initialize(employee, month)
@employee = employee
@month = month
end
def generate_payslip
# Code to read from database,
# generate payslip
GENERATORS[employee.type].new(employee, month).generate()
# and write it to a file
end
end
ここには、従業員のタイプに基づいて呼び出されるクラスをマップするGENERATORS定数があります。これを使用して、呼び出すクラスを決定できます。これで、新しい機能を追加する必要がある場合、そのための新しいクラスを作成して、GENERATORS定数に追加するだけで済みます。これは、何かを壊したり、既存のロジックについて考える必要なしに、クラスを拡張するのに役立ちます。あらゆるタイプの給与明細ジェネレーターを簡単に追加または削除できます。
Liskov Substitution Principle-LSP
リスコフの置換原則は、「SがTのサブタイプである場合、タイプTのオブジェクトをタイプSのオブジェクトに置き換えることができる」と述べています。 。
この原理を理解するために、まず問題を理解しましょう。オープン/クローズドの原則の下で、ソフトウェアを拡張できるように設計しました。特定の仕事をするサブクラスの給与明細ジェネレーターを作成しました。発信者の場合、呼び出しているクラスは不明です。呼び出し元が違いを認識できないように、これらのクラスは同じ動作をする必要があります。動作とは、クラス内のメソッドが一貫している必要があることを意味します。これらのクラスのメソッドには、次の特性が必要です。
- 同じ名前を持っている
- 同じデータ型で同じ数の引数を取る
- 同じデータ型を返す
給与明細ジェネレータの例を見てみましょう。 2つの発電機があります。1つは正社員用で、もう1つは請負業者用です。ここで、これらの給与明細が一貫した動作をするようにするには、基本クラスからそれらを継承する必要があります。 User
という基本クラスを定義しましょう 。
class User
def generate
end
end
オープン/クローズ原則の例で作成したサブクラスには、基本クラスがありませんでした。基本クラスがUser
になるように変更します :
class ContractorPayslipGenerator < User
def generate
# Code to generate payslip
end
end
class FullTimePayslipGenerator < User
def generate
# Code to generate payslip
end
end
次に、User
を継承するサブクラスに必要な一連のメソッドを定義します。 クラス。これらのメソッドは基本クラスで定義します。この場合、必要なのはgenerateという1つのメソッドだけです。
class User
def generate
raise "NotImplemented"
end
end
ここでは、raise
を持つgenerateメソッドを定義しました。 声明。したがって、基本クラスを継承するサブクラスには、generateメソッドが必要です。存在しない場合は、メソッドが実装されていないというエラーが発生します。このようにして、サブクラスに一貫性があることを確認できます。これにより、発信者は常にgenerate
を確認できます メソッドが存在します。
この原則は、物事を壊したり、多くの変更を加えたりすることなく、サブクラスを簡単に置き換えるのに役立ちます。
インターフェイス分離の原則は静的言語に適用でき、Rubyは動的言語であるため、インターフェイスの概念はありません。インターフェイスは、クラス間の抽象化ルールを定義します。
原則は述べています、
クライアントは、使用しないインターフェースに依存することを強制されるべきではありません。 -ロバートC.マーチン
これが意味することは、どのクラスでも使用できる一般化されたインターフェースよりも多くのインターフェースを持つ方が良いということです。一般化されたインターフェースを定義する場合、クラスは使用しない定義に依存する必要があります。
Rubyにはインターフェースがありませんが、クラスとサブクラスの概念を見て、似たようなものを構築しましょう。
リスコフの置換原則に使用された例では、サブクラスFullTimePayslipGenerator
一般クラスUserから継承されました。ただし、Userは非常に汎用的なクラスであり、他のメソッドを含めることができます。 Leave
などの別の機能が必要な場合 、Userのサブクラスである必要があります。 Leave
生成メソッドは必要ありませんが、このメソッドに依存します。したがって、ジェネリッククラスを使用する代わりに、このための特定のクラスを使用できます。
class Generator
def generate
raise "NotImplemented"
end
end
class ContractorPayslipGenerator < Generator
def generate
# Code to generate payslip
end
end
class FullTimePayslipGenerator < Generator
def generate
# Code to generate payslip
end
end
このジェネレーターは給与明細の生成に固有であり、サブクラスは一般的なUser
に依存する必要はありません。 クラス。
依存性逆転は、ソフトウェアモジュールを分離するために適用される原則です。
高レベルのモジュールは低レベルのモジュールに依存するべきではありません。どちらも抽象化に依存する必要があります。
上記の原則を使用した設計は、依存性逆転の原則に向けて私たちを導きます。単一責任を持つクラスは、機能するために他のクラスのものを必要とします。給与を生成するには、データベースにアクセスする必要があり、レポートが生成されたらファイルに書き込む必要があります。単一責任の原則により、私たちは単一のクラスに対して1つの仕事だけを持つようにしています。ただし、データベースからの読み取りやファイルへの書き込みなどは、同じクラス内で実行する必要があります。
これらの依存関係を取り除き、主要なビジネスロジックを切り離すことが重要です。これにより、変更中にコードが流動的になり、変更が予測可能になります。依存関係を反転する必要があり、モジュールの呼び出し元が依存関係を制御できる必要があります。給与明細ジェネレータでは、依存関係がレポートのデータのソースです。このコードは、呼び出し元がソースを指定できるように編成する必要があります。依存関係の制御は逆にする必要があり、呼び出し元が簡単に変更できます。
上記の例では、ContractorPayslipGenerator
モジュールは依存関係を制御します。データの読み取り場所と出力の保存方法を決定するのはクラスによって制御されるためです。これを元に戻すには、UserReader
を作成しましょう。 ユーザーデータを読み取るクラス:
class UserReader
def get
raise "NotImplemented"
end
end
ここで、これにPostgresからのデータを読み取らせたいとしましょう。この目的のために、UserReaderのサブクラスを作成します。
class PostgresUserReader < UserReader
def get
# Code to read data from Postgres
end
end
同様に、FileUserReader
からリーダーを取得できます 、InMemoryUserReader
、またはその他の必要なタイプのリーダー。ここで、FullTimePayslipGenerator
を変更する必要があります PostgresUserReader
を使用するようにクラス 依存関係として。
class FullTimePayslipGenerator < Generator
def initialize(datasource)
@datasource = datasource
end
def generate
# Code to generate payslip
data = datasource.get()
end
end
これで、発信者はPostgresUserReader
を渡すことができます。 依存関係として:
datasource = PostgresUserReader.new()
FullTimePayslipGenerator.new(datasource)
呼び出し元は依存関係を制御でき、必要に応じてソースを簡単に変更できます。
依存関係の反転は、クラスだけに適用されるわけではありません。また、構成を逆にする必要があります。たとえば、Postgresサーバーに接続する際には、DBURL、ユーザー名、パスワードなどの特定の構成が必要です。これらの構成をクラスにハードコーディングする代わりに、呼び出し元からそれらを渡す必要があります。
class PostgresUserReader < UserReader
def initialize(config)
config = config
end
def get
# initialize DB with the config
self.config
# Code to read data from Postgres
end
end
呼び出し元が構成を提供します:
config = { url: "url", user: "user" }
datasource = PostgresUserReader.new(config)
FullTimePayslipGenerator.new(datasource)
呼び出し元は依存関係を完全に制御できるようになり、変更管理は簡単で簡単です。
SOLID設計は、コードを切り離し、変更の負担を軽減するのに役立ちます。プログラムは、切り離され、再利用可能で、変更に対応できるように設計することが重要です。 5つのSOLID原則はすべて互いに補完し合い、共存する必要があります。適切に設計されたコードベースは、柔軟性があり、変更が簡単で、操作が楽しいものです。新しい開発者なら誰でも、コードに飛び込んで簡単に理解できます。
SOLIDが解決する問題の種類と、これを行う理由を理解することは非常に重要です。問題を理解することは、設計原則を受け入れ、より優れたソフトウェアを設計するのに役立ちます。
-
最高の無料ゲーム デザイン ソフトウェア
ゲーム開発者になりたい場合は、Java、ActionScript、C++ などのプログラミング言語をいくつか学習する必要があります。これらのプログラミング言語は、開発者が 2D または 3D ゲームを作成するのに役立ちます。しかし、今ではコーディングが不要なゲーム デザイン ソフトウェアがたくさんあります。 したがって、ツールの進歩により、正式な教育を受けていなくてもゲーム業界に参入できます。ビデオゲーム作成ソフトウェアには、シンプルで使いやすいインターフェイスが付属しています。ほとんどのソフトウェアは、グラフィック エディター、イベント システム、レベル エディター、特殊効果、および時間を
-
2022 年のベスト ブック デザイン ソフトウェア 10
ブック デザイン ソフトウェアは、ライターや著者がオンラインでブックを作成するために使用できます。このようなアプリケーションの多くは、初心者も専門家も同様に使用できます。この記事では、書籍の表紙と内部デザインを作成し、書籍をデジタル形式で保存するための有料および無料の書籍デザイン ソフトウェアに焦点を当てています。あなた自身または他の人のために本を作成するためのものを探しているなら、本をデザインするソフトウェアの検索はここで終わります.リストされているすべての製品には、複数の便利な機能が搭載されています。 最高のブック デザイン ソフトウェアのリスト 1. Adobe InDesign