paint-brush
Làm thế nào để giữ mã của bạn RẮNtừ tác giả@oxymoron_31
4,000 lượt đọc
4,000 lượt đọc

Làm thế nào để giữ mã của bạn RẮN

từ tác giả Nagarakshitha Ramu6m2023/03/15
Read on Terminal Reader

dài quá đọc không nổi

Nguyên tắc S.O.L.I.D là hướng dẫn chung để viết mã sạch trong lập trình hướng đối tượng. Chúng bao gồm Nguyên tắc chịu trách nhiệm duy nhất, Nguyên tắc đóng mở, Phân tách giao diện, Đảo ngược phụ thuộc và Nguyên tắc thay thế. Những nguyên tắc này có thể được áp dụng cho nhiều ngôn ngữ lập trình.
featured image - Làm thế nào để giữ mã của bạn RẮN
Nagarakshitha Ramu HackerNoon profile picture
0-item

Nhìn chung, tất cả các ngôn ngữ lập trình có thể được phân loại thành hai mô hình:

Lập trình mệnh lệnh / hướng đối tượng - Thực hiện theo trình tự các hướng dẫn được thực hiện từng dòng một.

Lập trình khai báo / chức năng - Không tuần tự nhưng liên quan nhiều hơn đến mục đích của chương trình. Toàn bộ chương trình giống như một chức năng hơn nữa có các chức năng con, mỗi chức năng thực hiện một nhiệm vụ nhất định.

Là một nhà phát triển cơ sở, tôi nhận ra điều đó một cách khó khăn (đọc: Tôi đã lo lắng nhìn chằm chằm vào hàng nghìn dòng mã) rằng đó không chỉ là viết mã chức năng, mà còn là mã dễ hiểu và linh hoạt về mặt ngữ nghĩa.

Mặc dù có nhiều phương pháp hay nhất để viết mã sạch trong cả hai mô hình, nhưng tôi sẽ nói về các nguyên tắc thiết kế RẮN liên quan đến mô hình lập trình hướng đối tượng.

RẮN là gì?

S — Trách nhiệm duy nhất
O—Nguyên tắc Đóng-Mở
L — Nguyên tắc thay thế Liskov
I - Phân chia giao diện
D — Đảo ngược phụ thuộc

Lý do chính của bất kỳ khó khăn nào trong việc nắm bắt các khái niệm này không phải vì chiều sâu kỹ thuật của chúng là không thể hiểu được, mà bởi vì chúng là những hướng dẫn trừu tượng được khái quát hóa để viết mã sạch trong lập trình hướng đối tượng. Chúng ta hãy xem xét một số sơ đồ lớp cấp cao để đưa các khái niệm này về nhà.

Đây không phải là sơ đồ lớp chính xác nhưng là bản thiết kế cơ bản để giúp hiểu những phương thức nào có trong một lớp.

Chúng ta hãy xem xét một ví dụ về quán cà phê trong suốt bài viết.

Nguyên tắc trách nhiệm đơn lẻ

Một lớp chỉ nên có một lý do để thay đổi

Hãy xem lớp này xử lý các đơn đặt hàng trực tuyến mà quán cà phê nhận được.

Có gì sai với nó?
Lớp duy nhất này chịu trách nhiệm cho nhiều chức năng. Nếu bạn phải thêm các phương thức thanh toán khác thì sao? Nếu bạn có nhiều cách gửi xác nhận thì sao? Thay đổi logic thanh toán trong lớp chịu trách nhiệm xử lý các đơn đặt hàng không phải là thiết kế tốt. Nó dẫn đến mã không linh hoạt cao.

Một cách tốt hơn là tách các chức năng cụ thể này thành các lớp cụ thể và gọi một thể hiện của chúng, như thể hiện trong hình minh họa bên dưới.

Nguyên tắc đóng mở

Các thực thể nên mở để mở rộng nhưng đóng để sửa đổi.

Tại quán cà phê, bạn phải chọn gia vị cho cà phê của mình từ danh sách các tùy chọn và có một lớp học xử lý việc này.

Quán cà phê quyết định thêm một loại gia vị mới, bơ. Lưu ý giá sẽ thay đổi như thế nào theo gia vị đã chọn và logic để tính giá nằm trong lớp Cà phê. Chúng tôi không chỉ phải thêm một lớp gia vị mới mỗi lần tạo ra các thay đổi mã có thể có trong lớp chính mà còn xử lý logic khác nhau mỗi lần.

Một cách tốt hơn là tạo một giao diện gia vị có thể có các lớp con ghi đè các phương thức của nó. Và lớp chính chỉ có thể sử dụng giao diện gia vị để chuyển các tham số và lấy số lượng và giá cho mỗi đơn hàng.

Điều này có hai lợi thế:

1. Bạn có thể tự động thay đổi thứ tự của mình để có các loại gia vị khác nhau hoặc thậm chí nhiều loại (cà phê với mocha và sô cô la nghe có vẻ tuyệt vời).

2. Lớp Condiments sẽ có mối quan hệ has-a với lớp Coffee, thay vì is-a. Vì vậy, Cà phê của bạn có thể có-một loại cà phê mocha/bơ/sữa chứ không phải Cà phê của bạn là-một loại cà phê mocha/bơ/sữa.

Nguyên tắc thay thế Liskov

Mỗi lớp con hoặc lớp dẫn xuất phải được thay thế cho lớp cơ sở hoặc lớp cha của nó.

Điều này có nghĩa là lớp con có thể trực tiếp thay thế lớp cha; nó phải có cùng chức năng. Tôi thấy khó hiểu cái này vì nó nghe giống như một số công thức toán học phức tạp. Nhưng tôi sẽ cố gắng làm rõ trong bài viết này.

Hãy xem xét các nhân viên trong quán cà phê. Có nhân viên pha chế, người quản lý và người phục vụ. Tất cả chúng đều có chức năng tương tự nhau.

Do đó, chúng ta có thể tạo một lớp Nhân viên cơ sở với tên, vị trí, getName, getPostion, takeOrder(), serve().

Mỗi lớp cụ thể, Người phục vụ, Nhân viên pha chế và Người quản lý có thể xuất phát từ điều này và ghi đè lên các phương thức tương tự để triển khai chúng khi cần thiết cho vị trí.

Trong ví dụ này, Nguyên tắc thay thế Liskov (LSP) được sử dụng bằng cách đảm bảo rằng bất kỳ lớp Nhân viên dẫn xuất nào cũng có thể được sử dụng thay thế cho lớp Nhân viên cơ sở mà không ảnh hưởng đến tính chính xác của mã.

Ví dụ: lớp Người phục vụ mở rộng lớp Nhân viên và ghi đè các phương thức takeOrder và serveOrder để bao gồm chức năng bổ sung dành riêng cho vai trò của người phục vụ. Tuy nhiên, quan trọng hơn, bất chấp sự khác biệt về chức năng, bất kỳ mã nào mong đợi một đối tượng của lớp Nhân viên cũng có thể hoạt động chính xác với một đối tượng của lớp Người phục vụ.

Hãy để chúng tôi xem một ví dụ để hiểu điều này

 public class Cafe {    public void serveCustomer (Staff staff) { staff.takeOrder(); staff.serveOrder(); } } public class Main {    public static void main (String[] args) { Cafe cafe = new Cafe(); Staff staff1 = new Staff( "John" , "Staff" ); Waiter waiter1 = new Waiter( "Jane" ); restaurant.serveCustomer(staff1); // Works correctly with Staff object
 restaurant.serveCustomer(waiter1); // Works correctly with Waiter object
 } }

Ở đây phương thức serveCustomer() trong lớp Cafe lấy đối tượng Staff làm tham số. Phương thức serveCustomer() gọi các phương thức takeOrder() và serveOrder() của đối tượng Staff để phục vụ khách hàng.

Trong lớp Main, chúng ta tạo một đối tượng Staff và một đối tượng Waiter. Sau đó chúng ta gọi phương thức serveCustomer() của lớp Cafe hai lần - một lần với đối tượng Staff và một lần với đối tượng Waiter.

Bởi vì lớp Người phục vụ có nguồn gốc từ lớp Nhân viên, bất kỳ mã nào mong đợi một đối tượng của lớp Nhân viên cũng có thể hoạt động chính xác với một đối tượng của lớp Người phục vụ. Trong trường hợp này, phương thức serveCustomer() của lớp Cafe hoạt động chính xác với cả đối tượng Staff và đối tượng Waiter, mặc dù đối tượng Waiter có chức năng bổ sung dành riêng cho vai trò của người phục vụ.

Phân chia giao diện

Các lớp không nên bị buộc phải phụ thuộc vào các phương thức mà chúng không sử dụng.

Vì vậy, có một chiếc máy bán hàng tự động rất linh hoạt tại quán cà phê có thể pha chế cà phê, trà, đồ ăn nhẹ và soda.

Điều gì là sai với nó? Không có gì về mặt kỹ thuật. Nếu bạn phải triển khai giao diện cho bất kỳ chức năng nào như pha chế cà phê, bạn cũng cần triển khai các phương pháp khác dành cho trà, soda và đồ ăn nhẹ. Điều này là không cần thiết và các chức năng này không liên quan đến từng chức năng khác. Mỗi chức năng đó có rất ít sự gắn kết giữa chúng.

sự gắn kết là gì? Nó là một yếu tố quyết định mức độ mạnh mẽ của các phương thức trong một giao diện liên quan đến nhau.

Và trong trường hợp máy bán hàng tự động, các phương pháp hầu như không phụ thuộc lẫn nhau. Chúng ta có thể tách biệt các phương pháp vì chúng có độ gắn kết rất thấp.

Bây giờ, bất kỳ giao diện nào nhằm triển khai một thứ, chỉ phải triển khai hàm takeMoney() chung cho tất cả các chức năng. Điều này phân tách các chức năng không liên quan trong một giao diện do đó tránh triển khai mạnh mẽ các chức năng không liên quan trong một giao diện.

Đảo ngược phụ thuộc

Các mô-đun cấp cao không nên phụ thuộc vào các mô-đun cấp thấp. Chi tiết phải phụ thuộc vào trừu tượng.

Hãy xem xét máy điều hòa không khí (máy làm mát) tại quán cà phê. Và nếu bạn giống tôi, nó luôn đóng băng trong đó. Hãy xem điều khiển từ xa và các lớp AC.

Ở đây, điều khiển từ xa là mô-đun cấp cao phụ thuộc vào AC, thành phần cấp thấp. Nếu tôi nhận được phiếu bầu, tôi cũng muốn có một cái máy sưởi :P Vì vậy, để có một bộ điều chỉnh nhiệt độ chung, thay vì bộ làm mát, chúng ta hãy tách rời điều khiển từ xa và điều khiển nhiệt độ. Nhưng lớp remoteControl được kết hợp chặt chẽ với AC, đây là một triển khai cụ thể. Để tách rời sự phụ thuộc, chúng ta có thể tạo một giao diện có chức năng chỉ tăngTemp() và giảmTemp() trong phạm vi, giả sử, 45-65 F.

Suy nghĩ cuối cùng

Như bạn có thể thấy, cả mô-đun cấp cao và cấp thấp đều phụ thuộc vào một giao diện trừu tượng hóa chức năng tăng hoặc giảm nhiệt độ.

Lớp bê tông, AC, thực hiện các phương pháp với phạm vi nhiệt độ áp dụng.

Bây giờ tôi có thể có được lò sưởi mà tôi muốn triển khai các phạm vi nhiệt độ khác nhau trong một lớp bê tông khác gọi là lò sưởi.

Mô-đun cấp cao, remoteControl chỉ phải lo lắng về việc gọi đúng phương thức trong thời gian chạy.