Loading...
sevdalin avatar sevdalin 38 Точки

Polymorphism - Wildfarm - Оценка на решението ми?

Здравейте,

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

 

Като цяло от задачата не се изисква кой знае какво, като дори съм учуден, че май няма подводни камъни, защото ми даде 100/100 от първият път, нещо за което принципно се празнува :D Но все пак ми отне 2-3 часа, докато я осмисля, структурирам правилно и т.н., тъй като все още материала е пресен, а се изисква да се използва почти всичко взето от този курс, за да се напише някакво basic OOP.

 

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

 

Тук е ZIP с кода и вътре в него условието на задачата също така: изтегли ме

 

Благодаря! :)

Тагове:
0
C# OOP Basics 08/06/2017 17:46:01
RoYaL avatar RoYaL Trainer 6849 Точки
Best Answer

Здравей,

Има няколко неща, които може да се подобрят.

    1. Имена на полета в класовете. Класът е Animal и в него има поле animalName. Тази преставка "animal" е излишна. Чие друго име да е, разбира се че на животното - нали е в класа Animal? :)

    2. В пропъртитата не ползваш "this". Ако ще ползваш this го ползвай навсякъде, ако няма - не го ползвай никъде. Въпросът е, че ако имаш съвпадение понякога на параметър и поле, ще се наложи да го ползваш, за да елиминираш двусмислието. Така, че най-добре го ползвай навсякъде.

    3. AnimalType ми се струва излишно по класовете. Класът Cat е нормално да е type=Cat. Не го използваш никъде до колкото виждам после. Така че може да го елиминираш.

    4. default condition-ите в switch обикновено в случай като твоя казват, че тази операция не може да се случи. Ако от входа дойде "pesho" не би трябвало да му връщаш Мишка. А по-скоро изключение

        4.1. Може да инстанцираш класовете с reflection. Разгледай как работи класът Activator.

    5. Когато използваш обект в стрингова операция, няма нужда да му извикваш експлицитно ToString() метода. Това му е идеята. Да се самоизвика, когато "стрингосаш" обекта косвено - примерно се опиташ да го принтираш.

    6. Never ever do this again "food.GetType().FullName.Equals("Meat")" :) Ако ти се налага да проверяваш нещо по тип (което не трбва да се случва, когато говорим за полиморфизъм) то използваш compile time check проверки. Какво имам предвид? Че, ако сега отида и преименувам класа Meat, кодът ще се компилира, но тигърът няма да се нахрани никога. Ако проверяваш с оператора "is" и преименувам класа - кодът няма да се компилира, защото използва "is" върху несъществуващ тип и ще се наложи да си оправиш кода, преди да го реализираш.

Най-добрият начин за подобен case, е една новост в C# 7 - Pattern matching.

НО !!! Pattern matching/type cas(t)ing не трябва да съществуват в нещо, което по природа е полиморфично. Използвай т.нар Generics в езика, за да постигнеш подобни ограничения. Например:

class Animal<T> where T : Food
{
    public abstract void Eat(T food);
}

class Tiger : Animal<Meat>
{
    public void Eat(Meat food)
    {
        // do smth with explicit Meat
    }
}

    7. От КПК гледна точка не трябва да ползваш статични методи, освен някакви helper-и, които примерно проверяват за невалидни данни и хвърлят exception или стрингове обвити в константи. В случая Console.WriteLine() е лоша практика. Методът MakeSound е трудно тестваем програматично дали вади правилните резултати. Ако искаш да накараш обекта да пише не по стандартния поток, а по файлове - трябва да го преработиш изцяло или да пренасочиш изцяло конзолата във файлове, но това ще прецака останалите неща, които я ползват. Най-добре е или да го връща този низ, или класа/метода да приемат някаква абстракция на поток и да пишат по нея:

public string MakeSound()
{
    return "ROAAR!!!";
}

или

public void MakeSound(IStreamWriter writer)
{
    writer.WriteLine("ROAAR!!!");
}

    8. if/else-а в Main метода за типа животно (args count-a) ти имат един и същ код на 3/4-ти.

Вместо

if ( smth )
{
    var obj = new MyObject(....);
    obj.DoSmth();
    obj.DoAnotherThing();
    Console.WriteLine(obj.ToString());
}
else
{
    var obj = new AnotherObject(....);
    obj.DoSmth();
    obj.DoAnotherThing();
    Console.WriteLine(obj.ToString());
}

Може би искаш

AbstractObject obj = null;

if ( smth )
{
    obj = new MyObject(....);
}
else
{
    obj = new AnotherObject(....);
}

obj.DoSmth();
obj.DoAnotherThing();
Console.WriteLine(obj.ToString());

или направо:

var obj = smth ? new MyObject(...) : new AnotherObject(...);
obj.DoSmth();
obj.DoAnotherThing();
Console.WriteLine(obj.ToString());

Това е за сега.

Успех!

3
09/06/2017 12:01:58
sevdalin avatar sevdalin 38 Точки

Първо да ти благодаря за изчерпателният и подобрен анализ. Сега да поотговоря на точките :)

 

1. За имената на полетата в класовете си наистина доста прав, не се бях замисля, че в класа Animal няма нужда полетата ми да започват с animal, тъй като е очевидно.

2. Да, да си призная като че ли, не бях много наясно, че ако ги поставя в конструктора трябва и в пропъртитата, но вече мисля че ще го запомня :)

3. Много си прав. И на мен ми се струва излишно, но единствената причина поради, която го сложих е защото в условието на задачата също го има. Ако погледнеш дадената Class Diagram на screehshot-a, ще видиш, че има такова поле.

4. Това ми е ясно и знам, че не е правилно. За това съм сложил note под този ред: "// default condition doesn't have sence in the logic, I just want to return something as I used switch instead of if/else". Но причината поради, която го оставих така, е защото не успях да измисля какво да върна за да може да го приема Animal като обект. Пробвах с new object(), ама не става, а не мога да върна празен клас Animal, защото той е абстрактен и не се инициализира. Трябваше да си пренапиша switch-a на if, else ама не ми се пренаписваше и за това така го оставих. Ще прочета за класа, който споменаваш.

5. Това не го знаех, не знам дали сме го учили ама наистина се учудих като видях код в който работи, просто като подам обект. Но аз по навик пак така си го написах. Но сега се замислих и се сетих, че Stringbuilder-а, работи по същият начин, дори аз го ползвам бе ToString(), но ако искаме да върнем String на някъде, тогава вече ползваме ToString(), make sense, след като каза че Console.Writeline всъщност го извиква имплицитно.

6. Разбирам, преправих го на is, не е перфектно, но поне е доста по-добре от преди. Всъщност "заблудата" ми да използвам начинът за сравнение беше защото видях на една лекция, че използват .GetTyp().FullName, но тогава беше за override на ToString() мисля, и на мен просто ми дойде като първото решение в главата, как да разбера дали е това което търся всъщност.

7. Схващам ти идеята. Ако използвам Console.Writeline в някой клас, кодът ми става зависим. И ако искам това приложение да го пусна на някоя друга OS, няма да стане. Така ако си изградя един клас, който примерно използва Console.Writeline и след това използвам само този метод, който всъщност принтира, дори да сменя OS, ще трябва да направя промяната само на 1 място, а не на 200 примерно. Просто защото задачата е малка и не съм се замислял, да правя нещо такова.

8. Да и това съм пропуснал, наистина е повтаря кода. Оправих го :)

 

Благодаря за стойностният отговор по темата!

0
RoYaL avatar RoYaL Trainer 6849 Точки

Здрасти отново,

    4. Когато входните данни са грешни и няма как по зададения вход да създадеш обект, не създавай празен обект - това само би объркало хората. Трябва по някакъв начин да индикираш, че това е проблемна ситуация. Може да върнеш NULL, но рискуваш ползвателите да не го забележат това и впоследствие някъде кодът им да резултира в проблем. Може би е по-добре да хвърлиш изключение.

Това, всъщност, е класическият пример за една 'рецепта' наречена - фабрика за обекти (Factory design pattern). В най-опростеният му вариант се имплементира точно по този начин, обикновено примерите са със switch/case и в default-а се хвърля изключение: https://en.wikipedia.org/wiki/Factory_method_pattern#C.23

1
e.manolov avatar e.manolov 80 Точки

Здравей, RoYal,

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

Всичко, което казваш е много правилно, но аз искам да обърна внимание относно т.1 как да именоваме полетата в класовете. Много често в лабовете / упражениенията се казва създайте примерно клас "Animal" с полета animalName, animalType, animalWeight и т.н. (не ме разбирай погрешно, нямам нищо против коментара ти или каквото и да било)

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

 

 

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