[Homework] OOP - Defining classes - Септември 2014
Понеже в този курс решенията на задачите търпят на широко обсъждане, което едва ли ще стане при проверката на домашни, мисля да споделям тук решенията си: Ето първото:
LaptopShop (ново условие)
Понеже в този курс решенията на задачите търпят на широко обсъждане, което едва ли ще стане при проверката на домашни, мисля да споделям тук решенията си: Ето първото:
LaptopShop (ново условие)
Добре си направил домашното... имам само една забележка ;)
Целта на курса е да ни научи да пишем максимално опростен, преизползваем, ефективен, структуриран правилно и разбираем за всички код. Повечето домашни в този курс си мисля че ще са от един тип: имаме тухла с дадени размери, след това стена с даден брой тухли или размери, след това етаж, после брой етажи, вид покрив и накрая къща. Мисля си че тези задачи трябва да се имплементират като едно цяло - както е в реалният живот.
Давам пример с твоето домашно:
Първо правиш лаптоп, и то много добре, взел си в предвид всичко което си се сетил. След това правиш персонален компютър, отново отлично... Тук идва момента когато един код трябва да се оптимизира, както е в реалният живот, а именно - преизползването на код, опростяване на кода, лесна модификация (има си друго име но ще го учим по-късно - coupling) и опростен поглед върху цялата "картинка"! След като разгледаме домашното - всичките му задачи, най-добре е да анзлизираме проблемите и да намерим общи точки, след което да съставим необходимото решение. Според мен тук трябва да се подходи по този начин (представям го в най-опростения вариант):
Компютърна част
(елементи на класа)
/ | \
Цена Име Производител
(Наследници)
/ / / \ \ \
Процесор Памет Видео карта Хард диск Захранване Батерия
Компютър
|
Има Списък/Колекция от компютърни части
|
/ \
има батеия и захранване само захранване
| |
(Наследници)
Лаптоп PC
Магазин
|
(Има списък със компютри)
Според мен 1 и 2 задача от домашното би трябвало да се приемат като една.
Тито правилно е следвал условието на задача-1 (Laptop Shop), изрично е посочено да бъдат създадени само два класа. Аз съм направил задачата по същият начин, но мисля че Тито е пропуснал следното: Класът Battery трябва да се наследи чрез композиране, т.е създаване на една инстанция от него, както той си го е направил, но тази инстанция трябва да съдържа полетата характерни за Battery: модел, живот на батерията . Класът Battery трябва да отговаря за валидирането на тези полета(ne Laptop), а след това да се създадът само свойства в Лаптоп които се пускат без валидиране. Така мисля че е поставен проблема в задача-1. Ето тук само схематично съм направил това : TУК . Мога ли да помоля Стоянов, да направи един солюшън на схемата която е постнал за Laptop Shop, ако има време, просто за по добра демоснтрация (или което избере за по подходящо).
Прав си, много по-добре е, ако батерията държи вида и издръжливостта си.
Не съм виждал досега подчертавките в полетата да са най-отзад. Виждал съм да са в началото на името на полето. Но не е добра практика да има nonalphanumeric символи в имената. По-добре свиквай без тях!
И още една забележка. Имената на полетата в CSharp са camelCase, а не се съединяват с долни тирета.
Това с подчертавките е deprecated code style и общо взето заместител на this спрямо четимост.
Абсолютно точно за този стил на писане направихте забележка, не се използва вече и слаша се слага отпред и така натам, аз обаче страшно се обърквам по стандартният начин :), тази писаница дето съм я направил наистина да не се гледа като стил, само да се проследи идеята. Обещавам че ще го роменя..в скоро време !
Леко бутнах условията на доманите:
Надявам се да не съм внесъл confusion.
Наков
Еми ще пишем пак, каква ни е работата!
Колегите трябва да са свикнали с идеята че сме бета тестери и тези неща са нормални за този випуск :)
bsdemon: Колега, това е симулация на реална ситуация, в която клиентът не знае какво иска и си променя заданието малко преди крайния срок. Случва се много често в IT индустрията. В реални условия това е повод да се предоговори цената на проекта, но тук просто остава да си пренапишем задачите и да се радваме, че ни подготвят за всичко, което може да ни сполети.
Колеги в новата първа задача не ви ли се струва че има малка грешка. По условие конструкторът с по-малко параметри трябва да извика този с повече параметри. Не че е невъзможно, но е нелогично!
И аз мн му се чудих ... ама сигурно има логика (все някаква си ... :) )
Примерно като на HTML - правете header с таблици .... ама да знаете че не се прави така
Сигурно е повече за практика да не се чудим кое? как? защо? ако го срещнем някъде.
Аз лично ще карам по условие ...
Обаче при мен изниква др въпрос.
Първо почнах да пера всичко е един фаил class Person, Main() и др. методи ги забих в един Program.cs.
После ми стана мн грозно така Person го направих в отделен файл. Сега се чудя методите за валидация на името и майла къде им е мястото .... при обекта, при Main() или в отделен file/class Methods ... това нещо ми обягва
Ако валидацията ти е много голяма е добре да я изведеш в private метод, в класа където е пропъртито, към което принадлежи валидацията. Или ако валидираш email, то изнасяш private метод в класа Person. Този метод викаш в сетера на пропъртито Email.
За нашите задачи обаче валидациите са по две-три сравнения и се правят само в пропъртитата.
Да, тези които са точно за определено нещо ясно .... там при обекта
Но да кажем при мен сега в случея за името ...
Не мисля да използвам някакви регулярни изрази и т.н. , предпочитам да си ги правя с методи. Да кажем Името искам да е по дълго от два символа и да съдържа само символи от 'А' - 'Z' или 'а' - 'z' или 'а' - 'з' или 'А' - 'З' ... т.н. и за всяко едно може да е разбито на по отделно ....
Мисълта ми е че когато методите за валидация можеш да ги ползваш за повече от един клас .... например ако имаш Човек, Куче, Място за разходка .... и трите ще си имат някакви имена напр.(Димитър, Жу, Борово око) .... за валидация искам да използвам един метод ... и на трите класа ли трябва да го пиша или да си направя dll за такива случай
Не мисля, че има грешка и че е нелогично, просто алтернатива на constructor with default parameters - Chaining
Имам малко въпроси и аз. Какво се има впредив под optional в условието?
Аз си представям така нещата, имаме да кажем инпут от user-a и го приемаме в конструктор, полетата, които са optional, ще влязат като празен стринг и не трябва да има проверка за тях при settera...няма нужда да гърми, те са си optional. Това е първия сценарии.
Втория вариант е да имаме отново конструктор с толкова параметри колкото е полето за попълване от клиента и да сложим default parameters, които няма да са празен стринг а нещо от рода на "N/A". Но дали ще е празен или "N/A" няма голяма разлика. Проверката нужна ли е?
Трети вариант, които пробвах е да има най-различни конструктури, всички комбинации са много и става голяма каша, коя стойност къде отива.
По-конкретно въпроса ми е как се прави в реалния живот и също така за целите на домашното дали да създадем просто 3-4 примерни конструктура или има някакъв начин ефективно да работим с optional стойностите?
Под optional означава, че или ги няма, или ги има. В C# една референтна променлива да я няма, означава да е null, а не да е празен стринг. Т.е. ако един стринг е празен, то той съществува, но няма стойност. Понеже числовите типове не могат да са Null, те се инициализират с 0;
За реализацията на optional reference параметри има два начина.
Първият: в конструктора им подаваш стойност по подразбиране null, напр. public Laptop(..., string ram=null,...) и (ако са повече от един) при извикването на конструктора ги подаваш(ако ти трябват) с наименовани променливи new Laptop(...,ram: someValue,...)
Другият начин е да направиш един конструктор само със задължителните стойности и в него да извикаш пълния конструктор със стойност null за незадължителните: напр пълен конструктор public Laptop(string model decimal price, string hdd, string ram,...){...}, непълен конструктор: public Laptop(string model, decimal price):this(model, price, null, null,...) {...}
При валидацията за незадължителните полета просто не правиш проверка дали са null, защото не е проблем да са null. Може обаче да проверяваш по други критерии. както в нашето условие е казано: параметърът е optional, но не може да е празен стринг означава може да е null, но ако не е null трябва да не е празен.
Числовите типове могат да са nullable
Пример: int? num = null;
Най-лесно и разбираемо мисля, че става с един конструктор(пълен), на който на незадължителните полета се задават стойности null, обаче целта на задачаите явно е да се упражни верижния конструктор, който вече е остарял според лекцията на Николай Костов от миналата година в курса по ОПП на Академията. Според него е добра практика да се използва наскоро въведения в .NET по-долу начин:
Колеги измъчих се малко ама ето първата задача.
Моля ви дайте коментари ако правя нещо неправилно. Според мен работи по условие.
Persons
Laptop-Shop
PC Catalog
SULS -- ToDo
Здравей, идеална е задачата, единствено забелязах, че не ползваш правилно ексепшъните.
При ArgumentNullException и ArgumentOutOfRangeException ако ще подаваш само един параметър, той трябва да самото име на параметъра, който прави проблема, а не съобщението за грешка, както се прави примерно в ArgumentException.
Тук може да видиш конструкторите.
Благодаря за корекциите, радвам се като малко дете на коледа, защото вече ми се изясняват нещата.
Колеги и аз ръчкам по новата първа задча, където наистина има доста неясности. Имам проблем с валидацията. Направих я в property-setter-a и си работи перфектно със всичките му там ексепшъни. Но накрая като тръгнах да си правя тестове в main метода се сетих, че като подам параметрите на новия обект през конструктора [ Person somePerson = new Person("Pesho",23, "pesho@epich.bg" ] реално не ми се прави валидация и всякакъв инпут си минава. Ексепшъните гърмят само, когато подам сотйност през пропъртито [ somePerson.age = 101 си гърми "Out of range"] . И веднага стигам до въпроса как се измъкваме в такива напълно релни ситуации? Винаги мога да нашия проверка и в конструктора, но това ми се струва леко малоумно да правя едно и също нещо два пъти.При по големи проекти това ше е цифра код копи-пейст. Има ли начин да направим валидацията в конструктора промерно и да "подадем" от него по някакъв начин вече валидираните параметри на пропърти сетъра. И дори да има такъв начин какво правим ако си направим нова инстанция на класа през parameterless конструктор [ Person somePerson = new Person( )] , без да подаваме параметри и след това през сетера ги нашием [ somePerson.name = "Pesho"]. Тогава и да сме си спестили двойната проверка отива на вятъра, защото подадения параметър не е минал валидаиция през конструктора! ..или поне аз така си мисля. Някой по на вътре с нещата да ме светне!
Колега аз направих всичко по лекцията, много са ми мътни още нещата. Проверката ми е в property-то и си работи. Според мен нещо си объркал, но не мога да кажа със сигурност. Дай да погледнем кода или погледни моя. Все ще го измислим :)
Здравей колега,
Първо по това, което си написал разбирам, че подаваш стойността на полета, а не на пропъртита, или пък не именуваш правилно. Прието е полетата да са малка буква, а пропъртитата с голяма - например ако somePerson.age е поле - somePerson.Аge ще е пропъртитито.
Полетата в общия случай са private и somePerson.age = 23 би трябвало да е невалидно извън класа (трябва да се използва somePerson.Аge).
Валидацията се прави в пропъртито, и когато има такава, в конструктура също се вика пропъртито, а не полето. По този начин винаги, когато задаваш стойност на дадено поле, тя първо минава през валидацията в пропъртито.
Ами не виждам какво мога да съм объркал. Проверка в пропъртито, но през конструктора не ми я хваща
Ето го кода. Най-отдолу са ми проверките(коентирал съм ги) Свиркайте ако видите нещо. А твоя ше го линкнеш ли, че нещо не го виждам из постовете
Имам питане по Laptop Shop - защо в клас Battery имаш едно публично поле GeneralInfo и едно private? Не може ли да се мине само с едно публично?
Имам private поле и публично пропърти, което е сложено за валидация на подаваните данни :) Не виждам как ще стане само с пропъртито
Ooo, вярно, те като са едно под друго, съм взела пропъртито за поле...
И аз имам 1 въпрос по първата задача. Там, където се прави проверка за валиден мейл - > if(null!=value && (value.Length==0 || !value.Contains("@"))) . Не мога да разбера логиката на самата проверка. if null != value не значи ли, че хвърляш exception в случай, че стойността, записана в email, е всичко друго освен null ? Може ли малко разяснение в тази логика?
Условието горе означава: ако value не е null , то хвърли грешка, ако е празен стринг или ако не съдържа @
Един вид, ако е различно от null и е празен ->хвърляй,
ако е различно от null и не съдържа @ -> пак хвърляй
По условие разрешените стойности са null или непразен стринг съдържащ @
Ако не сме дали стойност на email (email=null) не е необходимо да хвърляме грешка, защото email е optional
Ако имейл съществува (email!=null), то трябва да го проверим дали е празен и дали в него има @.
И това обяснение ме подсеща, че всъщност проверката за празен стринг може да отпадне, защото ако съдържа @, то значи не е празен :)
Благодаря за обяснението, сега схванах идеята! :)
Аз даже си мисля, че няма нужда да го проверяваме дали е празен стринга, а да го проверяваме само за това дали съдържа "@", защото ако стринга е празен той не съдържа "@".
Здравейте,
Аз имам един въпрос по задачата LaptopShop. В класа Laptop полето ни за батерия ще е instance на класа Battery. Следователно какъв е правилния начин да се зададе това поле и да му се напише пропъртито и по - точно сетъра.
Благодаря за отделеното време.
Диляна
Диляна, не претендирам, че това е правилният начин (т.к. сега ги уча тези неща и ще се радвам на коментар към подхода ми), но така ми се струва най-логично - в основния клас на програмата (Main) създаваш какъвто вид батерия ти трябва, а после генерираш всеки лаптоп с батерия по избор. Kак правя пропъртито - виж Laptop.cs:
Това е по-правилният начин, от този който ни се показа на лекциите. Инициализация в конструктора по принцип е лош вариант. Въпреки, че точно в конкретния случай няма почти никаво значение дали е по начинът на Karlie или в конструктора :)
Но в друг случай би било от голямо значение:
Първо е важно, ентититата, които значат нещо, да бъдат реално такива - т.е. реални класове. Ако за всеки лаптоп правя new Processor("Intel"); То значи имам огромна нужда от клас, който се казва IntelProcessor.
Представете си абстрактен клас Processor. И два класа, които го наследяват - IntelProcessor и AmdProcessor.
Сега, ясно е - всеки лаптоп има нужда от процесор.
В конструктора на Laptop искаш да инициализираш нов процесор, но не си наясно той Интелски ли ще е или AMD. В такъв случай ще трябва да направиш някакво извращение, за да инстанцираш всички възможности и после да манипулираш точно тази, която ти трябва (абстрактен клас не може да се инициализира). Което е МНОГО ЛОШО решение.
Според LSP обект от тип IntelProcessor е също така и обект от тип Processor. Това означава, че ако някой метод очаква параметър от тип Processor и ти му подадеш негов подтип (IntelProcessor) всичко ще върви на шест точки.
В такъв случай, ето го и конструкторът на нашия Лаптоп клас:
public Laptop(Processor processor)
{
this.Processor = processor;
}
Сега в main-a на LaptopShop класа правим следното:
Processor myLaptopProcessor = new AmdProcessor("4 GHz");
Laptop myLaptop = new Laptop(myLaptopProcessor);
Така имаме абсолютната гъвкавост да подадем какъвто подтип пожелаем, обектите да са независими един от друг, и в случай че искаме да направим някакви тестове, лесно можем да си направим mock object-и на съответните типове, в противен случай класът Лаптоп сам ще ги е инициализирал и не можем да ги заместим.
Pattern-ът се казва Dependency Injection и в обществото се води предпочитаният такъв.
Наистина е така... но си мисля че е абсурдно да сложим всички възможни компоненти на един лаптоп в отделни полета. Един лист от Компоненти или някакъв интерфейс който би ни свършил работа е по-удачния вариант. Много по лесно после боравим с данните. Иначе - this.processor + this.ram+this.....+this.card reader+this.wireless кофти става.
Може и от твоята гледна точка да се направи. Определено ще се получи по-добре. Но не мисля, че е задължително.