SOLID - 5 nguyên tắc của thiết kế hướng đối tượng

1. Single responsibility principle (S)
Mỗi class hoặc một method thì chỉ thực hiện một xử lý duy nhất.
Ví dụ class DBContext thực hiện xử lý CRUD:
public class DBContext
{
 public void Create() {}
 public void Update() {}
 public void Retrieve() { }
 public void Delete() { }
}
Nếu class này phải thực hiện những xử lý khác nữa thì cần phải tạo class khác, tránh việc thêm quá nhiều vào 1 class khiến class sẽ bị phình to, class kiểu này gọi là God Object.
Tương tự cho việc viết method, mỗi method thực hiện 1 xử lý duy nhất ví dụ hàm run() thì thực hiện xử lý chạy, nếu có thêm xử lý nhảy thì phải viết thêm hàm dance().

2. Open/closed principle (O)

Các class,method nên được thiết kế sao cho có thể dễ mở rộng, nhưng không phải sửa đổi nội dung của class, method đó.
Có thể hiểu qua ví dụ sau:
idol
Class Idol có xử lý catwalk() là một chức năng của object Idol, nếu có object NgocTrinh và HoNgocHa cũng là một idol nhưng ngoài khả năng catwalk() ra còn có khả năng dance(), và sing() thay vì add method trên vào class Idol thì tạo ra class mới kế thừa class Idol, override lại method catwalk() nếu như NgocTrinh và HoNgocHa có cách catwalk khác nhau.
Như vậy class Idol sẽ không phải sửa đổi và con sẽ không phải test lại class này, hạn chế tối đa side effects.
public class NgocTrinh : Idol
{
    public override void catwalk() { };
    public void dance() { };
}

3. Liskov substitution principle (L)

Đây là nguyên tắc do nhà khoa học máy tính Barbara Liskov sáng tạo ra, nội dung của nguyên tắc này nếu đọc trên Google chắc sẽ con sẽ rất “bối rối” vì khó hiểu:
Object của lớp kế thừa có thể thay thế lớp cha nhưng không được thay đổi behavior của lớp cha.
Tuy nhiên qua ví dụ cụ thể sẽ hiểu:
class Idol
{
    public void catwalk()
   {
        // catwalk bằng 2 chân
  };
}
class NgocTrinh : Idol
{
  public override void catwalk()
  {
      // catwalk bằng 4 chân
  };
}
Object Idol là người mẫu thực hiện catwalk() bằng 2 chân, tuy nhiên đối tượng em NgocTrinh lại override catwalk bằng 4 chân thì đây là một strange behaviour, vì e NgocTrinh là Idol và cũng là người nên đi lại bằng 4 chân là vi phạm nguyên tắc Liskov, hay vi phạm đến tính đa hình của đối tượng.

4. Interface Segregation Principle (I)

Thay vì dùng 1 interface lớn, ta nên tách thành nhiều interface nhỏ, với nhiều mục đích cụ thể.
Ví dụ:
interface IIdol {
          public void catwalk();
}
Đối tượng NgocTrinh, HoNgocHa đều implement interface IIDoil nên phải code xử lý method catwalk:
class NgocTrinh implements IIdol {
       public void catwalk () {
              // ....walking
      }
}
class HoNgocHa implements IIdol {
       public void catwalk () {
              // ....walking
       }
}
Nếu như Interface IIDol được add thêm các method như dance(), sing() thì class NgocTrinh, HoNgocHa cũng phải implement thêm những method tương ứng dẫn tới việc dư thừa vì NgocTrinh chỉ biết dance() chứ không biết sing() và ngược lạiHơn như việc này sẽ làm interface IIDol sẽ phình to ko hiệu quả cho việc maintain, do đó phải tách ra thành 2 interface nhỏ hơn (Idance, Ising) để tiện việc maintain như sau:
interface IDance {
          public void dance();
 }
interface ISing {
          public void sing();
 }
class NgocTrinh implements Iidol, Idance {}
class HoNgocHa implements Iidol, Ising {}
5. Dependency inversion principle (D)
Đây là nguyên lý khó hiểu nhất trong Solid, dễ gây loạn chưởng nhất cho coderCó thể hiểu qua ví dụ sau:
1.       Tỉ phú Hoàng Kiều kí hợp đồng làm ăn với Khắc Tiệp
2.       Khắc Tiệp chuyên cung cấp chân dài theo tiêu chí của Hoàng Kiều
3.       Tuy nhiên Khắc Tiệp do chạy sô nhiều nên thỉnh thoảng ko cung cấp đúng như yêu cầu của Hoàng Kiều. Hoàng Kiều lỡ kí hợp đồng rồi, phụ thuộc vào Khắc Tiệp nên bó tay
Ví dụ:
class HoangKieu {
  private KhacTiep supplier;
  public String getChanDai() {
    supplier = new KhacTiep();
    chandai = supplier.cungCapChanDai(NgocTrinh);
    return chandai;
}
class KhacTiep {
  public String cungCapChanDai (String idol) {
  String chandai;
           if (idol.equals(“NgocTrinh”)){
                 chandai = “Fake”; // tráo hàng
           }
  return chandai;
}
Ở vị dụ trên, module cấp cao HoangKieu phụ thuộc vào module cấp thấp KhacTiep, method cungCapChanDai thay đổi (ví dụ thay đổi kiểu tham số) dẫn tới phải sửa đổi method getChanDai ở module Hoang Kieu,làm giảm khả năng bảo trì. Ngoài ra, rất khó thực hiện Unit Test trong trường hợp này Theo nguyên tắc này thì sẽ thay đổi thiết kế như sau:
1.        Hoàng Kiều làm việc qua một trung gian (interface) Đại Lý
2.        Hoàng Kiều đưa yêu cầu cho Đại Lý cần cungcapChanDai(NgocTrinh)
3.        Đại Lý tìm KhacTiep để cung cấp nếu không thỏa mãn thì tìm nhà cung cấp khác ví dụ bầu sô QuangHuy
Viết code như sau:
interface DaiLy { public string cungCapChanDai (string idol); }
class KhacTiep implements DaiLy {
}
class QuangHuy implements DaiLy {
}
 class HoangKieu {
    String getChanDai(DaiLy data) {
     chandai = data.cungCapChanDai(NgocTrinh);
    }
}
Như vậy HoangKieu chỉ việc truyền object là KhacTiep hay QuangHuy vào method getChanDai là có được chân dài như mong muốn mà ko phải sửa đổi.
Kết luận
Có thể nói, Nguyên tắc SOLID giúp chúng ta xây dựng những hệ thống lớn, dễ mở rộng và bảo trì hơn. Nhưng mà có thể thấy, chúng ta phải trừu tượng hoá ứng dụng một chút, sử dụng nhiều interface hơn, gõ phím nhiều hơn một chút. Một số người cho rằng: Too much Java. Cái này đúng nếu như code base của chúng ta nhỏ và không bao giờ thay đổi. Nhưng thực tế, chẳng ai mong muốn ứng dụng/ business mình không phát triển cả. Một lần nữa có lẽ nó là nice problem to have. Các nguyên tắc này không mới, nhưng mình tin rằng nó mang lại khá nhiều lý luận cũng như công cụ để xây dựng các dụng lớn và dễ bảo trì nâng cấp cho mọi lập trình viên


Share on Google Plus

About Hà Tuấn Anh

0 nhận xét:

Đăng nhận xét