Na navigaci | Klávesové zkratky

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!

před 4 lety v rubrice PHP | shlédnuto 12188× | nahoru |

Flattr this
  1. Jod http://romcok.eu #1

    avatar

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

    před 4 lety
  2. Jod http://romcok.eu #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 4 lety
  3. Patrik Votoček (Vrtak-CZ) http://patrik.votocek.cz #3

    avatar

    Je WebPageStorage potomkem WebPage?

    před 4 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 4 lety
  5. Adams #5

    avatar

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

    před 4 lety
  6. Washo #6

    avatar

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

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

    avatar

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

    před 4 lety
  8. Aleš Roubíček http://rarous.net/ #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 http://johno.jsmf.net/ #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 4 lety
  10. Aleš Roubíček http://rarous.net/ #10

    avatar

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

    před 4 lety
  11. René Stein http://blog.renestein.net #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 4 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 4 lety
  13. Tomáš Fejfar http://blog.red-pill.cz #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 4 lety | reagoval [14] Tomáš Fejfar
  14. Tomáš Fejfar http://blog.red-pill.cz #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 4 lety | reagoval [15] Tomáš Fejfar
  15. Tomáš Fejfar http://blog.red-pill.cz #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 4 lety
  16. Martin Štěpař #16

    avatar

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

    před 4 lety
  17. Jiří Knesl http://www.knesl.com #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 4 lety | reagoval [28] paranoiq
  18. Jiří Knesl http://www.knesl.com #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 4 lety | reagoval [19] Aleš Roubíček
  19. Aleš Roubíček http://rarous.net/ #19

    avatar

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

    před 4 lety | reagoval [20] Jiří Knesl
  20. Jiří Knesl http://www.knesl.com #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 4 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 4 lety | reagoval [23] Toby77
  22. David Majda http://www.majda.cz/ #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 4 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 4 lety
  24. David Majda http://www.majda.cz/ #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 4 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 4 lety
  26. ady http://ady.nasracky.cz #26

    avatar

    Co takhle:

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

    a v constructoru WebPage:

    WebPageStorage wps = new WebPageStorage(‚http://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 4 lety | reagoval [27] ady
  27. ady http://ady.nasracky.cz #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 4 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 4 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 4 lety
  30. Jod http://romcok.eu #30

    avatar

    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.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 4 lety
  31. Tomas Petricek http://tomasp.net/blog #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 4 lety
  32. Tomik http://tomik.jmx.cz #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 4 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 4 lety | reagoval [34] Aleš Roubíček
  34. Aleš Roubíček http://rarous.net/ #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 4 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 4 lety | reagoval [36] Aleš Roubíček
  36. Aleš Roubíček http://rarous.net/ #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 4 lety | reagoval [38] paranoiq
  37. Jakub Vrána http://php.vrana.cz/ #37

    před 4 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 4 lety | reagoval [43] optik
  39. Borek http://www.borber.com/ #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 4 lety | reagoval [40] Aleš Roubíček [42] René Stein
  40. Aleš Roubíček http://rarous.net/ #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 4 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 4 lety
  42. René Stein http://blog.renestein.net #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 4 lety
  43. optik http://mirin.cz #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 4 lety
  44. René Stein http://blog.renestein.net #44

    avatar

    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.

    před 4 lety
  45. Borek http://www.borber.com/ #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 4 lety | reagoval [46] Aleš Roubíček [47] René Stein
  46. Aleš Roubíček http://rarous.net/ #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 4 lety | reagoval [49] Borek
  47. René Stein http://blog.renestein.net #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 4 lety | reagoval [48] Aleš Roubíček [51] Borek
  48. Aleš Roubíček http://rarous.net/ #48

    avatar

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

    před 4 lety
  49. Borek http://www.borber.com/ #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 4 lety
  50. Borek http://www.borber.com/ #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 4 lety | reagoval [53] René Stein
  51. Borek http://www.borber.com/ #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 4 lety | reagoval [52] Aleš Roubíček [54] René Stein
  52. Aleš Roubíček http://rarous.net/ #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 4 lety
  53. René Stein http://blog.renestein.net #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 4 lety | reagoval [55] Borek
  54. René Stein http://blog.renestein.net #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 4 lety
  55. Borek http://www.borber.com/ #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 4 lety | reagoval [57] Rene Stein
  56. Borek http://www.borber.com/ #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 4 lety | reagoval [58] René Stein [59] Borek
  57. Rene Stein http://blog.renestein.net #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 4 lety | reagoval [59] Borek
  58. René Stein http://blog.renestein.net #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 4 lety | reagoval [59] Borek
  59. Borek http://www.borber.com/ #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 4 lety | reagoval [60] Rene Stein
  60. Rene Stein http://blog.renestein.net #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 4 lety
  61. Karel Minařík http://www.karmi.cz #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 [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/platformách.

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

    před 4 lety | reagoval [62] Borek [65] Aleš Roubíček
  62. Borek http://www.borber.com/ #62

    avatar

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

  63. Karel Minařík http://www.karmi.cz #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 4 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 4 lety | reagoval [69] Aleš Roubíček
  65. Aleš Roubíček http://rarous.net/ #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 4 lety | reagoval [68] Karel Minařík
  66. Borek http://www.borber.com/ #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 4 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 4 lety
  68. Karel Minařík http://www.karmi.cz #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 4 lety | reagoval [69] Aleš Roubíček
  69. Aleš Roubíček http://rarous.net/ #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 4 lety
  70. Karel Minařík http://www.karmi.cz #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 4 lety | reagoval [71] Borek
  71. Borek http://www.borber.com/ #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 4 lety | reagoval [72] Karel Minařík
  72. Karel Minařík http://www.karmi.cz #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 4 lety | reagoval [73] Borek
  73. Borek http://www.borber.com/ #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 4 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 4 lety
  75. Karel Minařík http://www.karmi.cz #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 4 lety
  76. veena http://webexpo.cz #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 4 lety
  77. v6ak http://v6ak.profitux.cz #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 4 lety | reagoval [79] v6ak
  78. Jake Cooney http://jakecooney.com #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 4 lety
  79. v6ak http://v6ak.profitux.cz/ #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 4 lety
  80. David Grudl http://davidgrudl.com #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 4 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 4 lety
  82. v6ak http://v6ak.profitux.cz #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 4 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 4 lety
  84. Ondra http://www.pohlidame.cz #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 4 lety

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


Nejnovější články RSS článků