Якщо непорушні предмети хороші, чому люди продовжують створювати змінні предмети? [зачинено]


250

Якщо незмінні об'єкти¹ хороші, прості та пропонують переваги при одночасному програмуванні, чому програмісти продовжують створювати об'єкти, що змінюються²?

У мене є чотирирічний досвід роботи в програмуванні Java, і, як я це бачу, перше, що люди роблять після створення класу, - це генерувати геттери та сетери в IDE (таким чином роблячи його змінним). Чи є недостатня обізнаність чи ми можемо піти з використання змінних об'єктів у більшості сценаріїв?


¹ Незмінний об'єкт - це об'єкт, стан якого не може бути змінений після його створення.
² Змінюваний об'єкт - це об'єкт, який можна змінювати після його створення.


42
Я думаю, що, крім законних причин (про які Петер згадував нижче), "ледачий розробник" є більш поширеною причиною, ніж "дурний" розробник ". А перед" дурним розробником "є ще" неінформований розробник ".
Йоахім Зауер

201
На кожного євангельського програміста / блогера є 1000 завзятих читачів блогу, які негайно переосмислюють себе і застосовують новітні методи. Для кожного з них є 10 000 програмістів, що їх носом до шліфувального каменю отримують щоденну роботу та виводять продукт у двері. Ці хлопці використовують перевірені та надійні методи, які працювали над ними роками. Вони чекають, поки нові методики будуть широко прийняті, і покажуть фактичні переваги, перш ніж їх застосувати. Не називайте їх дурними, і вони нічого, крім ледачих, називайте замість них "зайнятими".
Бінарний страшник

22
@BinaryWorrier: незмінні об'єкти навряд чи "нова річ". Вони, можливо, не використовувались для об’єктів домену, але Java та C # мали їх з самого початку. Також: "ледачий" - це не завжди погане слово, деякі види "ледачий" - абсолютна перевага для розробника.
Йоахім Зауер

11
@Joachim: Я думаю, що досить очевидно, що "ледачий" був використаний у його пейоративному сенсі вище :) Крім того, незмінні об'єкти (на кшталт обчислення Лямбди та OOP ще в той день - так, я такий старий) не повинні бути новими щоб раптом стати ароматом місяця . Я не стверджую, що вони погані (вони ні), або що вони не мають свого місця (вони, очевидно, так роблять), просто йдіть легко людям, тому що вони не чули останнього Доброго слова і конвертували як палко себе (не звинувачуючи вас у "ледачому" коментарі, я знаю, ви намагалися пом'якшити це).
Бінарний страшник

116
-1, незмінні предмети не є "хорошими". Просто більш-менш підходить для конкретних ситуацій. Той, хто розповідає вам ту чи іншу техніку, об'єктивно «хороший» або «поганий» над іншою у всіх ситуаціях, продає вам релігію.
GrandmasterB

Відповіді:


326

Як об'єкти, що змінюються, так і непорушні мають свої переваги, плюси і мінуси.

Незмінні предмети дійсно полегшують життя у багатьох випадках. Вони особливо застосовні для типів значень, де об’єкти не мають ідентичності, тому їх можна легко замінити. І вони можуть зробити одночасне програмування способом більш безпечним та чистим (більшість сумнозвісних помилок, що знаходять одночасно, в кінцевому рахунку спричинені змінним станом, поділеним між потоками). Однак для великих та / або складних об'єктів створення нової копії об'єкта для кожної зміни може бути дуже дорогим та / або стомлюючим. А для об’єктів із чіткою ідентичністю зміна існуючих об'єктів набагато простіша та інтуїтивніша, ніж створення нової модифікованої копії.

Подумайте про ігрового персонажа. В іграх швидкість є першочерговим завданням, тому представлення ваших ігрових персонажів із змінними предметами, швидше за все, зробить вашу гру значно швидшою, ніж альтернативна реалізація, де нова копія ігрового персонажа створюється для кожної невеликої зміни.

Більше того, наше сприйняття реального світу неминуче засноване на змінних об'єктах. Коли ви заправляєте автомобіль паливом на АЗС, ви сприймаєте його як той самий об’єкт протягом усього часу (тобто його особистість зберігається, коли стан змінюється) - не так, як якщо старий автомобіль із порожнім баком замінили на новий поспіль Автомобільні екземпляри поступово все більше наповнюються. Отже, коли ми моделюємо деякий реальний домен у програмі, зазвичай простіше та легше реалізувати модель домену за допомогою змінних об'єктів для представлення реальних сутностей.

Окрім усіх цих законних причин, на жаль, найбільш вірогідною причиною, чому люди продовжують створювати змінні об’єкти, є інертність духу, також опір змінам. Зауважимо, що сьогодні більшість розробників пройшли підготовку ще до того, як незмінність (а також парадигма, що містить функціональне програмування) стала "модною" у своїй сфері впливу, і не оновлює свої знання про нові інструменти та методи нашої торгівлі - насправді багато хто з нас люди позитивно протистоять новим ідеям та процесам. "Я програмував так уже п ять років, і мене не хвилюють останні дурні примхи!"


27
Це вірно. Особливо в програмуванні GUI дуже зручний об'єкт, що змінюється.
Флоріан Саліхович

23
Це не просто опір, я впевнений, що багато розробників люблять спробувати найновіші та найбільші, але як часто нові проекти крутяться у середньому середовищі розробників, де вони можуть застосувати ці нові практики? Не є everone може чи не напише проект хобі, щоб просто випробувати непорушний стан.
Стівен Еверс

29
Два невеликих застереження до цього: (1) Візьміть рухливий ігровий персонаж. Наприклад, Pointклас в .NET є незмінним, але створювати нові точки в результаті змін легко і, таким чином, доступно. Анімація непорушного персонажа може бути дуже дешевою, роз'єднавши "рухомі частини" (але так, деякий аспект потім змінюється). (2) "великі та / або складні об'єкти" можуть бути непорушними. Струни часто великі і зазвичай виграють від непорушності. Я одного разу переписав складний клас графіків, щоб бути незмінним, зробивши код простішим та ефективнішим. У таких випадках ключовим є створення мутабельного конструктора.
Конрад Рудольф

4
@KonradRudolph, хороші бали, дякую. Я не хотів виключати використання незмінності у складних об'єктах, але правильно та ефективно реалізувати такий клас - це далеко не тривіальне завдання, і додаткові зусилля, які потрібні, можуть бути не завжди виправданими.
Péter Török

6
Ви добре зазначаєте, що стосується держави проти ідентичності. Ось чому Річ Хікі (автор Clojure) розбив їх у Clojure. Можна стверджувати, що автомобіль, який ви маєте з 1/2 ємності з бензином, не такий самий автомобіль, як автомобіль з 1/4 баком газу. У них однакова ідентичність, але вони не однакові, кожен "галочок" часу нашої реальності створює клон кожного предмета в нашому світі, наші мізки потім просто зшивають їх разом із загальною ідентичністю. У Clojure є відбитки, атоми, агенти тощо для відображення часу. І карти, вектори та списки за фактичний час.
Тимофі Болдрідж

130

Я думаю, що ви всі пропустили найочевиднішу відповідь. Більшість розробників створюють об'єкти, що змінюються, тому що змінюваність є типовим мовою в імперативних мовах. Більшість із нас мають кращі стосунки до свого часу, ніж постійно змінювати код подалі від налаштування за замовчуванням - правильніше чи ні. І незмінність - це не панацея більше, ніж будь-який інший підхід. Це робить деякі речі простішими, але робить інші набагато складнішими, як деякі відповіді вже вказували.


9
У більшості сучасних ІДЕ, яких я знаю, потрібно генерувати стільки ж зусиль, щоб генерувати лише геттери, як і генерувати як геттери, так і сетери. Хоча це правда , що додавання final, і constт.д. займає трохи додаткових зусиль ... якщо ви не створили шаблон коду :-)
Péter Török

10
@ PéterTörök, це не просто додаткові зусилля - це той факт, що ваші колеги-кодери захочуть повісити вас у вигляд, оскільки вони вважають ваш стиль кодування таким чужим для їх досвіду. Це також відлякує подібні речі.
Оноріо Катенач

5
Це може бути подолано більшою кількістю спілкування та освіти, наприклад, бажано поговорити або виступити з товаришами по команді про новий стиль кодування, перш ніж ввести його в існуючу базу коду. Це правда, що хороший проект має загальний стиль кодування, який не є лише сукупністю стилів кодування, яким надає перевагу кожен (минулий і теперішній) учасник проекту. Тому введення або зміна ідіом кодування повинно бути командним рішенням.
Péter Török

3
@ PéterTörök В імперативній мові змінні об'єкти є поведінкою за замовчуванням. Якщо ви хочете лише незмінні об’єкти, краще перейти на функціональну мову.
Еморі

3
@ PéterTörök Мало наївно вважати, що включення незмінності в програму не передбачає нічого іншого, як скасування всіх сетерів. Вам все одно потрібен спосіб, щоб стан програми мінявся поступово, і для цього вам потрібні будівельники, або незмінність ескізів, або мінливі проксі, всі вони потребують приблизно 1 мільярда разів більше зусиль, просто скинувши сетери.
Асад Саєдюддін

49
  1. Тут є місце для змінності. Принципи дизайну, керовані доменом, дають чітке розуміння того, що має бути змінним, а що - незмінним. Якщо ви подумаєте про це, то зрозумієте, що недоцільно уявляти систему, в якій кожна зміна стану об'єкта вимагає руйнування та перекомпозиції його та кожного об'єкта, на який він посилається. За допомогою складних систем це може легко призвести до повного витирання та відновлення об'єктного графіка всієї системи

  2. Більшість розробників не роблять нічого, коли вимоги до продуктивності є досить вагомими, що їм потрібно зосередитись на паралельності (або на багатьох інших питаннях, які поінформовані в цілому вважають доброю практикою).

  3. Є деякі речі, які ви просто не можете зробити з незмінними об'єктами, наприклад, у двосторонніх відносинах. Після того, як ви встановите значення асоціації на одному об’єкті, воно змінюється ідентичність. Отже, ви встановлюєте нове значення для іншого об'єкта, і воно також змінюється. Проблема в тому, що посилання на перший об'єкт більше не є дійсним, оскільки створений новий екземпляр для представлення об'єкта з посиланням. Продовження цього лише призведе до нескінченних регресій. Я трохи прочитав тематичне дослідження, прочитавши ваше запитання, ось як це виглядає. Чи є у вас альтернативний підхід, який дозволяє таку функціональність, зберігаючи незмінність?

        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., Якщо ви по-іншому сприймаєте, як підтримувати складні графіки з двосторонніми зв’язками, використовуючи лише незмінні об’єкти, то я б хотів це почути. (Я припускаю, що ми можемо погодитися, що колекція / масив є об’єктом)
smartcaveman

2
@AndresF., (1) Моє перше твердження не було універсальним, тому не було помилковим. Я фактично наводив приклад коду, щоб пояснити, як це обов'язково було правдою в певних випадках, які трапляються дуже часто в розробці додатків. (2) Взагалі двосторонні відносини вимагають змінності. Я не вірю, що винуватцем є Java. Як я вже говорив, я би радив оцінити будь-які конструктивні варіанти, які ви запропонували б, але на даний момент ви зауважуєте дуже схоже на "Ви помиляєтесь, бо я так сказав".
smartcaveman

14
@smartcaveman Щодо (2), я також не погоджуюся: загалом, "двонаправлене відношення" - це математичне поняття, ортогональне зміні. Як зазвичай реалізується в Java, вона вимагає змінності (я погодився з вами з цього приводу). Однак я можу придумати альтернативну реалізацію: клас відносин між двома об'єктами, з конструктором Relationship(a, b); в момент створення відносин обидві суб'єкти a& bвже існують, і самі відносини також непорушні. Я не кажу, що цей підхід практичний на Java; тільки що це можливо.
Андрес Ф.

4
@AndresF., Отже, виходячи з того, що ви говорите, якщо Rце Relationship(a,b)і те, і інше, aі bнепорушні, ні на жодне, aні bна посилання на нього R. Щоб це працювало, посилання потрібно було б зберігати десь в іншому місці (наприклад, статичний клас). Я правильно розумію ваш намір?
smartcaveman

9
Можна зберігати двосторонні відносини для незмінних даних, як вказує Чтулху через лінь. Ось один із способів зробити це: haskell.org/haskellwiki/Tying_the_Knot
Thomas Eding

36

Я читав "суто функціональні структури даних", і це дало мені зрозуміти, що існує досить багато структур даних, які набагато простіше реалізувати за допомогою змінних об'єктів.

Щоб реалізувати двійкове дерево пошуку, вам потрібно буде щоразу повертати нове дерево: вашому новому дереву доведеться зробити копію кожного зміненого вузла (немодифіковані гілки поділяються). Для вашої функції вставки це не так вже й погано, але для мене речі стали досить неефективними, коли я почав працювати над видаленням та повторним балансом.

Інша річ, яку потрібно усвідомити, - це те, що ви можете піти роки писати об'єктно-орієнтований код, і ніколи насправді не усвідомлювати, наскільки страшним може бути загальний стан, що змінюється, якщо ваш код не працює таким чином, що викриває проблеми з одночасністю.


3
Це книга Окасакі?
Механічний равлик

так. якось сухий, але тонна
корисної

4
Смішно, я завжди вважав, що Окасакіс червоне / чорне дерево настільки простіший. 10 рядків або близько того. Я думаю, що найбільша перевага полягає в тому, що ви насправді хочете зберегти і стару версію.
Томас Ейл

Незважаючи на те, що останнє речення, мабуть, було істинним у минулому, але не зрозуміло, що воно залишиться правдивим і в майбутньому, враховуючи сучасні тенденції до обладнання тощо
jk.

29

З моєї точки зору, це недостатня обізнаність. Якщо ви подивитеся на інші відомі мови JVM (Scala, Clojure), змінні об'єкти рідко бачать у коді, і тому люди починають використовувати їх у сценаріях, коли однієї нитки недостатньо.

Зараз я вивчаю Clojure і маю невеликий досвід роботи в Scala (4 роки + також на Java), і ваш стиль кодування змінюється через усвідомлення стану.


Можливо, «відомий», а не «популярний» був би кращим вибором слова.
День

Так, правильно.
Флоріан Саліхович

5
+1: Я згоден: після вивчення деяких Scala та Haskell я схильний використовувати final на Java та const у C ++ скрізь. Я також використовую незмінні об'єкти, якщо це можливо, і, хоча змінювані об'єкти все ще потрібні дуже часто, дивно, як часто ви можете використовувати непорушні об'єкти.
Джорджіо

5
Я прочитав цей мій коментар через два з половиною роки, і моя думка змінилася на користь непорушності. У моєму поточному проекті (який знаходиться в Python) ми дуже рідко використовуємо мутаційні об'єкти. Навіть наші постійні дані незмінні: ми створюємо нові записи в результаті деяких операцій і видаляємо старі записи, коли вони більше не потрібні, але ми ніколи не оновлюємо жоден запис на диску. Потрібно говорити, що це зробило наш одночасний багатокористувацький додаток набагато простішим у впровадженні та підтримці досі.
Джорджіо

13

Я думаю, що основний фактор, що сприяє, був ігнорований: Java Beans сильно покладаються на певний стиль мутування об'єктів, і (особливо з урахуванням джерела) досить багато людей, здається, сприймають це як (або навіть той ) канонічний приклад того, як усі Java слід писати.


4
+1, шаблон геттера / сеттера використовується занадто часто, як якась реалізація за замовчуванням після першого аналізу даних.
Яап

Це, мабуть, великий сенс ... "тому що так роблять всі інші" ... тому це повинно бути правильно. Для програми "Hello World" це, мабуть, найпростіше. Управління змінами стану об'єкта за допомогою ряду змінних властивостей ... це трохи складніше, ніж глибини розуміння "Hello World". Через 20 років я дуже здивований, що вершиною програмування 20 століття є написання методів getX та setX (як нудно) над кожним атрибутом об'єкта, який би не мав структури. Це лише один крок від прямого доступу до загальнодоступних об'єктів зі 100% можливістю зміни.
Darrell Teague

12

Кожна корпоративна система Java, над якою я працював у своїй кар’єрі, використовує або Hibernate, або API API Persistent (JPA). Hibernate та JPA по суті диктують, що ваша система використовує об'єкти, що змінюються, оскільки вся їх передумова полягає в тому, щоб вони виявляли та зберігали зміни у ваших об'єктах даних. Для багатьох проектів простота розвитку, яку приносить сплячка, є більш переконливою, ніж переваги незмінних об'єктів.

Очевидно, що об'єкти, що змінюються, існують набагато довше, ніж у сплячому режимі, тому сплячий режим, ймовірно, не є первісною причиною популярності об'єктів, що змінюються. Можливо, популярність мінливих предметів дозволила сплячці процвітати.

Але сьогодні, якщо багато молодших програмістів ріжуть зуби на корпоративних системах, використовуючи Hibernate або інший ORM, тоді, імовірно, вони підберуть звичку використовувати об'єкти, що змінюються. Такі структури, як сплячий режим, можуть посилити популярність об'єктів, що змінюються.


Відмінний момент. Для того, щоб бути всіма речами для всіх людей, такі рамки мають мало вибору, окрім як потрапити до найнижчого загального знаменника, використовувати проксі-сервера, що базуються на рефлексії, та отримати / встановити шлях до гнучкості. Звичайно, це створює системи, що мають мало-ні правил переходу держави або загальних засобів, за допомогою яких їх реалізувати, на жаль. Я не зовсім впевнений, що після багатьох проектів зараз, що краще для доцільності, розширюваності та коректності. Я схильний вважати гібридом. Динамічна доброта ORM, але з деякими визначеннями, які поля потрібні та які зміни стану повинні бути можливими.
Darrell Teague

9

Основний момент, який ще не згадується, - це те, що стан об'єкта, який може бути змінним, дає змогу ідентичність об'єкта, яка інкапсулює цей стан, незмінна.

Багато програм створені для моделювання реальних речей, які за своєю суттю змінюються. Припустимо, що о 12:51, деяка змінна AllTrucksмістить посилання на об'єкт № 451, що є коренем структури даних, яка вказує, який вантаж міститься у всі вантажні машини флоту в цей момент (12:51 ранку), а який - змінний BobsTruckможе бути використаний для отримання посилання на об'єкт №24601 вказує на об'єкт, який вказує, який вантаж міститься в вантажівці Боба в цей момент (12:51 ранку). О 12:52 ранку деякі вантажівки (включаючи Боба) завантажуються та вивантажуються, а структури даних оновлюються так, що AllTrucksтепер буде посилатися на структуру даних, яка вказує, що вантаж знаходиться у всіх вантажівок станом на 12:52 ранку.

Що має статися BobsTruck?

Якщо властивість «вантажу» кожного об'єкта вантажівки буде непорушною, то об’єкт №24601 назавжди буде представляти стан, який мав вантажівка Боба о 12:51 ранку. Якщо BobsTruckмає пряме посилання на об’єкт №24601, то, якщо код, який оновлення AllTrucksтакож не оновлюється BobsTruck, він перестане представляти поточний стан вантажівки Боба. Далі зауважте, що якщо BobsTruckне зберігається в якійсь формі об'єкта, що змінюється, єдиний спосіб, коли код, який оновлення AllTrucksміг би оновити, був би, якщо код був явно запрограмований для цього.

Якщо хтось хоче мати можливість BobsTruckспостерігати за станом вантажівки Боба, зберігаючи всі непорушні об'єкти, може BobsTruckбути незмінна функція, яка, зважаючи на значення, яке AllTrucksмає або було в будь-який конкретний час, призведе до стану вантажівки Боба на того часу. Можна навіть мати, щоб він виконував пару незмінних функцій - одна з яких була б, як вище, а друга з яких приймала б посилання на стан флоту та новий стан вантажівки, а також повертала посилання на новий стан флоту, який відповідає старому, за винятком того, що вантажівка Боба мала б новий стан.

На жаль, використання такої функції кожного разу, коли хочеться отримати доступ до штату вантажівка Боба, може стати досить дратівливою і громіздкою. Альтернативним підходом було б сказати, що об'єкт №24601 завжди і назавжди (доки хтось має на нього посилання) буде представляти поточний стан вантажівки Боба. Код, який бажає неодноразово отримувати доступ до поточного стану вантажівки Боба, не повинен був би виконувати деяку трудомістку функцію кожного разу - він може просто виконувати функцію пошуку один раз, щоб дізнатися, що об’єкт №24601 - вантажівка Боба, а потім просто отримати доступ до цього об'єкта будь-коли, коли він хоче побачити сучасний стан вантажівки Боба.

Зауважте, що функціональний підхід не позбавлений переваг в однопотоковому середовищі або в багатопотоковому середовищі, де потоки здебільшого просто спостерігатимуть дані, а не змінюватимуть їх. Будь-яка нитка спостерігача, яка копіює посилання на об'єкт, що міститься вAllTrucksа потім вивчає представлені таким чином стани вантажівки, побачить стан усіх вантажних автомобілів станом на той момент, коли він схопив посилання. Кожен раз, коли нитка спостерігача хоче побачити новіші дані, вона може просто знову захопити посилання. З іншого боку, наявність усього штату флоту, представленого одним незмінним об'єктом, виключає можливість одночасного оновлення двох потоків різних вантажних автомобілів, оскільки кожен потік, якщо залишити його на власні пристрої, створював би новий об'єкт "стан флоту", який включає новий стан своєї вантажівки та старі штати кожного іншого. Правильність може бути гарантована, якщо кожен потік використовує CompareExchangeдля оновлення AllTrucksлише те, що він не змінився і відповідає на помилкуCompareExchangeшляхом регенерації об'єкта стану та повторної операції, але якщо одночасна операція запису намагається більше ніж один потік, продуктивність, як правило, буде гіршою, ніж якщо б усі записи були виконані на одному потоці; чим більше ниток намагаються проводити такі одночасні операції, тим гірше буде отримати продуктивність.

Якщо окремі об'єкти вантажних автомобілів є змінними, але мають незмінні ідентичності , багатопотоковий сценарій стає більш чистим. На будь-якому вантажівці одночасно може працювати лише одна нитка, але нитки, що працюють на різних вантажних автомобілях, можуть робити це без втручання. Хоча існують способи, які можна наслідувати такій поведінці навіть при використанні незмінних об'єктів (наприклад, можна визначити об'єкти "AllTrucks", щоб встановлення стану вантажівки, що належить до XXX, до SSS, просто вимагало б створити об'єкт, який сказав "Станом на [Час]", стан вантажівки, що належить до [XXX], зараз є [SSS]; стан всього іншого - [стара цінність AllTrucks] ". Створення такого об'єкта було б досить швидким, що навіть за наявності суперечкиCompareExchangeпетля не займе багато часу. З іншого боку, використання такої структури даних суттєво збільшить час, необхідний для пошуку вантажівки конкретної людини. Використання змінних об'єктів з незмінною ідентичністю дозволяє уникнути цієї проблеми.


8

Немає правильного чи неправильного, просто залежить, що ви віддаєте перевагу. Є причина, чому деякі люди віддають перевагу мовам, які надають перевагу одній парадигмі над іншою, а одна модель даних над іншою. Це просто залежить від ваших уподобань та того, чого ви хочете досягти (і вміти легко використовувати обидва підходи, не відчужуючи важких фанатів тієї чи іншої сторони - це святий грааль, який деякі мови шукають).

Я думаю, що найкращий і найшвидший спосіб відповісти на ваше запитання - це перейти до плюсів і мінусів непорушності проти змінності .


7

Перевірте це повідомлення в блозі: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Він підсумовує, чому незмінні предмети краще, ніж змінні. Ось короткий список аргументів:

  • незмінні об'єкти простіші для побудови, тестування та використання
  • справді незмінні об'єкти завжди безпечні для ниток
  • вони допомагають уникнути часової зв’язки
  • їх використання не має побічних ефектів (відсутні захисні копії)
  • Проблема мутаційності особистості уникається
  • вони завжди мають атомну атомність
  • їх набагато простіше кешувати

Наскільки я розумію, люди використовують змінні об'єкти, оскільки вони все ще змішують ООП з імперативними процедурними програмуваннями.


5

У Java незмінним об'єктам потрібен конструктор, який буде приймати всі властивості об'єкта (або конструктор створює їх з інших аргументів або за замовчуванням). Ці властивості мають бути позначені остаточними .

З цим є чотири проблеми, пов'язані з прив'язкою даних :

  1. Метадані відображення Java Constructors не містять імен аргументів.
  2. Конструктори Java (і методи) не мають іменованих параметрів (їх також називають мітками), тому це стає заплутаним у багатьох параметрах.
  3. При успадкуванні іншого незмінного об'єкта повинен бути названий належний порядок конструкторів. Це може бути досить складним, якщо просто відмовитись і залишити одне з полів не остаточним.
  4. Більшість технологій прив'язки (наприклад, Spring MVC Data Binding, Hibernate тощо) працюватимуть лише з конструкторами за замовчуванням без аргументів (це було тому, що анотації існували не завжди).

Ви можете пом'якшити №1 та №2, використовуючи примітки, як-от @ConstructorPropertiesі створити інший об'єкт, що змінюється, для будівельника (як правило, вільно) для створення незмінного об'єкта.


5

Я здивований, що ніхто не згадав про переваги оптимізації продуктивності. Залежно від мови компілятор може зробити купу оптимізацій під час роботи з незмінними даними, оскільки знає, що дані ніколи не зміняться. Всілякі речі пропускаються, що дає величезні переваги від продуктивності.

Також непорушні об'єкти майже усувають весь клас державних помилок.

Він не такий великий, як має бути, тому що його складніше, не стосується кожної мови, і більшість людей навчали імперативному кодуванню.

Також я виявив, що більшість програмістів задоволені своєю скринькою і часто стійкі до нових ідей, які вони не повністю розуміють. Взагалі люди не люблять змін.

Також пам’ятайте, що стан більшості програмістів поганий. Більшість програм, які робляться в дикій природі, жахливі і спричинені нерозумінням та політикою.


4

Чому люди використовують будь-яку потужну функцію? Чому люди використовують метапрограмування, лінь чи динамічне введення тексту? Відповідь - зручність. Стан, що змінюється, такий простий. Це так просто оновити на місці, а поріг розміру проекту, коли незмінний стан є більш продуктивним для роботи, ніж стан, що змінюється, досить високий, тому вибір не відкидає вас на деякий час.


4

Мови програмування призначені для виконання комп'ютерами. Усі важливі складові комп'ютерів - процесор, оперативна пам’ять, кеш, диски - змінні. Коли вони не є (BIOS), вони справді непорушні, і ви також не можете створювати нові незмінні об'єкти.

Отже, будь-яка мова програмування, побудований поверх незмінних об'єктів, страждає від розриву у поданні в його реалізації. А для ранніх мов, таких як C, це було великим каменем спотикання.


1

Без змінних об'єктів у вас немає стану. Справді, це гарна річ, якщо ви можете керувати нею, і якщо є якийсь шанс, що на об'єкт може бути посилається більше, ніж на один потік. Але програма буде досить нудною. Багато програмного забезпечення, зокрема веб-серверів, уникають відповідальності за об'єкти, що змінюються, шляхом вимкнення мутабельності в базах даних, операційних системах, системних бібліотеках і т.д. розвиток доступний. Але незмінність все ще є.

Загалом, у вас є три типи класів: звичайні класи, які не є безпечними для потоків, і їх слід ретельно охороняти та захищати; незмінні класи, якими можна вільно користуватися; і змінні класи, безпечні для потоків, які можна вільно використовувати, але які слід писати з особливою обережністю. Перший тип - це клопіткий, найгірші - ті, що вважаються третім. Звичайно, перший тип - це прості в написанні.

Я зазвичай закінчую безліч звичайних, змінних занять, за якими мені доводиться дуже уважно спостерігати. У ситуації з декількома потоками необхідна синхронізація сповільнює все, навіть коли я можу уникнути смертоносних обіймів. Тому я звичайно роблю незмінні копії класу, що змінюється, і передаю їх тому, хто може ним користуватися. Нова незмінна копія потрібна щоразу, коли оригінальний мутує, тому я думаю, що часом у мене може бути сто копій оригіналу. Я повністю залежний від збору сміття.

Підсумовуючи, об'єкти, що не змінюються для потоків, що змінюються, добре, якщо ви не використовуєте декілька потоків. (Але багатопоточність читається скрізь - будьте обережні!) Їх можна безпечно використовувати інакше, якщо ви обмежите їх локальними змінними або суворо їх синхронізуєте. Якщо ви можете їх уникнути, використовуючи перевірений код інших людей (БД, системні дзвінки тощо), зробіть це. Якщо ви можете використовувати незмінний клас, зробіть це. І я думаю , загалом люди або не знають про багатопотокові проблеми, або їх (розумно) жахлять і використовують усілякі хитрощі, щоб уникнути багатопоточності (а точніше, покладаючи на це відповідальність за інше).

Як PS, я відчуваю, що геттери і сетери Java виходять з рук. Перевірте це .


7
Незмінний стан все ще є станом.
Джеремі Хайлер

3
@JeremyHeiler: Це правда, але це стан щось, що можна змінити. Якщо нічого не мутує, є лише один стан, який є тим самим, що взагалі немає.
RalphChapin

1

Багато людей мали гарні відповіді, тому я хочу зазначити те, що ви торкнулися, що було дуже спостережливим і надзвичайно правдивим і ніде не згадувалося тут.

Автоматичне створення сетерів та геттерів - це жахлива, жахлива ідея, але це перший спосіб, коли процесуально налаштовані люди намагаються примусити ОО до себе. Налаштування та геттери разом із властивостями повинні створюватися лише тоді, коли ви виявите, що вам потрібні, а не за замовчуванням

Насправді, хоча вам потрібні регулярні користувачі, єдиний спосіб, коли сеттери або властивості для запису повинні колись існувати у вашому коді, - це через модель конструктора, де вони закриваються після того, як об'єкт буде повністю ініційований.

Багато класів можна змінювати після створення, що добре, він просто не повинен мати його властивостями безпосередньо маніпулювати - натомість слід попросити маніпулювати його властивостями за допомогою викликів методів із фактичною діловою логікою в них (Так, сетер майже однаковий річ як безпосередньо маніпулювання майном)

Тепер це дійсно не стосується коду / мов стилю "Сценарій", а лише до коду, який ви створюєте для когось іншого, і очікуйте, що інші читатимуть багато разів протягом багатьох років. Мені довелося почати робити це розмежування останнім часом, тому що мені так подобається возитися з Гроови, і в цілях є величезна різниця.


1

Об'єкти, що змінюються, використовуються, коли після встановлення об'єкта потрібно встановити кратні значення.

У вас не повинно бути конструктора з, скажімо, шістьма параметрами. Натомість ви модифікуєте об'єкт методами встановлення.

Прикладом цього може бути об’єкт Report, у якому встановлені шрифти, орієнтація тощо.

Коротше кажучи: мутанти корисні, коли у вас є багато стану для встановлення об'єкта, і не було б практичним мати дуже довгий підпис конструктора.

EDIT: Шаблон Builder можна використовувати для побудови всього стану об’єкта.


3
це представлено так, ніби змінність та зміни після інстанції - це єдиний спосіб встановити кілька значень. Для повноти зауважте, що модель Builder пропонує рівні або навіть більш потужні можливості і не вимагає жертви незмінності. new ReportBuilder().font("Arial").orientation("landscape").build()
гнат

1
"Недоцільно мати дуже довгий підпис конструктора.": Ви завжди можете групувати параметри на більш дрібні об'єкти і передавати ці об'єкти як параметри конструктору.
Джорджіо

1
@cHao Що робити, якщо ви хочете встановити 10 чи 15 атрибутів? Крім того, не здається, що метод з ім'ям withFontreturn a Report.
Тулен Кордова

1
Що стосується встановлення 10 або 15 атрибутів, код для цього не був би незручним, якби (1) Java знала, як уникнути побудови всіх цих проміжних об'єктів, і якщо (2) знову, імена були стандартизовані. Це не проблема з незмінністю; проблема з Java не знає, як це зробити добре.
cHao

1
@cHao Зробити ланцюжок викликів для 20 атрибутів некрасиво. Некрасивий код має тенденцію бути неякісним кодом.
Тулен Кордова

1

Я думаю, що використання змінних об'єктів випливає з імперативного мислення: ви обчислюєте результат, змінюючи вміст змінних змінних поетапно (обчислення побічним ефектом).

Якщо ви думаєте функціонально, ви хочете мати незмінний стан і представляти наступні стани системи, застосовуючи функції та створюючи нові значення зі старих.

Функціональний підхід може бути більш чистим і надійним, але він може бути дуже неефективним через копіювання, так що ви хочете повернутися до спільної структури даних, яку ви поступово змінюєте.

Компроміс, який я вважаю найбільш розумним: Почніть з незмінних об'єктів, а потім перейдіть до змінних, якщо ваша реалізація недостатньо швидка. З цієї точки зору, систематичне використання змінних об'єктів з самого початку можна вважати якоюсь передчасною оптимізацією : ви вибираєте більш ефективну (але також складнішу для розуміння та налагодження) реалізацію з самого початку.

Отже, чому багато програмістів використовують змінні об’єкти? ІМХО з двох причин:

  1. Багато програмістів навчилися програмувати за допомогою імперативної (процедурної або об’єктно-орієнтованої) парадигми, тому незмінність є їх основним підходом до визначення обчислень, тобто вони не знають, коли і як використовувати незмінність, оскільки вони не знайомі з нею.
  2. Багато програмістів переживають про продуктивність занадто рано, тоді як часто більш ефективно спочатку зосередитися на написанні функціонально правильної програми, а потім спробувати знайти вузькі місця та оптимізувати її.

1
Питання не лише у швидкості. Існує багато типів корисних конструкцій, які просто неможливо реалізувати без використання змінних типів. Крім усього іншого, зв'язок між двома потоками вимагає, щоб, при абсолютному мінімумі, обидва повинні мати посилання на спільний об'єкт, на який можна помістити дані, а другий може їх прочитати. Крім того, часто семантично набагато зрозуміліше сказати "Змінити властивості P і Q цього об'єкта", ніж сказати "Візьміть цей об'єкт, побудуйте новий об'єкт, який точно такий, як він, за винятком значення P, а потім новий об'єкт, який просто так, крім Q ".
supercat

1
Я не фахівець з ПЗ, але спілкування з потоком AFAIK (1) можна досягти, створивши незмінне значення в одному потоці та прочитавши його в іншому (знову ж таки, AFAIK, це підхід Ерланга) (2) Позначення AFAIK для встановлення властивості не сильно змінюється (наприклад, у Haskell setProperty значення запису відповідає Java record.setProperty (значення)), змінюється лише семантика, оскільки результатом встановлення властивості є новий незмінний запис.
Джорджіо

1
"обидва повинні мати посилання на спільний об'єкт, на який можна помістити дані, а другий може їх прочитати": точніше, ви можете зробити об'єкт незмінним (усі члени містять C ++ або остаточний на Java) та встановити весь вміст у конструктор. Потім цей незмінний предмет передається від виробника до споживача.
Джорджіо

1
(2) Я вже вказав продуктивність як причину не використовувати незмінний стан. Щодо (1), звичайно, механізму, який реалізує зв'язок між потоками, потрібен певний стан, що змінюється, але ви можете приховати це від вашої моделі програмування, див., Наприклад, модель актора ( en.wikipedia.org/wiki/Actor_model ). Тож навіть якщо на нижчому рівні вам потрібна змінність для здійснення комунікації, ви можете мати верхній рівень абстракції, в якому ви спілкуєтесь між потоками, що надсилають незмінні об’єкти туди-сюди.
Джорджіо

1
Я не впевнений, що я вас повністю розумію, але навіть у чистої функціональної мови, де кожне значення незмінне, є одна змінна річ: стан програми, тобто поточний стек програми та змінні прив’язки. Але це середовище виконання. У будь-якому разі, я пропоную нам обговорити це в чаті деякий час, а потім очистити повідомлення від цього питання.
Джорджіо

1

Я знаю, що ви запитуєте про Java, але я весь час використовую змінний vs незмінний у target-c. Існує незмінний масив NSArray та змінний масив NSMutableArray. Це два різні класи, написані спеціально оптимізованим способом для точного використання. Якщо мені потрібно створити масив і ніколи не змінювати його вміст, я б використав NSArray, який є меншим об'єктом і набагато швидше в тому, що він робить, порівняно з змінним масивом.

Отже, якщо ви створюєте об'єкт Person, який є незмінним, тоді вам потрібен лише конструктор і getters, таким чином об'єкт буде меншим і використовуватиме менше пам'яті, що, в свою чергу, зробить вашу програму фактично швидшою. Якщо вам потрібно змінити об'єкт після створення, то об'єкт, що змінюється Person, буде кращим, щоб він міг змінювати значення замість створення нового об'єкта.

Отже: залежно від того, що ви плануєте робити з об'єктом, вибір мутабельних та незмінних може призвести до величезних змін, що стосується продуктивності.


1

На додаток до багатьох інших причин, наведених тут, проблема полягає в тому, що основні мови недостатньо підтримують незмінність. Принаймні, ви накладаєте штраф за незмінність тим, що вам потрібно додати додаткові ключові слова, такі як const або final, і вам доведеться використовувати нерозбірливі конструктори з багатьма, багатьма аргументами або кодами, тривалими моделями розробника.

Це набагато простіше в мовах, зроблених з незмінністю на увазі. Розглянемо цей фрагмент Scala для визначення особи класу, необов'язково з названими аргументами та створення копії зі зміненим атрибутом:

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")

Отже, якщо ви хочете, щоб розробники перейняли незмінність, це одна з причин, чому ви можете розглянути можливість переходу на більш сучасну мову програмування.


це , здається, не додає нічого істотного над точкою зроблена і пояснена в раніше 24 відповідей
комара

@gnat Яка з попередніх відповідей говорить про те, що більшість основних мов не гідно підтримують незмінність? Я думаю, що ця точка просто не була зроблена (я перевірив), але це IMHO є досить важливою перешкодою.
Ганс-Пітер Штерр

цей, наприклад, детально пояснює цю проблему на Java. І принаймні 3 інші відповіді, опосередковано пов’язані з нею
gnat

0

Незмінне означає, що ви не можете змінити значення, а змінне означає, що ви можете змінити значення, якщо ви думаєте з точки зору примітивів та об'єктів. Об'єкти відрізняються від примітивів на Java тим, що вони побудовані за типами. Примітиви побудовані у таких типах, як int, boolean та void.

Дуже багато людей думають, що примітиви та змінні об'єктів, які мають перед собою остаточний модифікатор, незмінні, однак, це не зовсім так. Отже, остаточне майже не означає незмінного для змінних. Перевірте це посилання на зразок коду:

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

Я думаю, що вагомою причиною було б моделювати "реальні" "змінні" об'єкти ", як інтерфейсні вікна. Я смутно пам'ятаю, як прочитав, що ООП був винайдений, коли хтось намагався написати програмне забезпечення для управління деякими вантажними портами.


1
Я поки не можу знайти, де я це прочитав про походження OOP, але згідно Вікіпедії , в Smalltalk написана деяка Комплексна регіональна інформаційна система великої контейнерної компанії OOCL .
Олексій

-2

Java, у багатьох випадках, мандатує об'єкти, що змінюються, наприклад, коли ви хочете порахувати або повернути щось відвідувачеві або запущеному, вам потрібні остаточні змінні, навіть без багатопотокової читання.


5
finalнасправді приблизно 1/4 кроку до незмінність. Не бачачи, чому ви це згадуєте.
cHao

ти насправді читав мій пост?
Ральф Н

Ви насправді читали питання? :) Це не мало абсолютно нічого спільного final. Виведення цього в цьому контексті викликає по-справжньому дивне співвідношення finalз "мутаційним", коли суть у finalтому, щоб запобігти певним мутаціям. (BTW, не мій downvote.)
cHao

Я не кажу, що ти десь не маєш справжньої точки. Я кажу, що ти не дуже добре це пояснюєш. Я начебто бачу, куди ви, мабуть, їдете з цим, але вам потрібно йти далі. Так і є, це просто виглядає розгублено.
cHao

1
Є на насправді дуже мало випадків , коли це змінюваний об'єкт , необхідні . Бувають випадки, коли код був би зрозумілішим, або в деяких випадках швидшим, за допомогою змінних об'єктів. Але врахуйте, що переважна більшість моделей дизайну - це лише наполовину передана функціональна програма для мов, які не підтримують її. Цікавою частиною цього є те, що FP вимагає змінити лише у дуже відібраних місцях (а саме там, де повинні виникати побічні ефекти), і ці місця, як правило, жорстко обмежені за кількістю, розміром та обсягом.
cHao
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.