[Homework] OOP - Encapsulation and Polymorphism - Септември 2014
Домашното:
Problem 1. Shapes
Problem 2. Bank of Kurtovo Konare
Problem 3. Game Engine = ToDo
готово за критика и въпроси.
Домашното:
готово за критика и въпроси.
Колеги, ето един почти пълен комплект със задачи:
BankSystem - Мисля че стана много добре. С интерфейси за акаунта, IDepositable и IWithdrawable, през абстрактен Account до конкретната имплементация на пресмятането на лихвата за всеки вид сметка.
Shapes - използвал съм максимално зададените свойства на BasicShape класа, за да има по-малко повторения. Дори се наложи да си припомня косинусовата теорема от гимназиалния курс
TheSlum - Създавам нов клас CustomEngine, който наследява наличния и презаписвам необходимите методи за създаване на герои и техните помощни обекти. Задачата според мен е завършена, но не дава крайния резултат от заданието. Прави ми впечатление, че докато не се подаде празен ред за приключване на играта не се изчисляват стъпките (turns) на играта и status командата дава различен резултат отколкото ако се подаде празния ред. Това прави много трудно дебъгването. Според мен имам пропуск именно в неразбиране какво точно се брои за един turn. Затова много се надявам някой да качи вярно решение!
PS. След добавените входни данни в условието се оказа че задачата е решена. Оправих само namespace-те.
externo:
задача 2:
От формулата,дадена за изчисление на лихвата става ясно, че метода CalculateInterest не връща само лихвата за посочените месеци, а баланса на акаунта след толкова месеци. Тоест където връщаш 0 в CalculateInterest би трябвало да върнеш this.Balance. И на мен ми беше странно, но това е положението.
Бих те посъветвал да поработиш повече по наименованията. Като ти видях класовете преди да ги отворя останах с впечатлението, че Deposit е някакъв абстрактен клас, който имплементира метод Deposit (decimal money) на някакъв интерфейс IDeposit.
При calculateInterest в Deposit не си изпълнил условието да не се пресмята лихва, ако балансът е положителен. Това означава, че ако балансът е отрицателен банката ще таксува клиентът си с някаква лихва. При теб банката остава "на сухо".
Бих направил методът CalculateInterest да приема само месеци като параметър, за amount ще се ползва this.Balance на съответния акаунт
За пресмятането на лихвата се налага да повтаряш изчислението във всички класове. Аз бих имплементрал този метод в абстрактния клас Account и да викам base.Calculate... където няма други условия.
При MortgageAccount: какво става ако периодът за CalculateInterest е по-дълъг от 12 месеца?! Няма я логиката, че за първите 12 месеца ще се изчислява половината лихва а от 13тия месец нататък ще се изчислява пълна лихва.
задача 1: Не виждам никаква логика триъгълникът (също и circle) ти да наследява BasicShape, след като не ползваш нищо от основния клас. По-добре (при твоята конструкция на програмата) да беше му подал само интерфейса IShape. или да ползваш width и height от основния клас като двете страни на триъгълника и да подадеш само третата. Макар, че за периметъра и площта дори не ти трябва третата страна(може и ъгъла между двете страни). Както и няма смисъл да изчисляваш периметъра още в конструктора. Нали затова имаш CalculatePerimeter метод - на който когато му потрябва ще си викне метода и ще си получи периметъра.
мерси, ще ги оправя
externo на задачата Shapes, в класа Triangle трябва да си направиш една проверка дали трите страни А, B и C могат да образуват триъгълник, защото не всички три отсечки реално могат. Трябва A+B > C, A+C > B и B+C > A да са изпълнени едновременно.Иначе ще ти гръмне метода public override double CalculateArea(). При неизпълнение на горното условие ще смяташ корен квадратен от отрицателно число. Примерно, ако страните ти са 5,2 и 1, полу-периметъра е 4 и във формулата ще имаш:
Math.Sqrt ( 4 * -1 * 2 * 3). Може да изнесеш проверката в един private method и да я пускаш в конструктора. Аз не направих задачата по условие, зашото според мен има нещо сбъркано в него. Няма как да имаме в BasicShapes widht и height, и Triangle, и Circle да го наследяват, Нито кръга нито триъгълника имат ширина и височина. Може да са имали в предвит Square вместо Triangle, и тогава нещата заспиват. Имаме си пропъртита width и height в абстрактния BasicShapes, и си ги наследяваме без проблем. Също така в абстрактния клас BasicShapes е добре да остввиш пропъртитата бес сетъри, за да можеш после в наследниците да ги направиш protected set. Нали ни учат да енкапсулираме здраво данните си . Ето едно не много code-wise решение от мен Shapes. Нямам реюз на пропъртита от перънт класа, но както казах не виждам как да имам.
благодаря за коментара
Здравейте, въпроса ми е относно трета задача (TheSlum) и по точно към последното изречение на условието.
Как разбирате това?
You are NOT allowed to edit directly any of the given classes / interfaces. You may edit the Main() method only.
Може да пипаме само по Main() метода или само по Engine класа. На мен лично ми се струва малко сбъркано условието.
Може да се пипа само в Main метода и по никой друг клас. Трябва да решиш задачата само чрез създаване на нови класове. Тоест само да я разширяваш, а не да я модифицираш. Спазен е Open Close принципа - Open for extension, closed for modification
Можем да променяме само Main и новите класове които създаваме.
Благодаря ви колеги.
Разгледах решението на Тито и се ориентирах какво трябва да направя.
Първите две задачи мисля, че станаха добре. Трета заработи, извежда някакви резултати, но не мога да прецена работили вярно, защото не знам параметрите на героите в примера. Ако някой смята, че нещо може да се подобри да казва. Отворен съм към критики.
Едни козметчини коментари по първа задача. :)
При Circle вместо да наследяваш BasicShape, трябва само да имплементираш интерфейса IShape (така е по условие) - така ще се отървеш и от "безмисленото" връзване към BasicShape класа, който няма никакъв смисъл (освен може би значението на името) спрямо окръжност (по скоро полетата/прпъртитата вътре в него). Правиш само едно пропърти за радиуса и имплементираш интерфейса. И не се налага нищо друго да буташ в другите файлове.
Наследявайки BasicShape наследявам на готово пропърти за радиуса и не ми се налага да повтарям код. Явно този момента е спорен и не е ясно кое е правилно и кое грешно.
Да наистина е така - спорно е. Но пък в случая ти не повтаряш код (отново може би е спорно), защото между BasicShape и Circle няма никакво наследяване и реално не повтаряш никакъв код, защото те са си различни абсолютно несвързани обекти (по скоро класове, защото BasicShape няма как да стане обект) - само имат общо поведение, чрез интерфейса.
Колеги, само да кажа, че искрено се надявам да няма толкова сложна задача като трета задача от това домашно на изпита! Просто много завъртяно и много време се иска, вече, над 5 часа пиша само по нея и успях да създам герои и предмети към тях, но нищо повече. Надявам се, че тази задача ни е дадена само за да видим нещо доста сложно, защото на този етап мисля, че доста малко хора могат да я решат, според мен е малко над нужното ниво за изпита.
Може би малко ще те разочаровам, но точно такива са вторите задачи от изпитите по ООП в Телерик. Предполагам, че и тук втората задача ще иска повече разбиране на кода, отколкото писане.
Вероятно си прав, още повече говориш от позицията на опита, който аз нямам. Все пак времето е ограничено, и една такава задача би го изяла изцяло според мен, особено за начинаещ, доста време ще отлети само за гледане на кода и дебъгване, за да се проследи логиката. Надеждата умира последна, дано поне едната е с нормална трудност, а втората за по-напреднали /нинджи/, че да може и начинаещите, но все пак с желание и отделили време да вземат изпита, да го направят, макар и не с отличие...
Аз лично бих се радвал ако за трета задача бъде дадено някакво авторско решение, тъй като се съмнявам някой да е успял да я имплементира напълно коректно, както е в примера. Извинявам се предварително, ако някой все пак е успял, но прегледах няколко решения и не видях напълно коректно такова.
Моето лично решение изкарва коректен резултат що се отнася до кой побеждава, но забравих да овъррайдна ToString метода на отделните герои и не се добавя специфичната информация за тях, както и самите цифри не съвпадат с аутпута в примера.
П.П.
На ttitto всъщност му работят нещата. Сега остава само да разгледам кое точно съм пропуснал :).
Ако дадеш линк към кода бих могъл да погледна и евентуално да помогна.
За 3 дни успях да направя 3та задача!
https://github.com/craghagBg/advancedCSharp/tree/master/OOP/game
Имам въпрос към задача 3(гейм енджина), или по-конкретно към дадения input и output.
Nakov: Mage(150 default hp + 200hp bonus(for 3 rounds) + 3*60=180(heal from Alex)) = 530 hp към момента в края на 3ти рунд. Междувременно от другия отбор Nakov бива удрян веднъж от warrior(150 default attack + 75 axe item - 50 nakov's defense) = 175 dmg. Към този момент кръвта на Nakov e 530-175 = 355. Завършва 3ти рунд и бонуса на Nakov свършва => Nakov hp = 155. На четвърти рунд Nakov получава heal(60) => Nakov's hp = 155+60 = 215 hp. Така и показва моя output, а този дадения в домашното показва 290 и не разбирам защо? Объркал ли съм нещо и ако е моля да ме насочите.
Играта започва чак след като свърши инпута (да, глупаво е, но идеята е просто да се разиграе симулация на битка, където всеки обект си има програмирано поведение).
Добавени са в условието и първоначални данни за всеки герой (Health points, Range, etc.), които отговарят на output-a.
ttitto много елегантно решение на BankSystem . Аз също ползвах интерфейс за IWithdrawable, който extend-ва IAccount , но не направих IDepositable, a директно в IAccount и абстрактния Аccount си правя метод за дeпозит. Не е ли по-правилно според теб така.Нали всички Account-и за depositable. Само, че ми възниква следния проблем: не мога да инстанцирам DepositAccount обект с конструктора записвайки го в IAccount променлива. Тоест трябва изрично да посоча че е IWithdrawable. Имам предвит ето така: IWithdrawable clientAccount = new DepositAccount(), а искам (и трябва) да мога да укажа, че променливата е IAccount. Виждам, че това става в твоя код и не мога да си обясня защо при мен не работи. Не трябва ли когато интерфейса IWithdrawable, екстендва IAccount обекти от тип witdrawable да могат да се заявяват като тип IAccount?? При класоете нещата си работят норамлно: обекти които са наследници на даден клас могат да се записват в пеоменливи от типа на парент класа. Тук при интерфейстите нещо ми се губи по веригата. Предполагам, че при теб работи зашото имаш и IDepositable, но не мога да разбера какво реално променя това. Мног ще съм благодарен, ако имаш възможност да погледнеш кода ми и да ме светнеш за какво иде реч! Ето я задачата: BankSystem.
И малко да се заям за едно-две неща от чисто техническа гледна точка по кода, което няма никаквпо отношение към OOP-то Просто тази материя (банки, лихви, финанси) ми е настоящoтo (надявам се скоро бившо) амплоа и не ме дава мира да си замълча. В условиет се иска да изчислим лихвата върху балансовата сметка за определения период (calculate their interest for a given period). Формулата в метода кяото ползваш реално ти връща цялата сума плюс лихвата. Трябва накрая да извадиш this.Balance от резултата, за да получиш само начислиената лихва. Второ: много хитро е изкарването на метода за смятане в абстрактния клас и после го извикваме с base и само изваждаме съответния период (return base.CalculateRate(months - 2); Това обаче не работи при MortgageAccount за компании, където лихвата е на полвина и просто разделяш на две, и не работи още повече във втория частен слуачй когато месеците са повече от 12 и имаме двойно допълнително изчисляване. Не мога да го обясня матемтически, но напиши си го на един лист да видиш:
[100 * (1 + (0.10 * 12))] / 2 = 110 , а 100 * (1 + (0.05 * 12)) = 80. А и трябва да извадим this.Balance. За това няма как да реюзнем метода там, трябва да си напишем цялата формула. Няма друг начин, няма допълнителен параметър, който да инжектирам в метоа освен period, за да заспат нещата (или пон аз не се сещам). И още нещо. Никъде няма проверка дали не ни подават отрицатален период (-8 месеца), от където автоматично трябва за всеки кейс при всички account-и да имаме по още един иф стейтмънт.Доста е оплетено, аз се зарибих и мисля, че успях да хвана всички възможни кейсове, което обаче доведе до повтаряне на код, и не виждам как мога да го избегна. Сигурно е възможно, но трябва да се правят доппълнителни класове за методите и да се вдига абстракицята. Представям си какво е да пишеш истински банков софтуеър, примерно за валутна търговия, където нещата са мега-дебели
А трета задача ме е страх даже да я почна. Въобще не ми се нравят игрите, колкото и да казват, че са най-добрия начин за прилагане и учене на OOП.
@borislavml : Проблемът ти е ето тук. Правиш наобратно наследяването на интерфейсите. Правилно е ...IAccount : IWithdrawable, а ти си ги обърнал.
Благодаря много за градивната критика. Ще оправям банковите сметки в понеделник.