Bạn có nên sử dụng các biến thành viên được bảo vệ không?


97

Bạn có nên sử dụng các biến thành viên được bảo vệ không? Những lợi thế là gì và những vấn đề này có thể gây ra?

Câu trả lời:


73

Bạn có nên sử dụng các biến thành viên được bảo vệ không?

Phụ thuộc vào việc bạn kén chọn trạng thái ẩn như thế nào.

  • Nếu bạn không muốn rò rỉ bất kỳ trạng thái nội bộ nào, thì cách khai báo tất cả các biến thành viên của bạn là riêng tư.
  • Nếu bạn không thực sự quan tâm đến việc các lớp con có thể truy cập trạng thái bên trong, thì bảo vệ là đủ tốt.

Nếu một nhà phát triển đến và phân lớp lớp con của bạn, họ có thể làm rối nó vì họ không hiểu nó đầy đủ. Với các thành viên riêng tư, khác với giao diện công khai, họ không thể xem chi tiết triển khai cụ thể về cách mọi thứ đang được thực hiện, điều này giúp bạn có thể linh hoạt thay đổi nó sau này.


1
Bạn có thể nhận xét về hiệu suất của biến được bảo vệ so với biến riêng với phương thức get / set không?
Jake

3
Tôi muốn nói rằng nó không phải là điều gì đó đáng lo ngại trừ khi bạn tìm thấy thông qua hồ sơ rằng cổ chai cuối cùng trở thành vật tiếp cận (điều mà hầu như không bao giờ có). Có những thủ thuật có thể được thực hiện để làm cho JIT thông minh hơn về mọi thứ nếu nó cuối cùng trở thành một vấn đề. Trong java chẳng hạn, bạn có thể gợi ý rằng trình truy cập có thể được nội tuyến bằng cách đánh dấu nó là cuối cùng. Mặc dù thành thật mà nói, hiệu suất của getters và setters ít quan trọng hơn nhiều so với việc xử lý tổ chức hệ thống và với các vấn đề hiệu suất thực tế như được xác định bởi một bộ hồ sơ.
Allain Lalonde

23
@Jake: Bạn không bao giờ nên đưa ra quyết định thiết kế dựa trên các giả định về hiệu suất. Bạn đưa ra quyết định thiết kế dựa trên những gì bạn nghĩ là thiết kế tốt nhất và chỉ khi việc lập hồ sơ thực tế cho thấy một điểm nghẽn trong thiết kế của bạn, bạn mới đi sửa chữa nó. Thông thường, nếu thiết kế âm thanh thì hiệu suất cũng tốt.
Mecki

Với các thành viên riêng tư, khác với giao diện công khai, họ không thể nhìn thấy chi tiết cụ thể về việc triển khai. Họ chỉ có thể mở lớp và tra cứu nó, vì vậy điều đó vô nghĩa ?!
Đen

2
@Black Rõ ràng Allain có nghĩa là 'họ không thể truy cập ' các thành viên đó và do đó không thể xây dựng mã chống lại họ, để tác giả của lớp tự do xóa / thay đổi các thành viên được bảo vệ sau này. (Tất nhiên, thành ngữ ma cô sẽ cho phép ẩn chúng một cách trực quan và khỏi các đơn vị dịch bao gồm cả tiêu đề.)
underscore_d

31

Cảm giác chung ngày nay là chúng gây ra sự kết hợp quá mức giữa các lớp dẫn xuất và cơ sở của chúng.

Chúng không có lợi thế cụ thể nào so với các phương thức / thuộc tính được bảo vệ (ngày xưa chúng có thể có một chút lợi thế về hiệu suất), và chúng cũng được sử dụng nhiều hơn trong thời đại mà tính kế thừa rất sâu sắc đang là mốt, mà hiện tại thì không.


2
Không nên no particular advantage over protected methods/propertiesno particular advantage over *private* methods/properties?
Penghe Geng

Không, bởi vì tôi đang / đang nói về những ưu điểm / nhược điểm của nhiều cách giao tiếp khác nhau giữa các lớp dẫn xuất và cơ sở của chúng - tất cả các kỹ thuật này sẽ được 'bảo vệ' - sự khác biệt là liệu chúng là biến thành viên (trường) hay thuộc tính / phương thức ( tức là các chương trình con của một số loại).
Will Dean

1
Cảm ơn vì đã làm rõ nhanh chóng. Tôi rất vui khi có câu trả lời của người đăng ban đầu trong một giờ cho câu hỏi của tôi cho một bài đăng cũ 6 năm. Bạn đừng nghĩ rằng có thể xảy ra ở hầu hết các diễn đàn trực tuyến khác :)
Penghe Geng

9
Điều đáng chú ý hơn vẫn là tôi thực sự đồng ý với bản thân trong khoảng thời gian đó ...
Will Dean

Một thứ tự kinh doanh của một phương thức khởi tạo là xem tất cả các biến trạng thái đều được khởi tạo rõ ràng. Nếu bạn tuân thủ quy ước này, bạn có thể sử dụng hàm supertạo để gọi hàm tạo mẹ; sau đó nó sẽ đảm nhận việc khởi tạo các biến trạng thái private trong lớp cha.
ncmathsadist

31

Nói chung, nếu điều gì đó không được cố tình coi là công khai, tôi sẽ đặt nó ở chế độ riêng tư.

Nếu một tình huống phát sinh trong đó tôi cần quyền truy cập vào biến hoặc phương thức riêng đó từ một lớp dẫn xuất, tôi sẽ thay đổi nó từ private thành protected.

Điều này hiếm khi xảy ra - tôi thực sự không phải là một fan hâm mộ của sự kế thừa, vì nó không phải là một cách đặc biệt tốt để mô hình hóa hầu hết các tình huống. Bằng mọi giá, tiếp tục, không phải lo lắng.

Tôi muốn nói rằng điều này là tốt (và có lẽ là cách tốt nhất để tiếp tục) đối với đa số các nhà phát triển.

Thực tế đơn giản của vấn đề là , nếu một số nhà phát triển khác đến cùng một năm sau đó và quyết định họ cần quyền truy cập vào biến thành viên riêng của bạn, họ sẽ chỉ cần chỉnh sửa mã, thay đổi nó thành được bảo vệ và tiếp tục công việc kinh doanh của họ.

Các ngoại lệ thực sự duy nhất cho điều này là nếu bạn đang kinh doanh vận chuyển dll nhị phân ở dạng hộp đen cho các bên thứ ba. Về cơ bản, điều này bao gồm Microsoft, các nhà cung cấp 'Kiểm soát lưới dữ liệu tùy chỉnh' và có thể một vài ứng dụng lớn khác có thư viện khả năng mở rộng. Trừ khi bạn thuộc thể loại đó, còn không thì bạn không nên bỏ thời gian / nỗ lực để lo lắng về loại điều này.


8

Vấn đề quan trọng đối với tôi là một khi bạn tạo một biến được bảo vệ, thì bạn không thể cho phép bất kỳ phương thức nào trong lớp của mình dựa vào giá trị của nó nằm trong một phạm vi, bởi vì một lớp con luôn có thể đặt nó ngoài phạm vi.

Ví dụ: nếu tôi có một lớp xác định chiều rộng và chiều cao của một đối tượng có thể kết xuất và tôi đặt các biến đó được bảo vệ, thì tôi không thể đưa ra giả định nào đối với (ví dụ), tỷ lệ khung hình.

Quan trọng là, tôi không bao giờ có thể đưa ra những giả định đó tại bất kỳ thời điểm nào kể từ thời điểm mã đó được phát hành dưới dạng thư viện, vì ngay cả khi tôi cập nhật bộ thiết lập của mình để duy trì tỷ lệ khung hình, tôi không đảm bảo rằng các biến đang được đặt thông qua bộ thiết lập hoặc được truy cập qua getters trong mã hiện có.

Bất kỳ lớp con nào trong lớp của tôi cũng không thể chọn đảm bảo điều đó, vì chúng cũng không thể thực thi các giá trị của biến, ngay cả khi đó là toàn bộ điểm của lớp con của chúng .

Ví dụ:

  • Tôi có một lớp hình chữ nhật với chiều rộng và chiều cao được lưu trữ dưới dạng các biến được bảo vệ.
  • Một lớp con hiển nhiên (trong ngữ cảnh của tôi) là lớp "Hình chữ nhật hiển thị", trong đó sự khác biệt duy nhất là tôi hạn chế chiều rộng và chiều cao ở các giá trị hợp lệ cho màn hình đồ họa.
  • Nhưng điều đó là không thể bây giờ , vì lớp DisplayedRectangle của tôi không thể thực sự ràng buộc các giá trị đó, vì bất kỳ lớp con nào của nó có thể ghi đè trực tiếp các giá trị, trong khi vẫn được coi là DisplayedRectangle.

Bằng cách hạn chế các biến ở chế độ riêng tư, sau đó tôi có thể thực thi hành vi mà tôi muốn thông qua setters hoặc getters.


7

Nói chung, tôi sẽ giữ các biến thành viên được bảo vệ của bạn trong trường hợp hiếm hoi mà bạn có toàn quyền kiểm soát đối với mã sử dụng chúng. Nếu bạn đang tạo một API công khai, tôi sẽ nói là không bao giờ. Dưới đây, chúng tôi sẽ đề cập đến biến thành viên như một "thuộc tính" của đối tượng.

Dưới đây là những gì lớp cha của bạn không thể làm sau khi tạo một biến thành viên được bảo vệ thay vì những người truy cập riêng tư:

  1. lười biếng tạo giá trị khi đang đọc thuộc tính. Nếu bạn thêm một phương thức getter được bảo vệ, bạn có thể tạo giá trị một cách lười biếng và chuyển nó trở lại.

  2. biết khi tài sản được sửa đổi hoặc xóa. Điều này có thể tạo ra lỗi khi lớp cha đưa ra các giả định về trạng thái của biến đó. Tạo một phương thức setter được bảo vệ cho biến giữ quyền kiểm soát đó.

  3. Đặt một điểm ngắt hoặc thêm đầu ra gỡ lỗi khi biến được đọc hoặc ghi vào.

  4. Đổi tên biến thành viên đó mà không cần tìm kiếm qua tất cả các mã có thể sử dụng nó.

Nói chung, tôi nghĩ rằng đó sẽ là trường hợp hiếm hoi mà tôi khuyên bạn nên tạo một biến thành viên được bảo vệ. Tốt hơn hết bạn nên dành vài phút để hiển thị thuộc tính thông qua getters / setters hơn là vài giờ sau đó để theo dõi lỗi trong một số mã khác đã sửa đổi biến được bảo vệ. Không chỉ vậy, bạn còn được bảo đảm chống lại việc thêm chức năng trong tương lai (chẳng hạn như tải chậm) mà không vi phạm mã phụ thuộc. Làm sau khó hơn làm bây giờ.


7

Ở cấp độ thiết kế, có thể thích hợp để sử dụng một thuộc tính được bảo vệ, nhưng để triển khai, tôi không thấy lợi ích nào trong việc ánh xạ điều này tới một biến thành viên được bảo vệ hơn là các phương thức trình truy cập / trình đột biến.

Các biến thành viên được bảo vệ có những nhược điểm đáng kể vì chúng cho phép mã máy khách (lớp con) truy cập vào trạng thái bên trong của lớp lớp cơ sở một cách hiệu quả. Điều này ngăn cản lớp cơ sở duy trì hiệu quả các bất biến của nó.

Vì lý do tương tự, các biến thành viên được bảo vệ cũng làm cho việc viết mã đa luồng an toàn trở nên khó khăn hơn đáng kể trừ khi được đảm bảo không đổi hoặc giới hạn trong một luồng duy nhất.

Các phương pháp bộ truy cập / bộ đột biến cung cấp độ ổn định API hơn đáng kể và khả năng triển khai linh hoạt trong quá trình bảo trì.

Ngoài ra, nếu bạn là người theo chủ nghĩa OO, các đối tượng cộng tác / giao tiếp bằng cách gửi tin nhắn, không phải trạng thái đọc / thiết lập.

Đổi lại họ cung cấp rất ít lợi thế. Tôi không nhất thiết phải xóa chúng khỏi mã của người khác, nhưng bản thân tôi không sử dụng chúng.


4

Trong hầu hết thời gian, việc sử dụng bảo vệ sẽ rất nguy hiểm vì bạn phá vỡ phần nào sự đóng gói của lớp, lớp này cũng có thể bị phá vỡ bởi một lớp dẫn xuất được thiết kế kém.

Nhưng tôi có một ví dụ điển hình: Giả sử bạn có thể có một số loại thùng chứa chung chung. Nó có một triển khai nội bộ và những người truy cập nội bộ. Nhưng bạn cần cung cấp ít nhất 3 quyền truy cập công khai vào dữ liệu của nó: bản đồ, hash_map, vector-like. Sau đó, bạn có một cái gì đó như:

template <typename T, typename TContainer>
class Base
{
   // etc.
   protected
   TContainer container ;
}

template <typename Key, typename T>
class DerivedMap     : public Base<T, std::map<Key, T> >      { /* etc. */ }

template <typename Key, typename T>
class DerivedHashMap : public Base<T, std::hash_map<Key, T> > { /* etc. */ }

template <typename T>
class DerivedVector  : public Base<T, std::vector<T> >        { /* etc. */ }

Tôi đã sử dụng loại mã này cách đây chưa đầy một tháng (vì vậy mã là từ bộ nhớ). Sau một số suy nghĩ, tôi tin rằng trong khi vùng chứa Base chung phải là một lớp trừu tượng, ngay cả khi nó có thể sống khá tốt, bởi vì việc sử dụng trực tiếp Base sẽ là một vấn đề khó khăn nên nó nên bị cấm.

Tóm tắt Như vậy, bạn đã bảo vệ dữ liệu được sử dụng bởi lớp dẫn xuất. Tuy nhiên, chúng ta phải tính đến thực tế là lớp Cơ sở phải là trừu tượng.


nó không phá vỡ tính đóng gói nữa so với các thành viên công cộng. Nó là một thiết lập để nói rằng các lớp dẫn xuất có thể sử dụng trạng thái của lớp không được hiển thị cho người dùng của lớp.
gbjbaanb

@gbjbaanb: Bạn đang tự mâu thuẫn với chính mình "nó không phá vỡ tính đóng gói nữa so với các thành viên công cộng" khác với "[chỉ] các lớp dẫn xuất có thể sử dụng trạng thái của lớp". "protected" là trung gian giữa công cộng và tư nhân. Vì vậy, "bảo vệ [...] phá vỡ phần nào sự đóng gói" vẫn đúng ...
paercebal

trên thực tế, trong ngôn ngữ c ++, các bộ điều hợp vùng chứa như std :: stack sẽ hiển thị đối tượng vùng chứa bên dưới với một biến được bảo vệ được gọi là "c".
Johannes Schaub - litb

Tôi biết bài viết này khá cũ, nhưng tôi cảm thấy cần thiết. Bạn không "phần nào" phá vỡ sự đóng gói, bạn hoàn toàn phá vỡ nó. protectedkhông được đóng gói nhiều hơn public. Tôi sẵn sàng được chứng minh là sai. Tất cả những gì bạn phải làm là viết một lớp có thành viên được bảo vệ và cấm tôi sửa đổi nó. Rõ ràng lớp phải không phải là lớp cuối cùng, vì toàn bộ điểm của việc sử dụng bảo vệ là để kế thừa. Hoặc một cái gì đó được đóng gói, hoặc nó không. Không có trạng thái ở giữa.
Taekahn

3

Trong ngắn hạn, có.

Các biến thành viên được bảo vệ cho phép truy cập vào biến từ bất kỳ lớp con nào cũng như bất kỳ lớp nào trong cùng một gói. Điều này có thể rất hữu ích, đặc biệt là đối với dữ liệu chỉ đọc. Tuy nhiên, tôi không tin rằng chúng luôn cần thiết vì bất kỳ việc sử dụng biến thành viên được bảo vệ nào cũng có thể được sao chép bằng cách sử dụng biến thành viên riêng và một vài getters và setters.


1
Ngược lại, các biến thành viên riêng cũng không bao giờ cần thiết; công cộng là đủ để sử dụng.
Alice

3

Chỉ đối với bản ghi, trong Mục 24 của "C ++ đặc biệt", ở một trong các chú thích cuối trang, Sutter nói "bạn sẽ không bao giờ viết một lớp có biến thành viên công khai hoặc được bảo vệ. Phải không? (Bất kể ví dụ nghèo nàn được thiết lập bởi một số thư viện .) "


2

Để biết thông tin chi tiết về công cụ sửa đổi truy cập .Net, hãy truy cập vào đây

Không có lợi thế hay bất lợi thực sự nào đối với các biến thành viên được bảo vệ, đó là một câu hỏi về những gì bạn cần trong tình huống cụ thể của mình. Nói chung, việc khai báo các biến thành viên là private và cho phép truy cập bên ngoài thông qua các thuộc tính được chấp nhận. Ngoài ra, một số công cụ (ví dụ như một số trình ánh xạ O / R) mong muốn dữ liệu đối tượng được đại diện bởi các thuộc tính và không nhận ra các biến thành viên công khai hoặc được bảo vệ. Nhưng nếu bạn biết rằng bạn muốn các lớp con của mình (và CHỈ là các lớp con của bạn) truy cập vào một biến nhất định thì không có lý do gì để không tuyên bố nó là được bảo vệ.


Muốn các lớp con truy cập vào một biến rất khác với việc muốn chúng có thể tự do thay đổi nó. Đó là một trong những đối số chính chống lại các biến được bảo vệ: bây giờ lớp cơ sở của bạn không thể giả định bất kỳ biến nào của nó được giữ, bởi vì bất kỳ lớp dẫn xuất nào hoàn toàn có thể làm bất cứ điều gì với các thành viên được bảo vệ. Đó là lý lẽ chính chống lại họ. Nếu họ chỉ cần truy cập dữ liệu, thì ... viết một trình truy cập . : P (Tôi sử dụng các biến được bảo vệ, mặc dù có lẽ nhiều hơn mức tôi nên làm và tôi sẽ cố gắng cắt giảm!)
underscore_d
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.