Начало / Инструкции / Getters и Setters са магии, които трябва да знаят своето място. Интерфейсен набор от данни, набор от свойства, стенограма

Getters и Setters са магии, които трябва да знаят своето място. Интерфейсен набор от данни, набор от свойства, стенограма

Джоузеф Крауфорд, един от моите читатели, прочете статия за това как не обичам да пиша гетери и сетери и ми предложи да използвам магическите методи __get и __set.
Ще ви кажа защо не е добра идея да ги използвате по обичайния начин. Също така ще ви разкажа една история, в която те наистина бяха полезни - създаване на статични типове в PHP (динамичен език).
За тези, които не са запознати с методите __get и __set, това са два "магически" метода, които работят по следния начин:
клас Животно ( функция __get($property) ( //... ) функция __set($property, $value) ( ​​​​//... ) ) $cow = ново животно; $cow->weight = "1 тон"; // същото като $cow->__set("weight", "1 ton") print $cow->weight; // същото като print $cow->__get("weight");

Обикновено горните методи се използват за създаване на динамични свойства. Какъв извод може да се направи от това? Ако искате да създадете произволни свойства, просто използвайте хеш (известен още като масив с ключове).
Какво е хубавото на гетерите и сетерите?
да видим:
клас Животно ( публичен $weightInKgs; ) $крава = ново Животно; $крава->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->свойства [$name] = $стойност;

) ) $крава = ново животно; $крава->weightInKgs = -100; //хвърля изключение
Ами ако имате клас с 10-20 свойства и проверки за тях? В този случай неприятностите са неизбежни.< 100) { throw new Exception("The weight is too small!") } if($this->публична функция __set($name, $value) ( ​​​​if($name == "тегло") ( if($value)

тегло != $тегло) ( Shepherd::notifyOfWeightChange($cow, $weight); ) ) if($name == "крака") ( if($value != 4) ( throw new Exception("Броят на краката) е твърде малко или твърде голямо") ) $this->numberOfLegs = $numberOfLegs; $this->numberOfHooves = $numberOfLegs; ) if($name == "milkType") ( .... схванахте идеята ... .) $this->properties[$name] = $value;
)< 100) { throw new Exception("The weight is too small!"); } if($this->Обратно, гетерите и сетерите са в най-добрия си вид, когато става въпрос за валидиране на данни.

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; ) публична функция getNumberOfLegs() ( return $this->numberOfLegs; ) публична функция setWeight($weight) ( if ($weight
тегло != $тегло) ( Shepherd::notifyOfWeightChange($cow, $weight); ) $this->weight = $weight;
) публична функция getWeight() (връща $this->weight; ) )
Нищо не може да се сравни с кратките функции (get, set;) от C#. Вероятно такава поддръжка скоро ще се появи в PHP, но засега нека не се отпускаме...
Отново, не съм написал всички тези гетери и сетери - PHP Storm го направи вместо мен. Просто написах следното:
клас Животно ( частно $weight; частно $numberOfLegs; )

И натисна Alt+Insert -> Getters и setters. PHPStorm генерира всичко автоматично.
Сега, като допълнително предимство на PHP Storm, когато работя с гетери и сетери, имам възможността да използвам функцията за автоматично довършване:

В случай на __get нямам тази опция, мога да напиша само това:
$крава->тегло = -100

Сега кравата „тежи“ (тегло) минус 100 кг.
Мога да забравя, че това е тегло в кг, просто напишете тегло и всичко ще работи.
Така че гетерите и сетерите могат да бъдат много полезни (но все пак не им се прекланяйте, вие не сте Java програмист). Ако просто искате безплатни свойства, използвайте масив:
$крава = масив ("тегло" => 100, "крака" => 4);

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

Реализация на интерфейс Комплекте неподредена колекция, която не може да съдържа дублирани данни.

Интерфейс Комплектвключва следните методи:

МетодОписание
добавяне (обект o) Добавяне на елемент към колекцията, ако липсва. Връща true, ако елементът е добавен.
addAll(Колекция c) Добавяне на елементи от колекцията, ако липсват.
ясно() Изчистване на колекцията.
съдържа (обект o) Проверка на наличието на елемент в набор. Връща true, ако елементът бъде намерен.
съдържа всички (Колекция c) Проверка на наличието на колекция в комплект. Връща true, ако всички елементи се съдържат в набора.
е равно (обект o) Проверка за равенство.
hashCode() Получаване на hashCode на набор.
isEmpty() Проверка на наличието на елементи. Връща true, ако в колекцията няма елементи.
итератор() Функция за получаване на итератор на колекция.
премахване (обект o) Премахване на елемент от набор.
removeAll(Колекция c) Премахва всички елементи от преминатата колекция от набора.
retainAll(Колекция c) Премахване на елементи, които не принадлежат към преминатата колекция.
размер () Брой елементи на колекцията
toArray() Преобразуване на набор в масив от елементи.
toArray(T a) Преобразуване на набор в масив от елементи. За разлика от предишния метод, който връща масив от обекти от тип Object, този метод връща масив от обекти от типа, предаден в параметъра.

За интерфейс на семейството Комплектвключват HashSet, TreeSetИ LinkedHashSet. В комплекти КомплектРазличните реализации използват различен ред на съхраняване на елементи. В 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()
  • публичен булев isEmpty()
  • публично булево добавяне (обект o)
  • public boolean addAll(Collection c)
  • публично булево премахване (обект o)
  • public boolean removeAll(Collection c)
  • public boolean съдържа (обект o)
  • публична невалидна изчистване()
  • public Object clone()
  • публичен итератор iterator()
  • публичен обект 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("Краставици"); // Следващият запис не трябва да е в набора hashSet.add("Картофи"); // Отпечатване на зададения размер на конзолата System.out.println("HashSet size = " + hashSet.size()); // Отпечатване на записи на итератора в конзолата

Трябва да видим само 4 записа в конзолата. Трябва да се отбележи, че редът, в който записите се добавят към набора, ще бъде непредвидим. HashSetизползва хеширане за ускоряване на извличането.

Пример за употреба HashSetс целочислени стойности. Добавяме стойности от 0 до 9 от 25 възможни произволно избрани стойности към набора - няма да има дублиране.

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

Трябва да се отбележи, че изпълнението HashSetне е синхронизиран. Ако множество нишки имат достъп до хеш набор едновременно и една или повече нишки трябва да променят набора, тогава той трябва да бъде външно синхронизиран. Това се прави най-добре по време на създаване, за да се предотврати случаен несинхронизиран достъп до набора:

Комплект set = Collections.synchronizedSet(нов HashSet ());

Набор от данни LinkedHashSet

Клас LinkedHashSetнаследява HashSetбез да добавя нови методи и поддържа свързан списък на елементите на набора в реда, в който са били вмъкнати. Това позволява подредено повторение на вмъкването в комплекта.

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

// Създаване на празен набор с първоначален капацитет (16) и стойност на коефициента на натоварване по подразбиране (0,75) public LinkedHashSet() // Създаване на набор от елементите на колекцията public LinkedHashSet(Collection c) // Създаване на набор с определен начален капацитет и стойност на фактора по подразбиране load (0.75) public LinkedHashSet(int initialCapacity) // Създаване на набор с посочения първоначален капацитет и фактор на натоварване public LinkedHashSet(int initialCapacity, float loadFactor)

Точно като HashSet, LinkedHashSetне е синхронизиран. Следователно, когато използвате тази реализация в приложение с много нишки, някои от които могат да правят промени в набора, синхронизирането трябва да се извърши на етапа на създаване:

Комплект set = Collections.synchronizedSet(нов LinkedHashSet ());

Набор от данни TreeSet

Клас TreeSetсъздава колекция, която използва дърво за съхраняване на елементи. Обектите се съхраняват в сортиран възходящ ред.

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

// Създаване на празен дървовиден набор, сортиран според естествения // ред на неговите елементи TreeSet() // Създаване на дървовиден набор, съдържащ елементите в посочения набор, // сортиран според естествения ред на неговите елементи. TreeSet(Колекцияc) // Създаване на празен дървовиден набор, сортиран според компаратора TreeSet(Comparatorcomparator) // Създаване на дървовиден набор, съдържащ същите елементи и използващ // същия ред като посочения сортиран набор TreeSet(SortedSet т)

Методи TreeSet

  • булево добавяне (обект o)
  • boolean addAll(Колекцияв)
  • Таван на обект (Обект o)
  • void clear()
  • TreeSet клонинг()
  • Компараторкомпаратор()
  • boolean съдържа (обект o)
  • Итератор низходящ итератор()
  • NavigableSet descendingSet()
  • Обект първи()
  • Етаж на обект (Обект o)
  • СортиранНабор слушалки (E e)
  • NavigableSet HeadSet (E e, булево включително)
  • Обект по-висок (Обект o)
  • булево isEmpty()
  • Итератор итератор()
  • Еласт()
  • E по-ниско (E e)
  • E pollFirst()
  • E pollLast()
  • булево премахване (обект o)
  • int size()
  • Сплитератор сплитер()
  • NavigableSet подмножество (E от Елемент, булево от Включително, E към Елемент, булево до Включително)
  • СортиранНабор подмножество (E от Елемент, E до Елемент)
  • СортиранНабор tailSet(E fromElement)
  • NavigableSet tailSet(E fromElement, boolean inclusive)
  • В следващия модифициран пример от използване TreeSetСтойностите ще бъдат изведени на конзолата в подредена форма.

    СортиранНабор treeSet = нов TreeSet (); treeSet.add("Цвекло"); treeSet.add("Краставици"); treeSet.add("Домати"); treeSet.add("Картофи"); treeSet.add("Морков"); // Този запис не трябва да бъде включен в набора treeSet.add("Картофи"); // Отпечатване на размера на набора в конзолата System.out.println("treeSet size = " + treeSet.size()); // Отпечатване на записи на итератора в конзолата itr = treeSet.iterator(); докато (itr.hasNext()) ( System.out.println(itr.next().toString()); ) Произволно произволно = ново Произволно(30); СортиранНабор (); for(int i = 0; i< 25; i++) iset.add(random.nextInt(10)); // Вывести в консоль записи Iteratoriset = нов TreeSet

    itr = iset.iterator(); докато (itr.hasNext()) ( System.out.println(itr.next().toString()); )

    Последна актуализация: 29.07.2018

    В допълнение към обикновените методи, езикът C# предоставя специални методи за достъп, наречени свойства. Те осигуряват лесен достъп до полетата на класа, откриват тяхната стойност или ги задават.

    Стандартното описание на свойството има следния синтаксис:

    [access_modifier] return_type custom_name ( // код на свойство )

    Например:

    Клас Person ( име на частен низ; име на обществен низ ( get ( име на връщане; ) set ( име = стойност; ) ) )

    Чрез това свойство можем да контролираме достъпа до променливата име. Стандартната дефиниция на свойство съдържа блокове get и set. В блока get връщаме стойността на полето, а в блока set я задаваме. Параметърът стойност представлява стойността, която трябва да бъде предадена.

    Можем да използваме това свойство по следния начин:

    Лице p = ново лице(); // Задаване на свойството - блокът Set се задейства // стойността "Tom" е стойността, предадена на свойството p.Name = "Tom"; // Вземете стойността на свойството и го присвоете на променлива - блокът Get string personName = p.Name се задейства;

    Може би може да възникне въпросът защо се нуждаем от свойства, ако можем да минем с обикновени полета на класа в тази ситуация? Но свойствата ви позволяват да добавите допълнителна логика, която може да е необходима, например, когато присвоявате стойност на променлива на клас. Например трябва да настроим проверка на възрастта:

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

    Блоковете set и get не трябва да присъстват в свойство едновременно. Ако дадено свойство е дефинирано само от блок get, тогава свойството е само за четене - можем да получим стойността му, но не и да я зададем. Обратно, ако дадено свойство има само зададен блок, тогава това свойство може да се записва само - можете само да зададете стойността, но не можете да я получите:

    Клас Person ( частно име на низ; // свойство само за четене public string Name ( get ( return name; ) ) private int age; // свойство само за запис public int Age ( set ( age = value; ) ) )

    Модификатори за достъп

    Можем да прилагаме модификатори за достъп не само към цялото свойство, но и към отделни блокове - получаване или задаване:

    Клас Лице (частно име на низ; име на публичен низ (вземете (връщане на име; ) частен набор (име = стойност; )) публично лице (име на низ, int възраст) (Име = име; Възраст = възраст; ) )

    Сега можем да използваме затворения блок само в този клас - в неговите методи, свойства, конструктор, но не и в друг клас:

    Лице p = ново лице ("Том", 24); // Грешка - наборът е деклариран с частния модификатор //p.Name = "John"; Console.WriteLine(p.Name);

    Когато използвате модификатори в свойствата, трябва да имате предвид редица ограничения:

      Модификатор за блок set или get може да бъде зададен, ако свойството има блокове set и get.

      Само един set или get блок може да има модификатор за достъп, но не и двата

      Модификаторът за достъп на блок set или get трябва да бъде по-рестриктивен от модификатора за достъп на свойство. Например, ако дадено свойство има модификатора public, тогава блокът set/get може да има само модификаторите protected internal, internal, protected, private

    Капсулиране

    Видяхме по-горе, че достъпът до променливите на частния клас се установява чрез свойства. Скриването на състоянието на клас от външна намеса по този начин представлява механизма на капсулиране, който представлява една от ключовите концепции на обектно-ориентираното програмиране. (Заслужава да се отбележи, че самата концепция за капсулиране има доста различни интерпретации, които не винаги се припокриват една с друга) Използването на модификатори за частен достъп защитава променлива от външен достъп. За да контролират достъпа, много езици за програмиране използват специални методи, гетери и сетери. В C# тяхната роля обикновено се играе от свойствата.

    Например, има клас Account, който има поле за сума, което представлява сума:

    Акаунт на клас ( public int sum; )

    Тъй като променливата сума е публична, можем да я осъществим навсякъде в програмата и да я променим, включително да зададем невалидна стойност, например отрицателна. Малко вероятно е подобно поведение да е желателно. Следователно капсулирането се използва за ограничаване на достъпа до променливата sum и скриването й в класа:

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

    Автоматични свойства

    Свойствата контролират достъпа до полетата на даден клас. Но какво, ако имаме дузина или повече полета, тогава дефинирането на всяко поле и писането на свойство от същия тип за него би било досадно. Поради това бяха добавени автоматични свойства към .NET framework. Имат съкратена декларация:

    Клас Лице ( публичен низ Име ( get; set; ) public int Възраст ( get; set; ) public Person ( низ име, int възраст) ( Име = име; Възраст = възраст; ) )

    Всъщност тук също се създават полета за свойства, само че те не се създават от програмиста в кода, а компилаторът автоматично ги генерира по време на компилация.

    Какво е предимството на автоматичните свойства, ако по същество те просто имат достъп до автоматично създадена променлива, защо да не имат директен достъп до променлива без автоматични свойства? Факт е, че по всяко време, ако е необходимо, можем да разширим автоматичното свойство в редовно свойство и да добавим някаква специфична логика към него.

    Струва си да се има предвид, че не можете да създадете автоматично свойство само за запис, какъвто е случаят със стандартните свойства.

    На автоматичните свойства могат да бъдат присвоени стойности по подразбиране (инициализиране на автоматични свойства):

    Клас Лице ( public string Name ( get; set; ) = "Tom"; public int Age ( get; set; ) = 23; ) class Program ( static void Main(string args) ( Person person = new Person(); Console .WriteLine(person.Name); // Tom Console.WriteLine(person.Age); // 23 Console.Read();

    И ако не посочим стойностите на свойствата Име и Възраст за обекта Person, тогава ще се прилагат стойностите по подразбиране.

    Автоматичните свойства също могат да имат модификатори за достъп:

    Клас Лице ( публичен низ Име ( частен набор; get;) публичен Лице (низ n) ( Име = n; ) )

    Можем да премахнем зададения блок и да направим свойството auto само за четене. В този случай, за да се съхрани стойността на това свойство, поле с модификатора само за четене ще бъде имплицитно създадено за него, така че трябва да се има предвид, че такива get-свойства могат да бъдат зададени или от конструктора на класа, както в примера по-горе или при инициализиране на свойството:

    Клас Лице (обществено име на низ (get;) = "Том")

    Стенограма за свойства

    Точно като методите, можем да съкратим свойствата. Например:

    Клас Person ( частно име на низ; // еквивалентно на публичен низ Име ( get ( върнато име; ) ) публичен низ Име => име; )

    За да контролирате как се използват полетата, можете да създадете get и set методи и да ги направите публични. Те предоставят възможност за контрол на достъпа до дадено поле. В същото време е по-добре да направите полето Възраст частно, така че да не може да бъде директно достъпно извън класа.

    публичен клас акаунт

    частна инт възраст;

    public int GetAge()

    върне this.age;

    public void SetAge(int inAge)

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

    this.age = inAge;

    Сега можем да контролираме достъпа до нашето поле, но това изисква писане на много допълнителен код. За да получите достъп до стойността на възрастта, трябва да извикате създадените методи:

    Акаунт s = нов акаунт();

    Console.WriteLine("Възраст: " + s.GetAge());

      1. Използване на свойства

    Свойствата улесняват управлението на данните. Свойството Age може да бъде декларирано по следния начин:

    публичен клас акаунт

    private int ageValue;

    if ((стойност > 0) && (стойност< 120))

    ageValue = стойност;

    връща възрастова стойност;

    Тук стойността на възрастта е свойство. Свойството декларира секции за писане и четене на неговата стойност. Действията, описани в тези раздели, са еквивалентни на описаните по-горе методи. В този случай свойствата се използват по същия начин като обикновените полета:

    Акаунт s = нов акаунт();

    Console.WriteLine("Възраст: " + s.Възраст);

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

    Проверка коректността на данните в свойствата . Ако се опитате да зададете невалидна стойност за възраст (например 150), горният код ще извърши проверка за валидност и ще отхвърли стойността (никой над 120 не може да има сметка в нашата банка), оставяйки стойността за старост. Единственият начин да разберете дали на дадено свойство е присвоена стойност е да проверите стойността на свойството след тази операция:

    Акаунт s = нов акаунт();

    int newAge = 150;

    ако (s.Age != newAge)

    Console.WriteLine("Стойността за възрастта не е зададена");

    Следният код се опитва да зададе възраст на невалидна стойност от 150 и след това проверява дали тази стойност е зададена. Ако методът Set се използва за присвояване на стойността, той може да върне стойността невярно Ако това не успее, използването на свойството изисква от потребителя да направи малко повече допълнителна работа.

    Различни начини за четене на стойност на свойство. Свойствата ви позволяват да извършвате други полезни действия.

    public int AgeInMonths

    върне this.ageValue * 12;

    Новото свойство AgeInMonths е описано тук. Той е само за четене, защото не съдържа зададен раздел. Връща стойността на възрастта в месеци, като използва същата стойност като свойството Възраст. Това означава, че можете да използвате няколко различни метода, за да получите една и съща стойност. Възможно е да създавате свойства само за четене, без да можете да ги променяте директно, както и свойства само за запис, въпреки че последните се използват рядко.

    Свойства на визуалните елементи . Има смисъл да използвате свойства в описанието на банкова сметка, където трябва да защитите данните в обекти. Но в Silverlight можете да въведете произволен текст в елемент TextBlock и изглежда няма нужда да проверявате валидността на въведената стойност. Изпълнението на този код ще забави процеса на въвеждане на стойност. Така че, като направим текстовата стойност публичен низ, програмата ще съдържа по-малко код и ще работи по-бързо.

    Но когато променим текста в елемента TextBlock, искаме текстът на страницата Silverlight също да се промени, например когато програмата Adder покаже резултата. Ако програмата просто промени стойността на полето, Silverlight няма как да разбере, че съобщението на екрана трябва да се актуализира.

    Въпреки това, ако Text е направено като свойство, когато стойността на елемента TextBlock се актуализира, ще се изпълни съответният метод, който може да актуализира съхранената стойност на текстовото поле и да извика метод за актуализиране на екрана, за да покаже новата стойност. Свойствата предоставят възможност за манипулиране на обект, когато стойността му се промени. Проста операция:

    resultTextBlock.Text = "0";

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