Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

Translate to English… Ins Deutsche übersetzen…

Čistý Programátorský Experiment

Dovolte mi malý experiment. Týká se všech programátorů, které baví návrh aplikací a OOP. Zadám vám velmi jednoduchý úkol, který má mnoho možných řešení. A spíš než konkrétní kód mě zajímá způsob uvažování. Budu rád, když se zapojí programátoři používající různé jazyky. Proto také zadání zapíši v pseudokódu.

Mějme třídu WebPage, které zadáme URL a ona načte stránku a vrátí jeji obsah, hlavičky a dokonce i náhled v podobě obrázku. Příklad použití:

page = new WebPage
page.url = 'http://phpfashion.com'
echo page.url
echo page.body
echo page.headers
echo page.thumbnail

Chápejme url, body, headers a thumbnail jako vlastnosti (properties, accessors) třídy WebPage. Je asi zřejmé z logiky věci a principu zapouzdření, že zatímco url umožňuje zápis i čtení, ostatní vlastnosti lze pouze číst.

Protože funkčnost třídy WebPage je výkonnostně i časově náročná, je vhodné jednou získaná data ukládat do databáze. Jak to ale implementovat? Sice nejsnadnější by bylo rovnou upravit kód třídy WebPage, jenže takový postup je špinavý. Proč by třída s jednou srozumitelnou funkcionalitou měla navíc ještě komunikovat s databází? Pověříme tím tedy třídu WebPageStorage. Ta nám bude, mimo jiné, schopna dodat objekt WebPage rovnou z databáze:

page = WebPageStorage.load('http://phpfashion.com')
echo page.url
echo page.body
echo page.headers
echo page.thumbnail

Otázka zní: jak to naprogramovat?

Zopakuji, že se pídíme po nejčistším řešení. Jak docílit toho, aby metoda load mohla vytvořit objekt a nastavit mu read-only vlastnosti body, headers a thumbnail na hodnoty načtené z databáze? Vytvořením setterů nebo zpřístupněním vnitřních proměnných by se porušil princip zapouzdření. Navrhli byste redesign třídy WebPage? Jaký? Navrhli byste redesign WebPageStorage? A co když úkol zkomplikujeme tím, že thumbnail se z databáze načte až při vyžádání?

Věřím tomu, že pro řadu čtenářů je zadání naprosto triviální. Přesto se zkuste zamyslet nad nejčistším řešením a vysvětlete jej v komentářích. Klidně obšírně. Díky!

Komentáře RSS 2.0 komentářů » přidat

avatar

#1 Jod http://romcok.eu nový

Už som žul lakeť, kedy bude čo čítať :)
„a nad čím rozmýšlať“ :)

Posláno 28. 10. 2009 ve 3.22 | Odpovědět
avatar

#2 Jod http://romcok.eu nový

Napchať WebPageStorage do konštruktoru a vnútri ho spracovať? Tak sa s tým dajú páchať rôzne zločiny.
„Vyhral som niečo?“ :))) (jednu po hube)

Posláno 28. 10. 2009 ve 3.29 | Odpovědět
avatar

#3 Patrik Votoček (Vrtak-CZ) http://patrik.votocek.cz nový

Je WebPageStorage potomkem WebPage?

Posláno 28. 10. 2009 ve 4.41 | Odpovědět
Na komentář reagoval [4] morousej

#4 morousej nový

#3 Patrik Votoček (Vrtak-CZ): Podle mně ne – úložiště webových stránek není nějakou webovou stránkou, tudíž není jejím potomkem po straně návrhu.

Posláno 28. 10. 2009 v 8.06 | Odpovědět
avatar

#5 Adams adam@surak.eu nový

A co dát metodě load další 3 volitelné parametry body, headers a thumbnail?

Posláno 28. 10. 2009 v 8.46 | Odpovědět
avatar

#6 Washo vjuchelka@gmail.com nový

Napada me v konstruktoru WebPage akceptovat parametr, ktery nastavi parametry na hodnoty z databaze.

Posláno 28. 10. 2009 v 8.47 | Odpovědět
Na komentář reagoval [7] Washo
avatar

#7 Washo vjuchelka@gmail.com nový

#6 Washo: … nastavi properties na hodnoty ulozene v databazi.
Sorry

Posláno 28. 10. 2009 v 8.51 | Odpovědět
avatar

#8 Aleš Roubíček http://rarous.net/ nový

WebPage jako taková by neměla znát způsob načítání dat. Proto je vhodné zvolit dependency injection a konkrétní WebPageLoader injektovat zvenčí. Pak už stačí udělat konkrétní implementace pro načítání z webu, z databáze a kompozitní implementaci, která se pokusí načíst z databáze a pokud tam nic není, načte z webu. K tomu použije předešlé dvě implementace. Vše je schováno třeba za rozhranní.

Veškerá magie se zapouzdřením je pak v metodě load, kde se zavolá metoda WebPageLoader.load, která může vracet DTO s načtenými daty, které potom nastavíme do lokálních položek.

WebPageLoader klidně může mít tři metody loadContent, loadHeaders, loadThumbnail, pak nebude třeba metody WebPage.load, ale můžeme je volat při přístupu na dannou vlastnost a máme tu i požadovaný lazzy loading.

Posláno 28. 10. 2009 v 9.18 | Odpovědět
avatar

#9 johno http://johno.jsmf.net/ nový

Ten problem, ktory mam s tvojim pseudokodom je ten, ze WebPage bez url podla mna nema zmysel, tym padom, je po zavolani page = new WebPage(); premenna v nejakom divnom, nedefinovanom nekonzistentnom sta­ve.

Riesim to tak, ze mam protected konstruktor, ktory len natiahne data z nejakej struktury do instancnych premennych. Potom mozem, vytvorit staticke create_metody, ktore ci uz data beru z db alebo priamo z webu, je uplne jedno. Trochu to zamotava, ten lazy loaded thumbnail, ale nieco som skusil. Je to ruby, ale nie je tam nic specificke, co by sa v php nedalo. Spustat som to neskusal, ide o ideu.

class WebPage
        attr_reader :url, :headers, :body

        def self.create_from_url(url)
                url, headers, body = download_with_curl(url) # TODO
                WebPage.new(url, headers, body)
        end

        def thumbnail
                @thumbnail ||= create_from_external_service(url) # TODO
        end

        protected
        def initialize(url, body, headers, thumbnail = nil)
                @url, @body, @headers, @thumbnail = url, body, headers, thumbnail
        end
end

class CachedWebPage < WebPage
        def self.create_from_url(url)
                page = load_from_db(url)
                if page.nil?
                        page = super(url)
                        save_to_db(page)
                end
                page
        end

        def thumbnail
                save_column_to_db(@url, super) if @thumbnail.nil?
                @thumbnail
        end
end

Co sa tyka statickych create_metod. Ovela lepsia finta by bola, keby bola volitelna polymorfia na zaklade typov v dynamickych jazykoch. Predstavoval by som si to nejako takto.

class WebPage
        def initialize(String url)
                # natiahneme z url
        end

        def initialize(Array data)
            # len naladujeme z nejakej struktury
        end
end
Posláno 28. 10. 2009 v 9.19 | Odpovědět
avatar

#10 Aleš Roubíček http://rarous.net/ nový

#8 Aleš Roubíček: To, co jsem tu popsal, je defacto návrhový vzor Strategie

Posláno 28. 10. 2009 v 9.25 | Odpovědět
avatar

#11 René Stein http://blog.renestein.net nový

Dostal jsem několikrát hlášku, že komentář je příliš dlouhý, tak jsem kód dal alespoň na clipboard.
Souhlasím z velké míry s tím co napsal Aleš – dokonce bych Aleši řekl, že jde o pěknou variaci na vzor Repository (Composite v pozadí), který v originále také používá dvě strategie – jednu pro nahrání objektů z paměti a druhou pro nahrání objektů z databáze.
Ve svém skeletonu řešení jsem zkusil jiný postup, který se víc drží původní myšlenky Davida a nepředpokládá tolik berličky typu DI kontajner.
Kód je komentovaný (C#), snad je z něj patrné, co dělá. šlo by ho samozřejmě vylepšit o automatické adaptéry pro lazy loading apod.

http://www.clipboard.cz/9hg

Posláno 28. 10. 2009 v 11.33 | Odpovědět
avatar

#12 Martiner mcaslavsky@gmail.com nový

Jak docílit toho, aby metoda load mohla vytvořit objekt a nastavit mu read-only vlastnosti body, headers a thumbnail na hodnoty načtené z databáze?
Javě bych to řešil tak, že pro body, headers, thumbnail mám jen gettery.
Pro ukládání do databáze použiju Hibernate/JPA, který načtené hodnoty z databáze nevkládá přes settery, ale vloží je přímo do property.
A co když úkol zkomplikujeme tím, že thumbnail se z databáze načte až při vyžádání?
V případě Hibernate žádný problém – lazy property loading.

Posláno 28. 10. 2009 ve 12.09 | Odpovědět
avatar

#13 Tomáš Fejfar http://blog.red-pill.cz nový

Já bych šel cestou DI.

$webPage= new WebPage($url);
$webPage->setStorage(new WebPage_Storage());
echo $webPage->thumbnail

A uvnitř metody pro získání thumbnailu

if (null === $this->_thumbnail) {
     $this->_thumbnail = $this->_storage->getThumbnail($this->_url);
}
return $this->_thumbnail;
Posláno 28. 10. 2009 ve 12.28 | Odpovědět
Na komentář reagoval [14] Tomáš Fejfar
avatar

#14 Tomáš Fejfar http://blog.red-pill.cz nový

#13 Tomáš Fejfar: ještě vysvětlení, proč si myslím, že tohle řešení je nejčistší.

  • Nezpřístupňuje vnitřní proměnné ven, čímž neporušuje zapouzdření
  • Můžeme si upravit WebPage_Storage podle našich představ (v rámci definovaného interfacu)
  • Je snadno upravitelné pro libovolnou proměnou $this->_{$property} = $this->_storage->get{$property}();
  • Název třídy storage je možné definovat vnitřně (ala např. Zend_Form_Elementy a nastavení viewHelperu) s možností poslání nového názvu přes configurační pole a ušetříme volání setStorage(), které tím bude jen pokud budeme chtít storage změnit ex-post
Posláno 28. 10. 2009 ve 12.35 | Odpovědět
Na komentář reagoval [15] Tomáš Fejfar
avatar

#15 Tomáš Fejfar http://blog.red-pill.cz nový

#14 Tomáš Fejfar: A ještě jedna věc, třídě WebPage_Storage se dá poslat celý objekt WebPage a ta si z něj vytahá nějaký PK podle vlastní logiky. Ale možná to je zbytečná komplikace. Myslím ale, že by pomohla rnovupoužitelnosti celého konceptu i na jiné věci než WebPage…

Posláno 28. 10. 2009 ve 12.39 | Odpovědět
avatar

#16 Martin Štěpař MartinStepar@email.cz nový

Lze považovat použití funkcí serialize a unserialize v PHP za čisté řešení?

Posláno 28. 10. 2009 ve 13.33 | Odpovědět
avatar

#17 Jiří Knesl http://www.knesl.com nový

Takže nejčistší řešení pro tuto (předpokládám) cache stránek bych viděl já následovně:

  1. kde je WebPage a CachedWebPage (kterou lze uložit do persistence), je i rozhraní IWebPage a továrna WebPageFactory, která vrací objekty implementující IWebPage.
  2. továrna WebPageFactory se může nějaké persistence zeptat, zda pro danou url existuje uchovaný result (tedy data CachedWebPage).
  3. pokud existuje, jsou data vytažena, třeba do objektu, který má i settery (ale také implementuje IWebPage, říkejme mu třeba EditableWebPa­geData). Tento je potom obalen nějakým Decoratorem (zde jsem zneužil název návrhového vzoru, který se používá většinou na přidávání vlastností – ale v tomto případě je skrytí metod vlastně přidáním vlastnosti), který také implementuje IWebPage a má už pouze gettery.

Proti tvému zadání jsem WebPageStorage rozdělil na WebPageFactory a WebPagePersistence, protože to jsou dvě odlišné (persistence a vytváření instance) zodpovědnosti. Snad to nevadí. :)

Obecně jsem hlavně vyšel z toho, že je možné oddělit od sebe proces vytváření instance, vnitřní strukturu objektu a jeho použití. Pak už bylo řešení triviální, ikdyž bylo potřeba celkem 6 tříd (což mi nevadí, malé třídy se stejně lépe testují a upravují).

Posláno 28. 10. 2009 ve 14.41 | Odpovědět
Na komentář reagoval [28] paranoiq
avatar

#18 Jiří Knesl http://www.knesl.com nový

Zkusím to ještě radši shrnout:

  • IWebPage (1 setter, 4 gettery)
  • WebPage implementuje IWebPage je naše náročná stránka
  • CachedWebPage implementuje IWebPage a je dekorátorem EditableWebPageData
  • EditableWebPageData implementuje IWebPage a má navíc 3 settery
  • WebPageFactory se ptá persistence, kdyžtak vytáhne EditableWebPageData a předá je do CachedWebPage, kterou vrátí
  • WebPagePersistence říká, zda má uloženou EditableWebPageData a případně ji vrátí (vloží/aktualizuje, bude-li to součást zadání)

5 tříd, 1 rozhraní, omlouvám se za špatné sčítání předtím.

Posláno 28. 10. 2009 ve 14.46 | Odpovědět
Na komentář reagoval [19] Aleš Roubíček
avatar

#19 Aleš Roubíček http://rarous.net/ nový

#18 Jiří Knesl: Jo, takhle nějak vypadá overengineered solution :)

Posláno 28. 10. 2009 v 16.04 | Odpovědět
Na komentář reagoval [20] Jiří Knesl
avatar

#20 Jiří Knesl http://www.knesl.com nový

#19 Aleš Roubíček: No tak rozdělit persistenci a factory ti snad jako overengineering nepřijde.

Definovat si rozhraní tam, kde mám 2 třídy, které se mají chovat stejně, to je taky přirozené. Právě naopak by byla hrubá chyba rozhraní nemít.

Osekáno o toto:

Takže my dva máme 2 řešení – tebou preferované Strategy (která bude taky potřebovat nějakou nějakou logiku zvenčí – tedy taky nějaký objekt, který jsi nezmínil), mnou preferovaný Decorator (který už tu logiku má v té obalující třídě).

Ať se dívám, jak se dívám, přijdou mi naše řešení identicky složitá.

Posláno 28. 10. 2009 v 16.21 | Odpovědět

#21 Toby77 nový

Použil bych Dekorátor. Třída WebPageCached by obalila třídu WebPage, navenek zprostředkovala všechny její metody, ale jejich výsledky by navíc cachovala.
Má to tu výhodu, že WebPage nemusí nic tušit o zápisu do DB a WebPage nic o stahování přes http.

$w = new WebPageChached( new WebPage);
$w->url = "http://example.com";
echo $w->body;
Posláno 28. 10. 2009 v 17.01 | Odpovědět
Na komentář reagoval [23] Toby77
avatar

#22 David Majda http://www.majda.cz/ nový

Předně, vlastnost url třídy WebPage bych realizoval jako read-only a nastavoval ji už v konstruktoru, kde by se (nepřímo) realizovalo i parsování. Třída WebPage by v mém podání tedy byla bezstavová, což zjednodušuje její logiku, ale zas by neuměla zpracovat víc URL po sobě.

Teď k problému. Do instance třídy WebPage je potřeba nějak dostat údaje o stránce. Volil bych cestu konstruktoru a třídě WebPage přidal druhý konstruktor s parametry url, body, headers a thumbnail.

Pokud by mi jazyk neumoňoval přidat druhý konstruktor (nebo používat jeden dvěma různými způsoby), tak bych zrušil původní konstruktor, přidaný kontruktor učinil privátní a použil dvě statické metody WebPage::new(url, body, headers, thumbnail) a WebPage::parse(url). Ty by uvnitř volaly onen privátní konstruktor a WebPage::parse by navíc řešila i parsování.

Pokud by se thumbnail měl načíst až na vyžádání, tak bych místo thumbnailu třídě WebPage předal callback, který by thumbnail na požádání vrátil. Konkrétní realizace callbacku záleží na použitém jazyce (např. předání uzávěru, nebo speciální objekt).

Posláno 28. 10. 2009 v 17.11 | Odpovědět

#23 Toby77 nový

#21 Toby77: Navrhuju zřejmě to samé, co Jiří Knesl, ale v tom jeho návrhu jsem se trochu ztratil. Rozhraní a fabriky neřeším, pro jednoduchej příklad mi to přijde zbytečné. Stejně tak lazy loading, kontrolu konzistence, atd. Že to půjde udělat, je evidentní.

Posláno 28. 10. 2009 v 17.12 | Odpovědět
avatar

#24 David Majda http://www.majda.cz/ nový

#8 Aleš Roubíček: Něco podobného tvému řešení mě taky napadlo. V podstatě to znamená udělat z WebPage hloupý objekt odpovědný jen za držení dat o stránce, případně jejich líné načtení odněkud. Nakonec mi ale přišlo, že už by to bylo až moc velké rozmělnění odpovědnosti.

BTW Davide moc hezká úloha – kdybych ještě učil na matfyzu, studenti by ji dost možná dostali zadanou na cvikách :-)

Posláno 28. 10. 2009 v 17.31 | Odpovědět
avatar

#25 rsvanda rsvanda@gmail.com nový

WebPageStorage.load by neměla vracet přímo instanci WebPage ale proxy, která bude řešit ten lazy loading. Do ní konečně můžete předem setnout co budete chtít.
Inspiraci zkuste u hibernate.org.

Posláno 28. 10. 2009 ve 20.10 | Odpovědět
avatar

#26 ady http://ady.nasracky.cz nový

Co takhle:

page = new WebPage(‚http://p­hpfashion.com‘)

a v constructoru WebPage:

WebPageStorage wps = new WebPageStorage(‚http://p­hpfashion.com‘);
a nasetovat WebPage z WebPageStorage objektu

v constructoru WebPageStorage:

zkusit loadnout z DB a pokud neni tak nasetovat/nebo jinak zkonstruovat, save do db a vratit?

Ale jsem lamer, takze mozna vsechno spatne :)

Posláno 28. 10. 2009 ve 20.22 | Odpovědět
Na komentář reagoval [27] ady
avatar

#27 ady http://ady.nasracky.cz nový

#26 ady: A sakra koukam, ze vse jiz bylo receno – mel jsem stranku na pozadi a nenapadlo me to reloadnout pred psanim komentare :)

Posláno 28. 10. 2009 ve 20.27 | Odpovědět
avatar

#28 paranoiq paranoiq@centrum.cz nový

hezký příklad. přiznám se, že většinou čistě řešení nechávám na později

„Mějme třídu WebPage, které zadáme URL a ona načte stránku“ – pokud má být kešovatelná, neměla by řešit, odkud se data berou. tudíž zodpovědnost za načtení dat bych jí sebral

zaujala mě řešení #8 Aleš Roubíček: s DI a #17 Jiří Knesl: s factory a proxy. zdají se být stejná, ale nejsou

půjdu od lesa. vytvoření náhledu je také velmi náročná operace a náhled je samozřejmě nutné kešovat. vytvoření náhledu přitom spadá do kompetence třídy WebPage. jen ta ví jak se má vykreslit. na počátku WebPage náhled nemá a tudíž si ho při prvním požadavku vytvoří (může to za ní udělat nějaký jiný objekt, ale určitě ne úložiště). pak by jej samozřejmě ráda uložila. pokud ji však na svět vyplivla nějaká factory, třída nemá vazbu na úložiště. tady příklad s factory selhává. pokud jí tuto vazbu dodáme, jsme zase u DI. je tedy lepší použít dependency injection rovnou

Posláno 28. 10. 2009 ve 21.36 | Odpovědět
avatar

#29 paranoiq paranoiq@centrum.cz nový

výsledek:
• WebPage – umí vygenerovat náhled. data dostává od WebPageDataSource a případně mu posílá náhled. DS dostává konstruktorem, nebo si jej bere líně třeba od nějakého globálního service providera
WebPageProvider – žádá o data své adaptéry. předává data úložišti
• WebPageStorage – umí uložit a vrátit data a náhled
• WebPageLoader – umí stáhnout data

DTO je u takhle jednoduchého příkladu asi zbytečný. pokud by bylo dat víc, tak by se určitě uplatnil taky

BTW: nechci ti do toho kecat Davide, ale když kladeš takovéhle otázky, měl bys s dlouhými komentáři počítat :P

Posláno 28. 10. 2009 ve 21.37 | Odpovědět
avatar

#30 Jod http://romcok.eu nový

Ja by som to spravil takto:

Pastie: http://pastie.org/673943

Example: http://webpage.romcok.eu/
(stačí vyplniť twitter nick do políčka a načítať, prípadne ešte uložiť do db. Z db to ťahá vtedy keď tam neni save button :P)

SVN example: svn export svn://home.rom­cok.eu/webpage-example/trunk
(databázu by to malo importnúť samo, ale šlo to len z 50%, riešenie na rýchlo)

Určite to má svoje chyby, ale ponáhlam sa preč :))

Posláno 28. 10. 2009 ve 21.45 | Odpovědět
avatar

#31 Tomas Petricek http://tomasp.net/blog nový

Jen tak pro zabavu, aby tu taky zaznelo reseni z uplne jineho sveta :-), tak ukazka toho, jak by se k reseni pristupovalo (uplne nejjednodussim zpusobem) pri pouziti funkcionalnich jazyku (e.g. F#). WebPage v tve ukazce vlastne plni dve funkce – reprezentace dat a nacitani informaci o strance z webu. Otazka je, jak do takoveto implementace pridat nacitani dat z cahce v DB. To jde samozrejme spatne, protoze ty dve funkce jsou svazane v jedne tride. Nejjdodussi je napsat si nejakou datovou strukturu na reprezentaci dat:

type WebPage = { url : string; body : string; thumbnail : Bitmap }

…a pak dve funkce, ktere data nactou z ruznych zdroju podle dane adresy – napriklad loadFromWeb a loadFromDB.

Abych si trosku rypnul – musim rict, ze me obcas prekvapuje jak silene slozita reseni se daji v modernich objektovych systemech vymyslet, na reseni tak trivialniho problemu :-).

Posláno 28. 10. 2009 ve 23.52 | Odpovědět
avatar

#32 Tomik http://tomik.jmx.cz nový

Ve zkratce:

(možná, že se to schoduje s nějakým výše zmíněným postupem, nevím, jen jsem to prolétl, tak se omlouvám…)

  • interface IWebPage (pokud by to šlo, určitě zadávání URL v kontruktoru – podle úlohy, pokud by nemusela ta třída parsovat více URL, a všechny property jen read-only)
  • class WebPage implementující interface – ta vždy načte aktuální data, nic si nikam neukládá
  • class WebPageCached implementující interface – ta v případě, že je vytvořená, tak se zeptá, zda nemá data v DB, pokud ano, vrátí je, pokud ne, požádá o ně WebPage, jinak pro abstrakci by bylo možné ještě přidat nějaké nastavení uložiště (interface IStorage, a potom implementace DbStorage, FileStorage, …)

WebPage i WebPageCached by parsovaly už v kostruktoru, resp. volaly by odpovídající metody.

K tomu stížení: pokud by byl záznam v DB, tak si v třídě WebPageCached pouze někam uložím příznak, že náhled existuje a načtu jej až lazy v getteru.

Posláno 29. 10. 2009 v 1.29 | Odpovědět
avatar

#33 paranoiq paranoiq@centrum.cz nový

v c++ by se tuším na naplnění interních parametrů dala použít nějaká friend třída. není to něco podobného jako právě přidaný internal access modifier z Nette?

Posláno 29. 10. 2009 v 8.17 | Odpovědět
Na komentář reagoval [34] Aleš Roubíček
avatar

#34 Aleš Roubíček http://rarous.net/ nový

#33 paranoiq: Jenže internal nebo friend umožňuje přístup pouze těm, co jsou ze stejného balíčku (assembly). Pokud chci mít možnost přidávat další (externí) způsoby nahrávání je tento přístup nedostatečný.

Posláno 29. 10. 2009 v 8.54 | Odpovědět
Na komentář reagoval [35] paranoiq
avatar

#35 paranoiq paranoiq@centrum.cz nový

#34 Aleš Roubíček: to ano. ale nikde není řečeno, že přístup musí být i z jiného balíčku (pak by byla na místě DI). mě se zdá, že právě k podpoře klíčového slova friend David směřuje. ale třeba je to jen můj dojem. rozhodně by se mi takové směřování líbilo :]

Posláno 29. 10. 2009 v 9.41 | Odpovědět
Na komentář reagoval [36] Aleš Roubíček
avatar

#36 Aleš Roubíček http://rarous.net/ nový

#35 paranoiq: Ano, na takové věci se přichází až později. :) Pak se to musí přepsat… Lepší je se držet Open-Closed principu už od začátku. :)

Posláno 29. 10. 2009 v 10.01 | Odpovědět
Na komentář reagoval [38] paranoiq

#37 Jakub Vrána http://php.vrana.cz/ nový

Posláno 29. 10. 2009 v 10.52 | Odpovědět
avatar

#38 paranoiq paranoiq@centrum.cz nový

#36 Aleš Roubíček: musím přiznat, že nemám tušení v čem open/closed princip spočívá. nastuduji si.
balíčkování platí pouze pro internal, nikoliv pro friend. ideální by IMO bylo přiřadit té soukromé vlastnosti či metodě nějaký friend interface, což ale nejde ani v tom c++ (šlo by to ale zařídit třebas v php)

Posláno 29. 10. 2009 ve 12.34 | Odpovědět
Na komentář reagoval [43] optik
avatar

#39 Borek http://www.borber.com/ nový

Moc hezký experiment, který ukazuje, jak kdo přemýšlí. Trošku mě překvapuje, že jsem zatím neviděl řešení, které mi připadne přímočaré a ve světě .NETu snad i evidentní:

  • Vytvořil bych dva projekty, jeden typu knihovna („framework“, pokud někdo uvažuje v těchto intencích) a jeden typu aplikační kód.
  • Třídy WebPage a WebPageStorage bych dal do projektu „knihovna“ (a když už bych byl u toho, přejmenoval bych je na WebPageInfo a WebPageLoader, které podle mého názoru lépe vystihují, co představují)
  • WebPageLoader by měl public metodu Load(url), která by řešila cachování / stahování.
  • WebPageInfo by na všech properties měla public getter a internal setter. To znamená, že z metody WebPageLoader.Load() můžu tyto vlastnosti nastavit, zatímco v aplikačním kódu se budou všechny vlastnosti tvářit jako read-only.

Základním trikem jsou tedy dva projekty a potom public gettery / internal settery. V jazyku, který „internal“ nebo podobný koncept nepodporuje, bych patrně volil runtime exception, protože mi ostatní řešení připadají ještě ošklivější.

Zajímavou otázkou je, proč je v komentářích k vidění tolik komplikovaných řešení (často asi zbytečně komplikovaných). Možná je to zadáním – ona je totiž třída WebPage, tak, jak je prezentována v první ukázce, trochu podivná, protože představuje současně webovou stránku a její downloader. Z pohledu OOP je to podle mého názoru špatná abstrakce, což je naštěstí vzápětí vylepšen ukázkou s WebPageStorage, ale zdá se mi, že utkvělá představa udělala v řadě případů své.

Posláno 29. 10. 2009 ve 13.34 | Odpovědět
avatar

#40 Aleš Roubíček http://rarous.net/ nový

#39 Borek: Možná jsem své řešení komplikovaně popsal, ale rozhodně implementačne komplikované není. :) BTW internal je takový cheat proti zapouzdření, v tomto scénáři to není čisté řešení. ;)

Posláno 29. 10. 2009 ve 13.43 | Odpovědět
Na komentář reagoval [45] Borek

#41 PHX nový

Osobně bych šel touto cestou:

  • v app používat pouze třídu MyWebPage, která by měla identické rozhraní jako WebPage ci WebPageStorage.
  • tato trída by se interně rozhodla, zda by načetla data z internetu a gettery jako napřmyPage.page by ìnterně směrovala na WebPagenebo by data načetla z cache (DB) a gettery by interně směrovala na WebPageStorage.
Posláno 29. 10. 2009 ve 13.50 | Odpovědět
avatar

#42 René Stein http://blog.renestein.net nový

#39 Borek: Borek:

  1. Řešení je specifické pro .Net a úvaha, že člením projekt podle viditelnosti pár členů třídy, mi přijde úspíš směvná – bez ohledu na to, že následky své návrhářské kocoviny mohu od verze 2.0 zmírnit podáním aspirinu ve formě InternalsVisi­bleTo.:) Stejně jsou specifická řešení používající friend členy v C++ nebo použití privátních tříd v Jave a C#, které implementují vybrané rozhraní, což jsou další možná řešení.
  2. Pokud jde o jednoduchost, proč třída WebPage nenabízí jen statické metody (náhrada konstruktorů (WebPage.Crea­te(string xy …) nebo nezveřejní ekvivalentní instanční konstruktory? V mém řešení jde vlastně o konstruktor, který umožňuje díky použití delegátů pár dalších triků se skládáním funkcí, aby řešení nebylo tak fádní.
  3. Vyhazování výjimky v set akcesoru mi přijde jako „prasárna“ – špinavá práce se nechává na kompilátoru. Jestliže z metody VŽDY vyhodím výjimku InvalidOperati­onException, říkám tím, že do třídy nepatří. Taková krásný antipřístup vedoucí k instancím s tzv. klientsky nebezpečným chováním.
Posláno 29. 10. 2009 ve 13.51 | Odpovědět

#43 optik http://mirin.cz nový

#38 paranoiq:
přesně ta „nepodpora“ C++ friend, v PHP mě napadla jako první, prostě nějak zajistit, aby WebPage mohl naplnit jen někdo, kdo není v jedné hierachi s ním. Zdá se mi, že navržená řešení se to snaží simulovat přes různé dekorátory a proxy.

Posláno 29. 10. 2009 ve 13.57 | Odpovědět
avatar

#44 René Stein http://blog.renestein.net nový

A ještě pro všechny fanoušky špatně strávených dekorátorů a proxy, kteří se tu přemnožili. :-D
Možná bych si jako první věc uvědomil, že za dekorátora se považuje hlavně třída se stejným rozhraním jaké má dekorovaný objekt.
Že u vzoru proxy by si klient neměl být vědom odlišnosti proxy od reálného subjektu.
A zkuste si porovnat, jak má vypadat třída WebPage a jaké to bude mít důsledky (v různých jazycích různé, já vím) – přetypovávání apod.

Posláno 29. 10. 2009 ve 14.04 | Odpovědět
avatar

#45 Borek http://www.borber.com/ nový

#40 Aleš Roubíček: Úplně souhlasím, tvé řešení komplikované není a technicky se mi moc líbí, jen implementuje API, které je podle mého názoru špatnou abstrakcí (kombinuje objekt reprezentující stránku a objekt načítače stránky). Takže po technické stránce nemám námitek :)

Posláno 29. 10. 2009 ve 14.19 | Odpovědět
avatar

#46 Aleš Roubíček http://rarous.net/ nový

#45 Borek: Mě se ta kombinace popravdě taky moc nelíbí, ale když to tak bylo v zadání. ;) Proto jsem samotný akt načtení raději abstrahoval za injektovanou strategii. :)

Posláno 29. 10. 2009 ve 14.28 | Odpovědět
Na komentář reagoval [49] Borek
avatar

#47 René Stein http://blog.renestein.net nový

#45 Borek: Raději bych se zcela vyhnul hodnocení „špatná“ a „dobrá“ abstrakce. Abstrakce, stejně jako celé modely, knihovny a frameworky se dají rozdělit na dobře nebo špatně použitelné – a mezi těmito extrémy je celá škála přechpdů . Žádné exaktní kritérium pro ohodnocení míry dobra abstrakce neexistuje, jen (mnohdy špatně chápaná) vodítka typu SRP. Spojení „dobrá abstrakce“ beru jako univerzální hůl, kterým se dá bít návrh libovolné třídy (možná kromě třídy s jedinou odpovědností vyjádřenou metodou DoIt :))

Posláno 29. 10. 2009 ve 14.29 | Odpovědět
Na komentář reagoval [48] Aleš Roubíček [51] Borek
avatar

#48 Aleš Roubíček http://rarous.net/ nový

#47 René Stein: A i ta by mohla být pouze funkcí. :)

Posláno 29. 10. 2009 ve 14.46 | Odpovědět
avatar

#49 Borek http://www.borber.com/ nový

#46 Aleš Roubíček: Já zadání vyčetl takhle: potřebuješ nějakou tovární metodou vytvořit objekt X a nastavit mu vlastnosti A, B a C. Jak to uděláš, když se ti dvakrát nelíbí, že by A, B a C měly mít public settery? (René to řeší tak, že všechny vlastnosti má jako parametry konstruktoru, já radši „internal“).

Ale je fakt, že úvod o třídě WebPage je hodně matoucí, takže kdo ví, jak přesně to vlastně celé bylo myšleno :)

Posláno 29. 10. 2009 ve 14.53 | Odpovědět
avatar

#50 Borek http://www.borber.com/ nový

René: Vaše řešení se nabízí, jen mě napadlo, jak budete řešit situaci, když bude mít třída WebPage vlastností dvacet?

Jinak rovněž rád uvidím, jak v dynamickém jazyku vaší volby zařídíte, aby byl getter public a setter z venku neviditelný. Souhlasím s vámi, že se zde bavíme na obecné úrovni, kde techniky z .NETu nemusí fungovat, proto bych taky rád slyšel dovysvětlení toho, jak jste to myslel s tím kompilátorem (jakým?).

Posláno 29. 10. 2009 ve 14.58 | Odpovědět
Na komentář reagoval [53] René Stein
avatar

#51 Borek http://www.borber.com/ nový

#47 René Stein: Ano, návrh API je umění a do značné míry subjektivní. Můžete ale odkázat na nějaký framework, kde by se „stahovač“ a jeho „výsledek“ reprezentovali jako jedna třída? V .NETu, Flexu i Javě tomu tak není.

Posláno 29. 10. 2009 v 15.02 | Odpovědět
avatar

#52 Aleš Roubíček http://rarous.net/ nový

#51 Borek: Já jsem již včera chtěl napsat: „jak to dělá Firefox?“ Protože tohle je jasná implementace načítání stránky v browseru včetně cache.

Posláno 29. 10. 2009 v 15.14 | Odpovědět
avatar

#53 René Stein http://blog.renestein.net nový

#50 Borek: Ano, to je to kdyby. Vyšel jsem z Davidova zadání – úprav a vylepšení si mohu vymyslet spoustu.
Když bude vlastností 20, použiju jiné řešení. :)
Ale i tady – pro předání dat něco jako DTO objekt („krabička na data“). V některých situacích stačí i IDictionary (hashovací tabulka s init hodnotami pro objekt). Pro lazy inicializaci mohu předat sadu funkcí místo hodnot apod. a samozřejmě společnou logiku pro lazy inicializaci zapouzdřit do předka pro celou vrstvu A/nebo použít AOP.
U výjimky jsem narážel např. na C++, které viditelnost na úrovni modifkátoru internal v C# nemá (i kdyč jdou použít jiné triky). Dynamické jazyky mají svá specifika, ve statickém jazyce je samozřejmě velký rozdíl mezi vyvoláním výjimky v set akcesoru za běhu aplikace a neexistencí set akcesoru při kompilaci.

Posláno 29. 10. 2009 v 15.41 | Odpovědět
Na komentář reagoval [55] Borek
avatar

#54 René Stein http://blog.renestein.net nový

#51 Borek: Na co narážíte? Na nejkřiklavější analogii s tímto příkladem v .NET, kterým asi je WebRequest, WebResponse a ResponseStream?
Třída v sobě může skrýt, že interně používá WebRequest apod.

Co třeba XmlTextReader?
XmlTextReader reader = null;
reader = new XmlTextReader(url or filename);

XmlTextReader má konstruktor, který říká – umím stáhnout data a pak na mě klidně volej metodu Read pro čtení výsledného XML.

A samozřejmě existuje i „dirty“ metoda Create na XmlReaderu.

Posláno 29. 10. 2009 v 15.46 | Odpovědět
avatar

#55 Borek http://www.borber.com/ nový

#53 René Stein: To řešení pro 20 vlastností by mě docela zajímalo – šlo by naznačit aspoň v pseudokódu?

Jinak problém vašeho původního řešení vidím v tom, že vytvoření instance WebPage je ve vašem API 2× (jednou přes konstruktor, podruhé přes factory). Z pohledu klientského kódu mi nebude na první pohled jasné, které z nich použít (případně kdy a proč).

Posláno 29. 10. 2009 ve 20.21 | Odpovědět
Na komentář reagoval [57] Rene Stein
avatar

#56 Borek http://www.borber.com/ nový

René: Ještě se vrátím k tomu, jak vám připadá úsměvné dělení do assembly jen kvůli dosažení žádané viditelnosti.

V .NETu nejsem žádný expert, nicméně pokud je zadání to, že WebPageLoader má vrátit instanci WebPage, jejíž vlastnosti se mají ke klientskému kódu tvářit jako read-only, jak to realizujete? Ve vašem kódu sice nelze vlastnosti na WebPage měnit, nicméně stále lze vytvářet nové instance s libovolnými vlastnostmi, což patrně není velký problém, ale zbytečně to do API přidává něco, co tam být nemusí. Je nějaká jiná možnost, jak se toho zbavit, kromě internal?

(Předchozí samozřejmě platí v mém chápání zadání, které se nemusí shodovat s vaším. Jen pátrám po tom, co vám na internal v tomto kontextu připadá tak úsměvné.)

Posláno 29. 10. 2009 ve 20.46 | Odpovědět
Na komentář reagoval [58] René Stein [59] Borek
avatar

#57 Rene Stein http://blog.renestein.net nový

#55 Borek: Ad 20 vlastnosti – DTO objekt znate dobre Borku, ne? Proste jednoducha struktura s get a set akcesory, ktera mi umozni predat do konstruktoru misto 20 argumentu jeden jediny. Podobne jako kdyz dotahuji DTO z db/servisni vrstvy.
Ad dvoji API – to jsem tam nechal kvuli puvodnimu zadani, nikdo vam nebrani, abyste i posledni verejny konstruktor skryl a donutil klienta pouzivat jen factory metody.
Mimochodem, jiz mnou zminovany XmlReader ma metodu Create (dokonce nekolik variant) a trida XmlTextReader ma sve specificke konstruktory.
(Psano z mobilu)

Posláno 29. 10. 2009 ve 21.20 | Odpovědět
Na komentář reagoval [59] Borek
avatar

#58 René Stein http://blog.renestein.net nový

#56 Borek: Ta usmevnost se tykala ne internal clenu jako takovych, ale faktu, ze vetsinou musite resit pri navrhu a rozdeleni aplikace do jednotlivych assembly tolik problemu, ze delat separatni knihovnu kvuli internal clenum je luxus. Ano – pokud maji tridy spolecne rysy a logicky patri do stejne assembly, tak je vse v poradku.
Konstruktor je misto, ktere overi, ze predane hodnoty do konstruktoru splnuji vsechny pozadavky na vytvoreni legalni-platne instance. Pokud ano, tak nevidim problem v pouziti konstruktoru/metod klientem. Ze staticke cleny udelate v . net internal, pokud to lze, a mate je prirozene a nenasilne v jedne assembly? Proc ne.
Ale kdyz se ptate na jine reseni bez internal clenu, zde je jedna moznost(moc se mi nelibi, tesne vazby mezi tridami, je prekombinovane a ma i dalsi nevyhody).

  1. Verejne rozhrani IWebPageLoader.
  2. Privatni trida WebPageLoaderInner ve tride WebPage implementujici rozhrani IWepPageLoader.
  3. Staticka metoda GetLoader ve tride WebPage typu IWebPageLoader vracejici instanci privatni tridy WebPageLoaderInner. Pri registraci loaderu do repozitare sluzeb (viz dale) statickou metodu ani nepotrebujete.
  4. Volitelne fasada – verejna trida WebPageLoader implementujici rozhrani IWebPageLoader, ktera interne deleguje na WebPage.Loader.

Vhodnejsi by bylo pri inicializaci modulu zaregistrovat instanci WebPageLoaderInner v repozitari sluzeb jako implementora rozhrani IWebPageLoader. Rozhrani IwebPageLoader muze byt dostupne pres „Extension Interface“ atd.
Privatni trida WebPageLoaderInner ma pristup ke vsemu potrebnemu v obalujici tride WebPage, vlastnosti jsou stale pouze pro cteni a nemam internal cleny. Prred klientem jsou vsechny slozitosti skryty. Jen jedna ukazka toho, na co jste se ptal…
A pres repozitar sluzeb se pomalu jinou stezkou vracime k DI, kterou jsme zminovali na pocatku s Alesem.:)

(Psano na mobilu)

BTW: Indiana Jones tady radi fakt drsne. Nejdriv mě upozornil, ze muj dlouhy komentar nikdo cist nebude a pak jej rovnou poslal do pr… (skvela napodoba kultovni sceny v prvnim dile ?)

Posláno 29. 10. 2009 ve 22.21 | Odpovědět
Na komentář reagoval [59] Borek
avatar

#59 Borek http://www.borber.com/ nový

#57 Rene Stein: DTO znamená, že se ve vašem API objeví další třída, která bude co se týče vlastností velmi podobná WebPage. Rovněž si nejsem úplně jistý, že #58 René Stein: řeší problém, který jsem popsal v #56 Borek:.

Předně ale myslím, že oba vnímáme zadání trošku jinak, takže se těžko na všem shodneme. Každopádně díky za vhled do toho, jak byste problém řešil vy. Třeba David někdy upřesní, jak to všechno myslel :)

Posláno 30. 10. 2009 v 11.53 | Odpovědět
Na komentář reagoval [60] Rene Stein
avatar

#60 Rene Stein http://blog.renestein.net nový

#59 Borek: DTO v API byt nemusi. Uvadel jsem nekde vyse i objekt Dictionary. Problem, resp. situaci, kterou jste popsal IMO muj postup resi (se vsemi vyhradami, ktere u nej jsou).
Take Borku diky za podnety. Myslim, ze v poslednich prispevcich jsme u spekulaci a hricek „kdyby“, ktere uz se Davidova textu moc netykaji. Logout:)

Posláno 30. 10. 2009 ve 12.11 | Odpovědět
avatar

#61 Karel Minařík http://www.karmi.cz nový

Tak já se přiznám, že jsem z celé té debaty v těch „robustních“ jazycích a cvičení s návrhovými vzory dost zpitomělý, nicméně zkusil jsem si to napsat v Ruby (cca 1hod), a výsledek je zde:

http://gist.github.com/222260

  • na 210 řádků
  • včetně prázdných řádků
  • včetně komentářů/doku­mentace
  • včetně relativně generické cache
  • včetně testů pro WebPage i cache
  • včetně fixtures pro cache

Mám jednu nefér výhodu – nedávno jsem dělal generické kešování do RubyGemu HTTParty [http://ephemera.karmi.cz/…for-httparty]

Dovolil jsem si přitom změnit zadání, které mi nedávalo smysl – nikde není zdůvodněno, proč potřebuji nějakou třídu WebPageStorage, která jaksi „kešovaně“ obaluje WebPage.

Já chci mít normální WebPage, a ta si případně nějak pořeší cachování, které skryje za svoje API, které může být v tomto případě zcela triviální: Crawler::WebPage.load('htpp://example.com')

Ve skutečném životě by samozřejmě bylo třeba:

  • Dopsat nějakou logiku na inicializaci cache – kam chci ukládat (DB, key:value store, atd.)
  • Implementovat cache na nějaký key:value store jako Redis, TokyoCabinet, MongoDB, etc etc
  • Zařídit expiraci (!!!) cache :)

Každopádně díky za užitečný experiment!, bylo by úžasné někde na Githubu udělat repositář, kde by šly skladovat různé implementace v různých jazycích/plat­formách.

A pro mně osobně z toho plyne, panebože, zaplať (Matzovi) za to Ruby. Zaplať tisíckrát.

Posláno 30. 10. 2009 ve 12.13 | Odpovědět
Na komentář reagoval [62] Borek [65] Aleš Roubíček
avatar

#62 Borek http://www.borber.com/ nový

#61 Karel Minařík: Zastav si myš nad „takový postup je špinavý“ :)

Posláno 30. 10. 2009 ve 12.39 | Odpovědět
avatar

#63 Karel Minařík http://www.karmi.cz nový

#62 Borek: Nerozumím. To zadání už nějak předpokládá implementaci, která mi přijde od začátku špatně.

Davidovo vysvětlení:

Proč by třída s jednou srozumitelnou funkcionalitou měla navíc ještě komunikovat s databází?

mi navíc nic okolo toho nevysvětluje. Třída WebPage samozřejmě nijak „nekomunikuje“ s databází. A nechápu, proč by pak mohla WebPageStorage „komunikovat“ s databází. Protože se tak „jmenuje“? :D

Rozdělení zodpovědností mezi zpracování HTTP requestu a cachování, to je to, co je tady zcela zásadní. A prvním svodem je tu samozřejmě dědičnost [http://www.artima.com/…ciples4.html].

No. Pokud je hra o tom „tady máš třídu a tu nesmíš změnit ale nějak se to musí cachovat“ – ještě, že nemusím takováhle „klimatizovaná a skleněná“ zadání moc řešit – , tak jsem nepochopil, čím se na tom zabýváme: tak se udělá nějaké to CachedWebPage.load(...), které buď vytáhne objekt z cache — a nebavme se prosím pořád o „databázi“ — nebo vrátí WebPage.load(…) a zároveň ho nacachuje. Tak jak je to i v té mojí implementaci. Atd. Atd.

No nic. Bože, ochraňuj mně prosím dál před .NETem, PHP a tímhle vším :)

Posláno 30. 10. 2009 ve 13.05 | Odpovědět
Na komentář reagoval [66] Borek
avatar

#64 Honza Kral honza.kral@gmail.com nový

Ma troska do mlyna

Idea je jednoducha – WebPage je jen lazy wrapper kolem nejakeho objektu, ktery je uplne tupy a je poskytovan nejakym loaderem, defaultne je nastaven tupy loader ktery to stahne, zparsuje a zpristupni na sobe jako atributy (nedelal jsem read-only, to necham jako domaci cviceni). Existuje i cachovana implementace na ukazku.

Vyhoda Lazy reseni je, ze narocne zpracovani (stahovani, parsovani) lze odlozit az do doby kdy nekdo skutecne ty data chce.

Posláno 30. 10. 2009 ve 13.14 | Odpovědět
Na komentář reagoval [69] Aleš Roubíček
avatar

#65 Aleš Roubíček http://rarous.net/ nový

#61 Karel Minařík: Můj komentář vycházel z implementace, kterou jsem si zkusil za pomocí TDD a Davidova textu vytvořit. Dám to nějak o víkendu dokupy a nahodim na github ;)

Posláno 30. 10. 2009 ve 13.40 | Odpovědět
Na komentář reagoval [68] Karel Minařík
avatar

#66 Borek http://www.borber.com/ nový

#63 Karel Minařík: Já proti tvému příkladu nic nemám, jen neřeší problém z článku. (Ten byl: máš třídu Loader, která vrací objekt WebPage. Loader na tomto objektu musí nastavit nějaké vlastnosti, ty se ale potom ke klientskému kódu mají tvářit jako read-only.)

Můžeme se dohadovat, jestli se nám tento návrh líbí nebo ne, ale jak David explicitně píše v článku, pokud chceš funkčnost realizovat jednou třídou, nejsi na tento experiment vhodný adept. Když totiž všechno zapouzdřím v jediné třídě, problém pochopitelně odpadá jak v .NETu, tak v PHP i v dalších jazycích, které rozlišují public/private a jiné modifikátoru přístupu. Jinými slovy, kdyby chtěl David funcionalitu realizovat jedinou třídou, tento článek by patrně vůbec nevznikl.

To je celé.

Posláno 30. 10. 2009 ve 13.55 | Odpovědět
Na komentář reagoval [70] Karel Minařík
avatar

#67 Honza Kral honza.kral@gmail.com nový

Jeste detail – priklad je skutecne spatne zadan – text a myslenka zadani jiz pocita s nejakou implementaci, kdyz to prezenu tak zadani zni za predpokladu ze to udelate takhle, jak to udelate?

Ja bych to skutecne resil takhle jednou tridou, ktera vyuziva jinou jako backend. Je to flexibilni (muzu mit externi backendy ktere vubec neznaji muj kod) a velmi jednoduche na implementaci. Snadno bych mohl vytvorit konfiguraci ktera mi bude specifikovat defaultni loader a podobne kejkle.

Posláno 30. 10. 2009 ve 14.12 | Odpovědět
avatar

#68 Karel Minařík http://www.karmi.cz nový

#65 Aleš Roubíček: Počkat, počkat, jaký komentář – myslíš ten svůj původní o „řešení“ někde úplně nahoře nebo se něco ztratilo?
#62 Borek: Něco takovéhohle by „jakoby“ bylo to „čisté řešení“ nebo ne? .)

module LostInTranslation
class WebPageStorage
def self.load(url)
return cache.get( url ) if cache.exist?( url ) # Already cached
webpage = Crawler::WebPa­ge.load( url )
cache.set(url.to_s, webpage) # Cache the result
return webpage
end
end
end

https://gist.github.com/…8ac18a0f6c69

(Nevím, jak utvořit správné <pre>)

Posláno 30. 10. 2009 ve 14.25 | Odpovědět
Na komentář reagoval [69] Aleš Roubíček
avatar

#69 Aleš Roubíček http://rarous.net/ nový

#68 Karel Minařík: Neztratilo, myslel jsem #8 Aleš Roubíček:. Což je BTW stejne řešení jako #64 Honza Kral:. Jen já říkám, že jde o vzor Strategie a Dependency Injection, a Honza to má za backendy. :) U mne je implementace složitější o rozhraní, která v Pythonu nejsou.

Posláno 30. 10. 2009 ve 14.36 | Odpovědět
avatar

#70 Karel Minařík http://www.karmi.cz nový

#66 Borek: Ale ne, kdepak jednou třídou. Cachování i reprezentace HTML stránky musí být oddělené a taky jsou. Můžeme se tedy bavit o tom, zda-li má WebPage vůbec nějak vědět o cachi nebo ne. Pokud ne, viz „architektura“ v LostInTranslation výše.
(Navíc, v zadání o nějaké třídě Loader nebyla řeč – jen o WebPage a WebPageStorage.)

Já se nijak nebouřím proti tomu, dělat jakékoliv kejkle (byť jsem jen ze čtení toho kódu docela zoufalý), ale tvrdím, že:

  • Zadání svádí (nebo předjímá) špatnou implementaci – v tom se shoduji s Honzou Králem. Já jsem celou dobu myslel, že přijít na tohle je právě zadání :D
  • Jinak totiž není jasné, jaký problém se vlastně má řešit. (Raději opakuji: pokud WebPage nemá mít možnost vědět o cache, tak si můžeme pohrát s design patterns a vymýšlet, jak by se celé to API dalo zkomplikovat. Viz LostInTranslation.)
  • Je myslím důležitější API, než nějaká mysteriozní „čistota“ . WebPageStorage.load(...) zní a vypadá naprosto strašlivě. WebPage.load(..., :cache => true) mi zní rozumněji.
  • A konečně, samozřejmě každý saháme po arzenálu, který máme k dispozici a na který jsme zvyklí. Ten je u vás samozřejmě jiný než třeba u mně a Honzy Krále. Mně to jen vyděsilo, připravovat takové kanóny na tak triviální úlohu :) (Poněvadž člověk občas viděl kam to vede, bohužel na reálných projektech.)
Posláno 30. 10. 2009 ve 14.44 | Odpovědět
Na komentář reagoval [71] Borek
avatar

#71 Borek http://www.borber.com/ nový

#70 Karel Minařík: Než budu odpovídat dál, rozlišujete v Ruby public/private/pro­tected? Nějak si totiž nejsem jistý, jestli jsme na stejné vlně.

Posláno 30. 10. 2009 v 15.00 | Odpovědět
Na komentář reagoval [72] Karel Minařík
avatar

#72 Karel Minařík http://www.karmi.cz nový

#71 Borek: Ano, rozlišujeme, ale odpovídej radši na svém blogu/Twitteru/Fa­cebooku, ať to tady nezanášíme detaily…

Posláno 30. 10. 2009 v 15.46 | Odpovědět
Na komentář reagoval [73] Borek
avatar

#73 Borek http://www.borber.com/ nový

#72 Karel Minařík: Naopak, nerad bych touhle debatou zanášel další stránky na malém českém internetu :)

Jen stručně: v tvém LostInTranslation, které už odpovídá struktuře ze zadání, máš dva způsoby, jak vytvořit instanci WebPage – přes WebPageStorage.load() (BTW, taky se mi to jméno nelíbí) a přes WebPage.load() (doufám, že obě tyto metody jsou public; nějak mi to ze zdrojáku není zřejmé). Z pohledu pak uživatele není jasné, proč bych měl použít jedno nebo druhé, což je problém API, ale to asi nemusím dvakrát vysvětlovat.

No a jestli teď použiješ argument, že právě proto bys návrh změnil, tak se zacyklíme :) Jen dodám, že návrh použitý Davidem je běžný a divil bych se, že by v Ruby / Rails žádná funkcionalita nebyla navržená podobně.

Posláno 30. 10. 2009 v 16.05 | Odpovědět
Na komentář reagoval [75] Karel Minařík
avatar

#74 Almad loguser@almad.net nový

Ze zadání a z odpovědí, rozumím tomu dobře, že se jedná o cvičení v návrhu aplikace, kde se nesmí měnit návrh tříd a jde o to udělat co nejlepší návrh?

Posláno 30. 10. 2009 v 17.20 | Odpovědět
avatar

#75 Karel Minařík http://www.karmi.cz nový

#73 Borek: Jamis Buck, LEGOs, Play-Doh, and Programming:

As I wrapped up my presentation on Copland and dependency injection, I asked if there were any questions.
Rich raised his hand. “Why didn’t you just use Ruby?”

Posláno 31. 10. 2009 v 9.36 | Odpovědět

#76 veena http://webexpo.cz nový

Ah, prokoukl jsem to! V contextu tohoto je to jasné.

Wait for it…

David si hledá nový programovací jazyk a nechal si udělat předváděčku zdarma ;-p

Posláno 31. 10. 2009 ve 14.43 | Odpovědět

#77 v6ak http://v6ak.profitux.cz nový

Cache přes Decorator nebo Adapter. Já bývám spíše pro Decorator.
Design třídy WebPage se mi nelíbí. Byl bych pro továrnu (WebPageLoader) s metodou load, která vytvoří neměnnou (immutable) WebPage. Případně může pro složitější případy mít metodu createRequestBu­ilder().
Měl bych tedy více implementací WebPageLoaderu. Jedna by byla pro přímé vyslání požadavku (můžeme tu ještě uvažovat o podpoře proxy), druhá by byla pro použití cache. (Třetí a další nevylučuji.) Pomocí Dependency Injection by CachedWebPageLoader získal jak WebPageLoader používaný v případě nenalezení v cache, tak cachovací úložiště.
Nevím, co je složitého na lazy loadingu thumbnailu. Jen by se to mělo projevit do kontraktu (a v případě řízených výjimek i do nich) v throws.
Předání parametrů třídě WebPage (rozhraní by zde IMHO bylo příliš a zbytečně benevolentní) bych viděl na konstruktor. Omezení je zde při přidávání nových položek do WebPage, i když menší než u rozhraní.

Posláno 31. 10. 2009 v 19.02 | Odpovědět
Na komentář reagoval [79] v6ak
avatar

#78 Jake Cooney http://jakecooney.com nový

class WebPageStorage
{
   private $webpage;
   public $headers;
   public $body;
   public $thumbnail;
   public function load($url)
   {
      $this->webpage = new WebPage;
      $this->webpage->url = $url;
      $this->headers =& $this->webpage->headers;
      $this->body =& $this->webpage->body;
      $this->thumbnail =& $this->webpage->thumbnail;

      // čachry s db
   }
}
$s = new WebPageStorage;
$s->load('...');
echo $s->headers;
echo $s->body;
echo $s->thumbnail;

Dal jsem public, ale jinak by tam byly nastavený gettery.

Posláno 31. 10. 2009 ve 22.15 | Odpovědět

#79 v6ak http://v6ak.profitux.cz/ nový

#77 v6ak: Trošku jsem pročetl komentáře a všiml jsem si, že jsem zapomněl na to, že nanačtení thumbnailu se musí použít strategie. Při návrhu jsem na to sice zapomněl, ale při implementaci by to snad ani jinak přirozeně nešlo.

Posláno 1. 11. 2009 v 9.47 | Odpovědět
avatar

#80 David Grudl http://davidgrudl.com nový

Děkuji všem za účast na experimentu!

Asi je čas prozradit, co jsem tím vším sledoval: chtěl jsem si ověřit domněnku, že programátoři odkojení na statických jazycích vysypou z rukávu řešení dle návrhových vzorů (overengineered way?), programátoři odkojení na dynamických jazycích přijdou s minimalistickým kódem (bastled way?) a programátoři v PHP budou na obě skupiny nechápavě zírat a ptát se, co je to interface, co je to zapouzdření a nakonec vyřeší úlohu pomocí serialize, ereg_replace a nebude to fungovat ;)

(ano, předchozí odstavec obsahuje prvky nadsázky)

Samozřejmě z komentářů nelze nic zásadního vyvozovat, snad jen to, že programátoři rádi uvažují ve svých jazycích. Takže pokud máte chuť rozšířit své schopnosti, potom je lepší, než hlouběji pronikat do tajů svého jazyka, naučit se jazyk jiný, nejlépe hodně jiný.

Posláno 2. 11. 2009 ve 21.06 | Odpovědět
Na komentář reagoval [81] Almad

#81 Almad nový

#80 David Grudl: @DavidGrudl: Mám pocit, že to je známá doporučená rada – každý rok jeden nový jazyk :)

Posláno 3. 11. 2009 ve 12.28 | Odpovědět

#82 v6ak http://v6ak.profitux.cz nový

Já teď dělám hlavně Javu a myslím, že to je poznat. A to i přesto, že již v PHP jsem začal jakoby řešit „řízené“ výjimky a OOP. Java mi pak díky tomu pěkně sedla.
Zajímavé je, že když jsem se vrátil po delší době k PHP, tak jsem mimo jiné začal psát trošku jiným stylem než v Javě.
Další zajímavost je, že v době, kdy jsem v PHP nedělal, jsem si jej nechával na bastlení. Třeba jsem potřeboval zbastlit v co nejkratším čase skript, který vygeneruje „skript“ pro A2uploader na nahrání adresáře. („Skript“ pro A2uploader neumí ani if ani goto ani funkce, je to prostě rovná imperativní nudle.) Použil jsem PHP, asi protože v Javě bych tíhnul k čistějším řešením, což by mě vzdálilo od úkolu „dokončit v nějaké podobě co nejdřív bez ohledu na budoucí udržitelnost“.
S těmi jazyky souhlasím. Vždyť „Kolik jazyků znáš, tolikrát jsi programátorem!“

Posláno 3. 11. 2009 ve 21.30 | Odpovědět
avatar

#83 amsys martin@restaurants.mu nový

Čisté, co je v tomto případě čisté? Jediné co mě teďka napadá je vylepšit kontruktor, v případě, že existuje nepovinný argument tak mu budeme věřit a přednastavíme jednorázově hodnoty tz.:

<?php
class NecoWebPage extends WebPage {
    public function __construct($uri, $data) {
        if (is_array($data))  {
            // populate
        else {
            parent::__construct($uri);
        }
    }
}
class WebPageLoader {
    public static function load($uri) {
        //...
        return new NecoWebPage($uri, $data);
    }
}

P.S.: Nebo strávíme hodiny nad nějakým jiným řešením a za měsíc nebudeme mít na nájem, nebo se pletu?

Posláno 9. 11. 2009 ve 14.41 | Odpovědět

#84 Ondra http://www.pohlidame.cz nový

Jednoznačně to chce úpravu WebPageLoader, může fungovat jako proxy, a lazy loading obstará Hibernate – až při prvním požadavku transparentně načte obrázek z db .

Posláno před měsícem | Odpovědět

Zanechat komentář

  • na jiné komentáře odkazujte zápisem např. [2]
  • vaše IP bude zaznamenána a zobrazena
  • můžete použít Texy! syntaxi. HTML značky nejsou povolené. Příklad syntaxe: "text odkazu":odkaz, **tučně**, *kurzíva*, `code`
  • můžeme si tykat
  • diskuse mohou být řešeny metodou Indiana Jones
Text komentáře
Kontakt (povinné)

(maskuje se)




Výtah na začátek článku na první komentář

Názory čtenářů v diskusích nejsou názory provozovatele webu, a ten za jejich obsah neodpovídá.

phpFashion © 2004, 2010 David Grudlo webu

Pokud není uvedeno jinak, podléhá obsah těchto stránek licenci Creative Commons BY-NC-ND Creative Commons License BY-NC-ND

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.