Cách phổ biến để xử lý khả năng hiển thị trong thư viện là gì?


12

Câu hỏi này về khi nào nên sử dụng riêng tư và khi nào sử dụng được bảo vệ trong các lớp học khiến tôi phải suy nghĩ. (Tôi cũng sẽ mở rộng câu hỏi này cho các lớp và phương thức cuối cùng, vì nó có liên quan. Tôi đang lập trình bằng Java, nhưng tôi nghĩ rằng điều này có liên quan đến mọi ngôn ngữ OOP)

Câu trả lời được chấp nhận sais:

Một nguyên tắc tốt là: làm cho mọi thứ riêng tư nhất có thể.

Và một số khác:

  1. Làm cho tất cả các lớp cuối cùng trừ khi bạn cần phân lớp chúng ngay lập tức.
  2. Làm cho tất cả các phương thức cuối cùng trừ khi bạn cần phân lớp và ghi đè chúng ngay lập tức.
  3. Làm cho tất cả các tham số phương thức là cuối cùng trừ khi bạn cần thay đổi chúng trong phần thân của phương thức, điều này hơi khó xử trong hầu hết các trường hợp.

Điều này khá đơn giản và rõ ràng, nhưng nếu tôi chủ yếu viết thư viện (Nguồn mở trên GitHub) thay vì các ứng dụng thì sao?

Tôi có thể đặt tên cho rất nhiều thư viện và tình huống, trong đó

  • Một thư viện đã được mở rộng theo cách mà các nhà phát triển sẽ không bao giờ nghĩ tới
  • Điều này phải được thực hiện với "ma thuật trình nạp lớp" và các bản hack khác vì những hạn chế về tầm nhìn
  • Các thư viện đã được sử dụng theo cách mà họ không được xây dựng và cách thức chức năng cần thiết "bị hack" trong
  • Thư viện không thể được sử dụng do một vấn đề nhỏ (lỗi, thiếu chức năng, hành vi "sai") không thể thay đổi do giảm tầm nhìn
  • Một vấn đề không thể khắc phục được dẫn đến các cách giải quyết lớn, xấu xí và lỗi, trong đó ghi đè một chức năng đơn giản (đó là riêng tư hoặc cuối cùng) có thể đã giúp

Và tôi thực sự bắt đầu đặt tên cho đến khi câu hỏi quá dài và tôi quyết định loại bỏ chúng.

Tôi thích ý tưởng không có nhiều mã hơn mức cần thiết, khả năng hiển thị nhiều hơn mức cần thiết, trừu tượng hơn mức cần thiết. Và điều này có thể hoạt động khi viết một ứng dụng cho người dùng cuối, trong đó mã chỉ được sử dụng bởi những người viết nó. Nhưng làm thế nào để giữ vững nếu mã này được sử dụng bởi các nhà phát triển khác, nơi mà nhà phát triển ban đầu nghĩ về mọi trường hợp sử dụng có thể xảy ra trước và các thay đổi / tái cấu trúc khó thực hiện?

Vì các thư viện nguồn mở lớn không phải là một điều mới, cách xử lý khả năng hiển thị phổ biến nhất trong các dự án như vậy với các ngôn ngữ hướng đối tượng là gì?



nếu bạn hỏi về nguồn mở, việc lập các nguyên tắc mã hóa phù hợp để giải quyết các vấn đề bạn đã liệt kê so với nguồn đóng sẽ trở nên ít ý nghĩa hơn, đơn giản là vì người ta có thể đóng góp các chỉnh sửa cần thiết ngay vào mã thư viện hoặc phân tách nó bất cứ điều chỉnh nào họ muốn
gnat

2
quan điểm của tôi không phải là về điều này mà là về việc bạn tham khảo nguồn mở không có ý nghĩa gì trong bối cảnh này. Tôi có thể tưởng tượng làm thế nào các nhu cầu thực dụng có thể biện minh cho sự sai lệch so với các nguyên tắc nghiêm ngặt trong một số trường hợp (còn được gọi là tích lũy nợ kỹ thuật ) nhưng từ quan điểm này, không quan trọng là mã đóng hay nguồn mở. Hay chính xác hơn, nó quan trọng theo hướng ngược lại so với điều bạn tưởng tượng ở đây bởi vì mã là nguồn mở có thể khiến những nhu cầu này trở nên ít bức xúc hơn vì nó cung cấp các tùy chọn bổ sung để giải quyết những điều này
gnat

1
@piegames: Tôi hoàn toàn đồng ý với một loại muôi đây, những vấn đề bạn scetched có nhiều khả năng xảy ra trong nguồn đóng libs - nếu nó là một lib OS với một giấy phép dễ dãi, nếu các nhà bảo trì bỏ qua một yêu cầu thay đổi, người ta có thể ngã ba lib và thay đổi tầm nhìn của chính mình, nếu cần thiết.
Doc Brown

1
@piegames: Tôi không hiểu câu hỏi của bạn. "Java" là một ngôn ngữ, không phải là một lib. Và nếu "thư viện mã nguồn mở nhỏ" của bạn có khả năng hiển thị quá nghiêm ngặt, thì việc mở rộng khả năng hiển thị sau đó thường không phá vỡ tính tương thích ngược. Chỉ có cách khác vòng.
Doc Brown

Câu trả lời:


15

Một sự thật đáng tiếc là nhiều thư viện được viết , không được thiết kế . Điều này thật đáng buồn, bởi vì một chút suy nghĩ trước đây có thể ngăn chặn rất nhiều vấn đề xuống đường.

Nếu chúng ta bắt đầu thiết kế một thư viện, sẽ có một số trường hợp sử dụng dự kiến. Thư viện có thể không đáp ứng trực tiếp tất cả các trường hợp sử dụng, nhưng có thể đóng vai trò là một phần của giải pháp. Vì vậy, thư viện cần phải đủ linh hoạt để thích nghi.

Hạn chế là thường không nên lấy mã nguồn của thư viện và sửa đổi nó để xử lý trường hợp sử dụng mới. Đối với các thư viện độc quyền, nguồn có thể không có sẵn và đối với các thư viện nguồn mở, việc duy trì phiên bản rẽ nhánh là điều không mong muốn. Có thể không khả thi để hợp nhất các sự thích ứng đặc biệt cao vào dự án thượng nguồn.

Đây là nơi mà nguyên tắc đóng open open xuất hiện: thư viện nên được mở để mở rộng mà không sửa đổi mã nguồn. Điều đó không tự nhiên đến. Đây phải là một mục tiêu thiết kế có chủ ý. Có rất nhiều kỹ thuật có thể giúp đỡ ở đây, các mẫu thiết kế OOP cổ điển là một số trong số đó. Nói chung, chúng tôi chỉ định các móc trong đó mã người dùng có thể cắm vào thư viện một cách an toàn và thêm chức năng.

Chỉ làm cho mọi phương thức được công khai hoặc cho phép mọi lớp được phân lớp là không đủ để đạt được khả năng mở rộng. Trước hết, rất khó mở rộng thư viện nếu không rõ nơi người dùng có thể móc vào thư viện. Ví dụ, ghi đè hầu hết các phương thức là không an toàn vì phương thức lớp cơ sở được viết với các giả định ngầm định. Bạn thực sự cần phải thiết kế để mở rộng.

Quan trọng hơn, một khi thứ gì đó là một phần của API công khai, bạn không thể lấy lại. Bạn không thể cấu trúc lại nó mà không phá vỡ mã nguồn. Độ mở sớm giới hạn thư viện trong một thiết kế tối ưu. Ngược lại, làm cho công cụ nội bộ ở chế độ riêng tư nhưng thêm móc nếu sau này cần có chúng là cách tiếp cận an toàn hơn. Mặc dù đó là một cách lành mạnh để giải quyết sự phát triển lâu dài của thư viện, nhưng điều này không thỏa đáng cho người dùng cần sử dụng thư viện ngay bây giờ .

Vì vậy, những gì xảy ra thay thế? Nếu có sự đau đớn đáng kể với trạng thái hiện tại của thư viện, các nhà phát triển có thể lấy tất cả kiến ​​thức về các trường hợp sử dụng thực tế được tích lũy theo thời gian và viết Phiên bản 2 của thư viện. Nó sẽ rất tuyệt! Nó sẽ sửa tất cả những lỗi do thiết kế! Nó cũng sẽ mất nhiều thời gian hơn dự kiến, trong nhiều trường hợp xì hơi. Và nếu phiên bản mới rất giống với phiên bản cũ, có thể khó khuyến khích người dùng di chuyển. Sau đó, bạn sẽ duy trì hai phiên bản không tương thích.


Vì vậy, tôi cần thêm các hook cho tiện ích mở rộng vì chỉ cần đặt nó ở chế độ công khai / overridable là không đủ. Và tôi cũng cần suy nghĩ khi nào nên phát hành thay đổi / API mới vì khả năng tương thích ngược. Nhưng những gì về khả năng hiển thị của các phương pháp đặc biệt?
piegames

@piegames Với khả năng hiển thị, bạn quyết định phần nào là công khai (một phần API ổn định của bạn) và phần nào là riêng tư (có thể thay đổi). Nếu ai đó cắt ngang với sự phản chiếu, đó là vấn đề của họ khi tính năng đó bị hỏng trong tương lai. Nhân tiện, các điểm mở rộng thường ở dạng phương thức có thể bị ghi đè. Nhưng có một sự khác biệt giữa một phương thức thể bị ghi đè và một phương thức được dự định ghi đè (xem thêm Mẫu Phương thức Mẫu).
amon

8

Mỗi lớp / phương thức mở rộng và công khai là một phần của API của bạn phải được hỗ trợ. Giới hạn được đặt thành một tập hợp con hợp lý của thư viện cho phép sự ổn định nhất và giới hạn số lượng điều có thể sai. Đó là một quyết định quản lý (và thậm chí các dự án OSS được quản lý ở mức độ) dựa trên những gì bạn có thể hỗ trợ một cách hợp lý.

Sự khác biệt giữa OSS và nguồn đóng là hầu hết mọi người đang cố gắng tạo và phát triển một cộng đồng xung quanh mã để có nhiều hơn một người duy trì thư viện. Điều đó nói rằng, có một số công cụ quản lý có sẵn:

  • Danh sách gửi thư thảo luận về nhu cầu của người dùng và cách triển khai mọi thứ
  • Hệ thống theo dõi sự cố (các vấn đề JIRA hoặc Git, v.v.) theo dõi lỗi và yêu cầu tính năng
  • Kiểm soát phiên bản quản lý mã nguồn.

Trong các dự án trưởng thành, những gì bạn sẽ thấy là một cái gì đó dọc theo những dòng này:

  1. Ai đó muốn làm một cái gì đó với thư viện mà ban đầu nó không được thiết kế để làm
  2. Họ thêm một vé để theo dõi vấn đề
  3. Nhóm có thể thảo luận vấn đề trong danh sách gửi thư hoặc trong các bình luận, và người yêu cầu luôn được mời tham gia thảo luận
  4. Thay đổi API được chấp nhận và ưu tiên hoặc từ chối vì một số lý do

Tại thời điểm đó, nếu thay đổi được chấp nhận nhưng người dùng muốn tăng tốc nó đã được sửa, họ có thể thực hiện công việc và gửi yêu cầu kéo hoặc bản vá (tùy thuộc vào công cụ kiểm soát phiên bản).

Không có API là tĩnh. Tuy nhiên, sự tăng trưởng của nó phải được định hình theo một cách nào đó. Bằng cách giữ tất cả mọi thứ đóng cửa cho đến khi có nhu cầu được chứng minh để mở mọi thứ, bạn tránh được danh tiếng của một thư viện lỗi hoặc không ổn định.


1
Hoàn toàn đồng ý và tôi đã thực hành thành công quy trình yêu cầu thay đổi mà bạn đã tìm kiếm cho các lib nguồn đóng của bên thứ 3, cũng như cho các lib nguồn mở.
Doc Brown

Theo kinh nghiệm của tôi, ngay cả những thay đổi nhỏ trong các thư viện nhỏ cũng rất nhiều việc (ngay cả khi chỉ để thuyết phục người khác) và có thể mất một thời gian (để chờ bản phát hành tiếp theo nếu bạn không đủ khả năng sử dụng ảnh chụp nhanh cho đến lúc đó). Vì vậy, đây rõ ràng không phải là một lựa chọn cho tôi. Mặc dù vậy, tôi rất quan tâm: có thư viện nào lớn hơn (trong GitHub) thực sự sử dụng khái niệm đó không?
piegames

Nó luôn luôn là rất nhiều công việc. Hầu như mọi dự án tôi đã đóng góp đều có một quy trình tương tự như vậy. Quay trở lại thời Apache của tôi, chúng tôi có thể thảo luận về một cái gì đó trong nhiều ngày bởi vì chúng tôi đam mê những gì chúng tôi tạo ra. Chúng tôi biết rất nhiều người sẽ sử dụng các thư viện, vì vậy chúng tôi phải thảo luận về những điều như liệu thay đổi được đề xuất có phá vỡ API hay không, tính năng được đề xuất có đáng không, khi nào chúng tôi nên làm điều đó? v.v.
Berin Loritsch

0

Tôi sẽ điều chỉnh lại câu trả lời của mình vì có vẻ như nó gây ấn tượng mạnh với một vài người.

khả năng hiển thị thuộc tính / phương thức lớp không liên quan gì đến bảo mật cũng như tính mở của nguồn.

Lý do tại sao khả năng hiển thị tồn tại là do các đối tượng dễ vỡ đối với 4 vấn đề cụ thể:

  1. đồng thời

Nếu bạn xây dựng mô-đun của mình không được bao gói, thì người dùng của bạn sẽ quen với việc thay đổi trạng thái mô-đun trực tiếp. Điều này hoạt động tốt trong một môi trường luồng đơn, nhưng một khi bạn thậm chí nghĩ về việc thêm các luồng; bạn sẽ bị buộc phải đặt trạng thái riêng tư và sử dụng các khóa / màn hình cùng với getters và setters khiến các luồng khác chờ tài nguyên, thay vì chạy đua với chúng. Điều này có nghĩa là các chương trình người dùng của bạn sẽ không hoạt động nữa vì các biến riêng tư không thể được truy cập theo cách thông thường. Điều này có thể có nghĩa là bạn cần viết lại rất nhiều.

Sự thật là việc mã hóa dễ dàng hơn với một thời gian chạy luồng đơn và từ khóa riêng cho phép bạn chỉ cần thêm từ khóa được đồng bộ hóa hoặc một vài khóa và mã của người dùng của bạn sẽ không bị hỏng nếu bạn đóng gói từ đầu .

  1. Giúp ngăn người dùng tự chụp trong khi sử dụng giao diện chân / hợp lý. Về bản chất, nó giúp bạn kiểm soát các bất biến của đối tượng.

Mỗi đối tượng có một loạt những điều nó đòi hỏi phải đúng để ở trạng thái nhất quán. Thật không may, những thứ này sống trong không gian có thể nhìn thấy của khách hàng bởi vì thật tốn kém khi di chuyển từng đối tượng vào quy trình riêng của nó và nói chuyện với nó qua tin nhắn. Điều này có nghĩa là rất dễ để một đối tượng làm hỏng toàn bộ chương trình nếu người dùng có khả năng hiển thị đầy đủ.

Điều này là không thể tránh khỏi, nhưng bạn có thể ngăn chặn việc vô tình đưa đối tượng vào trạng thái không nhất quán bằng cách đóng giao diện trên các dịch vụ của nó để ngăn sự cố do vô tình bằng cách chỉ cho phép người dùng tương tác với trạng thái của đối tượng thông qua giao diện được làm cẩn thận giúp chương trình mạnh mẽ hơn . Điều này không có nghĩa là người dùng không thể cố ý làm hỏng các bất biến, nhưng nếu có, đó là ứng dụng khách của họ gặp sự cố, tất cả những gì họ phải làm là khởi động lại chương trình (dữ liệu bạn muốn bảo vệ không nên được lưu trữ ở phía máy khách ).

Một ví dụ hay khác mà bạn có thể cải thiện khả năng sử dụng các mô-đun của mình là làm cho hàm tạo riêng tư; bởi vì nếu hàm tạo ném một ngoại lệ, nó sẽ giết chương trình. Một cách tiếp cận lười biếng để giải quyết điều này là làm cho nhà xây dựng đưa ra một lỗi thời gian biên dịch mà bạn không thể xây dựng nó trừ khi nó nằm trong khối thử / bắt. Bằng cách đặt hàm tạo riêng tư và thêm phương thức tạo tĩnh công khai, bạn có thể có phương thức tạo trả về null nếu không xây dựng được hoặc lấy hàm gọi lại để xử lý lỗi, giúp chương trình trở nên thân thiện hơn với người dùng.

  1. Phạm vi ô nhiễm

Nhiều lớp có rất nhiều trạng thái và phương thức và rất dễ bị choáng ngợp khi cố gắng cuộn qua chúng; Nhiều trong số các phương pháp này chỉ là nhiễu hình ảnh như các hàm trợ giúp, trạng thái. làm cho các biến và phương thức riêng tư giúp giảm ô nhiễm phạm vi và giúp người dùng dễ dàng tìm thấy các dịch vụ mà họ đang tìm kiếm.

Về bản chất, nó cho phép bạn thoát khỏi việc có các hàm trợ giúp bên trong lớp thay vì bên ngoài lớp; không có kiểm soát tầm nhìn mà không làm mất tập trung người dùng với một loạt các dịch vụ mà người dùng không bao giờ nên sử dụng, do đó bạn có thể thoát khỏi việc chia nhỏ các phương thức thành một loạt các phương thức trợ giúp (mặc dù nó sẽ gây ô nhiễm phạm vi của bạn, nhưng không phải là người dùng).

  1. bị ràng buộc với phụ thuộc

Giao diện được chế tạo tốt có thể ẩn cơ sở dữ liệu / cửa sổ / hình ảnh bên trong mà nó phụ thuộc vào công việc của nó và nếu bạn muốn thay đổi sang cơ sở dữ liệu khác / hệ thống cửa sổ khác / thư viện hình ảnh khác, bạn có thể giữ giao diện giống nhau và người dùng sẽ không thông báo.

Mặt khác, nếu bạn không làm điều này, bạn có thể dễ dàng rơi vào tình trạng không thể thay đổi sự phụ thuộc của mình, vì chúng bị lộ và mã phụ thuộc vào nó. Với một hệ thống đủ lớn, chi phí di chuyển có thể trở nên không thể chấp nhận được, trong khi đó, việc đóng gói nó có thể bảo vệ người dùng của khách hàng khỏi các quyết định trong tương lai để loại bỏ sự phụ thuộc.


1
"Không có điểm trong việc che giấu bất cứ điều gì" - vậy thì tại sao thậm chí nghĩ về việc đóng gói? Trong nhiều bối cảnh phản ánh không yêu cầu đặc quyền.
Frank Hileman

Bạn nghĩ về việc đóng gói vì nó mang lại cho bạn không gian thở trong khi phát triển mô-đun của bạn và giảm khả năng sử dụng sai. Ví dụ: nếu bạn có 4 luồng sửa đổi trực tiếp trạng thái bên trong của lớp, nó sẽ dễ dàng gây ra sự cố, trong khi đó biến riêng tư, khuyến khích người dùng sử dụng các phương thức công khai để thao tác trạng thái thế giới, có thể sử dụng màn hình / khóa để ngăn sự cố . Đây là lợi ích thực sự duy nhất của đóng gói.
Dmitry

Che giấu mọi thứ vì mục đích bảo mật là một cách dễ dàng để tạo ra một thiết kế mà cuối cùng bạn phải có lỗ hổng trong api của mình. Một ví dụ điển hình cho điều này là các ứng dụng đa tài liệu, nơi bạn có nhiều hộp công cụ và nhiều cửa sổ có cửa sổ phụ. Nếu bạn gặp khó khăn trong việc đóng gói, cuối cùng bạn sẽ gặp tình huống phải vẽ một thứ gì đó trên một tài liệu, bạn phải yêu cầu cửa sổ yêu cầu tài liệu bên trong yêu cầu tài liệu bên trong yêu cầu tài liệu bên trong yêu cầu vẽ một thứ gì đó và làm mất hiệu lực bối cảnh của nó. Nếu phía khách hàng muốn chơi với phía khách hàng, bạn không thể ngăn họ.
Dmitry

OK có ý nghĩa hơn, mặc dù bảo mật có thể đạt được thông qua kiểm soát truy cập nếu môi trường hỗ trợ điều đó và đó là một trong những mục tiêu ban đầu trong thiết kế ngôn ngữ OO. Ngoài ra, bạn đang thúc đẩy đóng gói và nói không sử dụng nó cùng một lúc; một chút bối rối.
Frank Hileman

Tôi không bao giờ có nghĩa là không sử dụng nó; Tôi có nghĩa là không sử dụng nó vì mục đích bảo mật; sử dụng nó một cách chiến lược để cải thiện trải nghiệm của người dùng và tạo cho mình một môi trường phát triển mượt mà hơn. quan điểm của tôi là nó không liên quan gì đến bảo mật cũng như tính mở của nguồn. Theo định nghĩa, các đối tượng phía khách hàng dễ bị xâm nhập và việc di chuyển chúng ra khỏi không gian xử lý của người dùng khiến cho những thứ không được bao bọc không thể truy cập được như được gói gọn.
Dmitry
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.