Nếu các đối tượng bất biến là tốt, tại sao mọi người cứ tạo ra các đối tượng có thể thay đổi? [đóng cửa]


250

Nếu các đối tượng bất biến là tốt, đơn giản và mang lại lợi ích trong lập trình đồng thời tại sao các lập trình viên cứ tạo ra các đối tượng có thể thay đổi²?

Tôi có bốn năm kinh nghiệm về lập trình Java và như tôi thấy, điều đầu tiên mọi người làm sau khi tạo một lớp là tạo ra các getters và setters trong IDE (do đó làm cho nó có thể thay đổi). Có thiếu nhận thức hay chúng ta có thể thoát khỏi việc sử dụng các đối tượng có thể thay đổi trong hầu hết các kịch bản?


¹ đối tượng Immutable là một đối tượng mà nhà nước không thể được sửa đổi sau khi được tạo ra.
² Đối tượng có thể thay đổi là một đối tượng có thể được sửa đổi sau khi được tạo.


42
Tôi nghĩ rằng, ngoài lý do chính đáng (như đã đề cập bởi Péter dưới đây), "nhà phát triển lười biếng" là một nguyên nhân phổ biến hơn nhà phát triển "ngu ngốc"". Và trước khi 'phát triển ngu ngốc' có còn 'phát triển không hiểu rõ'.
Joachim Sauer

201
Đối với mỗi lập trình viên / blogger truyền giáo, có 1000 người đọc blog khao khát ngay lập tức tự phát minh lại và áp dụng các kỹ thuật mới nhất. Đối với mỗi một trong số đó, có 10.000 lập trình viên ngoài kia với mũi đá mài để hoàn thành công việc một ngày và đưa sản phẩm ra khỏi cửa. Những kẻ đó đang sử dụng các kỹ thuật đã thử và đáng tin cậy đã làm việc cho họ trong nhiều năm. Họ chờ đợi cho đến khi các kỹ thuật mới được áp dụng rộng rãi và cho thấy lợi ích thực tế trước khi đưa chúng lên. Đừng gọi họ là ngu ngốc, và họ bất cứ điều gì ngoài lười biếng, thay vào đó hãy gọi họ là "bận rộn".
Nhị phân nhị phân ngày

22
@BinaryWorrier: các đối tượng bất biến hầu như không phải là "điều mới". Chúng có thể không được sử dụng nhiều cho các đối tượng miền, nhưng Java và C # đã có chúng ngay từ đầu. Ngoài ra: "lười biếng" không phải lúc nào cũng là một từ xấu, một số loại "lười biếng" là một lợi thế tuyệt đối cho một nhà phát triển.
Joachim Sauer

11
@Joachim: Tôi nghĩ khá rõ ràng rằng "lười biếng" đã được sử dụng theo nghĩa sai lầm ở trên :) Ngoài ra, các đối tượng bất biến (như Lambda Tính và OOP trở lại trong ngày - vâng tôi đã cũ) để đột nhiên trở thành hương vị của tháng . Tôi không tranh luận rằng họ là một điều xấu (họ không), hoặc họ không có vị trí của họ (rõ ràng là họ làm), chỉ cần dễ dàng với mọi người vì họ chưa nghe thấy Lời tốt nhất mới nhất và đã được chuyển đổi thành người nhiệt thành (không đổ lỗi cho bạn về nhận xét "lười biếng", tôi biết bạn đã cố gắng giảm thiểu nó).
Nhị phân nhị phân ngày

116
-1, đối tượng bất biến không có 'tốt'. Chỉ cần ít nhiều phù hợp cho các tình huống cụ thể. Bất cứ ai nói với bạn kỹ thuật này hay kỹ thuật khác là khách quan 'tốt' hoặc 'xấu' so với kỹ thuật khác cho tất cả các tình huống là bán tôn giáo cho bạn.
GrandmasterB

Câu trả lời:


326

Cả hai đối tượng đột biến và bất biến đều có những công dụng, ưu và nhược điểm riêng.

Đối tượng bất biến thực sự làm cho cuộc sống đơn giản hơn trong nhiều trường hợp. Chúng đặc biệt áp dụng cho các loại giá trị, trong đó các đối tượng không có danh tính để có thể dễ dàng thay thế chúng. Và họ có thể làm cho cách lập trình đồng thời trở nên an toàn và sạch sẽ hơn (hầu hết các lỗi đồng thời nổi tiếng là khó tìm được cuối cùng là do trạng thái có thể thay đổi được chia sẻ giữa các luồng). Tuy nhiên, đối với các đối tượng lớn và / hoặc phức tạp, việc tạo một bản sao mới của đối tượng cho mỗi thay đổi có thể rất tốn kém và / hoặc tẻ nhạt. Và đối với các đối tượng có bản sắc riêng biệt, việc thay đổi một đối tượng hiện có đơn giản và trực quan hơn nhiều so với việc tạo một bản sao mới, được sửa đổi của nó.

Hãy nghĩ về một nhân vật trong game. Trong các trò chơi, tốc độ là ưu tiên hàng đầu, do đó, việc đại diện cho các nhân vật trong trò chơi của bạn bằng các đối tượng có thể thay đổi rất có thể sẽ khiến trò chơi của bạn chạy nhanh hơn đáng kể so với triển khai thay thế trong đó một bản sao mới của nhân vật trò chơi được sinh ra cho mỗi thay đổi nhỏ.

Hơn nữa, nhận thức của chúng ta về thế giới thực chắc chắn dựa trên các đối tượng có thể thay đổi. Khi bạn đổ đầy nhiên liệu cho xe của bạn tại trạm xăng, bạn sẽ cảm nhận nó là cùng một vật thể (nghĩa là danh tính của nó được duy trì trong khi trạng thái của nó đang thay đổi) - không phải như thể chiếc xe cũ với bình xăng rỗng được thay thế bằng cái mới liên tiếp xe ô tô có thùng của họ dần dần đầy đủ hơn. Vì vậy, bất cứ khi nào chúng ta đang mô hình hóa một số miền trong thế giới thực trong một chương trình, việc triển khai mô hình miền sử dụng các đối tượng có thể thay đổi để biểu diễn các thực thể trong thế giới thực thường dễ dàng hơn và dễ dàng hơn.

Ngoài tất cả những lý do chính đáng này, than ôi, nguyên nhân có thể xảy ra nhất khiến mọi người cứ tạo ra những vật thể đột biến là quán tính của tâm trí, hay còn gọi là sự chống lại sự thay đổi. Lưu ý rằng hầu hết các nhà phát triển ngày nay đã được đào tạo tốt trước khi tính bất biến (và mô hình chức năng, lập trình chức năng) trở thành "xu hướng" trong phạm vi ảnh hưởng của họ và không cập nhật kiến ​​thức về các công cụ và phương pháp mới trong giao dịch của chúng tôi - trên thực tế, nhiều người trong chúng ta tích cực chống lại những ý tưởng và quy trình mới. "Tôi đã lập trình như thế này trong nn năm và tôi không quan tâm đến những mốt ngu ngốc mới nhất!"


27
Đúng rồi. Đặc biệt trong lập trình GUI, đối tượng có thể thay đổi rất tiện dụng.
Florian Salihovic

23
Đó không chỉ là sự phản kháng, tôi chắc chắn rất nhiều nhà phát triển sẽ thích thử những bản mới nhất và lớn nhất, nhưng tần suất các dự án mới xuất hiện trong môi trường của các nhà phát triển trung bình nơi họ có thể áp dụng những thực tiễn mới này? Không ai có thể hoặc sẽ viết một dự án sở thích chỉ để thử trạng thái bất biến.
Steven Evers

29
Hai cảnh báo nhỏ về điều này: (1) Lấy nhân vật trò chơi chuyển động. Các Pointlớp trong .NET ví dụ là không thay đổi nhưng việc tạo ra điểm mới là kết quả của những thay đổi rất dễ dàng và do đó giá cả phải chăng. Hoạt hình một nhân vật bất biến có thể được thực hiện rất rẻ bằng cách tách rời các bộ phận chuyển động của YouTube (nhưng vâng, một số khía cạnh sau đó có thể thay đổi). (2) Các đối tượng lớn và / hoặc phức tạp, rất có thể là bất biến. Chuỗi thường lớn, và thường được hưởng lợi từ sự bất biến. Tôi đã từng viết lại một lớp biểu đồ phức tạp thành bất biến, làm cho mã đơn giản và hiệu quả hơn. Trong những trường hợp như vậy, có một trình xây dựng có thể thay đổi là chìa khóa.
Konrad Rudolph

4
@KonradRudolph, điểm tốt, cảm ơn. Tôi không có ý loại trừ việc sử dụng tính bất biến trong các đối tượng phức tạp, nhưng thực hiện một lớp như vậy một cách chính xác và hiệu quả không phải là một nhiệm vụ tầm thường, và nỗ lực thêm có thể không phải lúc nào cũng hợp lý.
Péter Török

6
Bạn làm cho một điểm tốt về nhà nước vs bản sắc. Đây là lý do tại sao Rich Hickey (tác giả của Clojure) đã chia tay hai người trong Clojure. Người ta có thể lập luận rằng chiếc xe bạn có 1/2 bình xăng không phải là chiếc xe có 1/4 bình xăng. Chúng có cùng bản sắc, nhưng chúng không giống nhau, mỗi "tích tắc" trong thời gian thực tế của chúng ta tạo ra một bản sao của mọi đối tượng trong thế giới của chúng ta, bộ não của chúng ta sau đó chỉ đơn giản gắn kết chúng với một bản sắc chung. Clojure có refs, nguyên tử, tác nhân, vv để đại diện cho thời gian. Và bản đồ, vectơ và danh sách cho thời gian thực tế.
Timothy Baldridge

130

Tôi nghĩ rằng tất cả các bạn đã bỏ lỡ câu trả lời rõ ràng nhất. Hầu hết các nhà phát triển tạo ra các đối tượng có thể thay đổi bởi vì tính biến đổi là mặc định trong các ngôn ngữ bắt buộc. Hầu hết chúng ta có những điều tốt hơn để làm với thời gian của mình hơn là liên tục sửa đổi mã khỏi mặc định - chính xác hơn hay không. Và bất biến không phải là thuốc chữa bách bệnh hơn bất kỳ phương pháp nào khác. Nó làm cho một số điều dễ dàng hơn nhưng làm cho những điều khác khó khăn hơn nhiều như một số câu trả lời đã chỉ ra.


9
Trong hầu hết các IDE hiện đại mà tôi biết, thực tế phải mất cùng một nỗ lực để chỉ tạo ra các getters, như để tạo cả getters và setters. Mặc dù đúng là việc thêm final, constv.v ... cần thêm một chút nỗ lực ... trừ khi bạn thiết lập một mẫu mã :-)
Péter Török

10
@ PéterTörök không chỉ là nỗ lực bổ sung - thực tế là các lập trình viên đồng nghiệp của bạn sẽ muốn treo cổ bạn vì họ thấy phong cách mã hóa của bạn quá xa lạ với trải nghiệm của họ. Điều đó cũng làm nản lòng loại điều này.
Onorio Catenacci

5
Điều đó có thể được khắc phục với nhiều thông tin liên lạc và giáo dục hơn, ví dụ như nên nói chuyện hoặc thuyết trình cho các đồng đội của mình về một phong cách mã hóa mới trước khi thực sự đưa nó vào một cơ sở mã hiện có. Đúng là một dự án tốt có một phong cách mã hóa phổ biến không chỉ là sự pha trộn của các kiểu mã hóa được ưa chuộng bởi mọi thành viên dự án (quá khứ và hiện tại). Vì vậy, giới thiệu hoặc thay đổi thành ngữ mã hóa nên là một quyết định của nhóm.
Péter Török

3
@ PéterTörök Trong một ngôn ngữ bắt buộc, các đối tượng có thể thay đổi là hành vi mặc định. Nếu bạn chỉ muốn các đối tượng bất biến, tốt nhất là chuyển sang ngôn ngữ chức năng.
emory

3
@ PéterTörök Có một chút ngây thơ khi cho rằng việc kết hợp tính bất biến vào một chương trình không liên quan gì đến việc bỏ tất cả các setters. Bạn vẫn cần một cách để trạng thái của chương trình thay đổi tăng dần và đối với điều này, bạn cần các nhà xây dựng, hoặc bất biến popsicle, hoặc các proxy có thể thay đổi, tất cả đều mất khoảng 1 tỷ lần nỗ lực chỉ cần bỏ các setters.
Asad Saeeduddin

49
  1. Có một nơi cho sự biến đổi. Các nguyên tắc thiết kế hướng tên miền cung cấp một sự hiểu biết vững chắc về những gì nên có thể thay đổi và những gì nên bất biến. Nếu bạn nghĩ về nó, bạn sẽ nhận ra rằng việc hình thành một hệ thống trong đó mọi thay đổi trạng thái đối với một vật thể đòi hỏi sự phá hủy và tái cấu trúc của nó, và đối với mọi đối tượng tham chiếu đến nó là không thực tế. Với các hệ thống phức tạp, điều này có thể dễ dàng dẫn đến xóa sạch hoàn toàn và xây dựng lại toàn bộ biểu đồ đối tượng của hệ thống

  2. Hầu hết các nhà phát triển không thực hiện bất cứ điều gì trong đó các yêu cầu về hiệu suất đủ quan trọng mà họ cần tập trung vào đồng thời (hoặc rất nhiều vấn đề khác được coi là thông lệ tốt trên toàn cầu).

  3. Có một số điều bạn chỉ đơn giản là không thể làm với các đối tượng bất biến, như có mối quan hệ hai chiều. Khi bạn đặt giá trị liên kết trên một đối tượng, danh tính sẽ thay đổi. Vì vậy, bạn đặt giá trị mới trên đối tượng khác và nó cũng thay đổi. Vấn đề là tham chiếu của đối tượng đầu tiên không còn hợp lệ, bởi vì một thể hiện mới đã được tạo để thể hiện đối tượng với tham chiếu. Tiếp tục điều này sẽ chỉ dẫn đến hồi quy vô hạn. Tôi đã làm một nghiên cứu nhỏ sau khi đọc câu hỏi của bạn, đây là những gì nó trông như thế nào. Bạn có một cách tiếp cận khác cho phép chức năng như vậy trong khi duy trì tính bất biến?

        public class ImmutablePerson { 
    
         public ImmutablePerson(string name, ImmutableEventList eventsToAttend)
         {
              this.name = name;
              this.eventsToAttend = eventsToAttend;
         }
         private string name;
         private ImmutableEventList eventsToAttend;
    
         public string Name { get { return this.name; } }
    
         public ImmutablePerson RSVP(ImmutableEvent immutableEvent){
             // the person is RSVPing an event, thus mutating the state 
             // of the eventsToAttend.  so we need a new person with a reference
             // to the new Event
             ImmutableEvent newEvent = immutableEvent.OnRSVPReceived(this);
             ImmutableEventList newEvents = this.eventsToAttend.Add(newEvent));
             var newSelf = new ImmutablePerson(name, newEvents);
             return newSelf;
         }
        }
    
        public class ImmutableEvent { 
         public ImmutableEvent(DateTime when, ImmutablePersonList peopleAttending, ImmutablePersonList peopleNotAttending){
             this.when = when;     
             this.peopleAttending = peopleAttending;
             this.peopleNotAttending = peopleNotAttending;
         }
         private DateTime when; 
         private ImmutablePersonList peopleAttending;
         private ImmutablePersonList peopleNotAttending;
         public ImmutableEvent OnReschedule(DateTime when){
               return new ImmutableEvent(when,peopleAttending,peopleNotAttending);
         }
         //  notice that this will be an infinite loop, because everytime one counterpart
         //  of the bidirectional relationship is added, its containing object changes
         //  meaning it must re construct a different version of itself to 
         //  represent the mutated state, the other one must update its
         //  reference thereby obsoleting the reference of the first object to it, and 
         //  necessitating recursion
         public ImmutableEvent OnRSVPReceived(ImmutablePerson immutablePerson){
               if(this.peopleAttending.Contains(immutablePerson)) return this;
               ImmutablePersonList attending = this.peopleAttending.Add(immutablePerson);
               ImmutablePersonList notAttending = this.peopleNotAttending.Contains( immutablePerson ) 
                                    ? peopleNotAttending.Remove(immutablePerson)
                                    : peopleNotAttending;
               return new ImmutableEvent(when, attending, notAttending);
         }
        }
        public class ImmutablePersonList
        {
          private ImmutablePerson[] immutablePeople;
          public ImmutablePersonList(ImmutablePerson[] immutablePeople){
              this.immutablePeople = immutablePeople;
          }
          public ImmutablePersonList Add(ImmutablePerson newPerson){
              if(this.Contains(newPerson)) return this;
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length];
              for(var i=0;i<immutablePeople.Length;i++)
                  newPeople[i] = this.immutablePeople[i];
              newPeople[immutablePeople.Length] = newPerson;
          }
          public ImmutablePersonList Remove(ImmutablePerson newPerson){
              if(immutablePeople.IndexOf(newPerson) != -1)
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutablePeople[i] == newPerson;
                 newPeople[i] = this.immutablePeople[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutablePersonList(newPeople);
          }
          public bool Contains(ImmutablePerson immutablePerson){ 
             return this.immutablePeople.IndexOf(immutablePerson) != -1;
          } 
        }
        public class ImmutableEventList
        {
          private ImmutableEvent[] immutableEvents;
          public ImmutableEventList(ImmutableEvent[] immutableEvents){
              this.immutableEvents = immutableEvents;
          }
          public ImmutableEventList Add(ImmutableEvent newEvent){
              if(this.Contains(newEvent)) return this;
              ImmutableEvent[] newEvents= new ImmutableEvent[immutableEvents.Length];
              for(var i=0;i<immutableEvents.Length;i++)
                  newEvents[i] = this.immutableEvents[i];
              newEvents[immutableEvents.Length] = newEvent;
          }
          public ImmutableEventList Remove(ImmutableEvent newEvent){
              if(immutableEvents.IndexOf(newEvent) != -1)
              ImmutableEvent[] newEvents = new ImmutableEvent[immutableEvents.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutableEvents[i] == newEvent;
                 newEvents[i] = this.immutableEvents[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutableEventList(newPeople);
          }
          public bool Contains(ImmutableEvent immutableEvent){ 
             return this.immutableEvent.IndexOf(immutableEvent) != -1;
          } 
        }
    

2
@AndresF., Nếu bạn có cách khác để duy trì các biểu đồ phức tạp với các mối quan hệ hai chiều chỉ sử dụng các đối tượng bất biến, thì tôi rất thích nghe nó. (Tôi giả sử chúng ta có thể đồng ý rằng một bộ sưu tập / mảng là một đối tượng)
smartcaveman

2
@AndresF., (1) Tuyên bố đầu tiên của tôi không phổ biến nên không sai. Tôi thực sự đã cung cấp một ví dụ mã để giải thích nó thực sự đúng như thế nào trong một số trường hợp xảy ra rất phổ biến trong phát triển ứng dụng. (2) Các mối quan hệ hai chiều đòi hỏi tính đột biến, nói chung. Tôi không tin rằng Java là thủ phạm. Như tôi đã nói, tôi rất vui khi đánh giá bất kỳ giải pháp thay thế mang tính xây dựng nào mà bạn đề xuất, nhưng tại thời điểm này, các bình luận của bạn nghe rất giống như "Bạn đã sai vì tôi đã nói như vậy".
smartcaveman

14
@smartcaveman Giới thiệu (2), tôi cũng không đồng ý: nói chung, "mối quan hệ hai chiều" là một khái niệm toán học trực giao với tính đột biến. Như thường được triển khai trong Java, nó đòi hỏi phải có tính biến đổi (tôi đã đồng ý với bạn về điểm đó). Tuy nhiên, tôi có thể nghĩ đến một triển khai thay thế: một lớp Mối quan hệ giữa hai đối tượng, với một hàm tạo Relationship(a, b); tại điểm tạo mối quan hệ, cả hai thực thể a& bđã tồn tại và bản thân mối quan hệ cũng không thay đổi. Tôi không nói rằng cách tiếp cận này là thực tế trong Java; chỉ là nó có thể.
Andres F.

4
@AndresF., Vì vậy, dựa trên những gì bạn đang nói, Nếu RRelationship(a,b)và cả hai ablà bất biến, thì acũng không bgiữ một tài liệu tham khảo nào R. Để làm việc này, tham chiếu sẽ phải được lưu trữ ở một nơi khác (như một lớp tĩnh). Tôi có hiểu đúng ý định của bạn không?
smartcaveman

9
Có thể lưu trữ một mối quan hệ hai chiều đối với dữ liệu bất biến khi Chthulhu chỉ ra thông qua sự lười biếng. Đây là một cách để làm điều này: haskell.org/haskellwiki/Tying_the_Knot
Thomas Eding

36

Tôi đã đọc "cấu trúc dữ liệu chức năng thuần túy" và nó khiến tôi nhận ra rằng có khá nhiều cấu trúc dữ liệu dễ thực hiện hơn bằng cách sử dụng các đối tượng có thể thay đổi.

Để thực hiện cây tìm kiếm nhị phân, bạn phải trả về một cây mới mỗi lần: Cây mới của bạn sẽ phải tạo một bản sao của mỗi nút đã được sửa đổi (các nhánh chưa được sửa đổi được chia sẻ). Đối với chức năng chèn của bạn, điều này không quá tệ, nhưng đối với tôi, mọi thứ trở nên khá kém hiệu quả khi tôi bắt đầu thực hiện xóa và cân bằng lại.

Một điều khác để nhận ra là bạn có thể mất nhiều năm để viết mã hướng đối tượng và không bao giờ thực sự nhận ra trạng thái có thể thay đổi được chia sẻ khủng khiếp như thế nào, nếu mã của bạn không chạy theo cách sẽ phơi bày các vấn đề tương tranh.


3
Đây có phải là cuốn sách Okasaki?
Ốc cơ khí

Vâng. hơi khô nhưng một tấn thông tin tốt ...
Paul Sanwald

4
Thật buồn cười, tôi luôn nghĩ cây đỏ / đen của Okasakis đơn giản hơn nhiều. 10 dòng hoặc hơn. Tôi đoán lợi thế lớn nhất là khi bạn thực sự muốn giữ phiên bản cũ xung quanh là tốt.
Thomas Ahle

Trong khi câu cuối cùng có lẽ là đúng trong quá khứ, không rõ ràng rằng nó sẽ vẫn đúng trong tương lai, với các xu hướng phần cứng hiện tại, v.v.
jk.

29

Theo quan điểm của tôi, đó là một sự thiếu nhận thức. Nếu bạn nhìn vào các ngôn ngữ JVM đã biết khác (Scala, Clojure), các đối tượng có thể thay đổi hiếm khi được nhìn thấy trong mã và đó là lý do tại sao mọi người bắt đầu sử dụng chúng trong các tình huống trong đó việc phân luồng đơn là không đủ.

Tôi hiện đang học Clojure và có một chút kinh nghiệm về Scala (4 năm + ở Java) và phong cách mã hóa của bạn thay đổi vì nhận thức về trạng thái.


Có lẽ "được biết" hơn là "phổ biến" sẽ là một lựa chọn tốt hơn của từ.
Den

Vâng đúng vậy.
Florian Salihovic

5
+1: Tôi đồng ý: sau khi học một số Scala và Haskell, tôi có xu hướng sử dụng cuối cùng trong Java và const trong C ++ ở mọi nơi. Tôi cũng sử dụng các đối tượng bất biến nếu có thể và, trong khi các đối tượng có thể thay đổi vẫn rất cần thiết, thì thật đáng ngạc nhiên khi bạn có thể sử dụng các đối tượng bất biến.
Giorgio

5
Tôi đọc nhận xét này của tôi sau hai năm rưỡi, và ý kiến ​​của tôi đã thay đổi theo hướng có lợi cho sự bất biến. Trong dự án hiện tại của tôi (trong Python), chúng tôi đang sử dụng các đối tượng có thể thay đổi rất hiếm khi. Ngay cả dữ liệu liên tục của chúng tôi cũng không thay đổi: chúng tôi tạo các bản ghi mới do kết quả của một số thao tác và xóa các bản ghi cũ khi không cần thiết nữa, nhưng chúng tôi không bao giờ cập nhật bất kỳ bản ghi nào trên đĩa. Không cần phải nói, điều này đã làm cho ứng dụng đồng thời, nhiều người dùng của chúng tôi dễ dàng thực hiện và duy trì hơn cho đến nay.
Giorgio

13

Tôi nghĩ rằng một yếu tố góp phần lớn đã bị bỏ qua: Java Beans phụ thuộc rất nhiều vào một phong cách cụ thể của biến đổi các đối tượng, và (đặc biệt là xem xét các nguồn) khá một vài người dường như coi đó là một (hoặc thậm chí các ) ví dụ kinh điển về cách tất cả Java nên được viết


4
+1, mẫu getter / setter được sử dụng quá thường xuyên như một số loại triển khai mặc định sau khi phân tích dữ liệu đầu tiên.
Jaap

Đây có lẽ là một điểm lớn ... "bởi vì đó là cách mà mọi người khác đang làm" ... vì vậy nó phải đúng. Đối với một chương trình "Hello World", nó có lẽ là dễ nhất. Quản lý các thay đổi trạng thái đối tượng thông qua một loạt các thuộc tính có thể thay đổi ... đó là một chút khó khăn hơn so với chiều sâu của sự hiểu biết "Hello World". 20 năm sau, tôi rất ngạc nhiên rằng đỉnh cao của lập trình thế kỷ 20 là viết các phương thức getX và setX (tẻ nhạt) trên mọi thuộc tính của một đối tượng không có cấu trúc nào. Nó chỉ là một bước đi để truy cập trực tiếp các tài sản công cộng với khả năng biến đổi 100%.
Darrell Teague

12

Mọi hệ thống Java doanh nghiệp mà tôi đã làm việc trong sự nghiệp của mình đều sử dụng Hibernate hoặc API Persistence API (JPA). Hibernate và JPA về cơ bản chỉ ra rằng hệ thống của bạn sử dụng các đối tượng có thể thay đổi, bởi vì toàn bộ tiền đề của chúng là chúng phát hiện và lưu các thay đổi vào các đối tượng dữ liệu của bạn. Đối với nhiều dự án, sự dễ dàng phát triển mà Hibernate mang lại hấp dẫn hơn so với lợi ích của các đối tượng bất biến.

Rõ ràng các đối tượng có thể thay đổi đã tồn tại lâu hơn nhiều so với Hibernate, vì vậy Hibernate có lẽ không phải là "nguyên nhân" ban đầu của sự phổ biến của các đối tượng có thể thay đổi. Có lẽ sự phổ biến của các đối tượng đột biến đã cho phép Hibernate phát triển mạnh mẽ.

Nhưng ngày nay, nếu nhiều lập trình viên cơ sở cắt răng trên hệ thống doanh nghiệp bằng Hibernate hoặc ORM khác thì có lẽ họ sẽ từ bỏ thói quen sử dụng các đối tượng có thể thay đổi. Các khung như Hibernate có thể đang gây ra sự phổ biến của các đối tượng có thể thay đổi.


Điểm tuyệt vời. Để trở thành tất cả mọi thứ cho tất cả mọi người, các khung như vậy có rất ít sự lựa chọn ngoài việc giảm xuống mẫu số chung thấp nhất, sử dụng các proxy dựa trên sự phản chiếu và có được / thiết lập theo cách linh hoạt. Tất nhiên, điều này tạo ra các hệ thống có ít hoặc không có quy tắc chuyển đổi trạng thái hoặc một phương tiện phổ biến để thực hiện chúng không may. Tôi không hoàn toàn chắc chắn sau nhiều dự án bây giờ những gì tốt hơn cho sự nhanh chóng, mở rộng và chính xác. Tôi có xu hướng nghĩ về một con lai. Độ tốt ORM động nhưng với một số định nghĩa về trường nào là bắt buộc và những thay đổi trạng thái nào có thể xảy ra.
Darrell Teague

9

Một điểm quan trọng chưa được đề cập là việc có trạng thái của một đối tượng là có thể thay đổi làm cho nó có thể có danh tính của đối tượng gói gọn trạng thái đó là bất biến.

Nhiều chương trình được thiết kế để mô hình hóa những thứ trong thế giới thực vốn có thể thay đổi. Giả sử rằng vào lúc 12:51 sáng, một số biến AllTrucksgiữ tham chiếu đến đối tượng # 451, là gốc của cấu trúc dữ liệu cho biết hàng hóa nào được chứa trong tất cả các xe tải của một đội tàu tại thời điểm đó (12:51 sáng) và một số biến BobsTruckcó thể được sử dụng để có được một tham chiếu đến đối tượng # 24601 điểm đến một đối tượng cho biết hàng hóa nào được chứa trong xe tải của Bob tại thời điểm đó (12:51 sáng). Vào lúc 12:51 sáng, một số xe tải (bao gồm cả Bob) được tải và dỡ tải, và cấu trúc dữ liệu được cập nhật để AllTrucksgiờ đây sẽ có tham chiếu đến cấu trúc dữ liệu cho biết hàng hóa có trong tất cả các xe tải vào lúc 12:51 sáng.

Điều gì sẽ xảy ra BobsTruck?

Nếu thuộc tính 'hàng hóa' của mỗi đối tượng xe tải là bất biến, thì đối tượng # 24601 sẽ mãi mãi đại diện cho trạng thái mà xe tải của Bob có lúc 12:51 sáng. Nếu BobsTruckgiữ tham chiếu trực tiếp đến đối tượng # 24601, thì trừ khi mã cập nhật AllTruckscũng xảy ra để cập nhật BobsTruck, nó sẽ ngừng thể hiện trạng thái hiện tại của xe tải Bob. Lưu ý thêm rằng trừ khi BobsTruckđược lưu trữ trong một số dạng đối tượng có thể thay đổi, cách duy nhất mà mã cập nhật AllTruckscó thể cập nhật sẽ là nếu mã được lập trình rõ ràng để làm như vậy.

Nếu một người muốn có thể sử dụng BobsTruckđể quan sát xe tải của Bob trong khi vẫn giữ cho mọi vật thể không bị biến đổi, thì người ta có BobsTruckthể là một chức năng bất biến, với giá trị AllTruckscó hoặc có tại bất kỳ thời điểm cụ thể nào, sẽ mang lại trạng thái cho xe tải của Bob thời điểm đó Người ta thậm chí có thể có một cặp chức năng bất biến - một trong số đó sẽ như trên, và một trong số đó sẽ chấp nhận tham chiếu đến trạng thái hạm đội và trạng thái xe tải mới, và trả lại tham chiếu đến trạng thái hạm đội mới phù hợp với cái cũ, ngoại trừ xe tải của Bob sẽ có trạng thái mới.

Thật không may, việc phải sử dụng một chức năng như vậy mỗi khi muốn truy cập vào trạng thái xe tải của Bob có thể trở nên khá khó chịu và cồng kềnh. Một cách tiếp cận khác là nói rằng đối tượng # 24601 sẽ luôn luôn và mãi mãi (miễn là bất cứ ai có tham chiếu đến nó) đại diện cho trạng thái hiện tại của xe tải Bob. Mã sẽ muốn liên tục truy cập vào trạng thái hiện tại của xe tải Bob sẽ không phải chạy một số chức năng tốn thời gian - nó có thể chỉ cần thực hiện chức năng tra cứu một lần để tìm ra đối tượng # 24601 là xe tải của Bob, và sau đó chỉ đơn giản là truy cập vào đối tượng đó bất cứ lúc nào nó muốn thấy trạng thái hiện tại của xe tải Bob.

Lưu ý rằng cách tiếp cận chức năng không phải là không có lợi thế trong môi trường đơn luồng hoặc trong môi trường đa luồng, trong đó các luồng sẽ chủ yếu chỉ quan sát dữ liệu thay vì thay đổi nó. Bất kỳ luồng quan sát nào sao chép tham chiếu đối tượng có trongAllTrucksvà sau đó kiểm tra các trạng thái xe tải được đại diện qua đó sẽ thấy trạng thái của tất cả các xe tải tại thời điểm mà nó lấy tham chiếu. Bất cứ khi nào một chủ đề quan sát muốn xem dữ liệu mới hơn, nó có thể lấy lại tham chiếu. Mặt khác, việc toàn bộ trạng thái của hạm đội được đại diện bởi một đối tượng bất biến duy nhất sẽ loại trừ khả năng hai luồng cập nhật đồng thời các xe tải khác nhau, vì mỗi luồng nếu để lại các thiết bị của chính nó sẽ tạo ra một đối tượng "trạng thái hạm đội" mới bao gồm trạng thái mới của xe tải của nó và trạng thái cũ của nhau. Tính chính xác có thể được đảm bảo nếu mỗi luồng chỉ sử dụng CompareExchangeđể cập nhật AllTrucksnếu nó không thay đổi và phản hồi không thành côngCompareExchangebằng cách tạo lại đối tượng trạng thái của nó và thử lại thao tác, nhưng nếu nhiều hơn một luồng cố gắng thực hiện thao tác ghi đồng thời, hiệu năng thường sẽ tệ hơn so với việc tất cả việc ghi được thực hiện trên một luồng; càng nhiều luồng cố gắng hoạt động đồng thời như vậy, hiệu suất sẽ càng tệ.

Nếu các đối tượng xe tải riêng lẻ có thể thay đổi nhưng có danh tính bất biến , kịch bản đa luồng trở nên sạch hơn. Chỉ một luồng có thể được phép hoạt động tại một thời điểm trên bất kỳ xe tải nào, nhưng các luồng hoạt động trên các xe tải khác nhau có thể làm như vậy mà không bị can thiệp. Mặc dù có nhiều cách người ta có thể mô phỏng hành vi đó ngay cả khi sử dụng các đối tượng không thay đổi (ví dụ: người ta có thể định nghĩa các đối tượng "AllTrucks" để cài đặt trạng thái của xe tải thuộc XXX cho SSS sẽ chỉ cần tạo một đối tượng có nội dung "Kể từ [Thời gian], trạng thái của xe tải thuộc [XXX] hiện là [SSS], trạng thái của mọi thứ khác là [Giá trị cũ của AllTrucks] ". Tạo ra một đối tượng như vậy sẽ đủ nhanh để ngay cả khi có sự tranh chấp, aCompareExchangevòng lặp sẽ không mất nhiều thời gian. Mặt khác, sử dụng cấu trúc dữ liệu như vậy sẽ làm tăng đáng kể lượng thời gian cần thiết để tìm một chiếc xe tải của một người cụ thể. Sử dụng các đối tượng có thể thay đổi với danh tính bất biến sẽ tránh được vấn đề đó.


8

Không có đúng hay sai, nó chỉ phụ thuộc vào những gì bạn thích. Có một lý do tại sao một số người thích các ngôn ngữ ủng hộ một mô hình hơn một mô hình khác và một mô hình dữ liệu hơn một mô hình khác. Nó chỉ phụ thuộc vào sở thích của bạn, và vào những gì bạn muốn đạt được (và có thể dễ dàng sử dụng cả hai cách tiếp cận mà không khiến người hâm mộ khó tính ở bên này hay bên kia là một chén thánh mà một số ngôn ngữ đang tìm kiếm).

Tôi nghĩ rằng cách tốt nhất và nhanh nhất để trả lời câu hỏi của bạn là để bạn vượt qua Ưu và nhược điểm của tính không thay đổi so với tính bất biến .


7

Kiểm tra bài đăng trên blog này: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Nó tóm tắt tại sao các đối tượng bất biến tốt hơn là có thể thay đổi. Dưới đây là danh sách ngắn các đối số:

  • đối tượng bất biến đơn giản hơn để xây dựng, kiểm tra và sử dụng
  • đối tượng thực sự bất biến luôn luôn an toàn chủ đề
  • chúng giúp tránh khớp nối tạm thời
  • sử dụng của họ là miễn phí tác dụng phụ (không có bản sao phòng thủ)
  • vấn đề đột biến danh tính là tránh
  • họ luôn có nguyên tử thất bại
  • chúng dễ dàng hơn nhiều để lưu trữ

Mọi người đang sử dụng các đối tượng có thể thay đổi, theo như tôi hiểu, bởi vì họ vẫn đang trộn OOP với lập trình thủ tục bắt buộc.


5

Trong các đối tượng bất biến Java yêu cầu một hàm tạo sẽ lấy tất cả các thuộc tính của đối tượng (hoặc hàm tạo tạo chúng từ các đối số hoặc mặc định khác). Những tính chất cần được đánh dấu cuối cùng .

Có bốn vấn đề với điều này chủ yếu phải làm với ràng buộc dữ liệu :

  1. Java Con constructor phản ánh siêu dữ liệu không giữ lại các tên đối số.
  2. Các trình xây dựng Java (và các phương thức) không có các tham số được đặt tên (còn được gọi là nhãn) do đó nó gây nhầm lẫn với nhiều tham số.
  3. Khi kế thừa một đối tượng bất biến khác, thứ tự đúng của các hàm tạo phải được gọi. Điều này có thể khá khó khăn đến mức chỉ bỏ cuộc và để lại một trong những lĩnh vực không phải là cuối cùng.
  4. Hầu hết các công nghệ liên kết (như Spring MVC Data Binding, Hibernate, v.v.) sẽ chỉ hoạt động với các hàm tạo mặc định không có đối số (điều này là do các chú thích không phải lúc nào cũng tồn tại).

Bạn có thể giảm thiểu # 1 và # 2 bằng cách sử dụng các chú thích như @ConstructorPropertiesvà tạo một đối tượng xây dựng có thể thay đổi khác (thường thông thạo) để tạo đối tượng bất biến.


5

Tôi ngạc nhiên rằng không ai đã đề cập đến lợi ích tối ưu hóa hiệu suất. Tùy thuộc vào ngôn ngữ, trình biên dịch có thể tạo ra một loạt các tối ưu hóa khi xử lý dữ liệu bất biến vì nó biết dữ liệu sẽ không bao giờ thay đổi. Tất cả các loại công cụ được bỏ qua, mang lại cho bạn lợi ích hiệu suất rất lớn.

Ngoài ra các đối tượng bất biến gần như loại bỏ toàn bộ các lớp lỗi nhà nước.

Nó không lớn như nó phải vì nó khó hơn, không áp dụng cho mọi ngôn ngữ và hầu hết mọi người được dạy mã hóa bắt buộc.

Ngoài ra tôi đã thấy rằng hầu hết các lập trình viên đều hài lòng trong hộp của họ và thường chống lại những ý tưởng mới mà họ không hoàn toàn hiểu được. Nói chung mọi người không thích thay đổi.

Cũng nên nhớ rằng, trạng thái của hầu hết các lập trình viên là xấu. Hầu hết các chương trình được thực hiện trong tự nhiên là khủng khiếp và nó gây ra bởi sự thiếu hiểu biết và chính trị.


4

Tại sao mọi người sử dụng bất kỳ tính năng mạnh mẽ? Tại sao mọi người sử dụng lập trình meta, lười biếng hoặc gõ động? Câu trả lời là sự tiện lợi. Trạng thái đột biến là rất dễ dàng. Thật dễ dàng để cập nhật tại chỗ và ngưỡng của quy mô dự án nơi trạng thái bất biến có năng suất làm việc cao hơn trạng thái có thể thay đổi là khá cao nên sự lựa chọn sẽ không khiến bạn phải quay lại một lúc.


4

Ngôn ngữ lập trình được thiết kế để thực hiện bởi máy tính. Tất cả các khối xây dựng quan trọng của máy tính - CPU, RAM, bộ đệm, đĩa - đều có thể thay đổi. Khi chúng không (BIOS), chúng thực sự bất biến và bạn cũng không thể tạo các đối tượng bất biến mới.

Do đó, bất kỳ ngôn ngữ lập trình nào được xây dựng trên đầu các đối tượng bất biến đều phải chịu một khoảng cách đại diện trong quá trình thực hiện. Và đối với các ngôn ngữ ban đầu như C, đó là một trở ngại lớn.


1

Không có đối tượng đột biến, bạn không có trạng thái. Phải thừa nhận rằng đây là một điều tốt nếu bạn có thể quản lý nó và nếu có bất kỳ cơ hội nào một đối tượng có thể được tham chiếu từ nhiều hơn một luồng. Nhưng chương trình sẽ khá nhàm chán. Rất nhiều phần mềm, đặc biệt là máy chủ web, tránh chịu trách nhiệm về các đối tượng có thể thay đổi bằng cách loại bỏ khả năng biến đổi trên cơ sở dữ liệu, hệ điều hành, thư viện hệ thống, v.v. phát triển giá cả phải chăng. Nhưng tính đột biến vẫn còn đó.

Nói chung, bạn có ba loại lớp: lớp bình thường, lớp không an toàn, phải được bảo vệ và bảo vệ cẩn thận; các lớp bất biến, có thể được sử dụng tự do; và các lớp có thể thay đổi, an toàn luồng có thể được sử dụng tự do nhưng phải được viết hết sức cẩn thận. Loại thứ nhất là một trong những phiền hà, với những cái tồi tệ nhất là những người đang nghĩ là thuộc loại thứ ba. Tất nhiên, loại đầu tiên là những người dễ viết.

Tôi thường kết thúc với rất nhiều lớp học bình thường, có thể thay đổi mà tôi phải theo dõi rất cẩn thận. Trong một tình huống đa luồng, việc đồng bộ hóa cần thiết làm chậm mọi thứ ngay cả khi tôi có thể tránh được một cái ôm chết người. Vì vậy, tôi vô tình tạo ra các bản sao bất biến của lớp có thể thay đổi và trao chúng cho bất cứ ai có thể sử dụng nó. Một bản sao bất biến mới là cần thiết mỗi khi đột biến gốc, vì vậy tôi tưởng tượng nhiều lúc tôi có thể có hàng trăm bản gốc ngoài kia. Tôi hoàn toàn phụ thuộc vào Bộ sưu tập rác.

Tóm lại, các đối tượng không thể thay đổi, an toàn cho luồng là tốt nếu bạn không sử dụng nhiều luồng. (Nhưng đa luồng đang lan rộng khắp mọi nơi - hãy cẩn thận!) Chúng có thể được sử dụng một cách an toàn nếu bạn hạn chế chúng ở các biến cục bộ hoặc đồng bộ hóa chặt chẽ chúng. Nếu bạn có thể tránh chúng bằng cách sử dụng mã đã được chứng minh của người khác (DB, cuộc gọi hệ thống, v.v.), hãy làm như vậy. Nếu bạn có thể sử dụng một lớp bất biến, hãy làm như vậy. Và tôi nghĩ , nói chung , mọi người hoặc không biết về các vấn đề đa luồng hoặc (sợ hãi) về chúng và sử dụng tất cả các loại mánh khóe để tránh đa luồng (hay đúng hơn là đẩy trách nhiệm về nó ở nơi khác).

Là một PS, tôi cảm thấy rằng các getters và setters Java đang vượt ra khỏi tầm tay. Kiểm tra này .


7
Nhà nước bất biến vẫn là nhà nước.
Jeremy Heiler

3
@JeremyHeiler: Đúng, nhưng đó là tình trạng của một cái gì đó có thể thay đổi. Nếu không có gì đột biến, chỉ có một trạng thái, đó là điều tương tự như không có trạng thái nào cả.
RalphChapin

1

Rất nhiều người đã có câu trả lời tốt vì vậy tôi muốn chỉ ra điều gì đó mà bạn chạm vào đó là rất quan sát và cực kỳ đúng và không được đề cập ở nơi nào khác ở đây.

Tự động tạo setters và getters là một ý tưởng khủng khiếp, khủng khiếp, nhưng đó là cách đầu tiên những người có đầu óc thủ tục cố gắng buộc OO vào suy nghĩ của họ. Setters và getters, cùng với các thuộc tính chỉ nên được tạo khi bạn thấy bạn cần chúng chứ không phải mọi người mặc định

Trong thực tế, mặc dù bạn cần getters khá thường xuyên, cách duy nhất setters hoặc thuộc tính có thể ghi nên tồn tại trong mã của bạn là thông qua một mẫu xây dựng nơi chúng bị khóa sau khi đối tượng đã được khởi tạo hoàn toàn.

Nhiều lớp có thể thay đổi sau khi tạo, điều này không tốt, chỉ là các thuộc tính của nó bị thao túng trực tiếp - thay vào đó nên yêu cầu thao tác các thuộc tính của nó thông qua các lệnh gọi phương thức với logic nghiệp vụ thực tế trong chúng (Có, một setter khá giống nhau điều như trực tiếp thao túng tài sản)

Bây giờ, điều này thực sự không áp dụng cho mã / ngôn ngữ kiểu "Scripting" mà là mã bạn tạo cho người khác và mong người khác đọc nhiều lần trong nhiều năm. Gần đây tôi đã phải bắt đầu tạo ra sự khác biệt đó bởi vì tôi thích gây rối với Groovy rất nhiều và có một sự khác biệt rất lớn trong các mục tiêu.


1

Các đối tượng có thể thay đổi được sử dụng khi bạn phải đặt nhiều giá trị sau khi khởi tạo đối tượng.

Bạn không nên có một hàm tạo với sáu tham số. Thay vào đó bạn sửa đổi đối tượng bằng các phương thức setter.

Một ví dụ về điều này là một đối tượng Báo cáo, với setters cho phông chữ, định hướng, v.v.

Tóm lại: mutables rất hữu ích khi bạn có nhiều trạng thái để đặt thành một đối tượng và sẽ không thực tế nếu có chữ ký của hàm tạo rất dài.

EDIT: Mẫu Builder có thể được sử dụng xây dựng toàn bộ trạng thái của đối tượng.


3
điều này được trình bày như thể tính biến đổi và thay đổi sau khi khởi tạo là cách duy nhất để đặt nhiều giá trị. Để hoàn thiện lưu ý rằng mẫu Builder cung cấp các khả năng tương đương hoặc thậm chí mạnh hơn và không yêu cầu người ta phải hy sinh tính bất biến. new ReportBuilder().font("Arial").orientation("landscape").build()
gnat

1
"Sẽ không thực tế nếu có chữ ký của hàm tạo rất dài.": Bạn luôn có thể nhóm các tham số thành các đối tượng nhỏ hơn và truyền các đối tượng này làm tham số cho hàm tạo.
Giorgio

1
@cHao Nếu bạn muốn đặt 10 hoặc 15 thuộc tính thì sao? Ngoài ra, có vẻ không tốt khi một phương thức có tên withFonttrả về a Report.
Tulains Córdova

1
Theo như thiết lập 10 hoặc 15 thuộc tính, mã để làm như vậy sẽ không khó xử nếu (1) Java biết cách sắp xếp việc xây dựng tất cả các đối tượng trung gian đó và nếu (2) một lần nữa, các tên được chuẩn hóa. Đó không phải là vấn đề với sự bất biến; đó là một vấn đề với Java không biết làm thế nào để làm điều đó tốt.
cHao

1
@cHao Tạo chuỗi cuộc gọi cho 20 thuộc tính là xấu. Mã xấu có xu hướng là mã chất lượng kém.
Tulains Córdova

1

Tôi nghĩ rằng việc sử dụng các đối tượng có thể thay đổi bắt nguồn từ suy nghĩ bắt buộc: bạn tính toán một kết quả bằng cách thay đổi nội dung của các biến có thể thay đổi từng bước (tính toán theo hiệu ứng phụ).

Nếu bạn suy nghĩ theo chức năng, bạn muốn có trạng thái bất biến và thể hiện các trạng thái tiếp theo của hệ thống bằng cách áp dụng các hàm và tạo các giá trị mới từ các trạng thái cũ.

Cách tiếp cận chức năng có thể sạch hơn và mạnh mẽ hơn, nhưng nó có thể rất kém hiệu quả do sao chép, do đó bạn muốn quay lại cấu trúc dữ liệu được chia sẻ mà bạn sửa đổi tăng dần.

Sự đánh đổi mà tôi thấy hợp lý nhất là: Bắt đầu với các đối tượng bất biến và sau đó chuyển sang các đối tượng có thể thay đổi nếu việc triển khai của bạn không đủ nhanh. Từ quan điểm này, sử dụng các đối tượng có thể thay đổi một cách có hệ thống ngay từ đầu có thể được coi là một loại tối ưu hóa sớm : bạn chọn cách triển khai hiệu quả hơn (nhưng cũng khó hiểu và gỡ lỗi hơn) ngay từ đầu.

Vậy, tại sao nhiều lập trình viên sử dụng các đối tượng đột biến? IMHO vì hai lý do:

  1. Nhiều lập trình viên đã học cách lập trình bằng cách sử dụng một mô hình bắt buộc (hướng thủ tục hoặc hướng đối tượng), do đó tính biến đổi là cách tiếp cận cơ bản của họ để xác định tính toán, tức là họ không biết sử dụng tính bất biến khi nào và vì không quen thuộc với nó.
  2. Nhiều lập trình viên lo lắng về hiệu suất quá sớm trong khi trước tiên thường tập trung vào việc viết một chương trình đúng chức năng, sau đó cố gắng tìm ra các nút thắt cổ chai và tối ưu hóa nó.

1
Vấn đề không chỉ là tốc độ. Có nhiều loại cấu trúc hữu ích mà đơn giản là không thể thực hiện được nếu không sử dụng các loại có thể thay đổi. Trong số những thứ khác, giao tiếp giữa hai luồng yêu cầu, ở mức tối thiểu, cả hai phải có một tham chiếu đến một đối tượng chia sẻ mà một người có thể đặt dữ liệu và người còn lại có thể đọc nó. Hơn nữa, thông thường về mặt ngữ nghĩa sẽ rõ ràng hơn nhiều khi nói "Thay đổi thuộc tính P và Q của đối tượng này" so với nói "Lấy đối tượng này, xây dựng một đối tượng mới giống như ngoại trừ giá trị của P và sau đó là một đối tượng mới chỉ là như vậy ngoại trừ Q ".
supercat

1
Tôi không phải là chuyên gia về FP nhưng có thể đạt được giao tiếp luồng AFAIK (1) bằng cách tạo giá trị bất biến trong một luồng và đọc nó ở một luồng khác (một lần nữa, AFAIK, đây là cách tiếp cận Erlang) (2) AFAIK ký hiệu để đặt thuộc tính không thay đổi nhiều (ví dụ: trong giá trị bản ghi Haskell setProperty tương ứng với bản ghi Java.setProperty (giá trị)), chỉ có ngữ nghĩa thay đổi vì kết quả của việc đặt thuộc tính là bản ghi bất biến mới.
Giorgio

1
"cả hai phải có một tham chiếu đến một đối tượng chia sẻ mà một người có thể đặt dữ liệu và đối tượng khác có thể đọc nó": chính xác hơn, bạn có thể tạo một đối tượng bất biến (tất cả các thành viên const trong C ++ hoặc cuối cùng trong Java) và đặt tất cả nội dung trong constructor. Sau đó, đối tượng bất biến này được bàn giao từ luồng sản xuất sang luồng tiêu dùng.
Giorgio

1
(2) Tôi đã liệt kê hiệu suất là một lý do không sử dụng trạng thái bất biến. Về (1), tất nhiên cơ chế thực hiện giao tiếp giữa các luồng cần một số trạng thái có thể thay đổi, nhưng bạn có thể ẩn điều này khỏi mô hình lập trình của mình, xem ví dụ mô hình diễn viên ( en.wikipedia.org/wiki/Actor_model ). Vì vậy, ngay cả khi ở cấp độ thấp hơn, bạn cần có khả năng biến đổi để thực hiện giao tiếp, bạn có thể có mức độ trừu tượng cao hơn trong đó bạn giao tiếp giữa các luồng gửi các đối tượng bất biến qua lại.
Giorgio

1
Tôi không chắc là tôi hiểu bạn đầy đủ, nhưng ngay cả một ngôn ngữ chức năng thuần túy nơi mọi giá trị là bất biến, có một điều có thể thay đổi: trạng thái chương trình, tức là ngăn xếp chương trình hiện tại và các ràng buộc biến. Nhưng đây là môi trường thực thi. Dù sao, tôi đề nghị chúng ta nên thảo luận điều này trong trò chuyện một thời gian và sau đó dọn sạch các tin nhắn từ câu hỏi này.
Giorgio

1

Tôi biết bạn đang hỏi về Java, nhưng tôi luôn sử dụng mutable vs bất biến mọi lúc trong object-c. Có một mảng NSArray bất biến, và một mảng NSMutableArray có thể thay đổi. Đây là hai lớp khác nhau được viết cụ thể theo cách tối ưu hóa để xử lý việc sử dụng chính xác. Nếu tôi cần tạo một mảng và không bao giờ thay đổi nội dung của nó, tôi sẽ sử dụng NSArray, một đối tượng nhỏ hơn và nhanh hơn nhiều so với những gì nó làm, so với một mảng có thể thay đổi.

Vì vậy, nếu bạn tạo một đối tượng Person là bất biến thì bạn chỉ cần một hàm tạo và getters, do đó, đối tượng sẽ nhỏ hơn và sử dụng ít bộ nhớ hơn, điều này sẽ làm cho chương trình của bạn thực sự nhanh hơn. Nếu bạn cần thay đổi đối tượng sau khi tạo thì một đối tượng Person có thể thay đổi sẽ tốt hơn để nó có thể thay đổi các giá trị thay vì tạo một đối tượng mới.

Vì vậy: tùy thuộc vào những gì bạn dự định làm với đối tượng, việc chọn mutable vs bất biến có thể tạo ra sự khác biệt rất lớn, khi nói đến hiệu suất.


1

Bổ sung cho nhiều lý do khác được đưa ra ở đây, một vấn đề là ngôn ngữ chính không hỗ trợ tốt tính bất biến. Ít nhất, bạn bị phạt vì tính bất biến ở chỗ bạn phải thêm các từ khóa bổ sung như const hoặc cuối cùng và phải sử dụng các hàm tạo không thể đọc được với nhiều, nhiều đối số hoặc mẫu xây dựng mã dài.

Điều đó dễ dàng hơn nhiều trong các ngôn ngữ được tạo ra với tâm trí bất biến. Xem xét đoạn trích Scala này để xác định một Lớp người, tùy ý với các đối số được đặt tên và tạo một bản sao với thuộc tính đã thay đổi:

case class Person(id: String, firstName: String, lastName: String)

val joe = Person("123", "Joe", "Doe")
val spouse = Person(id = "124", firstName = "Mary", lastName = "Moe")
val joeMarried = joe.copy(lastName = "Doe-Moe")

Vì vậy, nếu bạn muốn các nhà phát triển chấp nhận sự bất biến, đây là một trong những lý do tại sao bạn có thể cân nhắc chuyển sang ngôn ngữ lập trình hiện đại hơn.


điều này dường như không thêm bất cứ điều gì đáng kể vào các điểm được thực hiện và giải thích trong 24 câu trả lời trước
gnat

@gnat Câu trả lời nào trước đây đưa ra quan điểm rằng hầu hết các ngôn ngữ chính không hỗ trợ bất biến? Tôi nghĩ rằng điểm đó đơn giản là chưa được thực hiện (tôi đã kiểm tra), nhưng đây là một trở ngại khá quan trọng.
Hans-Peter Störr

cái này ví dụ như đi sâu giải thích vấn đề này trong Java. Và ít nhất 3 câu trả lời khác liên quan gián tiếp đến nó
gnat

0

Bất biến có nghĩa là bạn không thể thay đổi giá trị và có thể thay đổi có nghĩa là bạn có thể thay đổi giá trị nếu bạn nghĩ theo nguyên tắc và đối tượng. Các đối tượng khác với nguyên thủy trong Java ở chỗ chúng được xây dựng theo kiểu. Nguyên thủy được xây dựng trong các loại như int, boolean và void.

Rất nhiều người nghĩ rằng các biến nguyên thủy và đối tượng có bộ sửa đổi cuối cùng trước chúng là bất biến, tuy nhiên, điều này không chính xác. Vì vậy, cuối cùng hầu như không có nghĩa là bất biến cho các biến. Kiểm tra liên kết này cho một mẫu mã:

public abstract class FinalBase {

    private final int variable; // Unset

    /* if final really means immutable than
     * I shouldn't be able to set the variable
     * but I can.
     */
    public FinalBase(int variable) { 
        this.variable = variable;
    }

    public int getVariable() {
        return variable;
    }

    public abstract void method();
}

// This is not fully necessary for this example
// but helps you see how to set the final value 
// in a sub class.
public class FinalSubclass extends FinalBase {

    public FinalSubclass(int variable) {
        super(variable);
    }

    @Override
    public void method() {
        System.out.println( getVariable() );
    }

    @Override
    public int getVariable() {

        return super.getVariable();
    }

    public static void main(String[] args) {
        FinalSubclass subclass = new FinalSubclass(10);
        subclass.method();
    }
}

-1

Tôi nghĩ rằng một lý do chính đáng sẽ là mô hình hóa các "đối tượng" có thể thay đổi trong cuộc sống thực, như các cửa sổ giao diện. Tôi mơ hồ nhớ rằng đã đọc rằng OOP được phát minh khi ai đó cố gắng viết phần mềm để kiểm soát một số hoạt động cảng hàng hóa.


1
Tôi chưa thể tìm thấy nơi tôi đã đọc điều này về nguồn gốc của OOP, nhưng theo Wikipedia , một số Hệ thống thông tin khu vực tích hợp của một công ty vận chuyển container lớn OOCL được viết bằng Smalltalk.
Alexey

-2

Java, trong nhiều trường hợp bắt buộc các đối tượng có thể thay đổi, ví dụ: khi bạn muốn đếm hoặc trả lại một cái gì đó trong một khách truy cập hoặc có thể chạy được, bạn cần các biến cuối cùng, thậm chí không có đa luồng.


5
finalthực sự là khoảng 1/4 của một bước đối với sự bất biến. Không thấy lý do tại sao bạn đề cập đến nó.
cHao

bạn đã thực sự đọc bài viết của tôi?
Ralf H

Bạn đã thực sự đọc câu hỏi? :) Nó hoàn toàn không có gì để làm với final. Đưa nó lên trong bối cảnh này gợi lên một sự kết hợp thực sự kỳ lạ finalvới "đột biến", khi mà mục đích chính finalngăn chặn một số loại đột biến nhất định. (BTW, không phải downvote của tôi.)
cHao

Tôi không nói rằng bạn không có một điểm hợp lệ ở đây ở đâu đó. Tôi đang nói rằng bạn không giải thích nó rất tốt. Tôi sắp xếp xem bạn có thể đi đâu, nhưng bạn cần đi xa hơn. Như là, nó chỉ có vẻ bối rối.
cHao

1
Thực tế, có rất ít trường hợp một đối tượng có thể thay đổi được yêu cầu . Có những trường hợp mã sẽ rõ ràng hơn, hoặc trong một số trường hợp nhanh hơn, bằng cách sử dụng các đối tượng có thể thay đổi. Nhưng hãy xem xét rằng phần lớn các mẫu thiết kế về cơ bản chỉ là sự thể hiện nửa vời của lập trình chức năng cho các ngôn ngữ vốn không hỗ trợ nó. Điều thú vị ở đây là, FP chỉ yêu cầu khả năng biến đổi ở những nơi rất chọn lọc (cụ thể là phải xảy ra tác dụng phụ) và những nơi đó thường bị giới hạn chặt chẽ về số lượng, kích thước và phạm vi.
cHao
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.