KNOW INTUIT, Лекция, Диаграма на класа в близък план

Винаги ли е необходимо да се създават нови класове?

Да започнем с един въпрос, който изглежда няма нищо общо с разглежданата тема, а именно – винаги ли е необходимо да се създава нов клас за всяка нова задача? Правилният отговор, разбира се, е „не“. Това би било странно и неефективно. „Номерът“ е, че можем да използваме съществуващи класове, адаптирайки тяхната функционалност за изпълнение на нови задачи. По този начин става възможно да не се създава класова система от нулата, а да се използват съществуващи решения, които са създадени по-рано при работа по предишни проекти. Въпреки това, нашето твърдение за странността и неефективността на създаването на нови класове не е крайната истина. Възможно е да има ситуации, когато съществуващите класове по някаква причина не отговарят на архитекта и тогава е необходимо да се създаде нов клас. Трябва обаче да се избягват ситуации, когато създаденият клас (или по-скоро неговият набор от операции и атрибути) практически повтаря съществуващия, като се различава само малко от него. Все пак е по-добре да не преоткривате колелото и да се опитвате да създавате класове въз основа на съществуващи и само ако няма подходящи класове, създайте свои собствени, които от своя страна могат (и трябва!) да служат като основа за други класове. Да не говорим, че създаването на класове включва значително количество усилия за кодиране и тестване. В общия случай горното може да се илюстрира с такава диаграма (фиг. 3.7):

лекция

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

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

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

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

И сега внимание - говорихме много за необходимостта от създаване на класове въз основа на съществуващи, но не казахме нито дума за това как да стане това. Дойде време да се изясни този въпрос. Така се доближаваме до концепцията загенерализацииилигенерализации, която играе много важна роля в ООП, като е един от основните му принципи.Обобщениетое връзка между по-общ обект, нареченсуперклас, и неговата специфична реализация, нареченаподклас. Понякога обобщението се нарича връзка от типа "е", което означава, че някои обекти (например кръг, квадрат, триъгълник) са въплъщение на по-общ обект (например клас "геометрична фигура"). В този случай всички атрибути и операции на суперкласа, независимо от модификаторите на видимост, са включени в подкласа.

Обобщението (или, както често се казва, наследяването) се обозначава много просто в диаграмите - незапълнена триъгълна стрелка, насочена към суперкласа (фиг. 3.8).

За да се научим как ефективно да моделираме наследяването, нека се обърнем към класиците, а именно Г. Буч. Тойпрепоръчва тази процедура да се извърши в следната последователност:

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

know

И ето пример за прилагане на този подход (фиг. 3.9):

класа

На пръв поглед изглежда странно, че класът "точка" няма никакви атрибути, докато кръгът има само радиус. С правоъгълник, изглежда, всичко е ясно - ширина и височина, но това е само къде се намира в пространството, този правоъгълник? Нека се опитаме да последваме съвета на Booch. И така, позицията на трите фигури може да бъде уникално определена с помощта на двойка числа. За точка това са общо взето единствените й характеристики, за окръжност и правоъгълник - техните центрове (под център на правоъгълник разбираме пресечната точка на неговите диагонали). Ето ги, общи атрибути! Така създадохме суперклас "Shape", който има два атрибута - централните координати. Всички останали класове в тази диаграма са свързани с класа "Shape" чрез обобщаваща релация, т.е. в тях трябва да се добавят само "липсващите" атрибути - радиус, ширина и височина. Атрибути, описващи координатите на центъра, тези класове първоначално имат като наследници на класа "Shape" - те ги наследяват. Обърнете внимание, че тук не разглеждаме класови операции: ясно е, че същата история би се случила с тях.

Така че, с наследството изглежда ще разберем. Време е за малко провокация от наша страна. Класовете наследници наследяват ли атрибутите и операциите на суперкласа?По този начин те също могат да наследяват своите интерфейси - тоест обекти от напълно различно естество могат да имат един и същ интерфейс! И така, как тогава да определим какъв клас е даден обект в крайна сметка? И изобщо необходимо ли е?

Наистина, обекти от различно естество (или по-просто различни класове) могат да поддържат един и същ интерфейс точно както потребителят очаква. Пример за това е горната диаграма с геометрични фигури. Всички разглеждани фигури имат например операцията за рисуване на екрана. От гледна точка на потребителя това е едно и също действие във всеки случай. Тези операции обаче се изпълняват по различен начин - в крайна сметка процедурата за изчертаване на правоъгълник е много различна от подобна процедура за кръг. Но за потребителя това няма значение: все пак подписът е същият! И може би това се дължи на друг от основните принципи на ООП -полиморфизма. Както току-що споменахме, работата на механизма за полиморфизъм се основава на съвпадението на сигнатурата на метода, деклариран в интерфейса, и сигнатурата на самия метод. Методите вътре в наследствените класове могат да бъдат (и най-вероятно ще!) бъдат отменени, техните реализации ще бъдат различни и техните подписи ще останат непроменени. По този начин (и лесно е да се усети силата на ООП в това), изпълнявайки едни и същи операции, различните обекти могат да се държат различно.

Полиморфизмът е основата за реализиране на механизма на интерфейсите в езиците за програмиране. Между другото, тук е отговорът на въпроса какъв клас е обектът: веднага щом потребителят осъществи достъп до някаква операция през интерфейса, се определя действителният клас на обекта и се извиква съответната операция от клас. Примери за полиморфизъм могат да се видят в най-обикновените неща, които използваме в ежедневието. Огледайте се - светът е изграден на ООП,Матрицата работи! Например позната кредитна карта е интерфейс за достъп до банкова сметка през банкомат (и не само), работи по същия начин във всяка държава, просто се държи малко по-различно, защото банкоматът издава пари в местна валута. Съгласете се, примерът не е много правилен, но много ясен! Смятаме, че след като наблюдава света около нас, самият читател ще може да даде много примери за полиморфизъм.

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