Php магічні методи get. Getter'и та Setter'и — магія, яка має знати своє місце

Щоб контролювати використання полів, можна створити методи getта set і зробити їх загальнодоступними. Вони дають можливість керувати доступом до поля. При цьому поле Age краще зробити закритим (private), щоб до нього не можна було отримати прямий доступ за межами класу.

public class Account

private int age;

public int GetAge()

return this.age;

public void SetAge(int inAge)

if ((inAge > 0) && (inAge< 120))

this.age = inAge;

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

Account s = new Account();

Console.WriteLine("Вік:" + s.GetAge());

    1. Використання властивостей

Властивості дозволяють зробити керування даними простішим. Властивість Age можна оголосити так:

public class Account

private int ageValue;

if ((value > 0) && (value< 120))

ageValue = value;

return ageValue;

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

Account s = new Account();

Console.WriteLine("Вік:" + s.Age);

Коли властивості Age надається значення, викликається код секції set. Ключове слово value означає значення, яке надається властивості. Під час читання значення властивості Age відбувається виклик коду секції get. Такий підхід поєднує переваги використання методів та дозволяє працювати з властивостями так само просто, як і з полями класу.

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

Account s = new Account();

int newAge = 150;

if (s.Age! = newAge)

Console.WriteLine("Значення віку не було встановлено");

Наведений код намагається привласнити віку неприпустиме значення 150, після чого перевіряється, чи це значення встановлено. Якби для надання значення використовувався метод Set, він міг би повернути значення false у разі невдачі, а при використанні властивості користувач повинен виконати трохи більше додаткової роботи.

Різні способи зчитування значення якості. Властивості дозволяють виконувати інші корисні дії.

public int AgeInMonths

return this.ageValue * 12;

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

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

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

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

resultTextBlock.Text = "0";

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

Останнє оновлення: 29.07.2018

Окрім звичайних методів у мові C# передбачені спеціальні методи доступу, які називають властивості . Вони забезпечують простий доступ до полів класу, дізнатися їх значення чи виконати їхню установку.

Стандартний опис властивості має наступний синтаксис:

[модифікатор_доступу] повертається_тип довільна_назва ( // код властивості )

Наприклад:

Class Person ( private string name ; public string Name ( get ( return name ; ) set ( name = value ; ) ) )

Тут у нас є закрите поле name і є загальнодоступна властивість Name. Хоча вони мають практично однакову назву за винятком регістру, але це не більше ніж стиль, назви у них можуть бути довільними і не обов'язково повинні збігатися.

Через цю властивість ми можемо керувати доступом до змінної name. Стандартне визначення властивості містить блоки get та set. У блоці get ми повертаємо значення поля, а блоці set встановлюємо. Параметр value представляє значення, що передається.

Ми можемо використовувати цю властивість таким чином:

Person p = new Person(); // Встановлюємо властивість - спрацьовує блок Set // значення "Tom" і є передається у властивість value p.Name = "Tom"; // Отримуємо значення якості та присвоюємо його змінної - спрацьовує блок Get string personName = p.Name;

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

Class Person (private int age; public int Age (set (if (value)< 18) { Console.WriteLine("Возраст должен быть больше 17"); } else { age = value; } } get { return age; } } }

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

Class Person (private string name; // властивість тільки для читання public string Name (get (return name;)) private int age; // властивість тільки для запису

Модифікатори доступу

Ми можемо застосовувати модифікатори доступу не тільки до всієї властивості, але і до окремих блоків - або get, або set:

Class Person ( private string name ; public string Name ( get ( return name ; ) private set ( name = value ; ) )

Тепер закритий блок set ми зможемо використовувати тільки в даному класі - у його методах, властивостях, конструкторі, але ніяк не в іншому класі:

Person p = new Person("Tom", 24); // Помилка - set оголошений з модифікатором private // p.Name = "John"; Console.WriteLine(p.Name);

При використанні модифікаторів у властивостях слід враховувати низку обмежень:

    Модифікатор для блоку set або get можна встановити, якщо властивість має обидва блоки (і set, і get)

    Тільки один блок set або get може мати модифікатор доступу, але не обидва відразу

    Модифікатор доступу блоку set або get повинен бути більшим, ніж модифікатор доступу властивості. Наприклад, якщо властивість має модифікатор public, то блок set/get може мати тільки модифікатори protected internal, internal, protected, private

Інкапсуляція

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

Наприклад, є певний клас Account, у якому визначено поле sum, що становить суму:

Class Account (public int sum;)

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

Class Account ( private int sum; public int Sum ( get (return sum;) set ( if (value > 0) ( sum=value; ) ) ) ) )

Автоматичні властивості

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

Class Person ( public string Name ( get; set; ) public int Age ( get; set; ) Public Person (string name, int age)

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

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

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

Автовластивості можна присвоїти значення за замовчуванням (ініціалізація автовластивостей):

Class Person ( public string Name ( get; set; ) = "Tom"; public int Age ( get; set; ) = 23; ) class Program ( .WriteLine(person.Name); // Tom Console.WriteLine(person.Age); // 23 Console.Read() )

І якщо ми не вкажемо для об'єкта Person значення властивостей Name та Age, то діятимуть значення за замовчуванням.

Автовластивості також можуть мати модифікатори доступу:

Class Person (public string Name (private set; get;) Public Person(string n) (Name = n;))

Ми можемо прибрати блок set і зробити автовластивість доступною тільки для читання. В цьому випадку для зберігання значення цієї властивості для нього неявно буде створюватися поле з модифікатором readonly, тому слід враховувати, що подібні get-властивості можна встановити або з конструктора класу, як у прикладі вище, або при ініціалізації властивості:

Class Person ( public string Name ( get;) = " Tom " )

Скорочений запис властивостей

Як і методи, ми можемо скорочувати властивості. Наприклад:

Class Person (private string name; // еквівалентно public string Name (get (return name;)) public string Name => name;)

Реалізація інтерфейсу Setє невпорядкованою колекцією, яка не може містити дублюючі дані.

Інтерфейс Setвключає такі методи:

МетодОпис
add(Object o) Додавання елемента до колекції, якщо він відсутній. Повертає true, якщо елемент додано.
addAll(Collection c) Додавання елементів колекції, якщо їх немає.
clear() Очищення колекції.
contains(Object o) Перевірте наявність елемента в наборі. Повертає true, якщо елемент знайдено.
containsAll(Collection c) Перевірка присутності колекції в наборі. Повертає true, якщо всі елементи містяться у наборі.
equals(Object o) Перевірка на рівність.
hashCode() Отримання набору hashCode.
isEmpty() Перевірити наявність елементів. Повертає true, якщо в колекції немає жодного елемента.
iterator() Функція отримання колекції ітератора.
remove(Object o) Видалення елемента з набору.
removeAll(Collection c) Видалення всіх елементів переданої колекції.
retainAll(Collection c) Видалення елементів, що не належать до переданої колекції.
size() Кількість елементів колекції
toArray() Перетворення набору в масив елементів.
доArray(T a) Перетворення набору в масив елементів. На відміну від попереднього методу, який повертає масив об'єктів типу Object, даний методповертає масив об'єктів типу, переданого параметрі.

До сімейства інтерфейсу Setвідносяться HashSet, TreeSetі LinkedHashSet. У множинах SetРізні реалізації використовують різний порядок зберігання елементів. У HashSet порядок елементів оптимізовано для швидкого пошуку. У контейнері TreeSet об'єкти зберігаються відсортованими за зростанням. LinkedHashSet зберігає елементи як додавання.

Набір даних HashSet

Конструктори HashSet:

// Створення порожнього набору з початковою ємністю (16) і // значенням коефіцієнта завантаження (0.75) за умовчанням public HashSet(); // Створення безлічі елементів колекції public HashSet(Collection c); // Створення множини із зазначеною початковою ємністю та зі // значенням коефіцієнта завантаження за умовчанням (0.75) public HashSet(int initialCapacity); // Створення безлічі із зазначеними початковою ємністю та // коефіцієнтом завантаження public HashSet(int initialCapacity, float loadFactor);

Методи HashSet

  • public int size()
  • public boolean isEmpty()
  • public boolean add(Object o)
  • public boolean addAll(Collection c)
  • public boolean remove(Object o)
  • public boolean removeAll(Collection c)
  • public boolean contains(Object o)
  • public void clear()
  • public Object clone()
  • public Iterator iterator()
  • public Object toArray()
  • public boolean retainAll(Collection c)

HashSetмістить методи аналогічно ArrayList. Винятком є ​​метод add(Object o), який додає об'єкт тільки у тому випадку, якщо він відсутній. Якщо об'єкт доданий, то метод add повертає значення - true, інакше false.

Приклад використання HashSet:

HashSet hashSet = новий HashSet (); hashSet.add("Картопля"); hashSet.add("Морква"); hashSet.add("Буряк"); hashSet.add("Огірки"); itr = hashSet.iterator(); while (itr.hasNext()) ( System.out.println(itr.next().toString()); )

У консолі ми повинні побачити лише 4 записи. Слід зазначити, що порядок додавання записів до набору буде непередбачуваним. HashSetвикористовує хешування для прискорення вибірки.

Приклад використання HashSetз цілими значеннями. У набір додаємо значення від 0 до 9 із 25 можливих випадковим чином вибраних значень - дублювання не буде.

Random random = New Random (30); Set iset = новий HashSet (); for(int i = 0; i< 25; i++) iset.add(random.nextInt(10)); // Вывести в консоль записи Iterator

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

Set set = Collections.synchronizedSet(new HashSet ());

Набір даних LinkedHashSet

Клас LinkedHashSetуспадковує HashSet, не додаючи нових методів, і підтримує зв'язковий список елементів набору в тому порядку, в якому вони вставлялися. Це дозволяє організувати впорядковану ітерацію вставки набір.

Конструктори LinkedHashSet:

// Створення порожнього набору з початковою ємністю (16) та зі значенням коефіцієнта завантаження (0.75) за умовчанням public LinkedHashSet() // Створення безлічі з елементів колекції public LinkedHashSet(Collection c) // Створення множини із зазначеною початковою ємністю та значення завантаження за замовчуванням (0.75) public LinkedHashSet(int initialCapacity) // Створення безлічі із зазначеними початковою ємністю та коефіцієнтом завантаження public LinkedHashSet(int initialCapacity, float loadFactor)

Так само як і HashSet, LinkedHashSetне синхронізується. Тому при використанні цієї реалізації в додатку з безліччю потоків, частина з яких може вносити зміни до набору, слід на етапі створення виконати синхронізацію:

Set set = Collections.synchronizedSet(new LinkedHashSet ());

Набір даних TreeSet

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

Конструктори TreeSet:

// Створення порожнього деревоподібного набору, із сортуванням згідно з природним // упорядкування його елементів TreeSet() // Створення деревоподібного набору, що містить елементи в зазначеному наборі, // з сортуванням згідно з природним упорядкуванням його елементів. TreeSet(Collectionc) // Створення порожнього деревоподібного набору, із сортуванням згідно з comparator TreeSet(Comparatorcomparator) // Створення деревоподібного набору, що містить ті ж елементи і використовує // те саме впорядкування як зазначеного сортованого набору TreeSet (SortedSet s)

Методи TreeSet

  • boolean add(Object o)
  • boolean addAll(Collectionc)
  • Object ceiling(Object o)
  • clear()
  • TreeSet clone()
  • Comparatorcomparator()
  • boolean contains(Object o)
  • Iterator descendingIterator()
  • NavigableSet descendingSet()
  • Object first()
  • Object floor(Object o)
  • SortedSet headSet(E e)
  • NavigableSet headSet(E e, boolean inclusive)
  • Object higher(Object o)
  • boolean isEmpty()
  • Iterator iterator()
  • E last()
  • E lower(E e)
  • E pollFirst()
  • E pollLast()
  • boolean remove(Object o)
  • int size()
  • Spliterator spliterator()
  • NavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)
  • SortedSet subSet(E fromElement, E toElement)
  • SortedSet tailSet(E fromElement)
  • NavigableSet tailSet(E відElement, boolean inclusive)
  • У наступному зміненому прикладі використання TreeSetу консоль будуть виведені значення упорядкованому вигляді.

    SortedSet treeSet = новий TreeSet (); treeSet.add("Буряк"); treeSet.add("Огірки"); treeSet.add("Помідори"); treeSet.add("Картопля"); treeSet.add("Морква"); // Даний запис не повинен потрапити в набір treeSet.add("Картопля"); // Вивести в консоль розмір набору System.out.println("Розмір treeSet =" + treeSet.size()); // Вивести в консоль запису Iterator itr = treeSet.iterator(); while (itr.hasNext()) ( System.out.println(itr.next().toString()); ) Random random = new Random(30); SortedSet (); for(int i = 0; i< 25; i++) iset.add(random.nextInt(10)); // Вывести в консоль записи Iteratoriset = новий TreeSet

    itr = iset.iterator(); while (itr.hasNext()) ( System.out.println(itr.next().toString()); )
    Джозеф Кроуфорд, один із моїх читачів, прочитав статтю про те, як я не люблю писати getter'и та setter'и і припустив, що я можу використовувати чарівні методи __get та __set.
    Я скажу вам, чому це не дуже хороша ідея використовувати їх звичайним способом. Крім того, я збираюся розповісти вам історію, де вони дійсно виявилися корисними, - йтиметься про створення статичних типів у PHP (динамічна мова).
    Для тих, хто не знайомий з методами __get і __set - це два магічні методи, які працюють наступним чином:

    Як правило, перераховані вище методи використовуються для створення динамічних властивостей. Який висновок можна із цього зробити? Якщо ви хочете створювати будь-які випадкові властивості, просто використовуйте хеш (він же масив із ключами).
    Що ж гарного у getter'ах та setter'ах?
    Давайте подивимося:
    class Animal (public $weightInKgs;) $cow = new Animal; $cow->weightInKgs = -100;

    Що? Вага із негативним значенням? Це з більшості поглядів неправильно.
    Корова не повинна важити менше ніж 100 кг (я так думаю:). У межах 1000 – допустимо.
    Як нам забезпечити таке обмеження.
    Використовувати __get та __set - досить швидкий спосіб.
    class Animal ( private $properties = array(); public function __get($name) ( if(!empty($this->properties[$name]))) ( return $this->properties[$name]; ) else ( throw new Exception("Undefined property ".$name." referenced.") ) ) public function __set($name, $value) ( ​​if($name == "weight") ( if($value)< 100) { throw new Exception("The weight is too small!") } } $this->properties[$name] = $value;

    )) $ cow = new Animal; $cow->weightInKgs = -100; // throws an Exception
    А якщо у вас є клас з 10-20 властивостями і перевірками для них? І тут неприємності неминучі.< 100) { throw new Exception("The weight is too small!") } if($this->public function __set($name, $value) ( ​​if($name == "weight") ( if($value)

    weight != $weight) ( Shepherd::notifyOfWeightChange($cow, $weight); ) ) if($name == "legs") ( if($value != 4) ( throw new Exception("The number of legs is too little or too big") ) $this->numberOfLegs = $numberOfLegs; $this->numberOfHooves = $numberOfLegs; ) if($name == "milkType") ( .... you get the idea ... . ) $this->properties[$name] = $value;
    )< 100) { throw new Exception("The weight is too small!"); } if($this->І навпаки, getter'и та setter'и виявляють себе з кращого боку, коли справа доходить до перевірки даних.

    class Animal ( private $weight; private $numberOfLegs; private $numberOfHooves; public $nickname; public function setNumberOfLegs($numberOfLegs) ( if ($numberOfLegs != 100) ( throw new exception big"); ) $this->numberOfLegs = $numberOfLegs; $this->numberOfHooves = $numberOfLegs; )
    Кожен метод відповідає тільки за власну область, завдяки цьому в коді легше орієнтуватися. Все одно виходить занадто багато коду, але він чистіший, ніж __set-версія. Існує добрий евристичний підхід, який полягає в наступному: якщо ваш метод (функція) займає більше, ніж 1 екран – потрібно скорочувати. Це покращить сприйняття коду.
    Ми також зберігаємо деяку бізнес-логіку. Копит завжди буде рівно стільки, скільки і ніг, а якщо ми помітимо зміну ваги худоби, ми відразу повідомимо пастуха.
    Так як ми не дбаємо про прізвиська корів і не перевіряємо їх, нехай дані будуть загальнодоступними без getter'ів та setter'ів.
    Знову ж таки, я дійсно не писав всіх цих getter'ів і setter'ів - PHP Storm зробив це за мене. Я просто написав таке:
    class Animal ( private $weight; private $numberOfLegs; )

    І натиснув Alt+Insert -> Getters and setters. PHPStorm згенерував все автоматично.
    Тепер у вигляді додаткової переваги PHP Storm при роботі з getter'ами та setter'ами у мене є можливість використовувати функцію автозаповнення:

    У випадку __get я не маю такої можливості, я можу лише написати це:
    $cow->wieght = -100

    Тепер корова "важить" (wIEghts) мінус 100 кг.
    Я можу забути, що це вага в кг, досить просто написати weight – все буде працювати.
    Отже, getter'и and setter'и бувають дуже корисні (але все ж таки не поклоняйтеся їм, ви ж не програміст Java). Якщо вам просто потрібні вільні властивості, використовуйте масив:
    $cow = array("weight" => 100, "legs" => 4);

    Цей трюк набагато легше провернути, ніж __get та __set.
    Але якщо ви хочете бути впевненим, що ваші дані завжди мають тільки допустимі значення, використовуйте setter'и з перевіркою. За наявності інтегрованого середовища розробки (IDE), типу PHP Storm, ви любитимете setter'и, тому що вони дуже прості у використанні. Замість $cow->setLegs() для PHP Storm достатньо буде набрати cosl. Та легко! Немає більше друкарських помилок, і ви можете бачити, які параметри приймає метод.
    Метод __set має ще один недолік. Він приймає лише один параметр. Що робити, якщо вам потрібно 2? Наприклад, як тут: $store1->setPrice("item-1", 100). Вам необхідно встановити ціну товару у магазині. Метод __set не дозволить вам цього зробити, а setter дозволить.

    mob_info