Tôi nhớ có một cuộc tranh luận tương tự với giảng viên của tôi khi học C ++ ở trường đại học. Tôi chỉ không thể thấy quan điểm của việc sử dụng getters và setters khi tôi có thể công khai biến. Bây giờ tôi hiểu rõ hơn với nhiều năm kinh nghiệm và tôi đã học được một lý do tốt hơn là chỉ đơn giản nói "để duy trì đóng gói".
Bằng cách xác định các getters và setters, bạn sẽ cung cấp một giao diện nhất quán để nếu bạn muốn thay đổi triển khai của mình, bạn sẽ ít có khả năng phá vỡ mã phụ thuộc. Điều này đặc biệt quan trọng khi các lớp của bạn được hiển thị thông qua API và được sử dụng trong các ứng dụng khác hoặc bởi các bên thứ 3. Vì vậy, những gì về những thứ đi vào getter hoặc setter?
Getters thường được thực hiện tốt hơn như là một sự thông qua đơn giản để truy cập vào một giá trị bởi vì điều này làm cho hành vi của họ có thể dự đoán được. Tôi nói chung, bởi vì tôi đã thấy các trường hợp trong đó getters đã được sử dụng để truy cập các giá trị được thao tác bằng phép tính hoặc thậm chí bằng mã có điều kiện. Nói chung là không tốt nếu bạn đang tạo các thành phần trực quan để sử dụng tại thời điểm thiết kế, nhưng có vẻ tiện dụng trong thời gian chạy. Tuy nhiên, không có sự khác biệt thực sự giữa điều này và sử dụng một phương thức đơn giản, ngoại trừ khi bạn sử dụng một phương thức, bạn thường có khả năng đặt tên một phương thức phù hợp hơn để chức năng của "getter" rõ ràng hơn khi đọc mã.
So sánh như sau:
int aValue = MyClass.Value;
và
int aValue = MyClass.CalculateValue();
Tùy chọn thứ hai cho thấy rõ rằng giá trị đang được tính toán, trong khi ví dụ đầu tiên cho bạn biết rằng bạn chỉ đang trả về một giá trị mà không biết gì về chính giá trị đó.
Bạn có thể tranh luận rằng những điều sau đây sẽ rõ ràng hơn:
int aValue = MyClass.CalculatedValue;
Tuy nhiên, vấn đề là bạn cho rằng giá trị đã bị thao túng ở nơi khác. Vì vậy, trong trường hợp của một getter, trong khi bạn có thể muốn giả định rằng điều gì đó khác có thể xảy ra khi bạn trả lại một giá trị, thật khó để làm cho những điều đó rõ ràng trong ngữ cảnh của một tài sản và tên thuộc tính không bao giờ nên chứa động từ mặt khác, nó làm cho khó hiểu trong nháy mắt liệu tên được sử dụng có nên được trang trí bằng dấu ngoặc đơn khi truy cập hay không.
Setters là một trường hợp hơi khác nhau tuy nhiên. Điều hoàn toàn phù hợp là một setter cung cấp một số xử lý bổ sung để xác thực dữ liệu được gửi đến một thuộc tính, đưa ra một ngoại lệ nếu đặt giá trị sẽ vi phạm các ranh giới đã xác định của thuộc tính. Tuy nhiên, vấn đề mà một số nhà phát triển gặp phải khi thêm xử lý vào setters là luôn có sự cám dỗ để người thiết lập thực hiện thêm một chút, chẳng hạn như thực hiện tính toán hoặc thao tác dữ liệu theo một cách nào đó. Đây là nơi bạn có thể nhận được các tác dụng phụ mà trong một số trường hợp có thể là không thể đoán trước hoặc không mong muốn.
Trong trường hợp setters tôi luôn áp dụng một quy tắc đơn giản, đó là làm ít nhất có thể với dữ liệu. Ví dụ, tôi thường sẽ cho phép thử nghiệm ranh giới và làm tròn để tôi có thể đưa ra các ngoại lệ nếu thích hợp hoặc tránh các ngoại lệ không cần thiết nơi chúng có thể tránh được một cách hợp lý. Thuộc tính dấu phẩy động là một ví dụ điển hình trong đó bạn có thể làm tròn các vị trí thập phân quá mức để tránh gây ra ngoại lệ, trong khi vẫn cho phép nhập các giá trị phạm vi với một vài vị trí thập phân bổ sung.
Nếu bạn áp dụng một số thao tác của đầu vào setter, bạn sẽ gặp vấn đề tương tự như với getter, rằng thật khó để cho phép người khác biết những gì setter đang làm bằng cách chỉ cần đặt tên cho nó. Ví dụ:
MyClass.Value = 12345;
Điều này có cho bạn biết bất cứ điều gì về những gì sẽ xảy ra với giá trị khi nó được trao cho setter không?
Làm thế nào về:
MyClass.RoundValueToNearestThousand(12345);
Ví dụ thứ hai cho bạn biết chính xác điều gì sẽ xảy ra với dữ liệu của bạn, trong khi ví dụ đầu tiên sẽ không cho bạn biết nếu giá trị của bạn sẽ được sửa đổi tùy ý. Khi đọc mã, ví dụ thứ hai sẽ rõ ràng hơn nhiều về mục đích và chức năng của nó.
Tôi có đúng rằng điều này sẽ hoàn toàn đánh bại mục đích có getters và setters ở vị trí đầu tiên và xác nhận và logic khác (tất nhiên không có tác dụng phụ lạ) nên được cho phép?
Có getters và setters không phải là đóng gói vì mục đích "tinh khiết", mà là đóng gói để cho phép mã dễ dàng được tái cấu trúc mà không mạo hiểm thay đổi giao diện của lớp, điều này sẽ phá vỡ tính tương thích của lớp với mã gọi. Xác thực là hoàn toàn phù hợp trong một setter, tuy nhiên có một rủi ro nhỏ là việc thay đổi xác thực có thể phá vỡ tính tương thích với mã gọi nếu mã gọi phụ thuộc vào xác thực xảy ra theo một cách cụ thể. Đây là một tình huống thường hiếm gặp và có rủi ro tương đối thấp, nhưng nó cần được lưu ý vì sự hoàn chỉnh.
Khi nào nên xác nhận xảy ra?
Xác thực sẽ xảy ra trong bối cảnh của trình thiết lập trước khi thực sự thiết lập giá trị. Điều này đảm bảo rằng nếu một ngoại lệ được đưa ra, trạng thái của đối tượng của bạn sẽ không thay đổi và có khả năng làm mất hiệu lực dữ liệu của nó. Tôi thường thấy tốt hơn khi ủy quyền xác thực cho một phương thức riêng biệt, đó sẽ là điều đầu tiên được gọi trong setter, để giữ cho mã setter tương đối không bị xáo trộn.
Là một setter được phép thay đổi giá trị (có thể chuyển đổi một giá trị hợp lệ thành một số biểu diễn bên trong chính tắc)?
Trong trường hợp rất hiếm, có thể. Nói chung, có lẽ tốt hơn là không. Đây là loại điều tốt nhất còn lại cho phương pháp khác.