Обектно-ориентираното програмиране (OOP) е програмна парадигма, организирана около обекти. На високо ниво ООП е свързано с възможността за структуриране на код, така че неговата функционалност да може да се споделя в цялото приложение. Ако се направи правилно, ООП може да доведе до много елегантно написани програми, които имат минимално дублиране на код.

Това се противопоставя на процедурното програмиране (PP), при което създавате програми в последователен ред и извиквате методи, когато искате споделено поведение между страниците в приложението. Общите процедурни езици за програмиране включват C и Go.

В този урок ще научите основните концепции на ООП за Ruby, обектно-ориентиран език за програмиране, в който всичко е обект. Ще използваме Ruby, тъй като един от определящите му атрибути — в допълнение към елегантния си синтаксис и четливост — е как прилага ООП техники. Това го прави чудесен език, с който да започнете да изучавате ООП.

Ние ще покрием:

  • Създаване на класове
  • Инстанциране на обекти
  • Инициализиране на аргументи
  • Работа с наследяване и
  • Частни и обществени методи.

При изучаването на тези концепции ще изградим собствено приложение: API конектор, който комуникира динамично с приложение, което изпраща текстово съобщение. Това ще включва разглеждане на това как да използваме концепции като наследяване и създаване на обекти, за да направим нашия код по-мащабируем и многократно използваем!

Този кратък урок е адаптиран от курса „Въведение в Ruby“ на Next Tech, който включва среда в пясъчна среда в браузъра и интерактивни задачи с автоматична проверка за изпълнение.

Можете да следвате кодовите фрагменти в този урок, като използвате пясъчника на Next Tech, който вече има предварително инсталиран Ruby. Ако изберете да използвате своя собствена IDE, уверете се, че Ruby е инсталиран, като следвате инструкциите на страницата за инсталиране.

Създаване на класове

Преди да започнем, нека дефинираме какво е обект. В основата си обектът е самостоятелна част от код, която съдържа данни („атрибути“) и поведение („методи“) и може да комуникира с други обекти. Обекти от същия тип се създават от класове, които действат като чертежи, които дефинират свойства и поведение.

Създаването на клас в Ruby е доста лесно. За да дефинирате клас, просто въведете думата class, последвана от името на класа, и го завършете с думата end. Всичко, което се съдържа между class и end, принадлежи към този клас.

Имената на класовете в Ruby имат много специфични изисквания за стил. Те трябва да започват с буква и ако представляват няколко думи, всяка нова дума също трябва да бъде главна буква — т.е. „CamelCase“.

Ще започнем със създаването на клас, наречен ApiConnector:

Класовете в Ruby могат да съхраняват както данни, така и методи. В много традиционни ООП езици като Java, трябва да създадете два метода за всеки елемент от данни, който искате да бъде включен в класа. Един метод, setter, задава стойността в класа. Другият метод, getter, ви позволява да извлечете стойността.

Процесът на създаване на методи за настройка и получаване за всеки атрибут на данни може да бъде уморителен и да доведе до невероятно дълги дефиниции на класове. За щастие Ruby разполага с набор от инструменти, наречени аксесори на атрибути.

Нека внедрим някои сетери и гетери за някои нови елементи от данни за нашия клас. Тъй като това е API конектор, би имало смисъл да има елементи от данни като title, description и url. Можем да добавим тези елементи със следния код:

Когато просто създадете клас, той не прави нищо - това е просто дефиниция. За да работим с класа, трябва да създадем негов екземпляр... ще разгледаме това по-нататък!

Инстанция

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

Планът на къщата действителната къща ли е? Разбира се, че не, той просто изброява атрибутите и дизайнерските елементи за това как ще бъде създаден домът. Така че, след като планът е завършен, действителният дом може да бъде построен - или "инстанциран".

Както беше обяснено в предишния раздел, в ООП класът е планът за обект. Той просто описва как ще изглежда даден обект и как ще се държи. Следователно инстанцирането е процес на вземане на дефиниция на клас и създаване на обект, който можете да използвате в програма.

Нека създадем нов екземпляр на нашия клас ApiConnector и да го съхраним в променлива, наречена api:

Сега, когато имаме създаден обект, можем да използваме променливата api, за да работим с атрибутите на класа. Например, можем да изпълним кода:

[Out:]
https://next.tech

В допълнение към създаването на атрибути, можете също да създавате методи в рамките на клас:

За достъп до този метод можем да използваме същия синтаксис, който използвахме с атрибутите за достъп:

Събирайки всичко това заедно, изпълнението на пълния код на класа по-долу ще доведе до отпечатването на съобщението url и test_method:

[Out:]
"https://next.tech"
"testing class call"

Метод на инициализиране

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

Нека актуализираме нашия ApiConnector, за да използваме метод за инициализиране:

В рамките на метода initialize създадохме променлива на екземпляр за всеки от параметрите, така че да можем да използваме тези променливи и в други части на приложението.

Премахнахме и метода attr_accessor, тъй като новият метод initialize ще се погрижи за това вместо нас. Ако имате нужда от възможността да извиквате елементите от данни извън класа, тогава пак ще трябва да имате извикването attr_accessor на място.

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

Накрая ще създадем класа и ще тестваме метода за инициализация:

[Out:]
"My title"
"My cool description"
"https://next.tech"

Работа с незадължителни стойности

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

Сега нашата програма ще има същия изход, дори ако не подадем стойността url, докато създаваме нов екземпляр на класа:

Използване на именувани аргументи

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

За да избегнете това объркване, можете да използвате именувани аргументи, като това:

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

Замяна на стойностите по подразбиране

Какво се случва, ако искаме да заменим стойност по подразбиране? Ние просто актуализираме нашето инстанционно повикване по следния начин:

Тази актуализация ще замени нашата стойност по подразбиране от https://next.tech и извикването на api.testing_initializer сега ще отпечата https://next.xyz като URL.

Наследство

Сега ще научим за важен обектно-ориентиран принцип, наречен наследяване. Преди да разгледаме как се изпълнява в Ruby, нека видим защо е важен за изграждането на приложения.

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

Вземете нашия ApiConnector клас. Да кажем, че имаме различни API класове за различни платформи, но всеки клас споделя редица общи данни или процеси. Вместо да дублираме код във всеки от класовете на API конектора, можем да имаме един родителски клас със споделените данни и методи. Оттам можем да създадем дъщерни класове от този родителски клас. С начина, по който работи наследяването, всеки от дъщерните класове ще има достъп до компонентите, предоставени от родителския клас.

Да речем например, че имаме три API: SmsConnector, PhoneConnector и MailerConnector. Ако напишем код поотделно за всеки от тези класове, ще изглежда така:

Както можете да видите, ние просто повтаряме един и същ код в различни класове. Това се счита за лоша програмна практика, която нарушава принципа на разработка „DRY“ (не повтаряй себе си). Вместо това можем да направим родителски клас ApiConnector и всеки от другите класове може да наследи общата функционалност от този клас:

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

Синтаксисът за използване на наследяването е да се дефинира името на дъщерния клас, последвано от символа <, след това името на родителския клас — т.е. нашите SmsConnector, MailerConnector и PhoneConnector класове наследяват от ApiConnector класа.

Всеки от тези дъщерни класове вече има достъп до пълния набор от елементи, предоставени в родителския клас ApiConnector. Например, ако създадем нов екземпляр на SmsConnector със следните параметри, можем да извикаме метода send_sms:

[Out:]
Sending SMS message with the title 'Hi there!' and description 'I'm an SMS message'.

Основно правило в ООП е да се гарантира, че даден клас изпълнява една единствена отговорност. Например класът ApiConnector не трябва да изпраща SMS съобщения, да извършва телефонни обаждания или да изпраща имейли, тъй като това биха били три основни отговорности.

Частни и публични методи

Преди да се потопим в частните и публичните методи, нека първо да се върнем към нашия оригинален клас ApiConnector и да създадем клас SmsConnector, който наследява от ApiConnector. В този клас ще създадем метод, наречен send_sms, който ще изпълнява скрипт, който се свързва с API:

Този метод ще изпрати title и url към API, който на свой ред ще изпрати SMS съобщение. Сега можем да инстанцираме класа SmsConnector и да извикаме съобщението send_sms:

Изпълнението на този код ще се свърже с API на SMS и ще изпрати съобщението. Можете да отидете в края на тази страница, за да видите вашето съобщение!

Сега, използвайки този пример, нека обсъдим типовете методи, предоставени от класовете.

Методът send_sms е публичен метод. Това означава, че всеки, който работи върху нашия клас, може да комуникира с този метод. Това може да не изглежда като голяма работа, ако работите върху приложение, върху което никой друг не работи. Въпреки това, ако създавате API или кодова библиотека, която е с отворен код за използване от други, жизненоважно е вашите публични методи да представляват елементи от функционалност, които всъщност искате други разработчици да използват.

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

И така, ако не можете да промените публичните методи, как можете да работите върху производствено приложение? Тук се намесват частните методи. Частният метод е метод, до който има достъп само класът, в който се съдържа. Той никога не трябва да се извиква от външни услуги. Това означава, че можете да промените поведението им, като приемем, че тези промени нямат ефект на доминото и променят публичните методи, от които могат да бъдат извикани.

Обикновено частните методи се поставят в края на файла след всички публични методи. За да обозначим частни методи, използваме думата private над списъка с методи. Нека добавим частен метод към нашия клас ApiConnector:

Забележете как извикваме този метод от вътрешността на метода initialize на класа ApiConnector? Ако стартираме този код, той ще даде следния резултат:

[Out:]
A secret message from the parent class

Сега дъщерните класове имат достъп до методите в родителския клас, нали? Е, не винаги. Нека премахнем метода secret_method от метода initialize в ApiConnector и се опитаме да го извикаме от нашия дъщерен клас SmsConnector, както е показано тук:

[Out:]
Traceback (most recent call last):
main.rb:29:in `<main>': private method `secret_method' called for #SmsConnector:0x000056188cfe19b0> (NoMethodError)

Това е така, защото класът SmsConnector има достъп само до публичните методи от родителския клас. Частните методи по своето естество са частни. Това означава, че те могат да бъдат достъпни само от класа, в който са дефинирани.

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

Обобщавайки

Надявам се, че този бърз урок за основните концепции на обектно-ориентираното програмиране в Ruby ви е харесал! Обхванахме създаването на класове, атрибути за достъп, инстанциране, инициализация, наследяване и частни и публични методи.

Ruby е мощен обектно-ориентиран език, използван от популярни приложения, включително нашето собствено тук в Next Tech. С тези основни познания за ООП вие сте на път да разработите свои собствени Ruby приложения!

Ако се интересувате да научите повече за програмирането с Ruby, вижте нашия курс Въведение в Ruby тук! В този курс ние обхващаме основни умения за програмиране, като променливи, низове, цикли и условни изрази, по-напреднали теми за ООП и обработка на грешки.