Hiểu Thế Nào Về Nguyên Lý "Programming to an Interface"
Trong khi lập trình hẳn không ít lần bạn đã được nghe hoặc đọc ở đâu đó về nguyên lý Programming to an Interface . Trong thực tế thì Programming to an Interface là một trong những nguyên lý đầu tiên được dạy cho các sinh viên trong ngành lập trình phần mềm. Tuy vậy vẫn không ít bạn sau khi ra ...
Trong khi lập trình hẳn không ít lần bạn đã được nghe hoặc đọc ở đâu đó về nguyên lý Programming to an Interface. Trong thực tế thì Programming to an Interface là một trong những nguyên lý đầu tiên được dạy cho các sinh viên trong ngành lập trình phần mềm. Tuy vậy vẫn không ít bạn sau khi ra trường vẫn thấy mơ mơ màng màng về nguyên tắc này. Cũng có thể là cách giải thích sách vở khiến các bạn khó hiểu. Tuy nhiên bạn cũng không cần phải lo ngại vì nguyên tắc này không khó hiểu như bạn nghĩ. Trước khi đi vào tìm hiều nguyên tắc này là gì hãy hiểu tại sao chúng ta cần dùng nó.
Tại Sao Cần Sử Dụng Nguyên Lý "Programming to an Interface"
Để sử dụng các loại thiết bị điện gia dụng như máy lạnh, máy giặt, nồi cơm ... chúng ta sẽ cần cung cấp điện để chúng có thể hoạt động. Và các loại thiết bị này đều có cách phích cắm để chúng ta có thể cắm vào các ổ cắm điện trong nhà.
Bạn có để ý là các loại phích cắm của các loại thiết bị trên thường có có hình dáng và thiết kế khác nhau. Ngay cả cùng một loại thiết bị như máy lạnh nếu được sản xuất bởi các hãng khác nhau thì phích cắm cũng có thể sẽ có các hình dáng và kích thước khác nhau. Tuy nhiên bạn có để ý rằng chúng đều có thể cắm được vào ổ cắm trên tường (trừ một số ít trường hợp ví dụ như phích cắm của máy tính laptop có thể có 3 chân):
Ảnh 1: Phích cắm điện Flachstecker
Ảnh 2: Phích cắm điện VDE
Ở trên chúng ta nói rằng ổ cắm đã đưa ra một giao diện thống nhất để các loại phích cắm với thiết kế khác nhau (nhưng tuân theo giao diện của ổ cắm) đều có thể sử dụng ổ cắm.
Ổ cắm không quan tâm tới việc điện sau đó sẽ được sử dụng bởi loại thiết bị nào nhưng nó đảm bảo rằng nếu các loại thiết bị này có phích cắm được thiết kế tuân theo giao diện của nó thì khi cắm vào nó sẽ cung cấp điện. Tất nhiên ổ cắm điện cũng sẽ không quan tâm tới việc thiết bị sẽ sử dụng nguồn điện mà nó cung cấp như thế nào (và cũng không thể bởi việc đó không thuộc phạm vi của ổ cắm).
Trong khi phát triển ứng dụng phần mềm, việc sử dụng interface là một trong những cách làm hiệu quả để tạo ra một tiêu chuẩn để các lập trình viên có thể tuân heo khi viết chương trình. Bạn nhớ lại rằng interface không thể hiện cách triển khai (implementation) mà ngược lại việc triển khai như thế nào sẽ được thực hiện bởi từng class cụ thể.
Sử Dụng Interface
Trong phát triển ứng dụng web thông thường chúng ta sẽ muốn log các lỗi nếu có xuất hiện khi người dùng truy cập trang. Có nhiều phương pháp viết log khác nhau như log vào file, log vào database...
Và thông thường với mỗi một phương pháp khác nhau bạn sẽ cần một class khác nhau:
class LogToFile { } class LogToDatabase { }
Việc triển khai cách log như thế nào sẽ hoàn toàn phụ thuộc vào developer. Tuy nhiên nếu bạn tạo ra một tiêu chuẩn để log lỗi bằng việc quy định mỗi class trên cần có một method với tên là log() để thực hiện công việc này. Lúc này bạn thấy có thể sử dụng interface như sau:
Interface LoggerInterface { public function log (String $info) {} } class LogToFile implements LoggerInterface { public function log(String $msg) { echo "Log error to file with message: $msg"; } } class LogToDatabase implements LoggerInterface { public function log(String $msg) { echo "Log error to database with message: $msg"; } }
Lưu ý rằng chúng ta không quy định quá trình log error sẽ thực hiện như thế nào trong method log() của LoggerInterface mà chỉ quy định tên method và các tham số cần truyền vào của method (còn gọi là method signature).
Tiếp theo chúng ta sẽ sử dụng một trong hai class LogToFile và LogToDatabase trong ứng dụng.
Đầu tiên chúng ta có class App để khởi tạo và chạy ứng dụng trong đó có một method với tên là setLogger() để gán loại log class nào sẽ được dùng:
class App () { function setLogger(LoggerInterface $logger) { $this->logger = $logger; } }
Đồng thời chúng ta cũng thêm một phương thức với tên là onError() để thực hiện việc log lỗi khi xuất hiện:
class App () { function setLogger(LoggerInterface $logger) { $this->logger = $logger; } function onError(Exception $err) { $logger = $this->logger; if ($logger) $logger->log($err->getMessage()); } }
Bạn có thể thấy với cách làm này chúng ta chỉ sử dụng kiểu dữ liệu là LogInterface cho đối số truyền vào cho phương thức setLogger() thay vì sử dụng một tên class cụ thể (concrete class). Điều này sẽ giúp code của chương trình linh động hơn vì việc đổi log class sẽ không ảnh hưởng tới phương thức setLogger() trong App. Hay nói cách khác việc thay đổi của quyết định ứng dụng sử dụng loại logger nào không phá với cấu trúc chương trình.