前の部分:
クラスを変更する条件は 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(); // ... } }
この例では、クラスはさまざまな领域でアクションを実行します。コンテキストを設定し、変更し、検証します。
// 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()) { // ... } } }
ソフトウェア エンティティ (クラス、モジュール、関数) は拡張に対してオープンである有需要がありますが、変更に対してはクローズされている有需要があります
// 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(...); } }
スーパークラスのオブジェクトは、アプリケーションを出现异常することなく、そのサブクラスのオブジェクトと置き換えることができる必需があります。
// 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
クラスに問題があります。 Designer
、 Programmer
、および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 {/../} }
新しい抽象化レイヤーEmployee
とContractor
を作成し、 access
メソッドをEmployee
クラスに移動して、特定の実現を定義しました。 Worker
クラスをサブクラスContractor
に置き換えても、 Worker
機能は壊れません。