Loading...
Hristo_Penchev avatar Hristo_Penchev 389 Точки

[Homework] OOP - Defining Classes - Problem {4} - Software University Learning System - съхранение на инстанция в тип object и извикване на метод

Боря се с четвърта задача. Създадох структурата от класове. Проблемът идва другаде:

http://pastebin.com/TS6ZcDQn - това е написаният до момента код.

В началото на цикъла потребителят си избира какъв обект да създаде - Trainer, OnlineStudent и т.н. Но понеже не знаем точно какво ще избере потребителят, съхраняваме създадения от него клас в object temp. На ред 85 проверяваме дали създаденият обект е Trainer и ако е такъв - даваме възможност на потребителя да тества CreateCourse() метода. Но на ред 93 ми изписва, че променливи от типа object нямат метод  CreateCourse(). Тоест явно след като запазим класа като object кодът вече не разпознава точно какъв клас е. Интересното обаче е, че ToString() работи, не знам защо. Та, въпросът ми е, как да накарам object temp да разбере, че е Trainer и да използва метода CreateCourse()? Може да стане с някакъв много заобиколен начин с 10 променливи за всеки тип и много условни конструкции, но сигурно има и по-умен вариант. Благодаря предварително!

-2
C# OOP Basics
HPetrov avatar HPetrov 822 Точки

Най-добре да пазиш обектите като типове на interfaces. Вместо примерно object или Trainer да ти бъдат в IPerson или ITrainer да бъдем по-точни. Обаче трябва да имаш такъв interface, който да има и такъв метод (CreateCourse) за да сработи. Може би има и друго решение но на мен първо това ми идва на ума. Ако типа на данни, в който се пази някаква променлива ти е на пример object може да забравиш за всички методи, който си си направил ти, защото всички класове в най-базовия си тип са object :) А базовия клас не знае за методите на наследниците си.

0
Hristo_Penchev avatar Hristo_Penchev 389 Точки

Interface ми е непонятен термин. Опитвам се да го намря в книгата "Въведение в програмирането", но не мога да се оправя. В коя глава е обяснено? Ако го няма, утре ще изровя някой туториал.
Друго, което не ми е ясно - искат да запазим всички инстанции в лист от обекти, от тях да извадим всички DropoutStudent и да изпечатаме причината за отпадането им. Как ще стане това, при положение, че само класа DropoutStudent има метод Reapply()? Предполагам пак ще е с интерфейс някак си. Само трябва да ги науча.

0
ttitto avatar ttitto 1153 Точки

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

За този малък проект може да се мине и без интерфейси, а само с абстрактни класове. Ако изработиш структурата по начина на тази класова диаграма, решението в maina става на един ред (ако не броим инициализирането на обектите)

диаграма

0
Hristo_Penchev avatar Hristo_Penchev 389 Точки

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

Тук съм качил пълния код на задачата, написан до момента:

https://github.com/HPenchev/test2

0
29/12/2014 00:03:04
Hristo_Penchev avatar Hristo_Penchev 389 Точки

Оказа се, че решението е съвсем просто. Явно аз не съм успял да формулирам въпроса си точно. Базовият клас може да бъде конвертиран към дъщерния си експлицитно: 

Trainer tempTrainer = (Trainer)temp;
tempTrainer.CreateCourse(course);

Така че след като сме направили проверка, че обектът temp е Trainer просто го обръщаме от object в Trainer и викаме метода CreateCourse().

0
29/12/2014 15:13:47
Hristo_Penchev avatar Hristo_Penchev 389 Точки

Ето и крайното решение:

https://github.com/HPenchev/test2

0
RoYaL avatar RoYaL Trainer 6849 Точки

На правилния път си. Нека изложа малко виждания.

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

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

Това, което мисля, че трябва да се нарпави е да имаш методи, които да се грижат за това. Например ToStudent(); ToTrainer(); etc.

Ето един примерен код по случая:

http://pastebin.com/qztjWza7

В този мини апликейшън, ако въведеш в конзолата "1" и после "apply" ще създадеш обект от тип Student и ще извикаш метода му ApplyToCourse() (p.ToStudent().ApplyToCourse()); Разбира се, нищо няма да те спре да напишеш "2" и "Apply" Тогава няма да си създал Student, а Trainer и ще се опиташ да кастнеш към Student и да извикаш метода. Разбира се, това би гръмнало с несъществуващ метод, но понякога може да се окаже, че методът го има, но го викаме от грешния тип. Тъй като не искаме това, всеки наследник на Person, вика на базовия си клас, т.е. на Person полето instance и го сетва на себе си. После в ToStudent() проверяваме дали това поле е сетнато на Student и ако не е - хвърляме ексепшън още там, преди да стигнем до кастване.

Така кастването е изнесено в метод и то прави съответните проверки и не е нужно всеки път да пишеш (Student)temp.

Наскоро открих една щукария в C#, която мисля, че би направила нещата още по-готини.

http://msdn.microsoft.com/en-us/library/xhbhezf4.aspx

Explicit оператора ти дава възможност да дадеш дефолтно поведение на класа, когато се опитва да бъде кастнат към друг тип експлицитно. Тук обаче ще трябва да счупиш йерархията (не е точно счупване де:)), така че класа който се грижи за това да не е пряк наследник или родител на останалите - т.е. типът от който почваш да е някакъв Builder а не точно Person, защото не може да използваш експлицит оператора между наследници. Така дори и отвън класа някой да се опита да кастне към друг тип, ще се извика този метод и ако не са изпълнени условията в него ще хвърли ексепшън например.

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

 

-1
29/12/2014 17:00:03
vladislav.karamfilov avatar vladislav.karamfilov 1123 Точки

Без да съм чел целия пост:

В момента, в който стигнеш до "Това, което мисля, че трябва да се нарпави е да имаш методи, които да се грижат за това. НапримерToStudent(); ToTrainer(); etc.", чупиш всичко ООП принципи. Един родителски клас НИКОГА не трябва да знае за съществуването на класове-наследници. ;)

3
RoYaL avatar RoYaL Trainer 6849 Точки

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

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

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

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

Иначе ми се струва малко грешно да се почва то Person, при положение че накрая стигаме до някакъв си опреелен студент или трейнър. Прекалено висока е абстракцията и не помага за преизползване. Но тук сигурно ще ме опровергаеш пак, като кажеш че е важно да се работи с най-високата абстракция :)

P.S.: Истината е, че овъррайдването на експлицитното кастване не съм го борил до сега за нещо различно от числови типове и най-вероятно ще е по-голяма философия от колкото си мисля :)

-1
29/12/2014 17:24:42
Filkolev avatar Filkolev 4482 Точки

Имам въпрос от малко по-различно естество за тази задача.

Направих йерархията и всичко е точно - не взимам данни от конзолата, а в мейн метода ръчно съм направил един списък (List<Person>) и съм добавил в него обекти от различните класове.

От този списък успях да извадя CurrentStudent-ите и да ги сортирам, след това ги принтирам.

Към момента принтирам ръчно, т.е. нямам override-нати ToString() методи за никой от класовете. Това означава обаче, че мога да принтирам само инфото, което е присъщо за класа CurrentStudent, т.е. не принтирам полетата на класовете наследници и по-конретно - не принтирам полето numberOfVisits за обектите от класа OnsiteStudent. Ето как изглежда като код:

  foreach (var student in sortedCurrentStudents)
{
     Console.WriteLine("{1}, {0}", student.FirstName, student.LastName);
     Console.WriteLine("\tAge: {0}", student.Age);
     Console.WriteLine("\tStudent Number: {0}", student.StudentNumber);
     Console.WriteLine("\tAverage Grade: {0:f2}", student.AverageGrade);
     Console.WriteLine("\tCurrent Course: {0}", student.CurrentCourse);
     Console.WriteLine("\tStatus: {0}", student.GetType());
}

Според вас това ОК ли е като решение? Кой е най-лесният начин да извадя на конзолата цялата информация за даден студент (включително и от кой клас е точно)? Да напиша ToString() методи за отделните класове? Или да имам проверки в цикъла и съобразно типа на обекта да принтирам присъщите му свойства, които ги няма в родителя му, както и типа?

0
19/01/2015 23:34:58
a.angelov avatar a.angelov 1316 Точки

Според мен по-правилно е с override-ване на ToString(), като във всеки наследник преизползваш ToString() на родителя и допълваш с информацията от наследника - примерно:

public override string ToString()
{
    return base.ToString() + " " + this.numberOfVisits;
}

5
20/01/2015 00:02:01
Filkolev avatar Filkolev 4482 Точки

Точно за това си мислех, дали не може да преизползвам вече написаните методи от родителите и само да допълвам. Само че днеска бая пописах и ме домързя да търся как става, а е доста елементарно. Благодаря!

0
AleksandurSeferinkin avatar AleksandurSeferinkin 333 Точки

Дъщерен клас знае кой клас наследява, но обратното е нелогично.

Всеки клас, който наследява друг, може да бъде разглеждан като обект от инстанция на класа, който наследява - без кастване. Това е идеята на 'polymorphism'! Също така в c# всеки обект пази информация за себе си. Винаги можеш да разбереш какъв е крайния тип на даден обект като извикаш метода 'GetType()'.

Друг начин да провериш дали даден обект е инстанция на даден тип е да ползваш оператора 'is' :

student.GetType() == typeof(DropoutStudent) или if (student is DropoutStudent)

След като разбереш, че обект е от 'този' тип, трябва да го кастнеш към такъв, за да го ползваш като такъв. По тази причина съществува кастване с резервираната дума 'as' (пример: this.student as Person), което връща 'null', ако кастването е невъзможно.

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

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

ПС: Тук можеш да видиш моето решение. Аз съм ползвал метода 'IsSubClassOf' на типа 'Type', за да разбера дали даден обект е от 'еди-кой-си' тип, или е дъщерен на 'еди-кой-си' тип.

ПС: Примери за проверяване на типовете на обектите.

ПС: ToString() работи за всички обекти, защото всички обекти/класове наследяват класа 'Object', дори да не искаш, дори и да не знаеш... Дъщерен клас може да извиква методи на базов клас, но обратното е нелогично. - кастването му е майката.

3
20/01/2015 06:19:53
Можем ли да използваме бисквитки?
Ние използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Можете да се съгласите с всички или част от тях.
Назад
Функционални
Използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Използваме „сесийни“ бисквитки, за да Ви идентифицираме временно. Те се пазят само по време на активната употреба на услугите ни. След излизане от приложението, затваряне на браузъра или мобилното устройство, данните се трият. Използваме бисквитки, за да предоставим опцията „Запомни Ме“, която Ви позволява да използвате нашите услуги без да предоставяте потребителско име и парола. Допълнително е възможно да използваме бисквитки за да съхраняваме различни малки настройки, като избор на езика, позиции на менюта и персонализирано съдържание. Използваме бисквитки и за измерване на маркетинговите ни усилия.
Рекламни
Използваме бисквитки, за да измерваме маркетинг ефективността ни, броене на посещения, както и за проследяването дали дадено електронно писмо е било отворено.