이전 부품:
클래스를 변경해야 하는 이유는 단 하나여야 합니다.
// 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
생성해야 합니다. 특정 실현은 Ananas 및 Banana 클래스를 상속받게 됩니다. 그들은 스스로 요청을 깨닫게 될 것입니다.
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
방법을 재정의하고 Liskov 대체 원칙을 위반했습니다.
이 원칙은 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
기능이 손상되지 않습니다.