Nguyên lý S.O.L.I.D trong JavaScript (P2)

Ở bài này chúng ta cùng tìm hiểu về nguyên lý thứ 3 trong S.O.L.I.D, tương ứng vói chữ L.

L - Liskov Substitution Principle

Đọc cái tên thì đã nguyên lý thôi đã thấy khó hiểu rồi phải không nào, Substitution nghĩa là thay thế rồi, vậy Liskov là gì nhỉ ? À thực ra cũng không khó hiểu lắm vì Liskov chỉ là tên người đã tạo ra nguyên lý này thôi, Barbara Liskov.

Nội dung nguyên lý:

Trong một chương trình, các đối tượng trong một chương trình có thể được thay thế bởi các class con mà không làm thay đổi tính đúng đắn của chương trình.

Giờ chúng ta cùng tìm hiểu về nguyên lý này thông qua 2 ví dụ nhé.

Ví dụ 1

Chính là bức ảnh này.

Rất đơn giản đó là một chú vịt và vịt chạy bằng pin.

OK chúng đều là vịt cả, nhưng liệu vịt chạy bằng pin MechanicalDuck trở thành class con của vịt Duck được không ?

Chúng ta sẽ áp dụng TDD vào chương trình (hiểu nôm na là viết test trước khi viết code), Chương trình của chúng ta chỉ kỳ vọng là vịt chỉ cần kêu được Quack, không cần làm gì nữa.

describe('Duck', function(){
  describe('#quack', function(){
    it('produces "Quack" sound', function(){
      const duck = new Duck();
      expect(duck.quack()).toEqual('Quack');
    });
  });
});

Giờ bắt đầu định nghĩa class Duck cơ bản nhất


class Duck{
  constructor(){
    // Duck initialization process
  }

  quack(){
    return 'Quack';
  }
}

Chúng ta chạy pass qua test, đáp ứng đủ yêu cầu của chương trình. Cool, giờ thì ta sẽ tạo class con MechanicalDuck cho vịt chạy bằng pin. Tất nhiên, nó cần có thể kêu quack được. Sự khác biệt duy nhất là nó cần pin để có thể vận hành.

class MechanicalDuck extends Duck{
  constructor(battery=null){
    super();
    this._battery = battery;
  }

  quack(){
    if(!this._battery){
      throw 'Need battery to operate.';
    }
    return 'Quack';
  }
}

Theo LSP, chúng ta có thể thay thế class cha bằng class con một cách an toàn. Giờ hãy thử áp dụng và thay thế MechanicalDuck cho Duck

Oh, test case đã thất bại. MechanicalDuck cần pin để kêu. Do đó, MechanicalDuck rõ ràng không thể thay thể Duck. Mặc dù giao diện của chúng có vẻ giống nhau, nhưng hành vi của chúng là hoàn toàn khác biệt. Vậy là việc sử dụng MechanicalDuck kế thừa Duck đã vi phạm LSP

Vậy cái gì sẽ trở thành class con thích hợp ?

Trong trường hợp này, đó là FemaleDuck.

class FemaleDuck extends Duck{
  constructor(){
    super();
    // Initialization of female stuff
    this._butt = new FemaleDuckButt();
  }

  layAnEgg(){
    const egg = this._butt.layAnEgg();
    return egg;
  } 
}

FemaleDuck sẽ pass qua test ở trên mà không làm thay đổi hành vi của Duck, chỉ mở rộng thêm chức năng cho Duck. Vịt của chúng ta giở có thể đẻ trứng.

OK, thông qua ví dụ trên, chắc các bạn cũng đã nắm được phần nào về nguyên lý này. Vậy áp dụng trong JavaScript thì sao nhỉ ?

Trong ngữ cảnh của JavaScript, nó có nghĩa là

  • Các phương thức của class con sẽ ghi đè phương thức của class cha với chính xác số lượng tham số
  • Mỗi tham số của phương thức ghi đè phải có type giống như phương thức của class cha
  • Type của giá trị trả về của phương thức ghi đè của class con phải giống class cha
  • Các type của exception được ném từ phương thức ghi đè phải giống với phương thức cha

Để tìm hiểu kỹ hơn thì chúng ta sẽ cùng vào ví dụ thứ 2

Ví dụ 2

Tìm hiểu về đại bàng và chim cánh cụt. Đại bàng và chim cánh cụt đều thuộc loài chim.


class Bird {
  fly(speed) {
    return `Flying at ${speed} km/h`;
  }
}

class Eagle extends Bird {
  dive() {
    // ...
  }

  fly(speed) {
    return `Soaring through the sky at ${speed}`;
  }
}

// Vi phạm LSP:
class Penguin extends Bird {
  fly() {
    return new Error("Sorry, I cant fly");
  }
}

Trong ví dụ này, class Eagle đã ghi đè phương thức fly, nó không vi phạm LSP bởi vì phương thức mới tương ứng với phương thức của class cha. Phương thức của class con ghi đè với class cha phải có cùng số lượng tham số => Eagle kế thừa cho Bird là hoàn toàn hợp lý.

Còn với chim cánh cụt thì sao nhỉ, thực tế thì bạn có thể hiểu là chim cánh cụt thì không thể bay, do đó để nó kế thừa Bird cũng đã sai sai rồi. Nhìn theo lý thuyết thì class Penguin đã vi phạm LSP ở 3 điểm sau:

  • Phương thức ghi đè không có cùng số lượng tham số
  • Type giá trị trả về của phương thức fly không giống nhau
  • Type của exception được ném ra không giống nhau

Bài viết đến đây là hết. Hi vọng có thể giúp các bạn hiểu thêm về nguyên lý thứ 3 này. Hẹn gặp lại các bạn ở bài viết sau về 2 nguyên lý còn lại nhé.

Nguồn:

https://carstenbehrens.com/liskov-substitution-principle/

https://maksimivanov.com/posts/liskov-substitution-principle/


All Rights Reserved