it-swarm-vi.com

Khi nào Getters và Setters được chứng minh

Getters và setters thường bị chỉ trích là không đúng OO. Mặt khác, hầu hết OO tôi đã thấy có nhiều getters và setters.

Khi nào getters và setters hợp lý? Bạn có cố gắng tránh sử dụng chúng? Có phải họ đang sử dụng quá mức nói chung?

Nếu ngôn ngữ yêu thích của bạn có thuộc tính (của tôi) thì những thứ như vậy cũng được coi là getters và setters cho câu hỏi này. Chúng giống nhau từ quan điểm phương pháp OO. Chúng chỉ có cú pháp đẹp hơn.

Nguồn cho phê bình Getter/Setter (một số lấy từ các bình luận để cho họ thấy rõ hơn):

Để chỉ ra những lời chỉ trích đơn giản: Getters và Setters cho phép bạn thao tác trạng thái bên trong của các đối tượng từ bên ngoài đối tượng. Điều này vi phạm đóng gói. Chỉ bản thân đối tượng nên quan tâm đến trạng thái bên trong của nó.

Và một ví dụ phiên bản thủ tục của mã.

struct Fridge
{
    int cheese;
}

void go_shopping(Fridge fridge)
{
     fridge.cheese += 5;
}

Phiên bản mã biến đổi:

class Fridge
{
     int cheese;

     void set_cheese(int _cheese) { cheese = _cheese; }
     int get_cheese() { return cheese; }
 }

void go_shopping(Fridge fridge)
{
     fridge.set_cheese(fridge.get_cheese() + 5);        
}

Các getters và setters làm cho mã phức tạp hơn nhiều mà không cần đóng gói đúng. Bởi vì trạng thái bên trong có thể truy cập được đối với các đối tượng khác, chúng tôi không thu được nhiều bằng cách thêm các getters và setters này.

Câu hỏi đã được thảo luận trước đây về Stack Overflow:

179
Winston Ewert

Có getters và setters không tự nó phá vỡ đóng gói. Điều gì làm phá vỡ đóng gói là tự động thêm một getter và setter cho mọi thành viên dữ liệu (mọi trường, in Java lingo), mà không cần suy nghĩ gì cả. tốt hơn là làm cho tất cả các thành viên dữ liệu công khai, nó chỉ là một bước nhỏ.

Điểm đóng gói không phải là bạn không thể biết hoặc thay đổi trạng thái của đối tượng từ bên ngoài đối tượng, mà là bạn nên có một chính sách để thực hiện nó.

  • Một số thành viên dữ liệu có thể hoàn toàn bên trong đối tượng và không nên có getters hay setters.

  • Một số thành viên dữ liệu nên ở chế độ chỉ đọc, vì vậy họ có thể cần getters nhưng không phải setters.

  • Một số thành viên dữ liệu có thể cần phải được giữ phù hợp với nhau. Trong trường hợp như vậy, bạn sẽ không cung cấp một setter cho mỗi cái, nhưng một phương thức duy nhất để đặt chúng cùng một lúc, để bạn có thể kiểm tra tính nhất quán của các giá trị.

  • Một số thành viên dữ liệu có thể chỉ cần được thay đổi theo một cách nhất định, chẳng hạn như tăng hoặc giảm theo một số tiền cố định. Trong trường hợp này, bạn sẽ cung cấp một phương thức increment() và/hoặc decrement(), thay vì setter.

  • Tuy nhiên, những người khác thực sự có thể cần phải đọc-ghi, và sẽ có cả getter và setter.

Hãy xem xét một ví dụ về class Person. Giả sử một người có tên, số an sinh xã hội và tuổi. Hãy nói rằng chúng tôi không cho phép mọi người thay đổi tên hoặc số an sinh xã hội. Tuy nhiên, tuổi của người đó nên được tăng thêm 1 mỗi năm. Trong trường hợp này, bạn sẽ cung cấp một hàm tạo sẽ khởi tạo tên và SSN cho các giá trị đã cho và sẽ khởi tạo tuổi thành 0. Bạn cũng sẽ cung cấp phương thức incrementAge(), sẽ tăng tuổi bằng 1. Bạn cũng sẽ cung cấp getters cho cả ba. Không có setters được yêu cầu trong trường hợp này.

Trong thiết kế này, bạn cho phép trạng thái của đối tượng được kiểm tra từ bên ngoài lớp và bạn cho phép nó được thay đổi từ bên ngoài lớp. Tuy nhiên, bạn không cho phép thay đổi trạng thái tùy ý. Có một chính sách, trong đó nêu rõ rằng tên và SSN hoàn toàn không thể thay đổi và tuổi có thể tăng thêm 1 năm một lần.

Bây giờ hãy nói rằng một người cũng có một mức lương. Và mọi người có thể thay đổi công việc theo ý muốn, có nghĩa là tiền lương của họ cũng sẽ thay đổi. Để mô hình hóa tình huống này, chúng tôi không có cách nào khác ngoài việc cung cấp phương thức setSalary()! Cho phép mức lương được thay đổi theo ý muốn là một chính sách hoàn toàn hợp lý trong trường hợp này.

Nhân tiện, trong ví dụ của bạn, tôi sẽ cung cấp cho các phương thức Fridge các phương thức putCheese()takeCheese(), thay vì get_cheese()set_cheese(). Sau đó, bạn vẫn sẽ đóng gói.


public class Fridge {
  private List objects;
  private Date warranty;

  /** How the warranty is stored internally is a detail. */
  public Fridge( Date warranty ) {
    // The Fridge can set its internal warranty, but it is not re-exposed.
    setWarranty( warranty );
  }

  /** Doesn't expose how the fridge knows it is empty. */
  public boolean isEmpty() {
    return getObjects().isEmpty();
  }

  /** When the fridge has no more room... */
  public boolean isFull() {
  }

  /** Answers whether the given object will fit. */
  public boolean canStore( Object o ) {
    boolean result = false;

    // Clients may not ask how much room remains in the fridge.
    if( o instanceof PhysicalObject ) {
      PhysicalObject po = (PhysicalObject)o;

      // How the fridge determines its remaining usable volume is a detail.
      // How a physical object determines whether it fits within a specified
      // volume is also a detail.
      result = po.isEnclosedBy( getUsableVolume() );
    }

     return result;
  }

  /** Doesn't expose how the fridge knows its warranty has expired. */
  public boolean isPastWarranty() {
    return getWarranty().before( new Date() );
  }

  /** Doesn't expose how objects are stored in the fridge. */
  public synchronized void store( Object o ) {
    validateExpiration( o );

    // Can the object fit?
    if( canStore( o ) ) {
      getObjects().add( o );
    }
    else {
      throw FridgeFullException( o );
    }
  }

  /** Doesn't expose how objects are removed from the fridge. */
  public synchronized void remove( Object o ) {
    if( !getObjects().contains( o ) ) {
      throw new ObjectNotFoundException( o );
    }

    getObjects().remove( o );

    validateExpiration( o );
  }

  /** Lazily initialized list, an implementation detail. */
  private synchronized List getObjects() {
    if( this.list == null ) { this.list = new List(); }
    return this.list;
  }

  /** How object expiration is determined is also a detail. */
  private void validateExpiration( Object o ) {
    // Objects can answer whether they have gone past a given
    // expiration date. How each object "knows" it has expired
    // is a detail. The Fridge might use a scanner and
    // items might have embedded RFID chips. It's a detail hidden
    // by proper encapsulation.
    if( o implements Expires && ((Expires)o).expiresBefore( today ) ) {
      throw new ExpiredObjectException( o );
    }
  }

  /** This creates a copy of the warranty for immutability purposes. */
  private void setWarranty( Date warranty ) {
    assert warranty != null;
    this.warranty = new Date( warranty.getTime() )
  }
}
163
Dima

Lý do cơ bản cho getters và setters trong Java rất đơn giản:

  • Bạn chỉ có thể chỉ định phương thức, không phải các trường, trong một giao diện.

Do đó, nếu bạn muốn cho phép một trường đi qua giao diện, bạn sẽ cần một trình đọc và phương thức nhà văn. Chúng thường được gọi là getX và setX cho trường x.

42
user1249

Từ http://www.adam-bien.com/cont/abien/entry/encapsulation_violation_with_getters_and

Kiểu JavaBean:

connection.setUser("dukie");
connection.setPwd("duke");
connection.initialize();

Kiểu OO:

connection.connect("dukie","duke");

Vâng, rõ ràng tôi thích cách tiếp cận sau; nó không làm hỏng chi tiết triển khai, nó đơn giản và ngắn gọn hơn, và tất cả các thông tin cần thiết đều được bao gồm trong lệnh gọi phương thức, vì vậy sẽ dễ dàng hơn để làm cho đúng. Tôi cũng thích thiết lập các thành viên riêng bằng cách sử dụng các tham số trong hàm tạo, bất cứ khi nào có thể.

Câu hỏi của bạn là, khi nào một getter/setter được chứng minh? Có lẽ khi cần thay đổi chế độ, hoặc bạn cần thẩm vấn một đối tượng để biết một số thông tin.

myObject.GetStatus();
myObject.SomeCapabilitySwitch = true;

Nghĩ về nó, khi tôi mới bắt đầu viết mã bằng C #, tôi đã viết rất nhiều mã theo kiểu Javabeans được minh họa ở trên. Nhưng khi tôi có được kinh nghiệm về ngôn ngữ, tôi bắt đầu thực hiện nhiều cài đặt thành viên hơn trong hàm tạo và sử dụng các phương thức trông giống kiểu trên OO.

20
Robert Harvey

Khi nào getters và setters hợp lý?

Khi hành vi "get" và "set" thực sự khớp với hành vi trong mô hình của bạn, đó là thực tế không bao giờ.

Mọi mục đích sử dụng khác chỉ là một mánh gian lận vì hành vi của lĩnh vực kinh doanh không rõ ràng.

Chỉnh sửa

Câu trả lời đó có thể đã xuất hiện dưới dạng flippant, vì vậy hãy để tôi mở rộng. Các câu trả lời ở trên hầu hết đều đúng nhưng tập trung vào các mô hình lập trình của OO và một số điều bỏ lỡ bức tranh lớn. Theo kinh nghiệm của tôi, điều này khiến mọi người nghĩ rằng tránh cài đặt và setters là một số quy tắc học thuật cho OO ngôn ngữ lập trình (ví dụ: mọi người nghĩ thay thế getters bằng thuộc tính là lớn)

Trong thực tế, nó là một hệ quả của quá trình thiết kế. Bạn không cần phải tham gia vào các giao diện và đóng gói và các mẫu, tranh luận về việc nó có hay không phá vỡ các mô hình này và điều gì là không hay OO. Điểm duy nhất là vấn đề cuối cùng là nếu không có gì trong miền của bạn hoạt động như thế này bằng cách đưa chúng vào thì bạn không mô hình hóa miền của mình.

Thực tế là rất khó có hệ thống nào trong không gian miền của bạn có getters và setters. Bạn không thể đi đến người đàn ông hoặc phụ nữ chịu trách nhiệm về bảng lương và chỉ cần nói "Đặt mức lương này thành X" hoặc "Nhận cho tôi mức lương này". Hành vi như vậy đơn giản là không tồn tại

Nếu bạn đang đặt mã này vào mã của mình, bạn không thiết kế hệ thống của mình để phù hợp với mô hình miền của bạn. Vâng, phá vỡ giao diện và đóng gói, nhưng đó không phải là vấn đề. Vấn đề là bạn đang mô hình hóa một cái gì đó không tồn tại.

Nhiều hơn bạn có thể đang thiếu một bước hoặc quy trình quan trọng, bởi vì có lẽ có một lý do tôi không thể đi lên để trả cuộn và nói đặt mức lương này thành X.

Khi mọi người sử dụng getters và setters họ có xu hướng Đẩy các quy tắc cho quá trình này vào sai vị trí. Điều này đang di chuyển ra khỏi miền của bạn thậm chí nhiều hơn. Sử dụng ví dụ trong thế giới thực, nó giống như tiền lương giả định rằng người ngẫu nhiên bước vào có quyền nhận được các giá trị này nếu không anh ta sẽ không yêu cầu họ. Không chỉ là không phải tên miền như thế nào, mà trên thực tế còn nói dối về cách thức tên miền.

14
Cormac Mulhall

Theo nguyên tắc chung, getters và setters là một ý tưởng tồi. Nếu một trường không phải là một phần logic của giao diện và bạn đặt nó ở chế độ riêng tư, điều đó tốt. Nếu nó là một phần logic của giao diện và bạn công khai nó, điều đó tốt. Nhưng nếu bạn đặt nó ở chế độ riêng tư và sau đó quay lại và làm cho nó trở nên công khai một cách hiệu quả bằng cách cung cấp một getter và setter, bạn sẽ quay trở lại nơi bạn đã bắt đầu ngoại trừ mã của bạn bây giờ dài hơn và bị xáo trộn.

Rõ ràng, có những ngoại lệ. Trong Java, bạn có thể cần sử dụng các giao diện. Thư viện tiêu chuẩn Java có các yêu cầu tương thích ngược cực kỳ vượt trội so với các biện pháp chất lượng mã thông thường. Thậm chí có khả năng bạn thực sự có thể xử lý trường hợp huyền thoại nhưng hiếm khi có cơ hội tốt bạn có thể sau đó thay thế một trường được lưu trữ bằng phép tính nhanh mà không phá vỡ giao diện. Nhưng đây là những trường hợp ngoại lệ. Getters và setters là một mô hình chống cần sự biện minh đặc biệt.

11
rwallace

cho dù trường có thể truy cập trực tiếp hoặc thông qua phương thức không thực sự quan trọng.

Lớp bất biến (những cái hữu ích) rất quan trọng. Và để bảo tồn chúng, đôi khi chúng ta cần không thể thay đổi một cái gì đó từ bên ngoài. Ví dụ. nếu chúng ta có lớp Square với chiều rộng và chiều cao tách biệt, việc thay đổi một trong số chúng làm cho nó trở thành một cái gì đó khác với hình vuông. Vì vậy, chúng ta cần thay đổi phương thức Nếu đó là Hình chữ nhật, chúng ta có thể có setters/trường công khai. Nhưng setter hơn sẽ kiểm tra xem nó lớn hơn 0 sẽ tốt hơn.

Và trong các ngôn ngữ cụ thể (ví dụ: Java) là lý do tại sao chúng ta cần các phương thức (giao diện) đó. Và một lý do khác cho phương pháp là khả năng tương thích (nguồn và nhị phân). Vì vậy, dễ dàng hơn để thêm chúng sau đó suy nghĩ liệu lĩnh vực công cộng sẽ đủ.

btw. Tôi thích sử dụng các lớp giữ giá trị bất biến đơn giản với các trường cuối cùng công khai.

3
user470365

Bạn có thể muốn thay đổi nội bộ của mình thành bất cứ điều gì trong khi vẫn giữ các giao diện giống nhau. Nếu giao diện của bạn không thay đổi, mã bạn sẽ không bị hỏng. Bạn vẫn có thể thay đổi nội bộ của bạn như bạn muốn.

2
George Silva

Hãy xem xét một lớp Size đóng gói chiều rộng và chiều cao. Tôi có thể loại bỏ setters bằng cách sử dụng hàm tạo nhưng làm thế nào để giúp tôi vẽ một hình chữ nhật với Size? Chiều rộng và chiều cao không phải là dữ liệu nội bộ cho lớp; chúng được chia sẻ dữ liệu phải có sẵn cho người tiêu dùng Kích thước.

Đối tượng bao gồm các hành vi và trạng thái - hoặc thuộc tính. Nếu không có trạng thái tiếp xúc thì chỉ có các hành vi là công khai.

Không có nhà nước, làm thế nào bạn sắp xếp một bộ sưu tập các đối tượng? Làm thế nào bạn sẽ tìm kiếm một ví dụ cụ thể của một đối tượng? Nếu bạn chỉ sử dụng các hàm tạo, điều gì xảy ra khi đối tượng của bạn có một danh sách dài các thuộc tính?

Không có tham số phương thức nào được sử dụng mà không cần xác nhận. Vì vậy, đó là sự lười biếng để viết:

setMyField(int myField){
    this.myField = myField;
}

Nếu bạn viết nó theo cách đó thì ít nhất bạn đã chuẩn bị cho việc sử dụng setter để xác nhận; nó tốt hơn một lĩnh vực công cộng - nhưng chỉ vừa đủ. Nhưng ít nhất bạn có một giao diện công cộng nhất quán mà bạn có thể quay lại và đưa vào các quy tắc xác thực mà không vi phạm mã của khách hàng.

Getters, setters, property, mutators, gọi chúng là những gì bạn sẽ, nhưng chúng là cần thiết.

0
Dale

Cách tiếp cận của tôi là thế này -

Khi tôi dự kiến ​​sẽ xử lý dữ liệu sau này, một getter/setter là hợp lý. Ngoài ra, nếu thay đổi đang xảy ra, tôi thường Đẩy dữ liệu vào getter/setter.

Nếu đó là cấu trúc POD, tôi để các vị trí có sẵn.

Ở mức độ trừu tượng hơn, câu hỏi là "ai quản lý dữ liệu" và điều đó phụ thuộc vào dự án.

0
Paul Nathan

Nếu getters và setters vi phạm đóng gói và OO thực sự, thì tôi thực sự gặp rắc rối.

Tôi luôn cảm thấy một đối tượng đại diện cho bất cứ điều gì tốt nhất đến với tâm trí mà bạn cần nó để làm.

Tôi vừa viết xong một chương trình tạo Mazes bằng Java và tôi có lớp đại diện cho "Maze Squares". Tôi có dữ liệu trong lớp này đại diện cho tọa độ, tường và booleans, v.v.

Tôi phải có một số cách để thay đổi/thao tác/truy cập dữ liệu này! Tôi phải làm gì khi không có getters và setters? Java không sử dụng các thuộc tính và đặt tất cả dữ liệu của tôi là cục bộ cho lớp này thành công khai là HOÀN TOÀN vi phạm đóng gói và OO.

0
Bryan Harrington

Nếu sử dụng getters và setters cảm thấy phức tạp, vấn đề có thể là ngôn ngữ, không phải là khái niệm.

Đây là mã từ ví dụ giây được viết bằng Ruby:

class Fridge
  attr_accessor :cheese
end

def go_shopping fridge
  fridge.cheese += 5
end

Lưu ý rằng nó trông rất giống ví dụ đầu tiên trong Java? Khi getters và setters được coi là công dân hạng nhất, chúng không phải là một việc vặt để sử dụng và tính linh hoạt được thêm vào đôi khi có thể là một lợi ích thực sự - ví dụ, chúng tôi có thể quyết định trả lại giá trị mặc định cho phô mai trên tủ lạnh mới:

class Fridge
  attr_accessor :cheese

  def cheese
    @cheese || 0
  end
end

Tất nhiên, sẽ có nhiều biến không nên phơi bày công khai. Không cần phải phơi bày các biến nội bộ sẽ làm cho mã của bạn trở nên tồi tệ hơn, nhưng bạn khó có thể đổ lỗi cho điều đó trên getters và setters.

0
Tobias Cohen