Решение на Трета задача от Димитър Димитров
Към профила на Димитър Димитров
Резултати
- 6 точки от тестове
- 0 бонус точки
- 6 точки общо
- 69 успешни тест(а)
- 0 неуспешни тест(а)
Код
Лог от изпълнението
..................................................................... Finished in 0.08742 seconds 69 examples, 0 failures
История (3 версии и 6 коментара)
Димитър обнови решението на 14.12.2013 19:42 (преди почти 11 години)
Димитър обнови решението на 14.12.2013 23:18 (преди почти 11 години)
Защо логиката по рисуването е изнесена в класовете, които ще се рисуват? Не е ли по-добре всеки от тях да дава свое консистентно представяне (списък от всичките си точки / (х, у) координати), което да се ползва от Canvas
? Например ако в момента решим, че центърът на координатната система трябва да е долу ляво, а не горе ляво трябва да променим или всички 'форми', или всички renderer-и.
Защо Html < Ascii
? Не можеше ли render
логиката да бъде в Base
или модул, който да се include-ва?
Отзад-напред... Ти беше ли на лекцията вчера? Там обсъждахме това решение, както и някои от слабите му страни.
Относно Html < Ascii
, както и сам си си отговорил, наследявам, за да преизползвам логиката от Ascii
. Дали не може тази логика да е в Base
– да, би могло. Така или иначе, в момента няма много смисъл от Base
класа, а ако преместя render
там, ще има повече смисъл. Въпросът е, обаче, че тази логика на обхождане и рендериране е специфична по-скоро за ASCII-варианти на рендеринг, без значение дали са форми на ASCII art или HTML и мястото ѝ не е точно в Base
, ако бихме имали и други renderer-и, примерно някакъв curses-базиран, или пък някакъв реално графичен рендерер, или да кажем, някакъв SVG такъв. Да е в модул, който да се include
-ва не смятам, че е добра идея. Това рядко е добра идея.
Логиката за растеризация смятам, че трябва да се намира в обекта "форма". Къде би била тази логика, иначе? Пак в някакви други обекти, защото не е малка. Не смятам, че паното трябва да се занимава с това. То просто носи знание за пиксели и толкова. Нещо като виртуална координатна система. Как се рендерира това пано, е задача на рендерерите. А как се растеризира геометрична форма на паното – задача на формите. Така съм разпределил отговорностите аз. Разбира се, моят избор има своите недостатъци, но не съм намерил друго по-подходящо решение.
Във връзка с проблема, който посочваш – "ако сменим координатната система...", не се сещам за решение, което би изисквало да променим по-малко неща. А и спекулативният дизайн не е добра идея. Тоест, неща тип "ако занапред стане така, че" не трябва да е нещо, на което слагаш голяма тежест, когато си правиш дизайна. Имаш конкретни изисквания сега и се съобразяваш с тях. Ако се променят занапред, тогава ще си промениш дизайна по съответния начин, но не предварително, защото вероятността да се променят точно така, както си представяш, е много малка. Да, вероятно ще има промени, но не и такива, каквито ти би очаквал.
Не успявам да видя и ползата от това всяка форма да си носи отговорността за растеризация, но да експортира само "списък от точки", защото не виждам смисъл в тези висящи в нищото точки. От тях има смисъл само ако са в контекста на координатна система, тоест пано.
Защо тогава нямаме клас, който да наследява Base
, от който Ascii
и Html
да наследяват?
Съгласен съм, че растеризирането трябва да е във 'формите'. Но мисля, че изобразяването върху паното трябва да става в паното. Или може би в клас, от който наследяват всички форми. Решението на проблема с огледално рисуване ще се състои само в добавянето на 1 метод в Canvas
, при представянето със списък от точки. Не мисля, че е чак такава спекулация - звучи нормално да искаме към програмата ни за рисуване да може да се добавят нови универсални за всички фигури операции (огледален образ, въртене, запълване, т.н.).
Не помня на лекции да сме говорили за това кога е по-добре да include-ваме модули и кога да наследяваме класове. Може ли да обясниш?
Защо тогава нямаме клас, който да наследява Base, от който Ascii и Html да наследяват?
Може, има някаква логика в това, но смятам, че са твърде много абстракции, излишно много за тази проста задача. Тук по-скоро бих махнал класа Base
и бих оставил Html < Ascii
, отколкото да добавя още един клас.
Друг вариант би бил Base
да се казва Text
или нещо подобно и да носи общата логика и за двата рендерера, като Ascii < Text
и Html < Text
. Така е малко по-подредено и е на ръба на позволеното ниво от абстракции за моделиране на този проблем :)
Съгласен съм, че растеризирането трябва да е във 'формите'. Но мисля, че изобразяването върху паното трябва да става в паното. Или може би в клас, от който наследяват всички форми.
Не мога да си представя това така, че да има смисъл от него. Ако напишеш конкретен примерен код и го споделиш като Gist, може да го обсъдим.
Решението на проблема с огледално рисуване ще се състои само в добавянето на 1 метод в Canvas, при представянето със списък от точки. Не мисля, че е чак такава спекулация - звучи нормално да искаме към програмата ни за рисуване да може да се добавят нови универсални за всички фигури операции (огледален образ, въртене, запълване, т.н.).
Сега мисля, че те разбирам. В предния си коментар нарекох спекулация очакването, че някой може да реши, че вместо горе вляво, началото на нашата КС да стане долу вляво, например. Това е спекулация за промяна в базовите изисквания. Но е спекулация.
Ако имаш предвид, че някой може да иска да рисува огледален образ, примерно (хоризонтален или вертикален), това е друго. Това е вече фийчър, може би полезен/интересен. Нормално е да си мислиш за такива фийчъри, неизбежно дори. Но, въпреки това, също е спекулация, макар и от различен тип. Няма го и в условието. Ако ти отговаряше за дизайна на библиотеката, може би щеше да искаш да вкараш такава функционалност. Това си е твое решение, като тук трябва да се пазиш от scope creep – да не нарастне твърде много (излишно много) обема от функционалности, които се опитваш да покриеш с кода си. Когато не отговаряш ти за изисквания дизайн (като фийчъри), какъвто е случаят тук и какъвто би бил в някакъв проект с клиент, да си мислиш такива неща е спекулация. Грешно е да си базираш дизайна на неслучили се изисквания.
Но отново, ако напишеш конкретен опростен примерен код, илюстриращ как смяташ, че би се адресирал по-добре този проблем и го пуснеш в Gist, можем да го коментираме. Всичко друго, което си говорим без код, на сухо, също е спекулация :)
Но хайде, като за последно по темата, да спекулираме, че някой дойде и каже "искам да мога да рисувам огледално и да запълвам фигури". Това са два различни фийчъра. Само към първата част има много начини да се подходи и много въпроси. Огледално обръщане на цяло пано, или само на една фигура? Само линията има смисъл да се обръща огледално при нас, а и това не е особено интересно и може да се постигне на база на съществуващата линия, с проста трансформация. Може би метод в линия, връщащ нова линия със завъртяни координати. Рендерирането остава непроменено. Огледално пано? Това има повече смисъл, това бих го направил като метод в пано, който връща ново пано. Изобщо, много различни начини за имплементация, в зависимост от конкретните изисквания. Тъй като няма такива изисквания, само бих спекулирал и това би ми навредило на дизайна. Запълването на фигура е интересен проблем и към него също има много начини да се подходи, в зависимост какво е изискването. Може да е нов клас, FilledRectangle
, например. Запълване на зона в пано (като инструмента "кофа" в MS Paint) е друга функционалност. Съвсем друга. За нея трябват други алгоритми и друг дизайн. Хиляди въпросителни, нула отговора = спекулация = лошо за дизайна.
Не помня на лекции да сме говорили за това кога е по-добре да include-ваме модули и кога да наследяваме класове. Може ли да обясниш?
Няма една универсална формула. Според случая. Като цяло, бих се стремял да избягвам твърде многото модули и функционалност в тях, защото по този начин това не е ОО-програмиране или ОО-дизайн, а са си функции, капсулирани в именувани пространства, тоест някаква форма на процедурно програмиране. Стремя се да мисля в контекста на обекти с отговорности и състояния. Много рядко се налага да имаш споделена функционалност тип "миксин", какъвто е, например, Enumerable
в Ruby. Той е сравнително добър пример, но няма много други такива.
Друг вариант би бил Base да се казва Text или нещо подобно и да носи общата логика и за двата рендерера, като Ascii < Text и Html < Text.
Това ми беше първоначалната идея, просто не обърнах внимание на името на класа, който се наследява.
Сега мисля, че те разбирам. В предния си коментар нарекох спекулация очакването, че някой може да реши, че вместо горе вляво, началото на нашата КС да стане долу вляво, например.
Това бях казал и осъзнавам, че този пример е глупав. Но като цяло идеята ми е, че далеч не е спекулация фактът, че за да стане използваема нашата програма трябва да добавим още много 'форми' и операции с тях (кой ще я ползва, ако няма базови неща като овал и запълване). Ако фигурите ни се самоначертават на паното имаме проблем като искаме да добавим нови операции. Това с огледалната линия, представена чрез друга линия, разчита на прекалено конкретни свойства на операцията (симетрия) и множеството ни от фигури (което се предполага, че ще се разширява много).
От друга страна можем безопасно да предположим, че операцията над фигури може да се извършва върху множеството от точки, което представя дадените фигури. Това не е задължително да е абсолютно вярно, но не мога да се сетя за контрапример, който не е изключително изкуствен. Sanity check: tool-овете във великия MS Paint :) не се интересуват какво сме нарисували, гледат само къде има пиксели. С други думи - ако искаме да добавим нова фигура, тя просто трябва да казва как може да се представи като списък от точки, а ако искаме да добавим нова операция над всички фигури просто добавяме операция, която се извършва над произволно множество от точки. Вече тази операция дали ще е във вътрешен клас за паното, или в Drawable/Shape (или някакво друго име за клас, от който всички фигури ще наследяват) е добър въпрос (може би по-добре второто), но не можем да се спасим от проблема, ако трябва да е във всяка отделна фигура.
Не съм сигурен какъв Gist искаш - ако просто да имплементирам огледален образ, въртене и запълване над произволно множество от точки не виждам какво ще покаже, освен че е възможно.
Мисля, че най-накрая разбирм какво имаш предвид с междинното представяне от точки. Искаш да го въведеш, за да представлява абстракция тип "фигура", с която да можеш да правиш операции, например "скалиране", ротации, огледални образи и прочее. Като генерични операции с растер, без значение какви са фигурите. Все едно това представяне е едно малко виртуално пано, само по себе си. Ако правилно съм те разбрал, да, в това има смисъл, ако ще развиваш библиотеката. В зависимост от конкретната идея отзад, може би и аз бих тръгнал в подобна посока.
Само че, пак казвам, изискванията на задачата са други. Няма такива операции като изисквания и понеже задачата е с реално замразена спецификация – няма и да има. Само в контекста на задачата, това би била една излишна абстракция. Харесват ми дизайн-идеите, които имаш в посока развитие на функционалността, но са тема на отделна дискусия. Например, ако искаш да видиш дали би се получил добър дизайн, може да доразвиеш решението си, добавяйки тези растерни операции, за които си мислиш (огледални образи и прочее) с дизайна така, както си го представяш и може да споделиш кода, за да го обсъдим и да видим дали е станало добре. Това имах предвид, като казах "gist" – от самите идеи обсъдихме колкото можахме, от тук нататък единственият начин да продължим дискусията конструктивно, би било да обсъждаме реален код.
Нашата цел, решавайки задачи и предизвикателства, е да се опитаме да измислим най-оптималния – прост и изчистен – дизайн на даден проблем. Не така, както ни се иска на нас да бъде зададен, или както очакваме, че би се развил занапред, а така, както е зададен. Това понякога (може би даже често) е трудно и за мен.
Бих нарекъл това затруднение "the programmmer's cognitive bias". Свикваме да мислим за решението на даден проблем в контекста на код, дизайн и архитектура и пропускаме факта, че понякога по-малко код или дори никакъв код са всъщност най-подходящото решение. Имаме и естествена склонност към усложняване, без това да се изисква. Искаме полагайки основи, тези основи да могат да понесат 120-етажен небостъргач, който не се срутва при земетресения със сила 10+ по Рихтер, разбира се – кой не би искал това за неговата структура? И лесно пропускаме факта, че "клиентът" е искал просто баскетболно игрище...
Интересна дискуския се получи, благодаря ти за въпросите :)