Tại sao sử dụng getters và setters / accessors?


1543

Lợi thế của việc sử dụng getters và setters - chỉ nhận và đặt - thay vì chỉ sử dụng các trường công khai cho các biến đó?

Nếu getters và setters đã từng làm nhiều hơn chỉ là get / set đơn giản, tôi có thể tìm ra cái này rất nhanh, nhưng tôi không rõ ràng 100% về cách:

public String foo;

còn tệ hơn:

private String foo;
public void setFoo(String foo) { this.foo = foo; }
public String getFoo() { return foo; }

Trong khi trước đây mất ít mã soạn sẵn.


6
@Dean J: Sao y với nhiều câu hỏi khác: stackoverflow.com/search?q=getters+setters
Asaph

8
Tất nhiên, cả hai đều tệ như nhau khi đối tượng không cần thay đổi thuộc tính. Tôi muốn làm cho mọi thứ riêng tư, và sau đó thêm getters nếu hữu ích và setters nếu cần.
Tordek

26
Google "truy cập là xấu xa"
OMG Ponies

34
"Người truy cập là ác" nếu bạn tình cờ viết mã chức năng hoặc các đối tượng bất biến. Nếu bạn tình cờ viết các đối tượng có thể thay đổi trạng thái, thì chúng rất cần thiết.
Christian Hayter

18

Câu trả lời:


981

Thực tế có nhiều lý do tốt để xem xét sử dụng các bộ truy cập thay vì trực tiếp phơi bày các trường của một lớp - ngoài lý lẽ đóng gói và làm cho các thay đổi trong tương lai dễ dàng hơn.

Dưới đây là một số lý do tôi biết:

  • Đóng gói hành vi liên quan đến nhận hoặc thiết lập thuộc tính - điều này cho phép chức năng bổ sung (như xác thực) được thêm dễ dàng hơn sau này.
  • Ẩn đại diện bên trong của tài sản trong khi phơi bày một tài sản bằng cách sử dụng một đại diện thay thế.
  • Cách ly giao diện công cộng của bạn khỏi sự thay đổi - cho phép giao diện công cộng không đổi trong khi việc triển khai thay đổi mà không ảnh hưởng đến người tiêu dùng hiện tại.
  • Kiểm soát ngữ nghĩa quản lý trọn đời và quản lý bộ nhớ (xử lý) của tài sản - đặc biệt quan trọng trong môi trường bộ nhớ không được quản lý (như C ++ hoặc Objective-C).
  • Cung cấp một điểm chặn gỡ lỗi khi một thuộc tính thay đổi trong thời gian chạy - gỡ lỗi khi và nơi một thuộc tính thay đổi thành một giá trị cụ thể có thể khá khó khăn nếu không có điều này trong một số ngôn ngữ.
  • Cải thiện khả năng tương tác với các thư viện được thiết kế để hoạt động chống lại getter / setters thuộc tính - Mocking, serialization và WPF xuất hiện trong tâm trí.
  • Cho phép những người thừa kế thay đổi ngữ nghĩa về cách hành xử của tài sản và được bộc lộ bằng cách ghi đè các phương thức getter / setter.
  • Cho phép getter / setter được truyền xung quanh dưới dạng biểu thức lambda chứ không phải giá trị.
  • Getters và setters có thể cho phép các cấp truy cập khác nhau - ví dụ: get có thể được công khai, nhưng bộ có thể được bảo vệ.

60
+1. Chỉ cần thêm: Cho phép tải lười biếng. Cho phép bản sao trên viết.
NewbiZ

6
@bjarkef: Tôi tin rằng publiccác phương thức truy cập gây ra sự sao chép mã và phơi bày thông tin. Bộ truy cập công cộng không cần thiết cho việc sắp xếp thứ tự (tuần tự hóa). Trong một số ngôn ngữ (Java), các bộ truy cập publiccó thể thay đổi kiểu trả về mà không phá vỡ các lớp phụ thuộc. Hơn nữa, tôi tin rằng họ chạy ngược lại các nguyên tắc OO. Xem thêm: stackoverflow.com/a/1462424/59087
Dave Jarvis

15
@sbi: Một điều được mua bằng cách sử dụng trình thiết lập thuộc tính, ngay cả trong khung được thiết kế tốt, là khả năng để một đối tượng thông báo cho đối tượng khác khi một thuộc tính thay đổi.
supercat

22
@supercat: Tuy nhiên, nói chung, tôi không quảng bá dữ liệu công khai. Thông báo về các đối tượng khác cũng có thể được thực hiện từ các phương thức thực sự cung cấp một sự trừu tượng hóa đáng kể trên một thùng chứa dữ liệu đơn thuần với (nhiều hoặc ít) các trường dữ liệu công khai. Thay vì plane.turnTo(dir); plane.setSpeed(spd); plane.setTargetAltitude(alt); plane.getBreaks().release();tôi muốn nói plane.takeOff(alt). Những trường dữ liệu bên trong nào cần được thay đổi để đưa máy bay vào takingOffchế độ không phải là vấn đề tôi quan tâm. Và những đối tượng khác ( breaks) phương thức thông báo tôi cũng không muốn biết.
sbi

8
@BenLee: Tôi thực sự không biết nói thế nào nữa. "Người truy cập" chỉ là một từ đồng nghĩa với "getters / setters", và trong hai bình luận đầu tiên của tôi, tôi đã giải thích lý do tại sao đó là lạm dụng thuật ngữ "OOP". Nếu bạn cần tiếp cận với nó và thao túng trực tiếp trạng thái, thì đó không phải là một đối tượng theo nghĩa OOP của thuật ngữ đó.
sbi

480

Bởi vì 2 tuần (tháng, năm) kể từ bây giờ khi bạn nhận ra rằng trình thiết lập của bạn cần phải làm nhiều hơn là chỉ đặt giá trị, bạn cũng sẽ nhận ra rằng tài sản đã được sử dụng trực tiếp trong 238 lớp khác :-)


84
Tôi đang ngồi và nhìn chằm chằm vào một ứng dụng 500k, nơi không bao giờ cần thiết. Điều đó nói rằng, nếu cần một lần, nó sẽ bắt đầu gây ra một cơn ác mộng bảo trì. Đủ tốt để đánh dấu cho tôi
Trưởng khoa J

20
Tôi chỉ có thể ghen tị với bạn và ứng dụng của bạn :-) Điều đó nói rằng, nó thực sự phụ thuộc vào ngăn xếp phần mềm của bạn. Delphi, ví dụ (và C # - tôi nghĩ vậy?) Cho phép bạn xác định các thuộc tính là công dân hạng 1 nơi họ có thể đọc / ghi một trường trực tiếp ban đầu nhưng - bạn cũng cần nó - làm như vậy thông qua các phương thức getter / setter. Nhiều tiện lợi. Java, than ôi, không - không đề cập đến tiêu chuẩn javabeans buộc bạn cũng phải sử dụng getters / setters.
ChssPly76

14
Mặc dù đây thực sự là một lý do tốt để sử dụng các bộ truy cập, nhiều môi trường lập trình và trình soạn thảo hiện cung cấp hỗ trợ cho việc tái cấu trúc (trong IDE hoặc dưới dạng bổ trợ miễn phí) phần nào làm giảm tác động của vấn đề.
LBushkin

62
nghe có vẻ như tối ưu hóa trước khi trưởng thành
cmcginty

41
Câu hỏi bạn cần đặt ra khi bạn băn khoăn có nên triển khai getters và setters là: Tại sao người dùng của một lớp cần phải truy cập vào các bộ phận của lớp không? Việc họ thực hiện trực tiếp hay che chắn bởi một lớp giả mỏng thực sự không quan trọng - nếu người dùng cần truy cập chi tiết triển khai, thì đó là dấu hiệu cho thấy lớp không cung cấp đủ sự trừu tượng. Xem thêm bình luận này .
sbi

358

Một trường công khai không tệ hơn một cặp getter / setter không làm gì ngoài việc trả lại trường và gán cho nó. Đầu tiên, rõ ràng rằng (trong hầu hết các ngôn ngữ) không có sự khác biệt về chức năng. Bất kỳ sự khác biệt phải ở các yếu tố khác, như khả năng duy trì hoặc khả năng đọc.

Một lợi thế được đề cập đến của các cặp getter / setter là không. Có yêu cầu này rằng bạn có thể thay đổi việc triển khai và khách hàng của bạn không cần phải biên dịch lại. Giả sử, setters cho phép bạn thêm chức năng như xác nhận sau này và khách hàng của bạn thậm chí không cần biết về nó. Tuy nhiên, việc thêm xác nhận vào một setter là một thay đổi đối với các điều kiện tiên quyết của nó, vi phạm hợp đồng trước đó, khá đơn giản, "bạn có thể đặt bất cứ thứ gì vào đây và bạn có thể nhận được điều tương tự sau đó từ getter".

Vì vậy, bây giờ bạn đã phá vỡ hợp đồng, thay đổi mọi tệp trong cơ sở mã là điều bạn nên làm, không nên tránh. Nếu bạn tránh, bạn đang giả định rằng tất cả các mã giả định hợp đồng cho các phương thức đó là khác nhau.

Nếu đó không phải là hợp đồng, thì giao diện đã cho phép khách hàng đặt đối tượng ở trạng thái không hợp lệ. Điều đó trái ngược hoàn toàn với đóng gói Nếu trường đó thực sự không thể được đặt thành bất cứ thứ gì ngay từ đầu, tại sao lại không xác nhận ở đó ngay từ đầu?

Lập luận tương tự này áp dụng cho các lợi thế được cho là khác của các cặp getter / setter truyền qua này: nếu sau này bạn quyết định thay đổi giá trị được đặt, bạn sẽ phá vỡ hợp đồng. Nếu bạn ghi đè chức năng mặc định trong lớp dẫn xuất, theo cách vượt quá một vài sửa đổi vô hại (như ghi nhật ký hoặc hành vi không thể quan sát khác), bạn đang phá vỡ hợp đồng của lớp cơ sở. Đó là vi phạm Nguyên tắc thay thế Liskov, được coi là một trong những nguyên lý của OO.

Nếu một lớp có các getters và setters câm cho mọi lĩnh vực, thì đó là một lớp không có bất biến nào, không có hợp đồng . Đó có thực sự là thiết kế hướng đối tượng? Nếu tất cả các lớp có các getters và setters đó, thì đó chỉ là một người giữ dữ liệu câm và những người giữ dữ liệu câm sẽ trông giống như những người nắm giữ dữ liệu câm:

class Foo {
public:
    int DaysLeft;
    int ContestantNumber;
};

Việc thêm các cặp getter / setter thông qua vào một lớp như vậy sẽ không có giá trị. Các lớp khác nên cung cấp các hoạt động có ý nghĩa, không chỉ các hoạt động mà các trường đã cung cấp. Đó là cách bạn có thể định nghĩa và duy trì các bất biến hữu ích.

Khách hàng : "Tôi có thể làm gì với một đối tượng của lớp này?"
Nhà thiết kế : "Bạn có thể đọc và viết một số biến."
Khách hàng : "Ồ ... tuyệt, tôi đoán vậy?"

Có nhiều lý do để sử dụng getters và setters, nhưng nếu những lý do đó không tồn tại, việc tạo các cặp getter / setter trong tên của các vị thần đóng gói sai không phải là một điều tốt. Những lý do hợp lệ để tạo ra getters hoặc setters bao gồm những điều thường được đề cập là những thay đổi tiềm năng bạn có thể thực hiện sau này, như xác nhận hoặc các biểu diễn nội bộ khác nhau. Hoặc có thể khách hàng có thể đọc được giá trị nhưng không thể ghi được (ví dụ: đọc kích thước của từ điển), vì vậy một getter đơn giản là một lựa chọn tốt. Nhưng những lý do đó nên có khi bạn đưa ra lựa chọn, và không chỉ là một điều tiềm năng mà bạn có thể muốn sau này. Đây là một ví dụ của YAGNI ( Bạn không cần nó ).


13
Câu trả lời tuyệt vời (+1). Lời chỉ trích duy nhất của tôi là tôi đã mất vài lần đọc để tìm ra cách "xác nhận" trong đoạn cuối khác với "xác nhận" trong một vài trường hợp đầu tiên (mà bạn đã đưa ra trong trường hợp sau nhưng được quảng bá ở phần trước); điều chỉnh từ ngữ có thể giúp về vấn đề đó.
Các cuộc đua nhẹ nhàng trong quỹ đạo

14
Đây là một câu trả lời tuyệt vời nhưng than ôi thời đại hiện tại đã quên mất "thông tin che giấu" là gì hoặc nó dùng để làm gì. Họ không bao giờ đọc về tính bất biến và trong hành trình tìm kiếm sự nhanh nhẹn nhất của họ, không bao giờ vẽ sơ đồ chuyển trạng thái đó xác định trạng thái pháp lý của một đối tượng là gì và do đó không phải là gì.
Darrell Teague

2
Một quan sát quan trọng là getters là hữu ích hơn nhiều so với setters. Một getter có thể trả về một giá trị được tính toán hoặc một giá trị được tính toán được lưu trong bộ nhớ cache. Tất cả một setter có thể làm là một số xác nhận và thay đổi privatetrường. Nếu một lớp không có setters, thật dễ dàng để làm cho nó bất biến.
Raedwald

7
Er, tôi đoán bạn không biết bất biến lớp là gì. Nếu nó là một cấu trúc không tầm thường, thì điều đó có nghĩa là nó có bất biến để duy trì và người ta không thể thiết kế nó mà không có ý tưởng. "Có một lỗi, một thành viên đã được cập nhật sai." cũng có khá nhiều bài đọc như "Một bản cập nhật thành viên đã vi phạm các bất biến của lớp". Một lớp được thiết kế tốt không cho phép mã máy khách vi phạm các bất biến của nó.
R. Martinho Fernandes

5
+1 cho 'chủ sở hữu dữ liệu câm nên trông giống như chủ sở hữu dữ liệu câm' Thật không may, mọi người dường như thiết kế mọi thứ xung quanh chủ sở hữu dữ liệu câm ngày nay và rất nhiều khung công tác thậm chí còn yêu cầu điều đó ...
Joeri Hendrickx

93

Nhiều người nói về những lợi thế của getters và setters nhưng tôi muốn chơi người ủng hộ của quỷ. Ngay bây giờ tôi đang gỡ lỗi một chương trình rất lớn, nơi các lập trình viên quyết định tạo ra mọi thứ getters và setters. Điều đó có vẻ tốt, nhưng đó là một cơn ác mộng kỹ thuật đảo ngược.

Giả sử bạn đang xem qua hàng trăm dòng mã và bạn bắt gặp điều này:

person.name = "Joe";

Đó là một đoạn mã đơn giản đẹp mắt cho đến khi bạn nhận ra nó là một setter. Bây giờ, bạn theo dõi setter đó và thấy rằng nó cũng đặt person.firstName, person.lastName, person.isHuman, person.hasReallyCommonFirstName và gọi person.update (), sẽ gửi một truy vấn ra cơ sở dữ liệu, v.v. nơi rò rỉ bộ nhớ của bạn đã xảy ra.

Hiểu một đoạn mã cục bộ thoạt nhìn là một đặc tính quan trọng của khả năng đọc tốt mà getters và setters có xu hướng phá vỡ. Đó là lý do tại sao tôi cố gắng tránh chúng khi tôi có thể, và giảm thiểu những gì chúng làm khi tôi sử dụng chúng.


8
Đúng. Hiện đang tái cấu trúc một codebase lớn, và điều này đã là một cơn ác mộng. Các getters và setters làm quá nhiều, bao gồm cả việc gọi các getters và setters rất bận rộn khác mà cuối cùng làm giảm khả năng đọc thành không có gì. Xác nhận là một lý do tuyệt vời để sử dụng các bộ truy cập, nhưng sử dụng chúng để làm nhiều hơn thế dường như loại bỏ bất kỳ lợi ích tiềm năng nào.
Fadecomic

31
Đây là lập luận chống lại cú pháp đường, không chống lại setters nói chung.
Phil

6
Đúng và đúng nhưng hơn nữa, nó chỉ ra lý do tại sao các setters tài sản cá nhân mở ra cánh cửa cho các trạng thái không hợp lệ mà hoàn toàn không có biện pháp khắc phục nào để đảm bảo tính toàn vẹn của bất kỳ đối tượng nào cho phép sự trừu tượng bị rò rỉ.
Darrell Teague

"Đó là một đoạn mã đơn giản tuyệt đẹp cho đến khi bạn nhận ra nó là một setter" - này, là bài viết gốc về Java hay về C #? :)
Honza Zidek

Tôi hoàn toàn đồng ý về khả năng đọc. Nó hoàn toàn rõ ràng những gì đang xảy ra khi person.name = "Joe";được gọi. Không có sự thay đổi trong cách thông tin, đánh dấu cú pháp cho thấy đó là một trường và tái cấu trúc đơn giản hơn (không cần phải cấu trúc lại hai phương thức và một trường). Thứ hai, tất cả những người lãng phí chu kỳ não nghĩ rằng "getIsValid ()" hoặc nên là "isValid ()", v.v. Tôi không thể nghĩ về một lần tôi đã được cứu khỏi một lỗi bởi một getter / setter.
Sẽ

53

Có nhiều lý do. Điều tôi thích nhất là khi bạn cần thay đổi hành vi hoặc điều chỉnh những gì bạn có thể đặt trên một biến. Chẳng hạn, giả sử bạn có phương thức setSpeed ​​(int speed). Nhưng bạn muốn rằng bạn chỉ có thể đặt tốc độ tối đa là 100. Bạn sẽ làm một cái gì đó như:

public void setSpeed(int speed) {
  if ( speed > 100 ) {
    this.speed = 100;
  } else {
    this.speed = speed;
  }
}

Bây giờ điều gì sẽ xảy ra nếu MỌI NƠI trong mã của bạn, bạn đang sử dụng trường công cộng và sau đó bạn nhận ra rằng bạn cần yêu cầu trên? Hãy vui vẻ săn lùng mọi cách sử dụng của lĩnh vực công cộng thay vì chỉ sửa đổi setter của bạn.

2 xu của tôi :)


62
Săn lùng mọi mục đích sử dụng của lĩnh vực công cộng không nên khó khăn như vậy. Làm cho nó riêng tư và để trình biên dịch tìm thấy chúng.
Nathan Fellman

12
tất nhiên điều đó đúng, nhưng tại sao làm cho nó khó hơn nó được thiết kế. Cách tiếp cận get / set vẫn là câu trả lời tốt hơn.
Hardryv

28
@Nathan: Tìm các cách sử dụng khác không phải là vấn đề. Thay đổi tất cả là.
Graeme Perrow

22
@GraemePerrow phải thay đổi tất cả là một lợi thế, không phải là vấn đề :( Điều gì xảy ra nếu bạn có mã giả định tốc độ có thể cao hơn 100 (bởi vì, bạn biết, trước khi bạn phá vỡ hợp đồng, nó có thể!) ( while(speed < 200) { do_something(); accelerate(); })
R. Martinho Fernandes

29
Đó là một ví dụ RẤT xấu! Ai đó nên gọi: myCar.setSpeed(157);và sau vài dòng speed = myCar.getSpeed();Và bây giờ ... Tôi chúc bạn gỡ lỗi vui vẻ trong khi cố gắng hiểu tại sao speed==100khi nào nên157
Piotr Aleksander Chmielowski

53

Trong một thế giới thuần túy hướng đối tượng getters và setters là một mô hình chống khủng khiếp . Đọc bài viết này: Getters / Setters. Tà ác. Thời kỳ . Tóm lại, họ khuyến khích các lập trình viên suy nghĩ về các đối tượng theo cấu trúc dữ liệu và kiểu suy nghĩ này là thủ tục thuần túy (như trong COBOL hoặc C). Trong ngôn ngữ hướng đối tượng, không có cấu trúc dữ liệu, mà chỉ có các đối tượng phơi bày hành vi (không phải thuộc tính / thuộc tính!)

Bạn có thể tìm hiểu thêm về chúng trong Phần 3.5 của Đối tượng thanh lịch (cuốn sách của tôi về lập trình hướng đối tượng).


7
Getters và setters đề xuất một mô hình miền thiếu máu.
Raedwald

7
Quan điểm thú vị. Nhưng trong hầu hết các bối cảnh lập trình, cái chúng ta cần là cấu trúc dữ liệu. Lấy ví dụ "Dog" của bài viết được liên kết. Có, bạn không thể thay đổi trọng lượng của một con chó trong thế giới thực bằng cách đặt một thuộc tính ... nhưng đó new Dog()không phải là một con chó. Nó là đối tượng chứa thông tin về một con chó. Và với cách sử dụng đó, việc có thể điều chỉnh trọng lượng ghi không chính xác là điều đương nhiên.
Stephen C

3
Chà, tôi nói với bạn rằng hầu hết các chương trình hữu ích không cần phải mô hình hóa / mô phỏng các đối tượng trong thế giới thực. IMO, đây không thực sự là về ngôn ngữ lập trình. Đó là về những gì chúng tôi viết chương trình cho.
Stephen C

3
Thế giới thực hay không, yegor hoàn toàn đúng. Nếu những gì bạn có thực sự là một "Struct" và bạn không cần phải viết bất kỳ mã nào tham chiếu nó theo tên, hãy đặt nó trong một hashtable hoặc cấu trúc dữ liệu khác. Nếu bạn cần phải viết mã cho nó thì hãy đặt nó làm thành viên của một lớp và đặt mã thao tác biến đó trong cùng một lớp và bỏ qua setter & getter. Tái bút Mặc dù tôi chủ yếu chia sẻ quan điểm của yegor, tôi đã tin rằng các hạt chú thích không có mã là một số cấu trúc dữ liệu hữu ích - đôi khi getters là cần thiết, setters không bao giờ tồn tại.
Bill K

1
Tôi đã mang đi - toàn bộ câu trả lời này, mặc dù đúng và có liên quan, không giải quyết trực tiếp câu hỏi. Có lẽ nên nói "Cả setters / getters và biến công khai đều sai" ... Để hoàn toàn cụ thể, Setters và biến công khai có thể không bao giờ được sử dụng trong khi getters khá giống với biến cuối cùng và đôi khi là một điều ác cần thiết nhưng không tốt hơn nhiều so với cái khác
Bill K

38

Một lợi thế của người truy cập và trình biến đổi là bạn có thể thực hiện xác nhận.

Ví dụ, nếu foolà công khai, tôi có thể dễ dàng đặt nó nullvà sau đó người khác có thể thử gọi một phương thức trên đối tượng. Nhưng nó không còn ở đó nữa! Với một setFoophương thức, tôi có thể đảm bảo rằng nó fookhông bao giờ được đặt thành null.

Bộ truy cập và bộ biến đổi cũng cho phép đóng gói - nếu bạn không thấy giá trị một khi được đặt (có lẽ nó được đặt trong hàm tạo và sau đó được sử dụng bởi các phương thức, nhưng sẽ không bao giờ bị thay đổi). Nhưng nếu bạn có thể cho phép các lớp khác nhìn thấy hoặc thay đổi nó, bạn có thể cung cấp trình truy cập và / hoặc trình biến đổi thích hợp.


28

Phụ thuộc vào ngôn ngữ của bạn. Bạn đã gắn thẻ "hướng đối tượng" này thay vì "Java", vì vậy tôi muốn chỉ ra rằng câu trả lời của ChssPly76 phụ thuộc vào ngôn ngữ. Ví dụ, trong Python, không có lý do để sử dụng getters và setters. Nếu bạn cần thay đổi hành vi, bạn có thể sử dụng một thuộc tính, bao bọc một getter và setter xung quanh truy cập thuộc tính cơ bản. Một cái gì đó như thế này:

class Simple(object):
   def _get_value(self):
       return self._value -1

   def _set_value(self, new_value):
       self._value = new_value + 1

   def _del_value(self):
       self.old_values.append(self._value)
       del self._value

   value = property(_get_value, _set_value, _del_value)

2
Vâng, tôi đã nói nhiều như vậy trong một bình luận bên dưới câu trả lời của tôi. Java không phải là ngôn ngữ duy nhất để sử dụng getters / setters như một cái nạng giống như Python không phải là ngôn ngữ duy nhất có thể định nghĩa các thuộc tính. Tuy nhiên, điểm chính vẫn còn - "tài sản" không giống với "lĩnh vực công cộng".
ChssPly76

1
@jcd - hoàn toàn không. Bạn đang xác định "giao diện" của mình (API công khai sẽ là thuật ngữ tốt hơn ở đây) bằng cách hiển thị các trường công khai của bạn. Khi đã xong, sẽ không quay trở lại. Các thuộc tính KHÔNG phải là các trường vì chúng cung cấp cho bạn một cơ chế để chặn các nỗ lực truy cập các trường (bằng cách định tuyến chúng đến các phương thức nếu chúng được xác định); tuy nhiên, không có gì nhiều hơn cú pháp đường trên các phương thức getter / setter. Nó cực kỳ tiện lợi nhưng nó không làm thay đổi mô hình cơ bản - phơi bày các trường không có quyền kiểm soát truy cập vào chúng vi phạm nguyên tắc đóng gói.
ChssPly76

14
@ ChssPly76 Tôi không đồng ý. Tôi có nhiều quyền kiểm soát như thể chúng là tài sản, bởi vì tôi có thể biến chúng thành tài sản bất cứ khi nào tôi cần. Không có sự khác biệt giữa một thuộc tính sử dụng getters và setters nồi hơi và thuộc tính thô, ngoại trừ thuộc tính thô nhanh hơn, bởi vì nó sử dụng ngôn ngữ cơ bản, thay vì gọi các phương thức. Về mặt chức năng, chúng giống hệt nhau. Cách đóng gói duy nhất có thể bị vi phạm là nếu bạn nghĩ dấu ngoặc đơn ( obj.set_attr('foo')) vốn đã vượt trội so với dấu bằng ( obj.attr = 'foo'). Truy cập công cộng là truy cập công cộng.
jcdyer

1
@jcdyer kiểm soát càng nhiều có, nhưng không dễ đọc, những người khác thường cho rằng obj.attr = 'foo'chỉ đặt biến mà không có gì khác xảy ra
Timo Huovinen

9
@TimoHuovinen Làm thế nào khác với người dùng trong Java giả định rằng obj.setAttr('foo')"chỉ đặt biến mà không có gì khác xảy ra"? Nếu đó là một phương thức công khai, thì đó là một phương thức công khai. Nếu bạn sử dụng nó để đạt được một số tác dụng phụ và nó là công khai, thì bạn nên có thể tin tưởng vào mọi thứ hoạt động như thể chỉ có tác dụng phụ dự định đó xảy ra (với tất cả các chi tiết thực hiện khác và các tác dụng phụ khác, sử dụng tài nguyên, bất cứ điều gì , ẩn khỏi mối quan tâm của người dùng). Điều này hoàn toàn không khác với Python. Cú pháp của Python để đạt được hiệu quả chỉ đơn giản hơn.
ely

25

Vâng, tôi chỉ muốn nói thêm rằng ngay cả khi đôi khi chúng cần thiết cho việc đóng gói và bảo mật các biến / đối tượng của bạn, nếu chúng ta muốn mã hóa Chương trình hướng đối tượng thực sự, thì chúng ta cần phải DỪNG LẠI TỪNG PHỤ KIỆN , vì đôi khi chúng ta phụ thuộc rất nhiều khi chúng không thực sự cần thiết và điều đó gần giống như khi chúng ta đặt các biến công khai.


24

Cảm ơn, điều đó thực sự làm rõ suy nghĩ của tôi. Bây giờ đây là (gần như) 10 (gần như) lý do chính đáng KHÔNG sử dụng getters và setters:

  1. Khi bạn nhận ra rằng bạn cần phải làm nhiều hơn là chỉ đặt và nhận giá trị, bạn có thể đặt trường ở chế độ riêng tư, điều này sẽ ngay lập tức cho bạn biết nơi bạn đã truy cập trực tiếp vào đó.
  2. Bất kỳ xác nhận nào bạn thực hiện trong đó chỉ có thể không có ngữ cảnh, mà việc xác thực hiếm khi xảy ra trong thực tế.
  3. Bạn có thể thay đổi giá trị được đặt - đây là một cơn ác mộng tuyệt đối khi người gọi chuyển cho bạn một giá trị mà họ [sốc kinh dị] muốn bạn lưu trữ NHƯ VẬY.
  4. Bạn có thể ẩn biểu diễn bên trong - thật tuyệt vời, vì vậy bạn chắc chắn rằng tất cả các thao tác này là đối xứng phải không?
  5. Bạn đã cách ly giao diện công cộng của mình khỏi các thay đổi dưới trang tính - nếu bạn đang thiết kế giao diện và không chắc chắn liệu truy cập trực tiếp vào một cái gì đó có ổn hay không, thì bạn nên tiếp tục thiết kế.
  6. Một số thư viện mong đợi điều này, nhưng không nhiều - phản ánh, tuần tự hóa, các đối tượng giả đều hoạt động tốt với các trường công khai.
  7. Kế thừa lớp này, bạn có thể ghi đè chức năng mặc định - nói cách khác, bạn có thể THỰC SỰ gây nhầm lẫn cho người gọi bằng cách không chỉ che giấu việc thực hiện mà còn khiến nó không nhất quán.

Ba người cuối cùng tôi sẽ rời đi (N / A hoặc D / C) ...


11
Tôi nghĩ rằng lập luận quan trọng là, "nếu bạn đang thiết kế một giao diện và không chắc chắn liệu truy cập trực tiếp vào một cái gì đó có ổn hay không, thì bạn nên tiếp tục thiết kế." Đó là vấn đề quan trọng nhất với getters / setters: Họ giảm một lớp thành một thùng chứa chỉ (ít nhiều) các lĩnh vực công cộng. Trong thực tuy nhiên, OOP, một đối tượng là hơn một container của trường dữ liệu. Nó đóng gói trạng thái và thuật toán để thao tác trạng thái đó. Điều quan trọng về tuyên bố này là trạng thái được cho là được gói gọn và chỉ bị thao túng bởi các thuật toán được cung cấp bởi đối tượng.
sbi

22

Tôi biết là hơi muộn, nhưng tôi nghĩ có một số người quan tâm đến hiệu suất.

Tôi đã thực hiện một bài kiểm tra hiệu suất nhỏ. Tôi đã viết một lớp "NumberHolder", trong đó, có một Số nguyên. Bạn có thể đọc Số nguyên đó bằng cách sử dụng phương thức getter anInstance.getNumber()hoặc bằng cách truy cập trực tiếp vào số bằng cách sử dụng anInstance.number. Chương trình của tôi đọc số 1.000.000.000 lần, thông qua cả hai cách. Quá trình đó được lặp lại năm lần và thời gian được in. Tôi đã có kết quả như sau:

Time 1: 953ms, Time 2: 741ms
Time 1: 655ms, Time 2: 743ms
Time 1: 656ms, Time 2: 634ms
Time 1: 637ms, Time 2: 629ms
Time 1: 633ms, Time 2: 625ms

(Thời gian 1 là cách trực tiếp, Thời gian 2 là người nhận)

Bạn thấy đấy, getter (hầu như) luôn nhanh hơn một chút. Sau đó, tôi đã thử với số lượng chu kỳ khác nhau. Thay vì 1 triệu, tôi đã sử dụng 10 triệu và 0,1 triệu. Kết quả:

10 triệu chu kỳ:

Time 1: 6382ms, Time 2: 6351ms
Time 1: 6363ms, Time 2: 6351ms
Time 1: 6350ms, Time 2: 6363ms
Time 1: 6353ms, Time 2: 6357ms
Time 1: 6348ms, Time 2: 6354ms

Với 10 triệu chu kỳ, thời gian gần như giống nhau. Dưới đây là 100 nghìn (0,1 triệu) chu kỳ:

Time 1: 77ms, Time 2: 73ms
Time 1: 94ms, Time 2: 65ms
Time 1: 67ms, Time 2: 63ms
Time 1: 65ms, Time 2: 65ms
Time 1: 66ms, Time 2: 63ms

Ngoài ra với số lượng chu kỳ khác nhau, getter nhanh hơn một chút so với cách thông thường. Tôi hy vọng điều này đã giúp bạn.


3
Có một chi phí "đáng chú ý" có chức năng gọi để truy cập bộ nhớ thay vì chỉ tải địa chỉ của đối tượng và thêm phần bù để truy cập các thành viên. Có thể VM là tối ưu hóa phẳng getter của bạn. Bất kể, chi phí được đề cập không đáng để mất tất cả các lợi ích của getters / setters.
Alex

16

Đừng sử dụng setters getters trừ khi cần thiết cho giao hàng hiện tại của bạn. Đừng nghĩ quá nhiều về những gì sẽ xảy ra trong tương lai, nếu có bất cứ điều gì được thay đổi yêu cầu thay đổi trong hầu hết các ứng dụng, hệ thống sản xuất.

Nghĩ đơn giản, dễ dàng, thêm phức tạp khi cần.

Tôi sẽ không lợi dụng sự thiếu hiểu biết của các chủ doanh nghiệp về kỹ thuật sâu sắc chỉ vì tôi nghĩ nó đúng hoặc tôi thích cách tiếp cận.

Tôi có hệ thống lớn được viết mà không có getters setters chỉ với các sửa đổi truy cập và một số phương thức để xác nhận n thực hiện logic biz. Nếu bạn thực sự cần thiết. Sử dụng bất cứ thứ gì.


16

Chúng tôi sử dụng getters và setters:

  • cho tái sử dụng
  • để thực hiện xác nhận trong các giai đoạn sau của lập trình

Các phương thức Getter và setter là các giao diện công cộng để truy cập các thành viên lớp riêng.


Thần chú đóng gói

Câu thần chú đóng gói là làm cho các trường riêng tư và phương thức công khai.

Phương pháp Getter: Chúng ta có thể truy cập vào các biến riêng tư.

Phương pháp Setter: Chúng ta có thể sửa đổi các trường riêng.

Mặc dù các phương thức getter và setter không thêm chức năng mới, chúng ta có thể thay đổi ý định quay lại sau để thực hiện phương thức đó

  • tốt hơn;
  • an toàn hơn; và
  • nhanh hơn

Bất cứ nơi nào một giá trị có thể được sử dụng, một phương thức trả về giá trị đó có thể được thêm vào. Thay vì:

int x = 1000 - 500

sử dụng

int x = 1000 - class_name.getValue();

Trong điều khoản của Giáo dân

Đại diện của lớp "Người"

Giả sử chúng ta cần lưu trữ các chi tiết này Person. Điều này Personcó các lĩnh vực name, agesex. Làm điều này liên quan đến việc tạo ra các phương thức cho name, agesex. Bây giờ nếu chúng ta cần tạo ra một người khác, nó trở nên cần thiết để tạo ra các phương pháp name, age, sexkhắp nơi trên một lần nữa.

Thay vì làm điều này, chúng ta có thể tạo một bean class(Person)với các phương thức getter và setter. Vì vậy, ngày mai chúng ta có thể tạo các đối tượng của Bean này class(Person class)bất cứ khi nào chúng ta cần thêm một người mới (xem hình). Vì vậy, chúng tôi đang sử dụng lại các trường và phương thức của lớp bean, tốt hơn nhiều.


15

Tôi đã dành khá nhiều thời gian để suy nghĩ về trường hợp Java và tôi tin rằng những lý do thực sự là:

  1. Mã cho giao diện, không phải việc thực hiện
  2. Các giao diện chỉ xác định các phương thức, không phải các trường

Nói cách khác, cách duy nhất bạn có thể chỉ định một trường trong giao diện là cung cấp phương thức viết giá trị mới và phương thức đọc giá trị hiện tại.

Những phương thức đó là getter và setter khét tiếng ....


1
Được rồi, câu hỏi thứ hai; trong trường hợp đó là một dự án mà bạn không xuất nguồn cho bất kỳ ai và bạn có toàn quyền kiểm soát nguồn ... bạn có thu được gì với getters và setters không?
Trưởng khoa J

2
Trong bất kỳ dự án Java không tầm thường nào, bạn cần mã hóa các giao diện để làm cho mọi thứ có thể quản lý và kiểm tra được (nghĩ các mockup và các đối tượng proxy). Nếu bạn sử dụng giao diện, bạn cần getters và setters.
Thorbjørn Ravn Andersen

15

Nó có thể hữu ích cho việc lười tải. Giả sử đối tượng trong câu hỏi được lưu trữ trong cơ sở dữ liệu và bạn không muốn lấy nó trừ khi bạn cần nó. Nếu đối tượng được lấy bởi một getter, thì đối tượng bên trong có thể là null cho đến khi ai đó yêu cầu nó, sau đó bạn có thể lấy nó trong cuộc gọi đầu tiên đến getter.

Tôi đã có một lớp trang cơ sở trong một dự án được giao cho tôi đang tải một số dữ liệu từ một vài cuộc gọi dịch vụ web khác nhau, nhưng dữ liệu trong các cuộc gọi dịch vụ web đó không phải lúc nào cũng được sử dụng trong tất cả các trang con. Các dịch vụ web, vì tất cả các lợi ích, tiên phong cho các định nghĩa mới về "chậm", vì vậy bạn không muốn thực hiện cuộc gọi dịch vụ web nếu bạn không phải thực hiện.

Tôi đã chuyển từ các trường công cộng sang getters và bây giờ các getters kiểm tra bộ đệm và nếu không có thì hãy gọi dịch vụ web. Vì vậy, với một gói nhỏ, rất nhiều cuộc gọi dịch vụ web đã bị ngăn chặn.

Vì vậy, getter cứu tôi khỏi cố gắng tìm ra, trên mỗi trang con, tôi sẽ cần gì. Nếu tôi cần nó, tôi gọi cho getter, và nó sẽ tìm nó cho tôi nếu tôi chưa có nó.

    protected YourType _yourName = null;
    public YourType YourName{
      get
      {
        if (_yourName == null)
        {
          _yourName = new YourType();
          return _yourName;
        }
      }
    }

Vì vậy, sau đó các getter gọi setter?
icc97

Tôi đã thêm một mẫu mã về cách tôi đã thực hiện trong quá khứ - về cơ bản, bạn lưu trữ lớp thực tế trong một thành viên được bảo vệ, sau đó trả lại thành viên được bảo vệ đó trong trình truy cập get, khởi tạo nó nếu nó không được khởi tạo.
quillbreaker


13

Một khía cạnh tôi đã bỏ lỡ trong các câu trả lời cho đến nay, đặc tả truy cập:

  • đối với thành viên, bạn chỉ có một đặc tả truy cập cho cả cài đặt và nhận
  • đối với setters và getters bạn có thể tinh chỉnh nó và xác định nó một cách riêng biệt

13

EDIT: Tôi đã trả lời câu hỏi này bởi vì có rất nhiều người học lập trình hỏi điều này và hầu hết các câu trả lời đều rất có năng lực về mặt kỹ thuật, nhưng chúng không dễ hiểu nếu bạn là người mới. Chúng tôi đều là người mới, vì vậy tôi nghĩ tôi sẽ thử một câu trả lời thân thiện với người mới hơn.

Hai cái chính là đa hình và xác nhận. Ngay cả khi đó chỉ là một cấu trúc dữ liệu ngu ngốc.

Hãy nói rằng chúng ta có lớp đơn giản này:

public class Bottle {
  public int amountOfWaterMl;
  public int capacityMl;
}

Một lớp rất đơn giản chứa được bao nhiêu chất lỏng trong đó, và công suất của nó là bao nhiêu (tính bằng mililit).

Điều gì xảy ra khi tôi làm:

Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacityMl = 1000;

Chà, bạn sẽ không mong đợi nó hoạt động chứ? Bạn muốn có một số loại kiểm tra vệ sinh. Và tệ hơn, nếu tôi không bao giờ chỉ định công suất tối đa thì sao? Trời ơi, chúng ta có một vấn đề.

Nhưng cũng có một vấn đề khác. Nếu chai chỉ là một loại container thì sao? Điều gì sẽ xảy ra nếu chúng ta có một số thùng chứa, tất cả đều có dung tích và lượng chất lỏng chứa đầy? Nếu chúng ta có thể tạo giao diện, chúng ta có thể để phần còn lại của chương trình chấp nhận giao diện đó, và các chai, jerrycans và tất cả các loại công cụ sẽ hoạt động thay thế cho nhau. Điều đó có tốt hơn không? Vì giao diện yêu cầu phương thức, đây cũng là một điều tốt.

Chúng tôi sẽ kết thúc với một cái gì đó như:

public interface LiquidContainer {
  public int getAmountMl();
  public void setAmountMl(int amountMl);
  public int getCapacityMl();
}

Tuyệt quá! Và bây giờ chúng ta chỉ cần thay đổi Chai thành này:

public class Bottle extends LiquidContainer {
  private int capacityMl;
  private int amountFilledMl;

  public Bottle(int capacityMl, int amountFilledMl) {
    this.capacityMl = capacityMl;
    this.amountFilledMl = amountFilledMl;
    checkNotOverFlow();
  }

  public int getAmountMl() {
    return amountFilledMl;
  }

  public void setAmountMl(int amountMl) {
     this.amountFilled = amountMl;
     checkNotOverFlow();
  }
  public int getCapacityMl() {
    return capacityMl;
  }

  private void checkNotOverFlow() {
    if(amountOfWaterMl > capacityMl) {
      throw new BottleOverflowException();
    }
}

Tôi sẽ để lại định nghĩa về BottleOverflowException như một bài tập cho người đọc.

Bây giờ hãy chú ý xem điều này mạnh hơn bao nhiêu. Chúng tôi có thể đối phó với bất kỳ loại container nào trong mã của chúng tôi ngay bây giờ bằng cách chấp nhận LiquidContainer thay vì Chai. Và làm thế nào những chai này đối phó với loại công cụ này tất cả có thể khác nhau. Bạn có thể có các chai ghi trạng thái của chúng vào đĩa khi nó thay đổi hoặc các chai lưu trên cơ sở dữ liệu SQL hoặc GNU biết những gì khác.

Và tất cả những điều này có thể có những cách khác nhau để xử lý các ca phẫu thuật khác nhau. Chai chỉ kiểm tra và nếu nó tràn ra, nó sẽ ném RuntimeException. Nhưng đó có thể là điều sai trái. (Có một cuộc thảo luận hữu ích để xử lý lỗi, nhưng tôi cố tình giữ nó rất đơn giản. Mọi người trong các bình luận có thể sẽ chỉ ra những sai sót của phương pháp đơn giản này.))

Và vâng, có vẻ như chúng ta đi từ một ý tưởng rất đơn giản để nhận được câu trả lời tốt hơn nhiều một cách nhanh chóng.

Cũng xin lưu ý rằng bạn không thể thay đổi dung tích của một chai. Bây giờ nó được đặt trong đá. Bạn có thể làm điều này với một int bằng cách khai báo nó cuối cùng. Nhưng nếu đây là một danh sách, bạn có thể làm trống nó, thêm những thứ mới vào nó, v.v. Bạn không thể giới hạn quyền truy cập để chạm vào các bộ phận bên trong.

Ngoài ra còn có điều thứ ba mà không phải ai cũng giải quyết: getters và setters sử dụng các cuộc gọi phương thức. Điều đó có nghĩa là chúng trông giống như các phương pháp bình thường ở mọi nơi khác. Thay vì có cú pháp cụ thể kỳ lạ cho DTO và công cụ, bạn có điều tương tự ở mọi nơi.


2
Cảm ơn về lời giải thích nửa vời đầu tiên về các giao diện tôi đã đọc, tìm mọi tham chiếu đến "điều khiển từ xa" hoặc "ô tô"
djvs

1
"Bạn có thể có các chai ghi trạng thái của chúng vào đĩa khi nó thay đổi hoặc các chai lưu trên cơ sở dữ liệu SQL" Tôi đã làm khó xD. Nhưng dù sao, ý tưởng tốt đẹp để trình bày nó!
Xerus

10

Trong các ngôn ngữ không hỗ trợ "thuộc tính" (C ++, Java) hoặc yêu cầu biên dịch lại máy khách khi thay đổi trường thành thuộc tính (C #), sử dụng phương thức get / set sẽ dễ sửa đổi hơn. Ví dụ, việc thêm logic xác thực vào phương thức setFoo sẽ không yêu cầu thay đổi giao diện chung của một lớp.

Trong các ngôn ngữ hỗ trợ các thuộc tính "thực" (Python, Ruby, có thể là Smalltalk?), Không có điểm nào để nhận / đặt phương thức.


1
Re: C #. Nếu bạn thêm chức năng vào get / set thì có yêu cầu biên dịch lại không?
hấp25

@ steam25: xin lỗi, gõ sai. Tôi có nghĩa là khách hàng của lớp sẽ phải được biên dịch lại.
John Millikin

5
Thêm logic xác thực vào phương thức setFoo sẽ không yêu cầu thay đổi giao diện của lớp ở cấp ngôn ngữ , nhưng nó thay đổi giao diện thực tế , còn gọi là hợp đồng, vì nó thay đổi các điều kiện tiên quyết. Tại sao người ta muốn trình biên dịch không coi đó là một thay đổi đột phá khi nó là ?
R. Martinho Fernandes

@ R.MartinhoFernandes làm thế nào để khắc phục vấn đề "hỏng" này? Trình biên dịch không thể biết nó có bị hỏng hay không. Đây chỉ là một mối quan tâm khi bạn đang viết thư viện cho người khác, nhưng bạn đang biến nó thành zOMG phổ quát ở đây là những con rồng!
Phil

3
Yêu cầu biên dịch lại, như đã đề cập trong câu trả lời, là một cách trình biên dịch có thể khiến bạn nhận thức được sự thay đổi có thể xảy ra. Và hầu hết mọi thứ tôi viết đều là "thư viện cho người khác", vì tôi không làm việc một mình. Tôi viết mã có giao diện mà những người khác trong dự án sẽ sử dụng. Có gì khác biệt? Chết tiệt, ngay cả khi tôi sẽ là người sử dụng các giao diện đó, tại sao tôi phải giữ mã của mình theo tiêu chuẩn chất lượng thấp hơn? Tôi không thích làm việc với các giao diện rắc rối, ngay cả khi tôi là người viết chúng.
R. Martinho Fernandes

6

Một trong những nguyên tắc cơ bản của thiết kế OO: Đóng gói!

Nó mang lại cho bạn nhiều lợi ích, một trong số đó là bạn có thể thay đổi việc triển khai getter / setter đằng sau hậu trường nhưng bất kỳ người tiêu dùng nào có giá trị đó sẽ tiếp tục hoạt động miễn là kiểu dữ liệu vẫn giữ nguyên.


23
Các getters đóng gói và setters cung cấp là mỏng cười. Xem ở đây .
sbi

Nếu giao diện công cộng của bạn nói rằng 'foo' thuộc loại 'T' và có thể được đặt thành bất cứ điều gì, bạn không bao giờ có thể thay đổi điều đó. Sau này, bạn không thể quyết định đặt loại 'Y', cũng như không thể áp đặt các quy tắc như ràng buộc kích thước. Do đó, nếu bạn có get / set công khai mà không có nhiều set / get, bạn sẽ không thu được gì mà một lĩnh vực công cộng sẽ không cung cấp và khiến nó trở nên cồng kềnh hơn khi sử dụng. Nếu bạn có một ràng buộc, chẳng hạn như đối tượng có thể được đặt thành null hoặc giá trị phải nằm trong một phạm vi, thì có, sẽ cần một phương thức thiết lập công khai, nhưng bạn vẫn đưa ra một hợp đồng từ việc đặt giá trị này có thể ' thay đổi
thecoshman

Tại sao không thể thay đổi hợp đồng?
Phil

5
Tại sao mọi thứ nên tiếp tục biên dịch nếu hợp đồng thay đổi?
R. Martinho Fernandes

5

Bạn nên sử dụng getters và setters khi:

  • Bạn đang xử lý một cái gì đó thuộc tính về mặt khái niệm, nhưng:
    • Ngôn ngữ của bạn không có thuộc tính (hoặc một số cơ chế tương tự, như dấu vết biến của Tcl) hoặc
    • Hỗ trợ tài sản ngôn ngữ của bạn không đủ cho trường hợp sử dụng này, hoặc
    • Các quy ước thành ngữ của ngôn ngữ của bạn (hoặc đôi khi là khung của bạn) khuyến khích các getters hoặc setters cho trường hợp sử dụng này.

Vì vậy, đây rất hiếm khi là một câu hỏi OO chung; đó là một câu hỏi dành riêng cho ngôn ngữ, với các câu trả lời khác nhau cho các ngôn ngữ khác nhau (và các trường hợp sử dụng khác nhau).


Từ quan điểm lý thuyết OO, getters và setters là vô dụng. Giao diện của lớp của bạn là những gì nó làm, không phải là trạng thái của nó. (Nếu không, bạn đã viết sai lớp.) Trong các trường hợp rất đơn giản, trong đó những gì một lớp thực hiện chỉ là, ví dụ, biểu thị một điểm trong tọa độ hình chữ nhật, * các thuộc tính là một phần của giao diện; getters và setters chỉ đám mây mà. Nhưng trong bất cứ điều gì ngoại trừ các trường hợp rất đơn giản, cả thuộc tính lẫn getters và setters đều không phải là một phần của giao diện.

Nói một cách khác: Nếu bạn tin rằng người tiêu dùng của lớp bạn thậm chí không nên biết rằng bạn có một spamthuộc tính, thì ít có khả năng thay đổi nó hơn, sau đó cung cấp cho họ một set_spamphương thức là điều cuối cùng bạn muốn làm.

* Ngay cả đối với lớp đơn giản đó, bạn có thể không nhất thiết muốn cho phép đặt xygiá trị. Nếu đây thực sự là một lớp học, không nên nó có phương pháp như translate, rotatevv? Nếu đó chỉ là một lớp vì ngôn ngữ của bạn không có bản ghi / cấu trúc / bộ dữ liệu được đặt tên, thì đây thực sự không phải là một câu hỏi của OOlahoma


Nhưng không ai từng làm thiết kế OO chung. Họ đang thiết kế và thực hiện, bằng một ngôn ngữ cụ thể. Và trong một số ngôn ngữ, getters và setters là xa vô dụng.

Nếu ngôn ngữ của bạn không có thuộc tính, thì cách duy nhất để biểu thị một thứ thuộc tính về mặt khái niệm, nhưng thực sự được tính toán hoặc xác nhận, v.v., là thông qua getters và setters.

Ngay cả khi ngôn ngữ của bạn có thuộc tính, có thể có trường hợp chúng không đủ hoặc không phù hợp. Ví dụ: nếu bạn muốn cho phép các lớp con kiểm soát ngữ nghĩa của một thuộc tính, trong các ngôn ngữ không có quyền truy cập động, một lớp con không thể thay thế một thuộc tính được tính cho một thuộc tính.

Đối với "điều gì sẽ xảy ra nếu tôi muốn thay đổi việc thực hiện sau này?" câu hỏi (được lặp lại nhiều lần theo cách diễn đạt khác nhau trong cả câu hỏi của OP và câu trả lời được chấp nhận): Nếu đó thực sự là một thay đổi triển khai thuần túy và bạn đã bắt đầu với một thuộc tính, bạn có thể thay đổi nó thành một thuộc tính mà không ảnh hưởng đến giao diện. Trừ khi, tất nhiên, ngôn ngữ của bạn không hỗ trợ điều đó. Vì vậy, đây thực sự chỉ là trường hợp tương tự một lần nữa.

Ngoài ra, điều quan trọng là phải tuân theo các thành ngữ của ngôn ngữ (hoặc khung) mà bạn đang sử dụng. Nếu bạn viết mã kiểu Ruby đẹp trong C #, bất kỳ nhà phát triển C # có kinh nghiệm nào khác ngoài bạn sẽ gặp khó khăn khi đọc nó, và điều đó thật tệ. Một số ngôn ngữ có các nền văn hóa mạnh hơn xung quanh các quy ước của chúng so với các ngôn ngữ khác. Không phải là sự trùng hợp ngẫu nhiên khi Java và Python, nằm ở hai đầu đối diện của quang phổ về cách thức của các thành ngữ, có hai nền văn hóa mạnh nhất.

Ngoài độc giả của con người, sẽ có các thư viện và công cụ mong đợi bạn tuân theo các quy ước và làm cho cuộc sống của bạn trở nên khó khăn hơn nếu bạn không. Kết nối các tiện ích của Trình tạo giao diện với bất kỳ thứ gì ngoại trừ các thuộc tính ObjC hoặc sử dụng các thư viện giả định Java nhất định mà không có getters, chỉ khiến cuộc sống của bạn trở nên khó khăn hơn. Nếu các công cụ quan trọng với bạn, đừng chống lại chúng.


4

Từ quan điểm thiết kế hướng đối tượng, cả hai phương án đều có thể gây hại cho việc duy trì mã bằng cách làm suy yếu việc đóng gói các lớp. Đối với một cuộc thảo luận, bạn có thể xem bài viết tuyệt vời này: http://typicalprogrammer.com/?p=23


3

Các phương thức Getter và setter là các phương thức truy cập, có nghĩa là chúng thường là một giao diện chung để thay đổi các thành viên lớp riêng. Bạn sử dụng các phương thức getter và setter để xác định một thuộc tính. Bạn truy cập các phương thức getter và setter như các thuộc tính bên ngoài lớp, mặc dù bạn định nghĩa chúng trong lớp là các phương thức. Các thuộc tính bên ngoài lớp có thể có một tên khác với tên thuộc tính trong lớp.

Có một số lợi thế khi sử dụng các phương thức getter và setter, chẳng hạn như khả năng cho phép bạn tạo các thành viên với chức năng tinh vi mà bạn có thể truy cập như các thuộc tính. Họ cũng cho phép bạn tạo các thuộc tính chỉ đọc và chỉ viết.

Mặc dù các phương thức getter và setter rất hữu ích, bạn nên cẩn thận không lạm dụng chúng bởi vì, trong số các vấn đề khác, chúng có thể làm cho việc bảo trì mã trở nên khó khăn hơn trong một số tình huống. Ngoài ra, họ cung cấp quyền truy cập để thực hiện lớp học của bạn, như các thành viên công cộng. Thực hành OOP không khuyến khích truy cập trực tiếp vào các thuộc tính trong một lớp.

Khi bạn viết các lớp, bạn luôn được khuyến khích biến càng nhiều càng tốt các biến đối tượng của bạn thành riêng tư và thêm các phương thức getter và setter tương ứng. Điều này là do có nhiều lần bạn có thể không muốn cho phép người dùng thay đổi một số biến nhất định trong các lớp của mình. Ví dụ: nếu bạn có một phương thức tĩnh riêng theo dõi số lượng phiên bản được tạo cho một lớp cụ thể, bạn không muốn người dùng sửa đổi bộ đếm đó bằng mã. Chỉ câu lệnh constructor mới nên tăng biến đó bất cứ khi nào nó được gọi. Trong tình huống này, bạn có thể tạo biến cá thể riêng và chỉ cho phép phương thức getter cho biến đếm, nghĩa là người dùng chỉ có thể truy xuất giá trị hiện tại bằng cách sử dụng phương thức getter và họ sẽ không thể đặt giá trị mới sử dụng phương thức setter.


3

phát triển . privatelà tuyệt vời khi bạn cần bảo vệ dữ liệu thành viên . Cuối cùng, tất cả các lớp nên là loại "miniprogram" có giao diện được xác định rõ ràng mà bạn không thể chỉ sử dụng với các phần bên trong .

Điều đó nói rằng, phát triển phần mềm không phải là về việc thiết lập phiên bản cuối cùng của lớp như thể bạn đang nhấn một số bức tượng bằng gang trong lần thử đầu tiên. Trong khi bạn đang làm việc với nó, mã giống như đất sét. Nó phát triển khi bạn phát triển nó và tìm hiểu thêm về miền vấn đề bạn đang giải quyết. Trong các lớp phát triển có thể tương tác với nhau hơn mức cần thiết (sự phụ thuộc mà bạn dự định đưa ra), hợp nhất với nhau hoặc tách ra. Vì vậy, tôi nghĩ rằng cuộc tranh luận sôi nổi với những người không muốn viết một cách tôn giáo

int getVar() const { return var ; }

Vì vậy, bạn có:

doSomething( obj->getVar() ) ;

Thay vì

doSomething( obj->var ) ;

Không chỉ getVar()ồn ào về mặt thị giác, nó mang lại ảo giác mà gettingVar()bằng cách nào đó là một quá trình phức tạp hơn thực tế. Làm thế nào bạn (với tư cách là người viết lớp) coi sự tôn nghiêm varđặc biệt khó hiểu với người dùng trong lớp của bạn nếu nó có bộ cài đặt passthru - thì có vẻ như bạn đang đặt những cánh cổng này để "bảo vệ" thứ gì đó mà bạn khẳng định là có giá trị, (sự tôn nghiêm var) nhưng ngay cả bạn thừa nhận varsự bảo vệ của bạn cũng không đáng bao nhiêu bởi khả năng cho bất cứ ai vào và set varvới bất kỳ giá trị nào họ muốn, mà không cần bạn nhìn trộm những gì họ đang làm.

Vì vậy, tôi lập trình như sau (giả sử cách tiếp cận kiểu "nhanh nhẹn" - tức là khi tôi viết mã không biết chính xác nó sẽ làm gì / không có thời gian hoặc kinh nghiệm để lên kế hoạch cho bộ giao diện kiểu thác nước phức tạp):

1) Bắt đầu với tất cả các thành viên công cộng cho các đối tượng cơ bản với dữ liệu và hành vi. Đây là lý do tại sao trong tất cả mã "ví dụ" C ++ của tôi, bạn sẽ chú ý đến tôi sử dụng structthay vì classở mọi nơi.

2) Khi hành vi bên trong của một đối tượng cho một thành viên dữ liệu trở nên đủ phức tạp, (ví dụ, nó thích giữ một nội bộ std::listtheo một thứ tự nào đó), các hàm loại truy cập được viết. Bởi vì tôi đang lập trình một mình, tôi không luôn luôn thiết lập các thành viên privatengay lập tức, nhưng ở đâu đó xuống sự tiến hóa của lớp các thành viên sẽ được "thăng chức" cho một trong hai protectedhoặc private.

3) Các lớp được bổ sung đầy đủ và có các quy tắc nghiêm ngặt về nội bộ của họ (tức là họ biết chính xác những gì họ đang làm và bạn không được "đụ" (thuật ngữ kỹ thuật) với các bên trong của nó) được classchỉ định, các thành viên riêng mặc định, và chỉ một vài thành viên được chọn mới được phép public.

Tôi thấy cách tiếp cận này cho phép tôi tránh ngồi ở đó và viết getter / setters một cách tôn giáo khi nhiều thành viên dữ liệu được di chuyển ra ngoài, chuyển xung quanh, v.v. trong giai đoạn đầu của quá trình tiến hóa của lớp.


1
"... một giao diện được xác định rõ ràng mà bạn không thể chỉ với các phần bên trong" và xác nhận trong setters.
Búa búa Agi

3

Có một lý do tốt để xem xét sử dụng người truy cập là không có thừa kế tài sản. Xem ví dụ tiếp theo:

public class TestPropertyOverride {
    public static class A {
        public int i = 0;

        public void add() {
            i++;
        }

        public int getI() {
            return i;
        }
    }

    public static class B extends A {
        public int i = 2;

        @Override
        public void add() {
            i = i + 2;
        }

        @Override
        public int getI() {
            return i;
        }
    }

    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.i);
        a.add();
        System.out.println(a.i);
        System.out.println(a.getI());
    }
}

Đầu ra:

0
0
4

3

Getterssetters được sử dụng để thực hiện hai trong số các khía cạnh cơ bản của lập trình hướng đối tượng là:

  1. Trừu tượng
  2. Đóng gói

Giả sử chúng ta có một lớp Nhân viên:

package com.highmark.productConfig.types;

public class Employee {

    private String firstName;
    private String middleName;
    private String lastName;

    public String getFirstName() {
      return firstName;
    }
    public void setFirstName(String firstName) {
       this.firstName = firstName;
    }
    public String getMiddleName() {
        return middleName;
    }
    public void setMiddleName(String middleName) {
         this.middleName = middleName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFullName(){
        return this.getFirstName() + this.getMiddleName() +  this.getLastName();
    }
 }

Ở đây, chi tiết triển khai của Tên đầy đủ được ẩn khỏi người dùng và không thể truy cập trực tiếp đến người dùng, không giống như một thuộc tính công khai.


1
Đối với tôi có hàng tấn getters và setters mà không có gì độc đáo là vô dụng. getFullName là một ngoại lệ vì nó làm một cái gì đó khác. Chỉ có ba biến công khai sau đó giữ getFullName sẽ giúp chương trình dễ đọc hơn nhưng vẫn ẩn điều đó trong tên đầy đủ. Nói chung tôi hoàn toàn ổn với getters và setters nếu a. họ làm một cái gì đó độc đáo và / hoặc b. bạn chỉ có một, vâng, bạn có thể có một trận chung kết công khai và tất cả những thứ đó nhưng không
FaclessTiger

1
Lợi ích của việc này là bạn có thể thay đổi nội bộ của lớp mà không cần thay đổi giao diện. Nói, thay vì ba thuộc tính, bạn có một - một chuỗi các chuỗi. Nếu bạn đã sử dụng getters và setters, bạn có thể thực hiện thay đổi đó và sau đó cập nhật getter / setters để biết rằng tên [0] là tên đầu tiên, tên [1] là giữa, v.v. Nhưng nếu bạn chỉ sử dụng thuộc tính công cộng , bạn cũng sẽ phải thay đổi mọi lớp nhân viên truy cập, bởi vì thuộc tính FirstName họ đang sử dụng không còn tồn tại.
Andrew Hows

1
@Andrew Nhận được từ những gì tôi đã thấy trong cuộc sống thực, khi mọi người thay đổi nội bộ của lớp, họ cũng thay đổi giao diện và thực hiện tái cấu trúc lớn tất cả các mã
Eildosa

2

Một cách sử dụng khác (trong các ngôn ngữ hỗ trợ các thuộc tính) là setters và getters có thể ngụ ý rằng một hoạt động là không tầm thường. Thông thường, bạn muốn tránh làm bất cứ điều gì đắt tiền về mặt tính toán trong một tài sản.


Tôi sẽ không bao giờ mong đợi một getter hoặc setter là một hoạt động đắt tiền. Trong những trường hợp như vậy, tốt hơn là sử dụng một nhà máy: sử dụng tất cả các setters bạn cần và sau đó gọi phương thức executehoặc đắt tiền build.
Hubert Grzeskowiak

2

Một lợi thế tương đối hiện đại của getters / setters là giúp duyệt mã dễ dàng hơn trong các trình soạn thảo mã được gắn thẻ (được lập chỉ mục). Ví dụ: Nếu bạn muốn xem ai thiết lập thành viên, bạn có thể mở cấu trúc phân cấp cuộc gọi của bộ cài đặt.

Mặt khác, nếu thành viên là công khai, các công cụ không cho phép lọc quyền truy cập đọc / ghi vào thành viên. Vì vậy, bạn phải trudge mặc dù tất cả các sử dụng của các thành viên.


bạn có thể thực hiện đúng clic> tìm cách sử dụng trên một thành viên chính xác như trên getter / setter
Eildosa

2

Trong một ngôn ngữ hướng đối tượng, các phương thức và các biến tố truy cập của chúng, khai báo giao diện cho đối tượng đó. Giữa phương thức khởi tạo và phương thức của trình truy cập và trình biến đổi, nhà phát triển có thể kiểm soát quyền truy cập vào trạng thái bên trong của một đối tượng. Nếu các biến được khai báo đơn giản thì không có cách nào để điều chỉnh truy cập đó. Và khi chúng tôi đang sử dụng setters, chúng tôi có thể hạn chế người dùng cho đầu vào mà chúng tôi cần. Có nghĩa là nguồn cấp dữ liệu cho chính biến đó sẽ đi qua một kênh thích hợp và kênh được xác định trước bởi chúng tôi. Vì vậy, nó an toàn hơn để sử dụng setters.


1

Ngoài ra, đây là "bằng chứng trong tương lai" lớp của bạn. Cụ thể, việc thay đổi từ một trường thành một thuộc tính là một sự phá vỡ ABI, vì vậy nếu sau này bạn quyết định rằng bạn cần nhiều logic hơn là chỉ "đặt / lấy trường", thì bạn cần phải phá vỡ ABI, điều này tất nhiên tạo ra vấn đề cho bất cứ điều gì khác đã được biên soạn chống lại lớp học của bạn.


2
Tôi cho rằng việc thay đổi hành vi của getter hoặc setter không phải là thay đổi đột phá. </ mỉa mai>
R. Martinho Fernandes

@ R.MartinhoFernandes không phải lúc nào cũng được. TBYS
Phil

1

Tôi chỉ muốn đưa ra ý tưởng về chú thích: @getter và @setter. Với @getter, bạn sẽ có thể obj = class.field nhưng không phải class.field = obj. Với @setter, ngược lại. Với @getter và @setter, bạn sẽ có thể làm cả hai. Điều này sẽ bảo toàn việc đóng gói và giảm thời gian bằng cách không gọi các phương thức tầm thường khi chạy.


1
Nó sẽ được thực hiện trong thời gian chạy với "phương pháp tầm thường". Thật ra, có lẽ không tầm thường.
Phil

Ngày nay điều này có thể được thực hiện với một bộ tiền xử lý chú thích.
Thorbjørn Ravn Andersen
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.