Na navigaci | Klávesové zkratky

Č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 = 'https://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('https://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!

před 15 lety v rubrice PHP | blog píše David Grudl | nahoru

Mohlo by vás zajímat

Komentáře

  1. Jod #1

    avatar

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

    před 15 lety
  2. Jod #2

    avatar

    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)

    před 15 lety
  3. Patrik Votoček (Vrtak-CZ) #3

    avatar

    Je WebPageStorage potomkem WebPage?

    před 15 lety | reagoval [4] morousej
  4. morousej #4

    #3 Patriku Votočku (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.

    před 15 lety
  5. Adams #5

    avatar

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

    před 15 lety
  6. Washo #6

    avatar

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

    před 15 lety | reagoval [7] Washo
  7. Washo #7

    avatar

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

    před 15 lety
  8. Aleš Roubíček #8

    avatar

    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.

  9. johno #9

    avatar

    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 stave.

    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
    před 15 lety
  10. Aleš Roubíček #10

    avatar

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

    před 15 lety
  11. René Stein #11

    avatar

    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

    před 15 lety
  12. Martiner #12

    avatar

    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.

    před 15 lety
  13. Tomáš Fejfar #13

    avatar

    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;
    před 15 lety | reagoval [14] Tomáš Fejfar
  14. Tomáš Fejfar #14

    avatar

    #13 Tomáši Fejfare, 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
    před 15 lety | reagoval [15] Tomáš Fejfar
  15. Tomáš Fejfar #15

    avatar

    #14 Tomáši Fejfare, 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…

    před 15 lety
  16. Martin Štěpař #16

    avatar

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

    před 15 lety
  17. Jiří Knesl #17

    avatar

    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 EditableWebPageData). 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í).

    před 15 lety | reagoval [28] paranoiq
  18. Jiří Knesl #18

    avatar

    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.

    před 15 lety | reagoval [19] Aleš Roubíček
  19. Aleš Roubíček #19

    avatar

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

    před 15 lety | reagoval [20] Jiří Knesl
  20. Jiří Knesl #20

    avatar

    #19 Aleši Roubíčku, 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á.

    před 15 lety
  21. Toby77 #21

    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;
    před 15 lety | reagoval [23] Toby77
  22. David Majda #22

    avatar

    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).

    před 15 lety
  23. Toby77 #23

    #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í.

    před 15 lety
  24. David Majda #24

    avatar

    #8 Aleši Roubíčku, 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 🙂

    před 15 lety
  25. rsvanda #25

    avatar

    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.

    před 15 lety
  26. ady #26

    avatar

    Co takhle:

    page = new WebPage(‚https://phpfashion.com/‘)

    a v constructoru WebPage:

    WebPageStorage wps = new WebPageStorage(‚https://phpfashion.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 :)

    před 15 lety | reagoval [27] ady
  27. ady #27

    avatar

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

    před 15 lety
  28. paranoiq #28

    avatar

    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

    před 15 lety
  29. paranoiq #29

    avatar

    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

    před 15 lety
  30. Jod #30

    avatar

    Ja by som to spravil takto:

    Pastie: https://web.archive.org/…rg:80/673943

    Example: https://web.archive.org/…omcok.eu:80/
    (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.romcok.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č :))

    před 15 lety
  31. Tomas Petricek #31

    avatar

    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 🙂.

    před 15 lety
  32. Tomik #32

    avatar
    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.

    před 15 lety
  33. paranoiq #33

    avatar

    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?

    před 15 lety | reagoval [34] Aleš Roubíček
  34. Aleš Roubíček #34

    avatar

    #33 paranoiqu, 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ý.

    před 15 lety | reagoval [35] paranoiq
  35. paranoiq #35

    avatar

    #34 Aleši Roubíčku, 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 :]

    před 15 lety | reagoval [36] Aleš Roubíček
  36. Aleš Roubíček #36

    avatar

    #35 paranoiqu, 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. :)

    před 15 lety | reagoval [38] paranoiq
  37. Jakub Vrána #37

    před 15 lety
  38. paranoiq #38

    avatar

    #36 Aleši Roubíčku, 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)

    před 15 lety | reagoval [43] optik
  39. Borek #39

    avatar

    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é.

    před 15 lety | reagoval [40] Aleš Roubíček [42] René Stein
  40. Aleš Roubíček #40

    avatar

    #39 Borku, 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í. ;)

    před 15 lety | reagoval [45] Borek
  41. PHX #41

    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.
    před 15 lety
  42. René Stein #42

    avatar

    #39 Borku, 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ě InternalsVisibleTo.:) 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.Create(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 InvalidOperationException, ří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.
    před 15 lety
  43. optik #43

    #38 paranoiqu,
    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.

    před 15 lety
  44. René Stein #44

    avatar

    A ještě pro všechny fanoušky špatně strávených dekorátorů a proxy, kteří se tu přemnožili. 😁
    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.

    před 15 lety
  45. Borek #45

    avatar

    #40 Aleši Roubíčku, Ú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 :)

    před 15 lety | reagoval [46] Aleš Roubíček [47] René Stein
  46. Aleš Roubíček #46

    avatar

    #45 Borku, 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. :)

    před 15 lety | reagoval [49] Borek
  47. René Stein #47

    avatar

    #45 Borku, 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 :))

    před 15 lety | reagoval [48] Aleš Roubíček [51] Borek
  48. Aleš Roubíček #48

    avatar

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

    před 15 lety
  49. Borek #49

    avatar

    #46 Aleši Roubíčku, 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 :)

    před 15 lety
  50. Borek #50

    avatar

    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?).

    před 15 lety | reagoval [53] René Stein
  51. Borek #51

    avatar

    #47 René Steine, 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í.

    před 15 lety | reagoval [52] Aleš Roubíček [54] René Stein
  52. Aleš Roubíček #52

    avatar

    #51 Borku, 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.

    před 15 lety
  53. René Stein #53

    avatar

    #50 Borku, 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.

    před 15 lety | reagoval [55] Borek
  54. René Stein #54

    avatar

    #51 Borku, 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.

    před 15 lety
  55. Borek #55

    avatar

    #53 René Steine, 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č).

    před 15 lety | reagoval [57] Rene Stein
  56. Borek #56

    avatar

    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é.)

    před 15 lety | reagoval [58] René Stein [59] Borek
  57. Rene Stein #57

    avatar

    #55 Borku, 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)

    před 15 lety | reagoval [59] Borek
  58. René Stein #58

    avatar

    #56 Borku, 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 ?)

    před 15 lety | reagoval [59] Borek
  59. Borek #59

    avatar

    #57 Rene Steine, 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 :)

    před 15 lety | reagoval [60] Rene Stein
  60. Rene Stein #60

    avatar

    #59 Borku, 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:)

    před 15 lety
  61. Karel Minařík #61

    avatar

    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ářů/dokumentace
    • 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 [https://web.archive.org/…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/platformách.

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

    před 15 lety | reagoval [62] Borek [65] Aleš Roubíček
  62. Borek #62

    avatar

    #61 Karle Minaříku, Zastav si myš nad „takový postup je špinavý“ :)

    před 15 lety | reagoval [63] Karel Minařík [68] Karel Minařík
  63. Karel Minařík #63

    avatar

    #62 Borku, 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 :)

    před 15 lety | reagoval [66] Borek
  64. Honza Kral #64

    avatar

    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.

    před 15 lety | reagoval [69] Aleš Roubíček
  65. Aleš Roubíček #65

    avatar

    #61 Karle Minaříku, 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 ;)

    před 15 lety | reagoval [68] Karel Minařík
  66. Borek #66

    avatar

    #63 Karle Minaříku, 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é.

    před 15 lety | reagoval [70] Karel Minařík
  67. Honza Kral #67

    avatar

    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.

    před 15 lety
  68. Karel Minařík #68

    avatar

    #65 Aleši Roubíčku, 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::WebPage.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>)

    před 15 lety | reagoval [69] Aleš Roubíček
  69. Aleš Roubíček #69

    avatar

    #68 Karle Minaříku, 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.

    před 15 lety
  70. Karel Minařík #70

    avatar

    #66 Borku, 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.)
    před 15 lety | reagoval [71] Borek
  71. Borek #71

    avatar

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

    před 15 lety | reagoval [72] Karel Minařík
  72. Karel Minařík #72

    avatar

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

    před 15 lety | reagoval [73] Borek
  73. Borek #73

    avatar

    #72 Karle Minaříku, 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ě.

    před 15 lety | reagoval [75] Karel Minařík
  74. Almad #74

    avatar

    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?

    před 15 lety
  75. Karel Minařík #75

    avatar

    #73 Borku, 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?”

    před 15 lety
  76. veena #76

    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

    před 15 lety
  77. v6ak #77

    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 createRequestBuilder().
    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í.

    před 15 lety | reagoval [79] v6ak
  78. Jake Cooney #78

    avatar
    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.

    před 15 lety
  79. v6ak #79

    #77 v6aku, 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.

    před 15 lety
  80. David Grudl #80

    avatar

    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ý.

    před 15 lety | reagoval [81] Almad
  81. Almad #81

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

    před 15 lety
  82. v6ak #82

    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!“

    před 15 lety
  83. amsys #83

    avatar

    Č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?

    před 15 lety
  84. Ondra #84

    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 .

    před 15 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.


phpFashion © 2004, 2024 David Grudl | o blogu

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