[OOP Exam1] - Сутрешна група - Решения и впечатления


8

Здравейте колеги,
Поздравявам ви с края на още един курс в академията smiley Днес видях на практика колко полезно е ООП-то и защо толкова държаха лекторите, че трябва да се знае.
Ще се радвам да видя и вашите решения и мнения за днешните задачи. Лично за мен и двете бяха изключително приятни.

Много се смях на IVulnerable уловката за ниджата, отне ми около 15мин, докато я схвана, но цялата работа протече изключитлно приятно.
А нинджата съм я направил sealed class, защото никой не може да бъде по-силен от една нинджа! :D

Software Academy - http://pastebin.com/MVRifjNh
Academt RPG - http://pastebin.com/1b5zPss1

Thank you OOP! :D




Отговори



5

 

Качили сме задачите, тестовете и решенията от ООП изпита в студентската система: http://telerikacademy.com/Courses/Courses/Details/29

Наков

от svetlin.nakov (31978 точки)


1

Мислех да мина и да отговоря с коментар на всеки от постовете, но реших да обобщя в един пост основните неща.

Първо, само аз ли съм на правил Topic клас? List<string> is so C# Part Two!

Но това далеч не е интересната част. Интересно е как дори в авторското решение кодът

if (string.IsNullOrEmpty(value))
{
    throw new ArgumentNullException();
}
X = value;


се повтаря четири пъти, с различни X естествено. Защо просто не се изведе в един валидиращ метод в статичен клас

public static void ValidateProperty(string valueref string property)
{
        if (value == null || value == string.Empty)
        {
                throw new ArgumentNullException();
        }
 
        property = value;
}


и да се вика в сетърите, вместо да се повтаря толкова много код? Да, може да се добави стринг за къстъм exception message. Колкото до coupling, не мисля, че зависимостта от метод за валидация в друг клас увеличава особено много couplingа. Още повече, когато се прочете името на метода ако се използва класа другаде, може да лесно да се подмени с друга валидация. Но тъй като е една и съща навсякъде, мисля че е голям oversight да не се изведе.

Второто нещо, което ми направи лошо впечатление в авторското решение, е хард коудването

result.Append("Course: Name=" + this.Name);

последвано от

string result = "Local" + base.ToString() + "; Lab=" + this.Lab;

и

string result = "Offsite" + base.ToString() + "; Town=" + this.Town;

Вместо това, много по- удачно би било да се напише 

result.Append(string.Format("{0}: Name={1}"this.GetType().Namethis.Name));

Това се преизползва посредством повикване на base.ToString() и вместо хард коуднато име на класа, ползва вградения reflection.

Така, now that that's out of the way, нека преминем към това какво трябва да валидираме и какво не, защото много хора не са наясно. В условието е казано "All properties in the above interfaces are mandatory (cannot be null or empty) except ICourse.Teacher". Ключовата дума е естествено properties. Не методи, не полета, не конструктори, пропъртита. И не в класовете, не в структурите, не в енумерациите, а в интерфейсите. Пропъртитата в интерфейсите са следните:

ITeacher.Name
ICourse.Name
ICourse.ITeacher
ILocalCourse.Lab
IOffsiteCourse.Town


от които само ICourse.ITeacher e извинено да няма валидация. Т.е. само четири пропъртита трябва да се валидират. И въпреки че пише "If a null value is passed ... throw ArgumentNullException", това не е съвсем коректно, тъй като при празен стринг също трябва да се хвърли ексепшън заради задължителността на пропъртитата, но тъй като не е упоменат типа на ексепшъна, трябва да се хвърли същия (Пепи, ти не си проверявала за празен стринг). Така е направено и в авторското решение.

Т.е. claim- ът на Пирин, че трябва да се проверяват Topic, Course etc. е съвсем необоснован. Нищо освен четирите пропъртита не трябва да се проверява по условие.

Така, нека преминем към решението на Киров. Имаш клас Courses. Това не е правилно. Класовете трябва да са съществителни имена в единствено число. Затова и интерфейсът не се казва ICourses, а ICourse. Така и твоят клас трябва да се казва Course. Не override- ваш ToString() в абстрактния родител Course, а само в наследниците. Това е голям пропуск. Не само от гледна точка на коректно ООП, но и от страна на условието, в което ти е подсказано, че ICourse.ToString() трябва да връща изредената информация. Използвайки parent клас Course и имплементирайки интерфейса в него, той поема задължението за оувъррайд, наследниците му само добавят. Мислех, че "намеците" в условието са достатъчни, за да се сети човек. Освен това, в ToString() на LocalCourse и OffsiteCourse викаш base.Name, base.Teacher.Name, base.allTopics защо? Така извикваш пропъртитата на Course, не на LocalCourse/OffsiteCourse. Нали знаеш, че след като са наследници, то те ги имат? Т.е. трябва да ползваш this.Name, this.Teacher.Name, this.allTopics, иначе се обръщаш към пропъртита, които са различни от текущите, особено когато имаш промяна. Това е все едно когато си въвеждаш информацията в личната карта, после там да пише Цвят на очите: син, защото цветът на очите на майка ти са сини, а твоите да са кафяви. Няма логика да викаш пропъртито на родителя. Нямам време да проверявам натам.

На Куртев и в LocalCourse и в OffsiteCourse няма хвърляне на ексепшън при въвеждане на null/empty за пропъртитата им Lab и Town съответно. В ToString() не е нужна, те просто НЕ МОГАТ да са null/empty. При Teacher липсва съвсем. Само при Course.Name e коректна.

Тук наистина само изтъквам пропуските, но мисля, че ще са от полза на всички. Щом имате 200 значи правите нещо вярно, не се чувствайте нахейтени, това е градивна критика, за да пишем правилен код.

Преминавам към втора задача. Не намирам авторово решение. Ще карам по решенията тук.

Вече беше споменато, но да повторя- задачата е на 99% идентична с GeometryAPI (както и прасетата имат 99% съвпадение с човешкото ДНК). Трябва да се направи нов custom Engine, който наследява Engine, да оувъррайдне командата за създаване на обект и в default на switch- а да викне base.КомандатаЗаСъздаванеНаОбект. Куртев го вика извън суича, което не е оптимално, но върши същата работа на цената на допълнителна инструкция. Третата проверка 


if (availableTargets[i].Owner != this.Owner
                    && availableTargets[i].Owner != 0
                    && availableTargets[i].HitPoints == availableTargets.Max(x => x.HitPoints))


не трябва да е в цикъла, или поне не в този си вариант. Всеки път се смята наново посредством Linq и ламбда израз обектът, с най- много hitPoints. Ако на всяка цена искаш да ползваш Linq, то една оптимизация е да се изведе извън цикъла в променлива, която да запази стойността и после да се проверява единствено за тази стойност. Иначе елементарният метод, с променлива, пазеща max до момента и след итериране веднъж през всички елементи връщаш резултат, е доста по- бърз. Най- малкото защото с оптимизираният вариант на твоята идея ще итерираш винаги повече от веднъж (и по- малко от 2 пъти). Но с оригиналния ти вариант, итерираш i пъти през всички елементи, което при много елементи означава изключително голямо забавяне.

Как се прави нинджата безсмъртна?

public bool IsDestroyed
{
        get
        {
                return false;
        }
}


Много леки задачи. Имам един минимален пропуск при проверката на нинджата дали обектът, който атакува, не е контролиран от същия играч, както и в първа задача можеше да извадя ToString() промените посредством StringBuilder в метод, тъй като ги повторих 2 пъти, но като цяло проверка на базови познания от ООП. Ако бяха 3 или 4 задачи, сигурно щеше да ми е по- интересно : )

Едит: В коментарите се заформи дискусия. Много хора споменават, че не било добър вариант нинджата да седи на 0 точки кръв и да не умира (което се получава, с горния код). И подкрепят логиката да има DefensePoints { get { return int.MaxValue; } }. Тази логика обаче се чупи ако си направя герой Paladin с пропърти AttackPoints { get { return -2; } }. Тогава при проверката 

int damage = attacker.AttackPoints - defenderDefensePoints;
 
if (damage > defender.HitPoints)
{
    damage = defender.HitPoints;
}


damage става равен на int.MaxValue, което е > 1 (кръвта на нинджата) и нашата нинджа умира, защото IsDestroyed проверява hitPoints <= 0 и връща true. С моята логика, нинджата наистина е безсмъртна, albeit на 0 кръв.


от Anubis_Black (1521 точки)


0
Ако си правиш нинждата безсмъртна по този начин, без да му дадеш DefensePoints да е int.MaxValue, той няма да умре, но ще се разкарва с 0 HP , което не е логично.

от FerdiNebi (20 точки)

0
Ghost Ninja :D Иначе много полезен пост +1 :)

от SVGN_H (3048 точки)



0
От къде мога да си изтегля самите условия за задачите, защото в BgCoder има само началния код?

от IceElementor (398 точки)


0
Виж отговора на Наков на втора страница.

от vphilipov (3591 точки)

0
ето линкче
http://downloads.academy.telerik.com/svn/oop/Lectures/9. Exam Preparation/OOP-Exam-March-2013-Variant-1.rar

от Nedko (1220 точки)



0

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

КОД


от IceElementor (398 точки)


0
Като гледам в ExtendEngine във case за knight вместо case "knight" си написал case " knight". Имаш един интервал, който е ненужен.

от nikostov (202 точки)

0
Абсолютно!На предната задача гледах 1 час и накрая от 30/100 до 100/100 ги направих след като поправих някакви излишни скобки и букви...та тази задача от 30/100 на 80/100.Благодаря,но все пак има още нещо :D.

от IceElementor (398 точки)