Nếu một biến có getter và setter, nó có nên công khai không?


23

Tôi có một lớp với một biến là riêng tư và lớp có một getter và setter cho biến đó. Tại sao không làm cho biến đó công khai?

Trường hợp duy nhất tôi nghĩ bạn phải sử dụng getters và setters là nếu bạn cần thực hiện một số thao tác bên cạnh bộ hoặc get. Thí dụ:

void my_class::set_variable(int x){
   /* Some operation like updating a log */
   this->variable = x;
}

10
Getters và setters đang ở đó để che chắn. Một ngày nào đó bạn có thể muốn làm this->variable = x + 5hoặc gọi một UpdateStatisticshàm trong setter và trong những trường hợp đó classinstancea->variable = 5sẽ gây ra vấn đề.
Coder

1
Một ví dụ khác là: set_inch, set_centimeter, get_inch, get_centimeter, với một số hành động ma quái.
rwong

Yep, getters và setters giúp bạn kiểm soát các biến thể hiện của bạn theo cách mà bạn muốn chúng được kiểm soát.
nhà phát triển

7
@Coder: bạn luôn có thể thêm getter / setter của mình khi có nhu cầu? Hay đây là điều bạn không thể dễ dàng thực hiện trong C ++ (kinh nghiệm của tôi chủ yếu là Delphi)?
Marjan Venema

Câu hỏi này: lập trình viên.stackexchange.com/ questions / 21802 / Có thể được quan tâm.
Winston Ewert

Câu trả lời:


21

Bạn đã bao giờ nghe nói về một tài sản?

Một thuộc tính là một trường có các bộ truy cập "tích hợp" (getters và setters). Ví dụ, Java không có các thuộc tính, nhưng nên viết các getters và setters vào một trường riêng. C # có thuộc tính.

Vì vậy, tại sao chúng ta cần getters và setters? Về cơ bản chúng ta cần nó để bảo vệ / che chắn cánh đồng. Chẳng hạn, bạn không truy cập vào trường trong tham chiếu bộ nhớ, bạn đang truy cập một phương thức sau đó sẽ thay đổi trường (tham chiếu). Phương pháp đó có thể thực hiện một số thao tác mà người dùng không muốn biết ( đóng gói hành vi ), như trong ví dụ của bạn. Ví dụ, hãy tưởng tượng rằng một tá lớp sử dụng trường công khai của bạn và bạn cần thay đổi cách sử dụng ... Bạn sẽ phải xem xét từng lớp đó để thay đổi cách họ đang sử dụng trường ... Không phải vì vậy "OOlysh".

Nhưng, ví dụ, nếu bạn có một trường boolean được gọi là dead. Bạn nên suy nghĩ kỹ trước khi khai báo setDead và isDead. Bạn nên viết các bộ truy cập có thể đọc được của con người , ví dụ, kill () thay vì setDead.

Tuy nhiên, có rất nhiều khung công tác giả định rằng bạn đang tuân theo quy ước đặt tên JavaBean (nói về Java ở đây), do đó, trong những trường hợp đó, bạn nên khai báo tất cả các getters và setters sau khi đặt tên.


2
Bây giờ "con người có thể đọc được" cuối cùng là một đối số mà tôi có thể "mò mẫm". Tất cả các lập luận khác mà tôi thường nghe là một số loại bằng chứng trong tương lai cũng dễ dàng được giải quyết khi có nhu cầu ...
Marjan Venema

13
Sự coi thường của bạn đối với "bằng chứng trong tương lai" là phổ biến, nhưng sai lầm. Có, bạn có thể giải quyết các thay đổi đó khi có nhu cầu, nếu bạn là khách hàng duy nhất của mã đó . Nếu bạn không bao giờ công bố giải pháp của mình cho khách hàng bên ngoài, bạn có thể chỉ cần gánh chịu nợ kỹ thuật và không ai cần biết. Nhưng các dự án nội bộ có thói quen trở nên công khai khi bạn ít mong đợi nhất, và sau đó khốn cho bạn.
Kilian Foth

2
Bạn thực sự nâng một điểm tuyệt vời. giết không phải là một bộ / nhận, đó là một hành động. Nó hoàn toàn che giấu việc thực hiện - đó là cách bạn mã hóa OO. Mặt khác, các thuộc tính cũng ngu ngốc như setters / getters - stupider ngay cả vì cú pháp dễ dàng khuyến khích mọi người chỉ cần thêm chúng vào các biến khi chúng thậm chí không cần thiết (OO-Furbar đang chờ xảy ra). Ai sẽ nghĩ sử dụng kill () khi họ chỉ có thể thêm thẻ "property" vào cờ chết của họ?
Bill K

1
Câu hỏi được gắn thẻ một câu hỏi C ++. C ++ không hỗ trợ các thuộc tính, vậy tại sao bạn thậm chí sẽ đăng bài này?
Thomas Eding

1
@ThomasEding: C ++ có phần độc đáo khi có các toán tử gán gán quá mức. Mặc dù C ++ không sử dụng thuật ngữ "thuộc tính" giống như các ngôn ngữ như C # hoặc VB.NET, nhưng khái niệm này vẫn còn đó. Trong C ++, có thể sử dụng quá tải toán tử để foo.bar = biz.baz+5sẽ gọi hàm trên bizđể đánh giá baz, sau đó thêm năm hàm và gọi hàm trên foođể đặt bargiá trị kết quả. Quá tải như vậy không được gọi là "tài sản", nhưng chúng có thể phục vụ cùng một mục đích.
supercat

25

Đây không phải là ý kiến ​​phổ biến nhất nhưng tôi không thấy nhiều sự khác biệt.

Setters và getters là một ý tưởng khá xấu. Tôi đã nghĩ về nó và thành thật mà nói tôi không thể tìm ra sự khác biệt giữa setter / getter một thuộc tính và một biến công khai trong thực tế.

Trong LÝ THUYẾT, setter và getter hoặc property thêm một vị trí để thực hiện một số hành động bổ sung khi một biến được đặt / nhận và theo lý thuyết, chúng tách biệt mã của bạn khỏi các thay đổi.

Trong thực tế, tôi hiếm khi thấy setters và getters được sử dụng để thêm một hành động và khi bạn muốn thêm một hành động bạn muốn thêm nó vào TẤT CẢ các setters hoặc getters của một lớp (như đăng nhập) sẽ khiến bạn nghĩ rằng phải có là một giải pháp tốt hơn.

Đối với việc cô lập các quyết định thiết kế, nếu bạn thay đổi một int thành một thời gian dài, bạn vẫn phải thay đổi setters của mình và ít nhất là kiểm tra mọi dòng truy cập chúng bằng tay - không cách ly nhiều ở đó.

Các lớp có thể thay đổi nên được tránh theo mặc định, vì vậy, thêm một setter nên là phương sách cuối cùng. Điều này được giảm nhẹ với mẫu trình xây dựng trong đó giá trị có thể được đặt cho đến khi đối tượng ở trạng thái mong muốn thì lớp có thể trở nên bất biến và setters của bạn sẽ đưa ra ngoại lệ.

Đối với getters - Tôi vẫn không thể tìm ra nhiều sự khác biệt giữa một getter và một biến cuối cùng công khai. Vấn đề ở đây là OO xấu trong cả hai trường hợp. Bạn không nên yêu cầu một giá trị từ một đối tượng và vận hành trên nó - bạn nên yêu cầu một đối tượng thực hiện thao tác cho bạn.

Nhân tiện, tôi không bao giờ ủng hộ các biến công khai - tôi đang nói setters và getters (và thậm chí cả các thuộc tính) quá gần với các biến công khai.

Vấn đề lớn chỉ đơn giản là những người không phải lập trình viên OO quá muốn sử dụng setters và getters để biến các đối tượng thành các thuộc tính (cấu trúc) được truyền qua và vận hành, hoàn toàn trái ngược với cách hoạt động của mã Hướng đối tượng.


Một điều tốt ngoài các câu trả lời khác ở đây, về setter getter là: Tôi có thể thêm một số xác nhận / chuyển đổi bên trong nó cho biến riêng tư thực sự giả định giá trị của nó.
WinW

1
@Kiran đúng, nhưng nếu bạn đặt nó trong hàm tạo, bạn có thể có các xác nhận VÀ bạn có một lớp bất biến. Đối với setters Tôi không nói rằng một biến có thể ghi công khai là tốt, tôi đang nói rằng thậm chí setters là xấu. Đối với getters tôi có thể coi một biến cuối cùng công khai là một thay thế khả thi.
Bill K

Trong một số ngôn ngữ như C #, các thuộc tính không tương thích nhị phân với các biến công khai, vì vậy nếu bạn tạo một phần biến công khai của API, nhưng sau đó muốn thêm một số xác thực, bạn sẽ phá vỡ chương trình của khách hàng khi bạn chuyển đổi nó thành một tài sản. Tốt hơn để làm cho nó một tài sản công cộng ngay từ đầu.
Robert Harvey

@RobertHarvey Điều đó vẫn có nghĩa là bạn đang phơi bày các thuộc tính. Toàn bộ quan điểm của tôi là nên tránh các thuộc tính phơi bày, và khi cần thiết, bạn nên cố gắng hạn chế chúng trong getters và làm cho lớp của bạn không thay đổi. Nếu lớp của bạn là bất biến thì tại sao bạn lại cần mã trong một getter - và do đó nó sẽ theo sau rằng bạn cũng có thể không có getter. Nếu bạn không có khả năng viết một lớp mà không có các thuộc tính có thể thay đổi thì có, một setter luôn tốt hơn một biến công khai có thể ghi được (và bị bắn vào chân luôn tốt hơn bị đâm vào ruột).
Bill K

1
Trạng thái có thể thay đổi là chấp nhận được, nhưng một lần nữa bạn nên yêu cầu đối tượng của mình làm điều gì đó cho bạn, không làm điều gì đó cho đối tượng của bạn - do đó khi bạn thay đổi trạng thái của mình, một setter không phải là một cách tuyệt vời để làm điều đó, hãy thử viết một phương thức kinh doanh bằng logic trong đó thay vào đó Ví dụ: "di chuyển (Up5) thay vì setZLocation (getZLocation () + 5)".
Bill K

8

Người sử dụng getters và setters đi vào nguyên tắc đóng gói . Điều này sẽ cho phép bạn thay đổi cách mọi thứ hoạt động bên trong lớp và giữ cho mọi thứ hoạt động.

Chẳng hạn, nếu 3 đối tượng khác gọi foo.barđể lấy giá trị của thanh và bạn quyết định thay đổi tên của thanh thành xa thì bạn có vấn đề trong tay. Nếu các đối tượng được gọi foo.barbạn sẽ phải thay đổi tất cả các lớp có cái này. Nếu một setter / getter được sử dụng thì bạn không có gì để thay đổi. Một khả năng khác là thay đổi loại biến trong trường hợp đó chỉ cần thêm một số mã chuyển đổi vào getter / setter và bạn vẫn ổn.


1
+1 - biến thành viên là triển khai, getter và setter là giao diện. Chỉ giao diện nên được công khai. Ngoài ra, tên getter và setter sẽ có ý nghĩa về mặt trừu tượng, bất kể có một biến thành viên đơn giản nào nằm dưới chúng hoặc một số thực thi khác. Có các trường hợp ngoại lệ - trường hợp một hệ thống con được xây dựng từ các lớp được liên kết chặt chẽ là cách dễ nhất - nhưng dù sao cũng chỉ đẩy ranh giới ẩn dữ liệu lên một mức, đến lớp đại diện cho toàn bộ hệ thống con.
Steve314

Tôi không thể đơn giản giới thiệu getter và / hoặc setter khi tôi cần thay đổi công việc bên trong lớp sao? Đó là một cái gì đó dễ dàng được thực hiện trong Delphi, nhưng có lẽ không phải trong các ngôn ngữ khác?
Marjan Venema

@ marjan Chắc chắn, bạn có thể giới thiệu getter / setter tại bất kỳ thời điểm nào sau này. Vấn đề sau đó là bạn phải quay lại và thay đổi TẤT CẢ mã đang sử dụng trường công khai thay vì getter / setter. Tôi không chắc tôi là người bối rối hay bạn. 0.o
Pete

3
Cá nhân, tôi nghĩ rằng đây là một chút tranh luận không có thật. Nếu bạn thay đổi ý nghĩa của một biến nhưng biến đó có getter và setter, thì nó không quan trọng nó được gọi là gì. Nó không phải là một phần có thể nhìn thấy của giao diện. Bạn có nhiều khả năng muốn thay đổi tên của các getters và setters để chỉ ra cho khách hàng sự thay đổi hoặc làm rõ ý nghĩa của biến được đóng gói. Trong trường hợp sau này, bạn không ở vị trí nào tốt hơn với biến công khai. Điều này khá khác biệt với lập luận rằng một biến có getters và setters được phơi bày nhiều hơn là đóng gói.
CB Bailey

1
Một hoạt động tái cấu trúc hầu như là miễn phí, vì vậy tôi không thực sự mua cái này. Thêm vào đó, hình thức rất tệ khi sử dụng setter hoặc getter trừ khi bạn thực sự cần nó - cuối cùng bạn sẽ có được những người không hiểu khái niệm chỉ tự động thêm setters và getters cho các thành viên thay vì khi họ cần chỉ là hạt nhỏ giọt của cái ác trong suốt codebase của bạn.
Bill K

5

Sử dụng getters và setters cũng cho phép bạn kiểm soát nội dung nào được lưu trữ trong một biến cụ thể. Nếu nội dung cần phải thuộc một loại hoặc giá trị nhất định, một phần của mã setter của bạn có thể là để đảm bảo rằng giá trị mới đáp ứng các yêu cầu này. Nếu biến là công khai, bạn không thể đảm bảo các yêu cầu này được đáp ứng.

Cách tiếp cận này cũng làm cho mã của bạn dễ thích nghi và dễ quản lý hơn. Việc thay đổi kiến ​​trúc của một lớp sẽ dễ dàng hơn nhiều nếu bạn có các hàm giữ cho kiến ​​trúc đó bị ẩn khỏi tất cả các lớp hoặc các hàm khác sử dụng lớp đó. Thay đổi đã được đề cập của một tên biến chỉ là một trong nhiều thay đổi dễ thực hiện hơn nhiều nếu bạn có các chức năng như getters và setters. Ý tưởng tổng thể là giữ kín càng nhiều càng tốt, đặc biệt là các biến lớp của bạn.


Cảm ơn rất nhiều! Tôi đã suy nghĩ giống như trường hợp bạn đề cập. Nếu tôi muốn một biến có nội dung trong [0,1] Tôi nên kiểm tra giá trị đến trong setter của nó :)
Oni

Khi bạn là nhà phát triển duy nhất làm việc trong một dự án, bạn sẽ dễ dàng nghĩ rằng những thứ như thế này là vô ích nhưng khi bạn thấy mình làm việc với một nhóm, bạn sẽ thấy rằng thói quen của các kỹ thuật như thế này sẽ rất hữu ích và giúp bạn tránh rất nhiều lỗi xuống đường
Kenneth

Nếu bạn đang sử dụng C #, sử dụng các thuộc tính, bạn có thể sử dụng một thuộc tính để đặt một cá thể riêng nếu cần dựa trên một số logic trong phần setter của thuộc tính.
nhà phát triển

2

Bạn nói "Trường hợp duy nhất tôi nghĩ rằng bạn phải sử dụng getters và setters là nếu bạn cần thực hiện một số thao tác bên cạnh tập hợp hoặc get.".

Bạn nên sử dụng getters và setters nếu tại một thời điểm nào đó trong tương lai bạn có thể cần thực hiện một số thao tác bên cạnh bộ và nhận và bạn không muốn thay đổi hàng ngàn dòng mã nguồn khi điều đó xảy ra.

Bạn nên sử dụng getters và setters nếu bạn không muốn ai đó lấy địa chỉ của biến và chuyển nó đi, với hậu quả tai hại nếu biến đó có thể được thay đổi mà không có bất kỳ mã nguồn nào đề cập đến nó, hoặc thậm chí sau khi đối tượng dừng tồn tại nữa .


Đoạn cuối của bạn tạo ra một điểm tuyệt vời giúp cắt giảm cả hai cách: yêu cầu sử dụng getters và setters sẽ ngăn các mã khác lấy địa chỉ của một cái gì đó. Trong trường hợp sẽ có những lợi ích đáng kể và một vài nhược điểm khi có mã bên ngoài lấy địa chỉ của sự vật, hạn chế đó có thể là một điều xấu. nếu có ít lợi ích và nhược điểm nghiêm trọng để cho phép hành vi như vậy, hạn chế nó có thể là một điều tốt.
supercat

1

Trước hết hãy rõ ràng về mô hình.

  • Cấu trúc dữ liệu -> một bố cục của bộ nhớ có thể được duyệt và thao tác bằng các chức năng có kiến ​​thức phù hợp.
  • Đối tượng -> một mô-đun khép kín che giấu việc thực hiện và cung cấp giao diện có thể được truyền qua.

Một getter / setter hữu ích ở đâu?

Getters / Setters có hữu ích trong cấu trúc dữ liệu không? Không.

Cấu trúc dữ liệu là một đặc tả bố trí bộ nhớ phổ biến và được thao tác bởi một nhóm các hàm.

Nói chung, bất kỳ chức năng mới cũ nào cũng có thể xuất hiện và thao tác cấu trúc dữ liệu, nếu nó thực hiện theo cách mà các chức năng khác vẫn có thể hiểu được thì chức năng đó sẽ gia nhập gia đình. Nếu không, nó là một chức năng giả mạo và một nguồn lỗi.

Đừng hiểu sai ý tôi, có thể có một số họ chức năng cảnh báo về cấu trúc dữ liệu đó với snitches, áo khoác và tác nhân kép ở khắp mọi nơi. Thật tốt khi mỗi người có cấu trúc dữ liệu riêng để chơi, nhưng khi họ chia sẻ nó ... chỉ cần tưởng tượng một số gia đình tội phạm không đồng ý với chính trị, nó có thể trở thành một mớ hỗn độn rất nhanh.

Với các chức năng mở rộng lộn xộn mà các gia đình có thể đạt được, có cách nào để mã hóa cấu trúc dữ liệu để các hàm lừa đảo không làm rối tung mọi thứ? Vâng, chúng được gọi là Đối tượng.

Là getters / setters hữu ích trong các đối tượng? Không.

Toàn bộ vấn đề bao bọc một cấu trúc dữ liệu trong một đối tượng là để đảm bảo rằng không có chức năng giả mạo nào có thể tồn tại. Nếu chức năng muốn gia nhập gia đình, nó phải được xem xét kỹ lưỡng trước và sau đó trở thành một phần của đối tượng.

Điểm / mục đích của getter và setter là cho phép các hàm bên ngoài đối tượng thay đổi bố cục bộ nhớ của đối tượng trực tiếp. Nghe có vẻ như một cánh cửa mở để cho phép trong rogs ...

Vỏ máy

Có hai tình huống là một getter / setter công khai có ý nghĩa.

  • Một phần của cấu trúc dữ liệu trong đối tượng được quản lý bởi đối tượng, nhưng không được đối tượng kiểm soát.
  • Một giao diện mô tả sự trừu tượng hóa ở mức cao của cấu trúc dữ liệu trong đó một số phần tử được dự kiến ​​sẽ không kiểm soát đối tượng thực hiện.

Container và giao diện container là ví dụ hoàn hảo cho cả hai tình huống này. Container quản lý các cấu trúc dữ liệu (danh sách liên kết, bản đồ, cây) bên trong nhưng bàn tay kiểm soát phần tử cụ thể cho tất cả và lặt vặt. Giao diện trừu tượng hóa điều này và bỏ qua việc thực hiện hoàn toàn và chỉ mô tả các kỳ vọng.

Thật không may, nhiều triển khai đã hiểu sai điều này và xác định giao diện của các loại đối tượng này để cấp quyền truy cập trực tiếp vào đối tượng thực tế. Cái gì đó như:

interface Container<T>
{
    typedef ...T... TRef; //<somehow make TRef to be a reference or pointer to the memory location of T
    TRef item(int index); 
}

Nó bị hỏng. Việc triển khai Container phải rõ ràng trao quyền kiểm soát các phần bên trong của chúng cho bất kỳ ai sử dụng chúng. Tôi vẫn chưa thấy một ngôn ngữ có giá trị có thể thay đổi trong đó ngôn ngữ này tốt (các ngôn ngữ có ngữ nghĩa giá trị bất biến theo định nghĩa tốt từ góc độ tham nhũng dữ liệu, nhưng không nhất thiết từ góc độ gián điệp dữ liệu).

Bạn có thể cải thiện / sửa lỗi getters / setter bằng cách chỉ sử dụng ngữ nghĩa sao chép hoặc bằng cách sử dụng proxy:

interface Proxy<T>
{
     operator T(); //<returns a copy
     ... operator ->(); //<permits a function call to be forwarded to an element
     Proxy<T> operator=(T); //< permits the specific element to be replaced/assigned by another T.
}

interface Container<T>
{
     Proxy<T> item(int index);
     T item(int index); //<When T is a copy of the original value.
     void item(int index, T new_value); //<where new_value is used to replace the old value
}

Có thể cho rằng một chức năng giả mạo vẫn có thể chơi tình trạng lộn xộn ở đây (với đủ nỗ lực hầu hết mọi thứ đều có thể), nhưng ngữ nghĩa sao chép và / hoặc proxy làm giảm cơ hội cho một số lỗi.

  • tràn ra
  • chảy tràn
  • các tương tác với thành phần phụ là loại được kiểm tra / loại có thể kiểm tra được (trong các ngôn ngữ bị mất loại này là một lợi ích)
  • Các yếu tố thực tế có thể hoặc không thể là cư dân bộ nhớ.

Getters / Setters riêng

Đây là pháo đài cuối cùng của getters và setters làm việc trực tiếp trên loại. Trong thực tế, tôi thậm chí sẽ không gọi những getters và setters này nhưng truy cập và thao tác.

Trong bối cảnh này, đôi khi thao túng một phần cụ thể của cấu trúc dữ liệu luôn luôn / hầu như luôn luôn / thường yêu cầu giữ sổ sách cụ thể để xảy ra. Giả sử khi bạn cập nhật thư mục gốc của cây, bộ đệm nhìn sang một bên cần phải được xóa hoặc khi bạn truy cập vào phần tử dữ liệu ngoài, cần phải lấy / giải phóng khóa. Trong những trường hợp này, nên áp dụng hiệu trưởng DRY và phân chia các hành động đó cùng nhau.

Trong bối cảnh riêng tư, các chức năng khác trong gia đình vẫn có thể thực hiện các bước 'getters và setters' này và thao tác cấu trúc dữ liệu. Do đó tại sao tôi nghĩ về họ nhiều hơn như là người truy cập và thao tác. Bạn có thể truy cập dữ liệu trực tiếp hoặc dựa vào một thành viên khác trong gia đình để có phần đó đúng.

Getters / Setters được bảo vệ

Trong một bối cảnh được bảo vệ, nó không quá khác biệt với bối cảnh công cộng. Các chức năng giả mạo nước ngoài có thể muốn truy cập vào cấu trúc dữ liệu. Vì vậy, không, nếu chúng tồn tại, chúng hoạt động như getters / setters công cộng.


0

Từ kinh nghiệm C ++, setter / getter hữu ích cho 2 tình huống:

  1. nếu bạn phải thực hiện kiểm tra độ tỉnh táo trước khi gán giá trị cho thành viên như chuỗi hoặc mảng.
  2. nó có thể hữu ích khi quá tải chức năng là cần thiết như giá trị int để gán bool khi -1 (hoặc giá trị âm) là sai. và ngược lại cho getter.

Ngoài ra, bảo mật là một điểm hợp lệ nhưng tầm quan trọng của nó chỉ giới hạn ở rất ít ứng dụng phát triển như các mô-đun liên quan đến đăng nhập hoặc truy cập db. Tại sao phải mã hóa thêm khi chỉ có vài người sử dụng mô-đun của bạn?

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.