Các phần trước:
Một lớp chỉ nên có một lý do để thay đổi
// 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(); // ... } }
Trong ví dụ này, lớp của chúng tôi thực hiện các hành động theo các hướng khác nhau: nó thiết lập ngữ cảnh, thay đổi và xác thực nó.
// 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()) { // ... } } }
Các thực thể phần mềm (lớp, mô-đun, chức năng) phải mở để mở rộng nhưng đóng để sửa đổi
// 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 }
Trong ví dụ này, vấn đề nằm ở lớp HttpRequestCost
, trong phương thức getDeliveryCost
chứa các điều kiện để tính toán các loại sản phẩm khác nhau và chúng tôi sử dụng các phương thức riêng biệt cho từng loại sản phẩm. Vì vậy, nếu cần thêm một loại sản phẩm mới, chúng ta nên sửa đổi lớp HttpRequestCost
và nó không an toàn; chúng ta có thể nhận được kết quả bất ngờ.
Để tránh điều đó, chúng ta nên tạo một request
phương thức trừu tượng trong lớp Product
mà không cần thực hiện. Sự nhận thức cụ thể sẽ kế thừa các lớp: Ananas và Banana. Họ sẽ nhận ra yêu cầu cho chính mình.
HttpRequestCost
sẽ lấy tham số product
theo giao diện lớp Product
và khi chúng ta chuyển các phần phụ thuộc cụ thể vào HttpRequestCost
, nó sẽ tự nhận ra phương thức 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(...); } }
Các đối tượng của siêu lớp có thể được thay thế bằng các đối tượng của lớp con của nó mà không làm hỏng ứng dụng.
// 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'); } }
Trong ví dụ này, chúng tôi gặp vấn đề với lớp Contractor
. Designer
, Programmer
và Seller
đều là Công nhân và họ được kế thừa từ lớp cha Worker
. Nhưng đồng thời, Nhà thiết kế không được tiếp cận khu vực khép kín vì họ là Nhà thầu chứ không phải Nhân viên. Và chúng tôi đã ghi đè phương thức access
và phá vỡ Nguyên tắc thay thế Liskov.
Nguyên tắc này cho chúng ta biết rằng nếu chúng ta thay thế siêu lớp Worker
bằng lớp con của nó, chẳng hạn như lớp Designer
, thì chức năng sẽ không bị hỏng. Nhưng nếu chúng ta làm như vậy thì chức năng của lớp Programmer
sẽ bị hỏng - phương thức access
sẽ có những hiện thực không mong muốn từ lớp 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 {/../} }
Chúng tôi đã tạo các lớp trừu tượng mới Employee
và Contractor
, đồng thời chuyển phương thức access
sang lớp Employee
và xác định cách thực hiện cụ thể. Nếu chúng ta thay thế lớp Worker
bằng lớp con Contractor
, chức năng Worker
sẽ không bị hỏng.