Точкові позначення проти позначень повідомлення для заявлених властивостей


81

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

Яка ваша думка про позначення крапками проти позначення повідомлень для доступу до власності?

Будь ласка, спробуйте зосередити увагу на Objective-C - моє одне упередження, яке я викладу, полягає в тому, що Objective-C є Objective-C, тому ваші переваги, щоб вони були схожими на Java або JavaScript, не є дійсними.

Дійсні коментарі стосуються технічних питань (порядок операцій, пріоритет відбору, продуктивність тощо), чіткість (структура проти природи об'єкта, як за, так і проти!), Стислість тощо.

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


14
+1 це чудове запитання, і ви отримаєте багато суперечливих відповідей. =)
Дейв ДеЛонг

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

Відповіді:


64

Не використовуйте крапку для поведінки. Використовуйте крапку для доступу або встановлення таких атрибутів, як речі, зазвичай атрибути, оголошені як властивості.

x = foo.name; // good
foo.age = 42; // good

y = x.retain; // bad

k.release; // compiler should warn, but some don't. Oops.

v.lockFocusIfCanDraw; /// ooh... no. bad bad bad

Людям, що не знайомі з Objective-C, я б рекомендував не використовувати крапки ні для чого, крім матеріалів, оголошених як @property. Коли ви відчуєте мову, робіть те, що здається правильним.

Наприклад, я вважаю наступне цілком природним:

k = anArray.count;
for (NSView *v in myView.subviews) { ... };

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


18
Ого, я навіть не знав, що ти можеш використовувати синтаксис крапок для будь-чого іншого, крім властивостей. Колір мене здивував! (Але я згоден, крапку слід використовувати лише для властивостей)
jbrennan

13
Це неправильно. Точкові позначення - це прямий синонім для виклику еквівалентного методу; нічого більше, нічого менше.
bbum

2
Цікаво, чому позначення крапками не обмежувалось доступними до @ властивості. Здається, це дозволило б авторам класів вирішувати сірі області, над якими повинні бути дозволені методи в точковому позначенні, і не було б настільки двозначним, використовувати чи ні методи, як-от .uppercaseStringякби класу було дозволено висловити свій намір і мати його накладений компілятором. Можливо, є щось, чого мені не вистачає, що вимагає поточної реалізації позначення крапками.

4
Це могло бути обмежено @propertyлише, але для цього споживачеві API потрібно буде вивчити інший логічний вимір інформації, а розробники API цілком імовірно втопляться в суперечках щодо того, що повинно, а що не повинно бути @property. Зверніть увагу, що Xcode надає перевагу - важче зважує - @propertyпередбачені методи під час заповнення коду .... Це заохочується, але не застосовується.
bbum

1
@rodamn Це відповідає на це; він рекомендує використовувати крапку із @propertyзаявленими засобами доступу, лише коли нова мова. Коли ви отримуєте впевненість у кодуванні в Objective-C, робіть те, що вважаєте правильним. Цікаво; багато з нас повернулися до дуже обмеженого використання точки.
bbum

22

Дозвольте мені почати з того, що я почав програмувати на Visual / Real Basic, а потім перейшов на Java, тому я досить звик до синтаксису крапок. Однак, коли я нарешті перейшов до Objective-C і звик до дужок, а потім побачив введення Objective-C 2.0 та його синтаксис крапок, я зрозумів, що мені це дійсно не подобається. (для інших мов це добре, бо саме так вони котяться).

У мене є три основні яловичі фігури із синтаксисом крапок у Objective-C:

Яловичина №1: незрозуміло, чому у вас можуть виникати помилки. Наприклад, якщо у мене є рядок:

something.frame.origin.x = 42;

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

something.frame.origin.x = 42;

Тоді це компілюється просто чудово, оскільки somethingце сама структура, яка має член NSRect, і я можу використовувати її як значення lvalue.

Якби я приймав цей код, мені потрібно було б витратити якийсь час, намагаючись з’ясувати, що somethingце таке. Це структура? Це об’єкт? Однак, коли ми використовуємо синтаксис дужок, це стає набагато зрозумілішим:

[something setFrame:newFrame];

У цьому випадку немає абсолютно ніякої двозначності, somethingє об'єктом чи ні. Введення двозначності - це моя яловичина №1.

Яловичина №2: У C точковий синтаксис використовується для доступу до членів структур, а не до методів виклику. Програмісти можуть замінити setFoo:і fooметоди об'єктів, але все одно отримати до них доступ через something.foo. На мою думку, коли я бачу вирази з використанням крапкового синтаксису, я очікую, що вони будуть простим присвоєнням івару. Це не завжди так. Розглянемо об’єкт контролера, який є посередником масиву та подання таблиці. Якщо я зателефоную myController.contentArray = newArray;, я міг би очікувати, що це буде заміна старого масиву новим. Однак оригінальний програміст міг перевизначити setContentArray:не лише встановлення масиву, але й перезавантаження табличного перегляду. З рядка немає жодних ознак такої поведінки. Якби я побачив[myController setContentArray:newArray];, тоді я міг би подумати: "Ага, метод. Мені потрібно перейти до визначення цього методу, щоб переконатися, що я знаю, що він робить".

Тому я думаю, що мій підсумок Beef # 2 полягає в тому, що ви можете замінити значення синтаксису крапок за допомогою власного коду.

Яловичина №3: Я думаю, це виглядає погано. Як програміст Objective-C, я повністю звик до синтаксису дужок, тому читати і бачити рядки та рядки красивих дужок, а потім раптово ламатися і foo.name = newName; foo.size = newSize;т. Д. Для мене це трохи відволікає. Я усвідомлюю, що деякі речі вимагають синтаксису крапок (структури C), але це єдиний раз, коли я їх використовую.

Звичайно, якщо ви пишете код для себе, тоді використовуйте те, що вам зручно. Але якщо ви пишете код, який плануєте використовувати з відкритим джерелом, або пишете те, що не очікуєте підтримувати назавжди, то я б радив використовувати синтаксис дужок. Це, звичайно, лише моя думка.

Нещодавня публікація в блозі проти синтаксису крапок: http://weblog.bignerdranch.com/?p=83

Спростування до вищезазначеного повідомлення: http://eschatologist.net/blog/?p=226 (з оригінальною статтею на користь синтаксису крапок: http://eschatologist.net/blog/?p=160 )


5
Яловичина №1 здається мені трохи вибагливою. Я не розумію, як це викликає занепокоєння більше, ніж ви можете спробувати надіслати повідомлення структурі, а потім доведеться з'ясувати, що змінна насправді є структурою. Ви насправді бачите, що багато людей, які розуміють, як це працює, заплутуються від цього на практиці? Це схоже на те, що ви один раз зіпсуєте, дізнаєтесь про річ lvalue, а потім зробите це правильно в майбутньому - так само, як, скажімо, не намагаючись поводитися з NSNumbers як із ints.
Чак,

2
@Chuck це дещо надзвичайний випадок, але я роблю досить багато розробок контрактів, що передбачає успадкування проектів і необхідність працювати з ними. Зазвичай something це об'єкт, але я стикався з кількома місцями, де оригінальні автори створювали структури (для швидкості, меншого використання пам'яті тощо), і мені доводиться витрачати добрі пару хвилин, намагаючись зрозуміти, чому код не t компілювати.
Дейв Делонг

14

Я новий розробник Cocoa / Objective-C, і я беру на це наступне:

Я дотримуюся позначень обміну повідомленнями, хоча я починав з Obj-C 2.0, і хоча позначення крапками є більш звичним відчуттям (Java - моя перша мова.) Моя причина цього досить проста: я все ще не розумію точно чому вони додали крапкові позначення до мови. Для мене це здається непотрібним, "нечистим" доповненням. Хоча, якщо хтось може пояснити, як це корисно для мови, я був би радий це почути.

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


2
+1 чудова відповідь. Я б справді хотів знати, чому це також пов’язано. Можливо, bbum або mmalc знають відповідь ... (вони обидва працюють в )
Дейв ДеЛонг,

11

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

Після цього потрібна невелика преамбула, ось плюси і мінуси, які я бачив:

Крапкові позначення плюси і мінуси

  • плюси

    • читабельність: крапкові позначення читати легше, ніж вкладені дужки, що передають масаж
    • Це спрощує взаємодію з атрибутами та властивостями: використовуючи крапкові позначення для властивостей та нотації повідомлень для методів, ви можете досягти розділення стану та поведінки на рівні синтетаксу.
    • Можна використовувати оператор складеного присвоєння (1) .
    • використовуючи @propertyнотації та крапки, компілятор робить для вас велику роботу, він може генерувати код для належного управління пам'яттю при отриманні та налаштуванні властивості; саме тому точкові позначення пропонують самі офіційні керівництва Apple.
  • мінуси

    • Точкові позначення дозволено лише для доступу до оголошеного @property
    • Оскільки Objective-C є шаром, що перевищує стандартний C (розширення мови), точкові позначення насправді не дають зрозуміти, чи є об'єкт, до якого здійснюється доступ, об'єктом або структурою. Часто здається, що ви отримуєте доступ до властивостей структури.
    • викликаючи метод із крапковим позначенням, ви втрачаєте іменовані параметри переваги читабельності
    • коли змішані позначення повідомлень та позначення крапками здається, що ви кодуєте двома різними мовами

Приклади коду:

(1) Приклад використання складеного оператора:

//Use of compound operator on a property of an object
anObject.var += 1;
//This is not possible with standard message notation
[anObject setVar:[anObject var] + 1];

7

Використання стилю мови, що відповідає самій мові, є найкращою порадою тут. Однак це не випадок написання функціонального коду в системі ОО (або навпаки), а позначення крапками є частиною синтаксису в Objective-C 2.0.

Будь-яку систему можна зловживати. Існування препроцесора в усіх мовах на базі C достатньо, щоб робити справді досить дивні речі; просто подивіться на Конкурс Obfuscated C якщо вам потрібно точно побачити, наскільки дивно це може стати. Чи означає це, що препроцесор автоматично працює погано, і ви ніколи не повинні ним користуватися?

Використання крапкового синтаксису для доступу до властивостей, які були визначені як такі в інтерфейсі, можна зловживати. Існування зловживання в потенції не обов'язково повинно бути аргументом проти цього.

Доступ до власності може мати побічні ефекти. Це ортогонально синтаксису, який використовується для набуття цієї властивості. CoreData, делегування, динамічні властивості (first + last = full) обов'язково виконають певну роботу під ковдрами. Але це могло б сплутати 'змінні екземпляра' зі 'властивостями' об'єкта. Немає жодної причини, чому властивості повинні обов'язково зберігатись як є, особливо якщо їх можна обчислити (наприклад, довжина рядка, наприклад). Тож, використовуєте ви foo.fullName чи [foo fullName], все одно буде проводитися динамічне оцінювання.

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

Зрештою, це зводиться до питання "чи схоже це на структуру". Це, мабуть, головний диференціатор у дискусіях до цього часу; якщо у вас є структура, вона працює інакше, ніж якщо у вас є об’єкт. Але це завжди було правдою; ви не можете надіслати структуру повідомлення, і вам потрібно знати, чи воно засноване на стеку чи на посиланні / на malloc. Уже існують ментальні моделі, які відрізняються між собою з точки зору використання ([[[CGRect alloc] init] або структури CGRect?). Вони ніколи не були єдиними з точки зору поведінки; ви повинні знати, з чим маєте справу в кожному конкретному випадку. Додавання позначення властивостей для об’єктів навряд чи заплутає будь-якого програміста, який знає, які їх типи даних; а якщо цього не сталося, вони мають більші проблеми.

Що стосується послідовності; (Завдання-) C несумісний у собі. = використовується як для призначення, так і для рівності на основі лексичного положення у вихідному коді. * використовується для вказівників та множення. BOOL - це символи, а не байти (або інше ціле значення), незважаючи на те, що YES та NO становлять 1 та 0 відповідно. Послідовність або чистота - не те, для чого була розроблена мова; мова йшла про те, щоб щось зробити.

Тож якщо ви не хочете ним користуватися, не використовуйте його. Зробіть це по-іншому. Якщо ви хочете ним скористатися, і ви це розумієте, добре, якщо ви ним користуєтесь. Інші мови мають справу з поняттями загальних структур даних (карти / структури) та типів об’єктів (із властивостями), часто використовуючи однаковий синтаксис для обох, незважаючи на те, що одна є лише структурою даних, а інша - багатим об’єктом. Програмісти в Objective-C повинні мати рівноцінну здатність мати змогу мати справу з усіма стилями програмування, навіть якщо це не ваш улюблений.


6

Я використовую його для властивостей, тому що

for ( Person *person in group.people){ ... }

читається трохи легше, ніж

for ( Person *person in [group  people]){ ... }

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

Я також буду використовувати його при зміні колекції, наприклад:

[group.people addObject:another_person];

є трохи читабельнішим, ніж

[[group people] addObject:another_person];

У цьому випадку акцент повинен бути зроблений на додаванні об’єкта до масиву замість ланцюжка двох повідомлень.


5

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

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

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

**** Редагувати: Як зазначив bbum, ви також можете використовувати синтаксис крапок для виклику будь-якого методу на об’єкті (я про це не знав). Тож я скажу, що моя думка щодо dot-синтаксису стосується лише властивостей об’єкта, а не щоденного надсилання повідомлень **


3

Я набагато віддаю перевагу синтаксису обміну повідомленнями ... але саме тому, що саме про це я дізнався. Враховуючи велику кількість моїх занять, а також те, що не в стилі Objective-C 1.0, я не хотів би їх змішувати. У мене немає справжньої причини, крім "того, до чого я звик", що я не використовую синтаксис крапок ... КРИМ для цього, це зводить мене з розуму

[myInstance.methodThatReturnsAnObject sendAMessageToIt]

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

[[myInstance methodThatReturnsAnObject] sendAMessageToIt]

є читабельнішим. Але кожному своє!


3
Я роблю це час від часу, але лише якщо я отримую доступ до власності. Наприклад: [self.title lowercaseString]чи що. Але я стилістично бачу, чому це докучливо змішувати та поєднувати синтаксис. Єдина причина, по якій я це роблю, полягає в тому, щоб прямо стежити за тим, що є властивістю, а що є методом, який повертає об’єкт (я знаю, що властивості насправді є саме такими, але ви, сподіваємось, отримуєте те, що я маю на увазі).
jbrennan

3

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


3

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

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

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

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


2

На мій погляд, крапковий синтаксис робить Objective-C менш Smalltalk-esque. Це може зробити код простішим, але додає двозначності. Є чи це struct, unionчи об'єкт?


2

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

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


Як зауваження, посилання на об’єкти Objective-C не є прямими змінними конструкцій C, а скоріше нагадують „вказівники на структури”, тому синтаксис крапок не має сенсу отримувати прямий доступ до членів; саме оператор стрілки ->виконує цю роль (звичайно, коли зазначені ivars не обмежені в доступі).
Ніколас Міарі

1

Я думаю, що я міг би перейти на обмін повідомленнями замість крапкової нотації, тому що в моїй голові object.instanceVar - це просто instanceVar, який належить об’єкту , для мене це зовсім не схоже на виклик методу , який він є, тож можуть бути речі у вашому Accessor і чи використовуєте ви instanceVar або self.instanceVar може мати набагато більшу різницю, ніж просто неявний та явний. Тільки мої 2 ¢.


1

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

NSView *myView = ...;
myView.frame.size.width = newWidth;

Виглядає нормально. Але це не так. Це те саме, що

[myView frame].size.width = newWidth;

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


1

Називайте мене лінивим, але якби мені довелося набирати сингл "." проти двох [] кожного разу, щоб отримати однакові результати, я віддав би перевагу одному. Я ненавиджу багатослівні мови. () В Lisp звів мене з глузду. Елегантна мова, така як математика, є стислою та ефективною, а всі інші не підходять.


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

1

Використовуйте крапкові позначення (коли тільки можете)

На методах екземпляра, що повертають якесь значення

Не використовуйте крапкові позначення

На методах екземпляра, що повертають void, на методах init або на методі Class.

І мій особистий улюблений виняток

NSMutableArray * array = @[].mutableCopy;

0

Я особисто взагалі не використовую крапкові позначення в коді. Я використовую його лише у виразі прив'язки CoreData KVC, коли це потрібно.

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

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


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

-1

Добре, крапкові позначення в Objective-C виглядають справді дивними. Але я все ще не можу зробити без цього:

int width = self.frame.size.width;

Працює нормально, але:

int height = [[[self frame] size] height];

Дає мені "Не вдається перетворити на тип покажчика". Однак я б дуже хотів, щоб мій код відповідав нотації повідомлень.


5
-frame повертає NSRect, який є структурою C, а не об'єктом Objective-C. Ось чому другий приклад викликає помилку компілятора. Це має бути: int height = [self frame] .size.height;

1
Домовились. Але крапка після дужки виглядає жахливо! Зазвичай я спочатку зберігаю rect у локальній змінній.
Ніколас Міарі,

-2

Це чудове запитання, і я бачу багато різних відповідей на це. Хоча багато хто торкався цих тем, я спробую відповісти на це з іншого боку (деякі, можливо, зробили це неявно):

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


Чи знаєте ви, що в ObjectiveC селектори в будь-якому випадку оцінюються, вирішуються та викликаються під час виконання? Отже, позначення крапками є чистим синонімом старого стилю дужок. Технічних відмінностей взагалі немає. Ваші припущення щодо швидкості просто невірні - спробуйте, скористайтеся Інструментами і побачите себе.
До
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.