paint-brush
クリーンなコード: 単一責任、オープン/クローズ、リスコフ置換 TS における SOLID 原則 [パート 4] に@alenaananich
4,707 測定値
4,707 測定値

クリーンなコード: 単一責任、オープン/クローズ、リスコフ置換 TS における SOLID 原則 [パート 4]

Alena Ananich7m2023/11/09
Read on Terminal Reader

長すぎる; 読むには

この記事では、最初の 3 つの SOLID 原則、単一責任原則、オープン/クローズド原則、リスコフ置換原則を実際の例で見ていきます。
featured image - クリーンなコード: 単一責任、オープン/クローズ、リスコフ置換 TS における SOLID 原則 [パート 4]
Alena Ananich HackerNoon profile picture


前の部分:


私たちはコードをクリーンで柔軟性があり、固执しやすいものにするためのアプローチを引き続き検討しています。それでは、クリーンなアーキテクチャ ソリューションの調査を始めましょう。


SOLID 原則は Robert C. Martin によって導入され、オブジェクト朝着プログラミングおよびソフトウェア設計のベスト プラクティスとして広く認められています。


この記事では、最开始の 3 つの SOLID 原則、単一責任原則、オープン/クローズド原則、リスコフ置換原則を実際の例に基づいて説明します。

目次:

  1. 単一責任の原則
  2. オープン/クローズの原則
  3. リスコフ置換原理

1. 単一責任原則 (SRP)

クラスを変更する条件は 1 つだけである必备があります


言い換えれば、クラスは単一の責任または仕事を持つ用不着があります。単一の責任を持つクラスは、看法、変更、单纯が便捷であるため、これは更重要です。コードの 1 つの領域の変更がシステム列席会议に蔓延到することがないため、バグが発生するリスクが軽減されます。


実際の例と、単一責任の原則に従う具体方法を見てみましょう。
 // Bad class UserSettingsService { constructor(user: IUser) { this.user = user; } changeSettings(settings: IUserSettings): void { if (this.isUserValidated()) { // ... } } getUserInfo(): Promise<IUserSettings> { // ... } async isUserValidated(): Promise<boolean> { const userInfo = await this.getUserInfo(); // ... } }
この例では、クラスはさまざまな领域でアクションを実行します。コンテキストを設定し、変更し、検証します。


SRP に従うには、これらの異なる責任を拼接する一定があります。
 // Better class UserAuth { constructor(user: IUser) { this.user = user; } getUserInfo(): Promise<IUserSettings> { // ... } async isUserValidated(): boolean { const userInfo = await this.getUserInfo(); // ... } } class UserSettings { constructor(user: IUser) { this.user = user; this.auth = new UserAuth(user); } changeSettings(settings: IUserSettings): void { if (this.auth.isUserValidated()) { // ... } } }


どうしてそれが重要ですか?

SRP はコードのモジュール性を強化し、双方依存関係から生じる課題を世界上最大限に抑えることを意义としています。コードを関数に編成し、クラスを分離し、モジュール化を促進することにより、コードの再借助性が高まり、既存の機能の再コーディングに費やす時間を節約できます。

2. オープン/クローズ原則 (OCP)

ソフトウェア エンティティ (クラス、モジュール、関数) は拡張に対してオープンである有需要がありますが、変更に対してはクローズされている有需要があります


既存のコードを変更せずに新しい機能を追加できる用不着があります。この原則に従うことで、既存のコードの安定性を損なうことなく新しい機能やコンポーネントをシステムに導入できるため、これは重要性です。


これにより、コードの再借助性が促進され、機能を拡張する際に大規模な変更を行う需要性が軽減されます。
 // Bad class Product { id: number; name: string[]; price: number; protected constructor(id: number, name: string[], price: number) { this.id = id; this.name = name; this.price = price; } } class Ananas extends Product { constructor(id: number, name: string[], price: number) { super(id, name, price); } } class Banana extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } } class HttpRequestCost { constructor(product: Product) { this.product = product; } getDeliveryCost(): number { if (product instanceOf Ananas) { return requestAnanas(url).then(...); } if (product instanceOf Banana) { return requestBanana(url).then(...); } } } function requestAnanas(url: string): Promise<ICost> { // logic for ananas } function requestBanana(url: string): Promise<ICost> { // logic for bananas }

この例では、問題はクラスHttpRequestCostにあります。このメソッドのgetDeliveryCostには、さまざまな種類の製品を計算するための条件が含まれており、製品の種類ごとに個別のメソッドを使用しています。したがって、新しいタイプの製品を追加する必要がある場合は、 HttpRequestCostクラスを変更する必要がありますが、これは安全ではありません。予期せぬ結果が得られる可能性があります。


これを回避するには、実現せずにProductクラスで抽象メソッドrequestを作成する必要があります。特定の実現は、アナナスとバナナというクラスを継承します。彼らは自分自身の要求を実現します。


HttpRequestCost Productクラス インターフェイスに従ってproductパラメーターを受け取り、 HttpRequestCostに特定の依存関係を渡すと、それ自体のrequestメソッドがすでに実現されます。

 // Better abstract class Product { id: number; name: string[]; price: string; constructor(id: number, name: string[], price: string) { this.id = id; this.name = name; this.price = price; } abstract request(url: string): void; } class Ananas extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } request(url: string): void { // logic for ananas } } class Banana extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } request(url: string): void { // logic for bananas } } class HttpRequestCost { constructor(product: Product) { this.product = product; } request(): Promise<void> { return this.product.request(url).then(...); } }


どうしてそれが重要ですか?

この原則に従って、コードの結合を減らし、システムを予期しない動作から守ります。

3. リスコフ置換原理 (LSP)

スーパークラスのオブジェクトは、アプリケーションを出现异常することなく、そのサブクラスのオブジェクトと置き換えることができる必需があります。


この原則を理解是什么するために、次の例を見てみましょう。
 // Bad class Worker { work(): void {/../} access(): void { console.log('Have an access to closed perimeter'); } } class Programmer extends Worker { createDatabase(): void {/../} } class Seller extends Worker { sale(): void {/../} } class Designer extends Worker { access(): void { throwError('No access'); } }

この例では、 Contractorクラスに問題があります。 DesignerProgrammer 、およびSellerすべて Worker であり、親クラスWorkerから継承されます。しかし同時に、設計者は従業員ではなく請負業者であるため、閉じた境界にアクセスすることはできません。そして、 access方法をオーバーライドし、リスコフ置換原則を破りました。


この原則は、スーパークラスWorkerそのサブクラス (たとえばDesignerクラス) に置き換えた場合でも、機能が壊れるべきではないことを示しています。しかし、それを行うと、 Programmerクラスの機能が壊れてしまい、 accessメソッドにDesignerクラスからの予期しない認識が発生することになります。


リスコフ置換原則に従って、サブクラスの実現を書き換えるべきではなく、具像化の種類ごとに既定の実現を定義する新しい具像化層を做成する重要があります。


それを纠正しましょう:
 // Better class Worker { work(): void {/../} } class Employee extends Worker { access(): void { console.log('Have an access to closed perimeter'); } } class Contractor extends Worker { addNewContract(): void {/../} } class Programmer extends Employee { createDatabase(): void {/../} } class Saler extends Employee { sale(): void {/../} } class Designer extends Contractor { makeDesign(): void {/../} }

新しい抽象化レイヤーEmployeeContractorを作成し、 accessメソッドをEmployeeクラスに移動して、特定の実現を定義しました。 WorkerクラスをサブクラスContractorに置き換えても、 Worker機能は壊れません。

どうしてそれが重要ですか?

LSP に準拠している場合は、通常クラス インスタンスが用得着な場合はどこでもサブクラス インスタンスを置き換えることができ、プログラムは意図したとおりに動作するはずです。これにより、コードの再用とモジュール化が促進され、コードの変更に対する意志力が強化されます。


次の記事では、インターフェイスの分離と依存関係の反転 SOLID の原則について説明します。
바카라사이트 바카라사이트 온라인바카라