table of contents · table of contents mastering python design patterns credits about the author...

203

Upload: others

Post on 13-Oct-2020

4 views

Category:

Documents


0 download

TRANSCRIPT

  • TableofContentsMasteringPythonDesignPatternsCreditsAbouttheAuthorAbouttheReviewerswww.PacktPub.comSupportfiles,eBooks,discountoffers,andmore

    Whysubscribe?FreeaccessforPacktaccountholders

    PrefaceDesignpatternsCommonmisunderstandingsaboutdesignpatternsDesignpatternsandPythonWhatthisbookcoversWhatyouneedforthisbookWhothisbookisforConventionsReaderfeedbackCustomersupport

    DownloadingtheexamplecodeErrataPiracyQuestions

    1.TheFactoryPatternFactoryMethod

    Areal-lifeexampleAsoftwareexampleUsecasesImplementation

    AbstractFactoryAreal-lifeexampleAsoftwareexampleUsecasesImplementation

    Summary2.TheBuilderPatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    3.ThePrototypePatternAreal-lifeexampleAsoftwareexample

  • UsecasesImplementationSummary

    4.TheAdapterPatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    5.TheDecoratorPatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    6.TheFacadePatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    7.TheFlyweightPatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    8.TheModel-View-ControllerPatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    9.TheProxyPatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    10.TheChainofResponsibilityPatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    11.TheCommandPatternAreal-lifeexample

  • AsoftwareexampleUsecasesImplementationSummary

    12.TheInterpreterPatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    13.TheObserverPatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    14.TheStatePatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    15.TheStrategyPatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    16.TheTemplatePatternAreal-lifeexampleAsoftwareexampleUsecasesImplementationSummary

    Index

  • MasteringPythonDesignPatterns

  • MasteringPythonDesignPatternsCopyright©2015PacktPublishing

    Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

    Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

    PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

    Firstpublished:January2015

    Productionreference:1220115

    PublishedbyPacktPublishingLtd.

    LiveryPlace

    35LiveryStreet

    BirminghamB32PB,UK.

    ISBN978-1-78398-932-4

    www.packtpub.com

    http://www.packtpub.com

  • CreditsAuthor

    SakisKasampalis

    Reviewers

    EvanDempsey

    AmitabhSharma

    YogendraSharma

    PatrycjaSzabłowska

    CommissioningEditor

    KunalParikh

    AcquisitionEditor

    OwenRoberts

    ContentDevelopmentEditor

    SumeetSawant

    TechnicalEditors

    TanviBhatt

    GauravSuri

    CopyEditors

    ShivangiChaturvedi

    NithyaP.

    AdithiShetty

    ProjectCoordinator

    AboliAmbardekar

    Proofreaders

    AmeeshaGreen

  • JoyceLittlejohn

    Indexer

    TejalSoni

    Graphics

    AbhinashSahu

    ProductionCoordinator

    AparnaBhagat

    CoverWork

    AparnaBhagat

  • AbouttheAuthorSakisKasampalis(@SKasampalis)isasoftwareengineerlivingintheNetherlands.Heisnotdogmaticaboutparticularprogramminglanguagesandtools;hisprincipleisthattherighttoolshouldbeusedfortherightjob.OneofhisfavoritetoolsisPythonbecausehefindsitveryproductive.

    SakiswasalsothetechnicalreviewerofMasteringObject-orientedPythonandLearningPythonDesignPatterns,publishedbyPacktPublishing.

    Iwanttothankmysweetheart,Georgia,forsupportingthiseffort.ManythankstoOwenRobertswhoencouragedmetowritethisbook.IalsowanttothankSumeetSawantforbeingaverykindandcooperativecontentdevelopmenteditor.Lastbutnotleast,Iwanttothankthereviewersofthisbookfortheirvaluablefeedback.

  • AbouttheReviewersEvanDempseyisasoftwaredeveloperfromWaterford,Ireland.Whenheisn'thackinginPythonforfunandprofit,heenjoyscraftbeers,commonLisp,andkeepingupwithmodernresearchinmachinelearning.Heisacontributortoseveralopensourceprojects.

    AmitabhSharmaisaprofessionalsoftwareengineer.Hehasworkedextensivelyonenterpriseapplicationsintelecommunicationsandbusinessanalytics.Hisworkisfocusedonservice-orientedarchitecture,datawarehouses,andlanguagessuchasJava,Python,andothers.

    IwouldliketothankmygrandfatherandmyfatherforallowingmetolearnallthatIcan.Iwouldalsoliketothankmywife,Komal,forhersupportandencouragement.

    YogendraSharmawasbornandbroughtupinasmallbutculturaltown,Pratapgarh,inthestateofRajasthan.Hisbasiceducationhasbeenimpartedinhishometownitself,andhecompletedhisBTechinComputerSciencefromJaipur.Heisbasicallyanengineerbyheartandatechnicalenthusiastbynature.

    HehasvastexperienceinthefieldsofPython,Djangoframework,webappsecurity,networking,Web2.0,andC++.

    AlongwithCCNA,manyotheresteemedcertificationshavebeenawardedtohim.HeisanactivememberofInternationalAssociationofEngineers,Ubuntu,India,andComputerSocietyofIndia.

    Morerecently,heparticipatedinbugbountyprogramsandwonmanybugbounties,includingtherespectedYahoo,Ebay,PayPalbugbounty.Hehasbeenappointedassecurityresearcherforseveralrespectedorganizations,suchasAdobe,Ebay,Avira,Moodle,Cisco,Atlassian,Basecamp,CodeClimate,Abacus,Rediff,Assembla,RecruiterBox,Tumbler,Wrike,Indeed,HybridSaaS,Sengrid,andSnapEngag.

    Hehasreviewedmanybooksfromreputedpublishinghouses.YoucanfindhimonLinkedInathttp://in.linkedin.com/in/yogendra0sharma.

    Iwouldliketothankallmyfriendswhoalwaysencouragedmetodosomethingnewandbelievinginme.

    PatrycjaSzabłowskaisaPythondeveloperwithsomeJavabackground,withexperiencemainlyinbackenddevelopment.ShegraduatedfromNicolausCopernicusUniversityinToruń,Poland.

    SheiscurrentlyworkinginWarsaw,Poland,atGrupaWirtualnaPolska.Sheisconstantlyexploringtechnicalnoveltiesandisopen-mindedandeagertolearnaboutthenextPython

    http://in.linkedin.com/in/yogendra0sharma

  • libraryorframework.HerfavoriteprogrammingmottoisCodeisreadmuchmoreoftenthanitiswritten.

    I'dliketothankmyhusband,Wacław,forencouragingmetoexplorenewfrontiers,andalsomyparentsforteachingmewhatmattersthemost.

  • www.PacktPub.com

    Supportfiles,eBooks,discountoffers,andmoreForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.

    DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatformoredetails.

    Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

    https://www2.packtpub.com/books/subscription/packtlib

    DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt'sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt'sentirelibraryofbooks.

    Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

    FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandviewnineentirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.

    http://www.PacktPub.comhttp://www.PacktPub.commailto:[email protected]://www.PacktPub.comhttps://www2.packtpub.com/books/subscription/packtlibhttp://www.PacktPub.com

  • Preface

    DesignpatternsInsoftwareengineering,adesignpatternisarecommendedsolutiontoasoftwaredesignproblem.Designpatternsgenerallydescribehowtostructureourcodetosolvecommondesignproblemsusingbestpractices.Itisimportanttonotethatadesignpatternisahigh-levelsolution;itdoesn'tfocusonimplementationdetailssuchasalgorithmsanddatastructures[GOF95,page13],[j.mp/srcmdp].Itisuptous,assoftwareengineers,todecidewhichalgorithmanddatastructureisoptimaltousefortheproblemwearetryingtosolve.

    NoteIfyouarewonderingwhatisthemeaningofthetextwithin[],pleasejumptotheConventionssectionofthisprefaceforamomenttoseehowreferencesareformattedinthisbook.

    Themostimportantpartofadesignpatternisprobablyitsname.Thebenefitofnamingallpatternsisthatwehave,onourhands,acommonvocabularytocommunicate[GOF95,page13].Thus,ifyousendsomecodeforreviewandyourpeerreviewergivesfeedbackmentioning"IthinkthatyoucanuseaStrategyhereinsteadof...",evenifyoudon'tknoworrememberwhatastrategyis,youcanimmediatelylookitup.

    Asprogramminglanguagesevolve,somedesignpatternssuchasSingletonbecomeobsoleteorevenantipatterns[j.mp/jalfdp],othersarebuiltintheprogramminglanguage(iterator),andnewpatternsareborn(Borg/Monostate[j.mp/amdpp],[j.mp/wikidpc]).

    http://j.mp/srcmdphttp://j.mp/jalfdphttp://j.mp/amdpphttp://j.mp/wikidpc

  • CommonmisunderstandingsaboutdesignpatternsThereareafewmisunderstandingsaboutdesignpatterns.Onemisunderstandingisthatdesignpatternsshouldbeusedrightfromthestartwhenwritingcode.Itisnotunusualtoseedevelopersstrugglingwithwhichpatterntheyshoulduseintheircode,eveniftheyhaven'tfirsttriedtosolvetheproblemintheirownway[j.mp/prsedp],[j.mp/stedp].

    Notonlyisthiswrong,butitisalsoagainstthenatureofdesignpatterns.Designpatternsarediscovered(incontrasttoinvented)asbettersolutionsoverexistingsolutions.Ifyouhavenoexistingsolution,itdoesn'tmakesensetolookforabetterone.Justgoaheadanduseyourskillstosolveyourproblemasbestasyouthink.Ifyourcodereviewershavenoobjectionsandthroughtimeyouseethatyoursolutionissmartandflexibleenough,itmeansthatyoudon'tneedtowasteyourtimeonstrugglingaboutwhichpatterntouse.Youmighthaveevendiscoveredabetterdesignpatternthantheexistingone.Whoknows?Thepointisdonotlimityourcreativityinfavorofforcingyourselftouseexistingdesignpatterns.

    Asecondmisunderstandingisthatdesignpatternsshouldbeusedeverywhere.Thisresultsincreatingcomplexsolutionswithunnecessaryinterfacesandhierarchies,whereasimplerandstraightforwardsolutionwouldbesufficient.Donotreatdesignpatternsasapanaceabecausetheyarenot.Theymustbeusedonlyifthereisproofthatyourexistingcode"smells",andishardtoextendandmaintain.Trythinkingintermsofyouaren'tgonnaneedit(YAGNI[j.mp/c2yagni])andKeepitsimplestupid(KISS[j.mp/wikikis]).Usingdesignpatternseverywhereisasevilasprematureoptimization[j.mp/c2pro].

    http://j.mp/prsedphttp://j.mp/stedphttp://j.mp/c2yagnihttp://j.mp/wikikishttp://j.mp/c2pro

  • DesignpatternsandPythonThisbookfocusesondesignpatternsinPython.Pythonisdifferentthanmostcommonprogramminglanguagesusedinpopulardesignpatternsbooks(usuallyJava[FFBS04]orC++[GOF95]).Itsupportsduck-typing,functionsarefirst-classcitizens,andsomepatterns(forinstance,iteratoranddecorator)arebuilt-infeatures.Theintentofthisbookistodemonstratethemostfundamentaldesignpatterns,notallpatternsthathavebeendocumentedsofar[j.mp/wikidpc].ThecodeexamplesfocusonusingidiomaticPythonwhenapplicable[j.mp/idiompyt].IfyouarenotfamiliarwiththeZenofPython,itisagoodideatoopenthePythonREPLrightnowandexecuteimportthis.TheZenofPythonisbothamusingandmeaningful.

    http://j.mp/wikidpchttp://j.mp/idiompyt

  • WhatthisbookcoversPart1:Creationalpatternspresentsdesignpatternsthatdealwithobjectcreation.

    Chapter1,TheFactoryPattern,willteachyouhowtousetheFactorydesignpattern(FactoryMethodandAbstractFactory)toinitializeobjects,andcoverthebenefitsofusingtheFactorydesignpatterninsteadofdirectobjectinstantiation.

    Chapter2,TheBuilderPattern,willteachyouhowtosimplifythecreationofobjectsthataretypicallycomposedbymorethanonerelatedobjects.

    Chapter3,ThePrototypePattern,willteachyouhowtocreateanewobjectthatisafullcopy(hence,thenameclone)ofanexistingobject.

    Part2:Structuralpatternspresentsdesignpatternsthatdealwithrelationshipsbetweentheentities(classes,objects,andsoon)ofasystem.

    Chapter4,TheAdapterPattern,willteachyouhowtomakeyourexistingcodecompatiblewithaforeigninterface(forexample,anexternallibrary)withminimalchanges.

    Chapter5,TheDecoratorPattern,willteachyouhowtoenhancethefunctionalityofanobjectwithoutusinginheritance.

    Chapter6,TheFacadePattern,willteachyouhowtocreateasingleentrypointtohidethecomplexityofasystem.

    Chapter7,TheFlyweightPattern,willteachyouhowtoreuseobjectsfromanobjectpooltoimprovethememoryusageandpossiblytheperformanceofyourapplications.

    Chapter8,TheModel-View-ControllerPattern,willteachyouhowtoimprovethemaintainabilityofyourapplicationsbyavoidingmixingthebusinesslogicwiththeuserinterface.

    Chapter9,TheProxyPattern,willteachyouhowtoimprovethesecurityofyourapplicationbyaddinganextralayerofprotection.

    Part3:Behavioralpatternspresentsdesignpatternsthatdealwiththecommunicationofthesystem'sentities.

    Chapter10,TheChainofResponsibilityPattern,willteachyouhowtosendarequesttomultiplereceivers.

    Chapter11,TheCommandPattern,willteachyouhowtomakeyourapplicationcapableofrevertingalreadyappliedoperations.

    Chapter12,TheInterpreterPattern,willteachyouhowtocreateasimplelanguageontopofPython,whichcanbeusedbydomainexpertswithoutforcingthemtolearnhowtoprograminPython.

  • Chapter13,TheObserverPattern,willteachyouhowtosendnotificationstotheregisteredstakeholdersofanobjectwheneveritsstatechanges.

    Chapter14,TheStatePattern,willteachyouhowtocreateastatemachinetomodelaproblemandthebenefitsofthistechnique.

    Chapter15,TheStrategyPattern,willteachyouhowtopick(duringruntime)analgorithmbetweenmanyavailablealgorithms,basedonsomeinputcriteria(forexample,theelementsize).

    Chapter16,TheTemplatePattern,willteachyouhowtomakeaclearseparationbetweenthecommonanddifferentpartsofanalgorithmtoavoidunnecessarycodeduplication.

  • WhatyouneedforthisbookThecodeiswrittenexclusivelyinPython3.Python3is,inmanyaspects,notcompatiblewithPython2.x[j.mp/p2orp3].ThefocusisonPython3.4.0butusingPython3.3.0shouldalsobefine,sincetherearenosyntaxdifferencesbetweenPython3.3.0andPython3.4.0[j.mp/py3dot4].Ingeneral,ifyouinstallthelatestPython3versionfromwww.python.org,youshouldbefinewithrunningtheexamples.Mostmodules/librariesthatareusedintheexamplesareapartofthePython3distribution.Ifanexamplerequiresanyextramodulestobeinstalled,instructionsonhowtoinstallthemaregivenbeforepresentingtherelatedcode.

    http://j.mp/p2orp3http://j.mp/py3dot4http://www.python.org

  • WhothisbookisforTheaudienceofthisbookisPythonprogrammerswithanintermediatebackgroundandaninterestindesignpatternsimplementedinidiomaticPython.ProgrammersofotherlanguageswhoareinterestedinPythoncanalsobenefit,butit'sbetteriftheyfirstreadsomematerialsthatexplainhowthingsaredoneinPython[j.mp/idiompyt],[j.mp/dspython].

    http://j.mp/idiompythttp://j.mp/dspython

  • ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.

    Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:"WewillusetwolibrariesthatarepartofthePythondistributionforworkingwithXMLandJSON:xml.etree.ElementTreeandjson."

    Ablockofcodeissetasfollows:

    @propertydefparsed_data(self):returnself.data

    Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:

    @propertydefparsed_data(self):returnself.data

    Anycommand-lineinputoroutputiswrittenasfollows:

    >>>python3factory_method.py

    Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:"ClickingtheNextbuttonmovesyoutothenextscreen."

    NoteWarningsorimportantnotesappearinaboxlikethis.

    TipTipsandtricksappearlikethis.

    Bookreferencesfollowtheformat[Author,page].Forexample,thereference[GOF95,page10]referstothe10thpageoftheGOF(DesignPatterns:ElementsofReusableObject-OrientedSoftware)book.Attheendofthebook,thereisasectiondevotedtoallbookreferences.

  • Webreferencesfollowtheformat[j.mp/shortened].TheseareshortenedURLaddressesthatyoucantypeorcopy/pasteintoyourwebbrowserandberedirectedtothereal(usuallylongerandsometimesuglier)webreference.Forexample,typingj.mp/idiompytinyouwebbrowser'saddressbarshouldredirectyoutohttp://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html.

    http://j.mp/idiompythttp://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html

  • ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.

    Tosendusgeneralfeedback,simplye-mail,andmentionthebook'stitleinthesubjectofyourmessage.

    Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

    mailto:[email protected]://www.packtpub.com/authors

  • CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

    DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

    ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.

    Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.

    PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

    Pleasecontactusatwithalinktothesuspectedpiratedmaterial.

    Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.

    QuestionsIfyouhaveaproblemwithanyaspectofthisbook,youcancontactusat,andwewilldoourbesttoaddresstheproblem.

    http://www.packtpub.comhttp://www.packtpub.com/supporthttp://www.packtpub.com/submit-erratahttps://www.packtpub.com/books/content/supportmailto:[email protected]:[email protected]

  • Chapter1.TheFactoryPatternCreationaldesignpatternsdealwithanobjectcreation[j.mp/wikicrea].Theaimofacreationaldesignpatternistoprovidebetteralternativesforsituationswhereadirectobjectcreation(whichinPythonhappensbythe__init__()function[j.mp/divefunc],[Lott14,page26])isnotconvenient.

    IntheFactorydesignpattern,aclientasksforanobjectwithoutknowingwheretheobjectiscomingfrom(thatis,whichclassisusedtogenerateit).Theideabehindafactoryistosimplifyanobjectcreation.Itiseasiertotrackwhichobjectsarecreatedifthisisdonethroughacentralfunction,incontrasttolettingaclientcreateobjectsusingadirectclassinstantiation[Eckel08,page187].Afactoryreducesthecomplexityofmaintaininganapplicationbydecouplingthecodethatcreatesanobjectfromthecodethatusesit[Zlobin13,page30].

    Factoriestypicallycomeintwoforms:theFactoryMethod,whichisamethod(orinPythonicterms,afunction)thatreturnsadifferentobjectperinputparameter[j.mp/factorympat];theAbstractFactory,whichisagroupofFactoryMethodsusedtocreateafamilyofrelatedproducts[GOF95,page100],[j.mp/absfpat].

    FactoryMethodIntheFactoryMethod,weexecuteasinglefunction,passingaparameterthatprovidesinformationaboutwhatwewant.Wearenotrequiredtoknowanydetailsabouthowtheobjectisimplementedandwhereitiscomingfrom.

    Areal-lifeexampleAnexampleoftheFactoryMethodpatternusedinrealityisinplastictoyconstruction.Themoldingpowderusedtoconstructplastictoysisthesame,butdifferentfigurescanbeproducedusingdifferentplasticmolds.ThisislikehavingaFactoryMethodinwhichtheinputisthenameofthefigurethatwewant(duckandcar)andtheoutputistheplasticfigurethatwerequested.Thetoyconstructioncaseisshowninthefollowingfigure,whichisprovidedbywww.sourcemaking.com[j.mp/factorympat].

    http://j.mp/wikicreahttp://j.mp/divefunchttp://j.mp/factorympathttp://j.mp/absfpathttp://www.sourcemaking.comhttp://j.mp/factorympat

  • AsoftwareexampleTheDjangoframeworkusestheFactoryMethodpatternforcreatingthefieldsofaform.TheformsmoduleofDjangosupportsthecreationofdifferentkindsoffields(CharField,EmailField)andcustomizations(max_length,required)[j.mp/djangofacm].

    UsecasesIfyourealizethatyoucannottracktheobjectscreatedbyyourapplicationbecausethecodethatcreatesthemisinmanydifferentplacesinsteadofasinglefunction/method,youshouldconsiderusingtheFactoryMethodpattern[Eckel08,page187].TheFactoryMethodcentralizesanobjectcreationandtrackingyourobjectsbecomesmucheasier.NotethatitisabsolutelyfinetocreatemorethanoneFactoryMethod,andthisishowitistypicallydoneinpractice.EachFactoryMethodlogicallygroupsthecreationofobjectsthathavesimilarities.Forexample,oneFactoryMethodmightberesponsibleforconnectingyoutodifferentdatabases(MySQL,SQLite),anotherFactoryMethodmightberesponsibleforcreatingthegeometricalobjectthatyourequest(circle,triangle),andsoon.

    TheFactoryMethodisalsousefulwhenyouwanttodecoupleanobjectcreationfromanobjectusage.Wearenotcoupled/boundtoaspecificclasswhencreatinganobject,wejustprovidepartialinformationaboutwhatwewantbycallingafunction.Thismeansthatintroducingchangestothefunctioniseasywithoutrequiringanychangestothecodethatusesit[Zlobin13,page30].

    Anotherusecaseworthmentioningisrelatedtoimprovingtheperformanceandmemoryusageofanapplication.AFactoryMethodcanimprovetheperformanceandmemoryusagebycreatingnewobjectsonlyifitisabsolutelynecessary[Zlobin13,page28].Whenwecreateobjectsusingadirectclassinstantiation,extramemoryisallocatedeverytimeanewobjectiscreated(unlesstheclassusescachinginternally,whichisusuallynotthecase).Wecanseethatinpracticeinthefollowingcode(fileid.py),itcreatestwoinstancesofthesameclassAandusestheid()functiontocomparetheirmemoryaddresses.Theaddressesarealsoprintedintheoutputsothatwecaninspectthem.Thefactthatthememoryaddressesare

    http://j.mp/djangofacm

  • differentmeansthattwodistinctobjectsarecreatedasfollows:

    classA(object):pass

    if__name__=='__main__':a=A()b=A()

    print(id(a)==id(b))print(a,b)

    Executingid.pyonmycomputergivesthefollowingoutput:

    >>python3id.pyFalse

    NotethattheaddressesthatyouseeifyouexecutethefilearenotthesameasIseebecausetheydependonthecurrentmemorylayoutandallocation.Buttheresultmustbethesame:thetwoaddressesshouldbedifferent.There'soneexceptionthathappensifyouwriteandexecutethecodeinthePythonRead-Eval-PrintLoop(REPL)(interactiveprompt),butthat'saREPL-specificoptimizationwhichisnothappeningnormally.

    ImplementationDatacomesinmanyforms.Therearetwomainfilecategoriesforstoring/retrievingdata:human-readablefilesandbinaryfiles.Examplesofhuman-readablefilesareXML,Atom,YAML,andJSON.Examplesofbinaryfilesarethe.sq3fileformatusedbySQLiteandthe.mp3fileformatusedtolistentomusic.

    Inthisexample,wewillfocusontwopopularhuman-readableformats:XMLandJSON.Althoughhuman-readablefilesaregenerallyslowertoparsethanbinaryfiles,theymakedataexchange,inspection,andmodificationmucheasier.Forthisreason,itisadvisedtopreferworkingwithhuman-readablefiles,unlessthereareotherrestrictionsthatdonotallowit(mainlyunacceptableperformanceandproprietarybinaryformats).

    Inthisproblem,wehavesomeinputdatastoredinanXMLandaJSONfile,andwewanttoparsethemandretrievesomeinformation.Atthesametime,wewanttocentralizetheclient'sconnectiontothose(andallfuture)externalservices.WewillusetheFactoryMethodtosolvethisproblem.TheexamplefocusesonlyonXMLandJSON,butaddingsupportformoreservicesshouldbestraightforward.

    First,let'stakealookatthedatafiles.TheXMLfile,person.xml,isbasedontheWikipediaexample[j.mp/wikijson]andcontainsinformationaboutindividuals(firstName,lastName,gender,andsoon)asfollows:

    http://j.mp/wikijson

  • JohnSmith25212ndStreetNewYorkNY10021212555-1234646555-4567maleJimyLiar19182ndStreetNewYorkNY10021212555-1234malePattyLiar20182ndStreetNewYorkNY10021212555-1234001452-8819female

    TheJSONfile,donut.json,comesfromtheGitHubaccountofAdobe[j.mp/adobejson]andcontainsdonutinformation(type,price/unitthatis,ppu,topping,andsoon)asfollows:

    http://j.mp/adobejson

  • [{"id":"0001","type":"donut","name":"Cake","ppu":0.55,"batters":{"batter":[{"id":"1001","type":"Regular"},{"id":"1002","type":"Chocolate"},{"id":"1003","type":"Blueberry"},{"id":"1004","type":"Devil'sFood"}]},"topping":[{"id":"5001","type":"None"},{"id":"5002","type":"Glazed"},{"id":"5005","type":"Sugar"},{"id":"5007","type":"PowderedSugar"},{"id":"5006","type":"ChocolatewithSprinkles"},{"id":"5003","type":"Chocolate"},{"id":"5004","type":"Maple"}]},{"id":"0002","type":"donut","name":"Raised","ppu":0.55,"batters":{"batter":[{"id":"1001","type":"Regular"}]},"topping":[{"id":"5001","type":"None"},{"id":"5002","type":"Glazed"},{"id":"5005","type":"Sugar"},{"id":"5003","type":"Chocolate"},{"id":"5004","type":"Maple"}]},{"id":"0003","type":"donut","name":"OldFashioned","ppu":0.55,"batters":{"batter":[{"id":"1001","type":"Regular"},{"id":"1002","type":"Chocolate"}]},"topping":[{"id":"5001","type":"None"},{"id":"5002","type":"Glazed"},{"id":"5003","type":"Chocolate"},{"id":"5004","type":"Maple"}]}

  • ]

    WewillusetwolibrariesthatarepartofthePythondistributionforworkingwithXMLandJSON:xml.etree.ElementTreeandjsonasfollows:

    importxml.etree.ElementTreeasetreeimportjson

    TheJSONConnectorclassparsestheJSONfileandhasaparsed_data()methodthatreturnsalldataasadictionary(dict).Thepropertydecoratorisusedtomakeparsed_data()appearasanormalvariableinsteadofamethodasfollows:

    classJSONConnector:

    def__init__(self,filepath):self.data=dict()withopen(filepath,mode='r',encoding='utf-8')asf:self.data=json.load(f)

    @propertydefparsed_data(self):returnself.data

    TheXMLConnectorclassparsestheXMLfileandhasaparsed_data()methodthatreturnsalldataasalistofxml.etree.Elementasfollows:

    classXMLConnector:

    def__init__(self,filepath):self.tree=etree.parse(filepath)

    @propertydefparsed_data(self):returnself.tree

    Theconnection_factory()functionisaFactoryMethod.ItreturnsaninstanceofJSONConnectororXMLConnectordependingontheextensionoftheinputfilepathasfollows:

    defconnection_factory(filepath):iffilepath.endswith('json'):connector=JSONConnectoreliffilepath.endswith('xml'):connector=XMLConnectorelse:raiseValueError('Cannotconnectto{}'.format(filepath))returnconnector(filepath)

    Theconnect_to()functionisawrapperofconnection_factory().Itaddsexceptionhandlingasfollows:

    defconnect_to(filepath):

  • factory=Nonetry:factory=connection_factory(filepath)exceptValueErrorasve:print(ve)returnfactory

    Themain()functiondemonstrateshowtheFactoryMethoddesignpatterncanbeused.Thefirstpartmakessurethatexceptionhandlingiseffectiveasfollows:

    defmain():sqlite_factory=connect_to('data/person.sq3')

    ThenextpartshowshowtoworkwiththeXMLfilesusingtheFactoryMethod.XPathisusedtofindallpersonelementsthathavethelastnameLiar.Foreachmatchedperson,thebasicnameandphonenumberinformationareshownasfollows:

    xml_factory=connect_to('data/person.xml')xml_data=xml_factory.parsed_data()liars=xml_data.findall(".//{person}[{lastName}='{}']".format('Liar'))print('found:{}persons'.format(len(liars)))forliarinliars:print('firstname:{}'.format(liar.find('firstName').text))print('lastname:{}'.format(liar.find('lastName').text))[print('phonenumber({}):'.format(p.attrib['type']),p.text)forpinliar.find('phoneNumbers')]

    ThefinalpartshowshowtoworkwiththeJSONfilesusingtheFactoryMethod.Here,there'snopatternmatching,andthereforethename,price,andtoppingofalldonutsareshownasfollows:

    json_factory=connect_to('data/donut.json')json_data=json_factory.parsed_dataprint('found:{}donuts'.format(len(json_data)))fordonutinjson_data:print('name:{}'.format(donut['name']))print('price:${}'.format(donut['ppu']))[print('topping:{}{}'.format(t['id'],t['type']))fortindonut['topping']]

    Forcompleteness,hereisthecompletecodeoftheFactoryMethodimplementation(factory_method.py)asfollows:

    importxml.etree.ElementTreeasetreeimportjson

    classJSONConnector:def__init__(self,filepath):self.data=dict()withopen(filepath,mode='r',encoding='utf-8')asf:self.data=json.load(f)

  • @propertydefparsed_data(self):returnself.data

    classXMLConnector:def__init__(self,filepath):self.tree=etree.parse(filepath)

    @propertydefparsed_data(self):returnself.tree

    defconnection_factory(filepath):iffilepath.endswith('json'):connector=JSONConnectoreliffilepath.endswith('xml'):connector=XMLConnectorelse:raiseValueError('Cannotconnectto{}'.format(filepath))returnconnector(filepath)

    defconnect_to(filepath):factory=Nonetry:factory=connection_factory(filepath)exceptValueErrorasve:print(ve)returnfactory

    defmain():sqlite_factory=connect_to('data/person.sq3')print()

    xml_factory=connect_to('data/person.xml')xml_data=xml_factory.parsed_dataliars=xml_data.findall(".//{}[{}='{}']".format('person','lastName','Liar'))print('found:{}persons'.format(len(liars)))forliarinliars:print('firstname:{}'.format(liar.find('firstName').text))print('lastname:{}'.format(liar.find('lastName').text))[print('phonenumber({}):'.format(p.attrib['type']),p.text)forpinliar.find('phoneNumbers')]print()

    json_factory=connect_to('data/donut.json')json_data=json_factory.parsed_dataprint('found:{}donuts'.format(len(json_data)))fordonutinjson_data:print('name:{}'.format(donut['name']))print('price:${}'.format(donut['ppu']))[print('topping:{}{}'.format(t['id'],t['type']))fortindonut['topping']]

    if__name__=='__main__':main()

    Hereistheoutputofthisprogramasfollows:

  • >>>python3factory_method.pyCannotconnecttodata/person.sq3

    found:2personsfirstname:Jimylastname:Liarphonenumber(home):212555-1234firstname:Pattylastname:Liarphonenumber(home):212555-1234phonenumber(mobile):001452-8819

    found:3donutsname:Cakeprice:$0.55topping:5001Nonetopping:5002Glazedtopping:5005Sugartopping:5007PowderedSugartopping:5006ChocolatewithSprinklestopping:5003Chocolatetopping:5004Maplename:Raisedprice:$0.55topping:5001Nonetopping:5002Glazedtopping:5005Sugartopping:5003Chocolatetopping:5004Maplename:OldFashionedprice:$0.55topping:5001Nonetopping:5002Glazedtopping:5003Chocolatetopping:5004Maple

    NoticethatalthoughJSONConnectorandXMLConnectorhavethesameinterfaces,whatisreturnedbyparsed_data()isnothandledinauniformway.Differentpythoncodemustbeusedtoworkwitheachconnector.Althoughitwouldbenicetobeabletousethesamecodeforallconnectors,thisisatmosttimesnotrealisticunlessweusesomekindofcommonmappingforthedatawhichisveryoftenprovidedbyexternaldataproviders.AssumingthatyoucanuseexactlythesamecodeforhandlingtheXMLandJSONfiles,whatchangesarerequiredtosupportathirdformat,forexample,SQLite?FindanSQLitefileorcreateyourownandtryit.

    Asitisnow,thecodedoesnotforbidadirectinstantiationofaconnector.Isitpossibletodothis?Trydoingit.

    TipHint:FunctionsinPythoncanhavenestedclasses.

  • AbstractFactoryTheAbstractFactorydesignpatternisageneralizationofFactoryMethod.Basically,anAbstractFactoryisa(logical)groupofFactoryMethods,whereeachFactoryMethodisresponsibleforgeneratingadifferentkindofobject[Eckel08,page193].

    Areal-lifeexampleAbstractFactoryisusedincarmanufacturing.Thesamemachineryisusedforstampingtheparts(doors,panels,hoods,fenders,andmirrors)ofdifferentcarmodels.Themodelthatisassembledbythemachineryisconfigurableandeasytochangeatanytime.WecanseeanexampleofthecarmanufacturingAbstractFactoryinthefollowingfigure,whichisprovidedbywww.sourcemaking.com[j.mp/absfpat].

    AsoftwareexampleThedjango_factorypackageisanAbstractFactoryimplementationforcreatingDjangomodelsintests.Itisusedforcreatinginstancesofmodelsthatsupporttest-specificattributes.Thisisimportantbecausethetestsbecomereadableandavoidsharingunnecessarycode[j.mp/djangoabs].

    http://www.sourcemaking.comhttp://j.mp/absfpathttp://j.mp/djangoabs

  • UsecasesSincetheAbstractFactorypatternisageneralizationoftheFactoryMethodpattern,itoffersthesamebenefits:itmakestrackinganobjectcreationeasier,itdecouplesanobjectcreationfromanobjectusage,anditgivesusthepotentialtoimprovethememoryusageandperformanceofourapplication.

    Butaquestionisraised:howdoweknowwhentousetheFactoryMethodversususinganAbstractFactory?TheansweristhatweusuallystartwiththeFactoryMethodwhichissimpler.IfwefindoutthatourapplicationrequiresmanyFactoryMethodswhichitmakessensetocombineforcreatingafamilyofobjects,weendupwithanAbstractFactory.

    AbenefitoftheAbstractFactorythatisusuallynotveryvisiblefromauser'spointofviewwhenusingtheFactoryMethodisthatitgivesustheabilitytomodifythebehaviorofourapplicationdynamically(inruntime)bychangingtheactiveFactoryMethod.Theclassicexampleisgivingtheabilitytochangethelookandfeelofanapplication(forexample,Apple-like,Windows-like,andsoon)fortheuserwhiletheapplicationisinuse,withouttheneedtoterminateitandstartitagain[GOF95,page99].

    ImplementationTodemonstratetheAbstractFactorypattern,Iwillreuseoneofmyfavoriteexamples,includedinPython3Patterns&Idioms,BruceEckel,[Eckel08,page193].Imaginethatwearecreatingagameorwewanttoincludeamini-gameaspartofourapplicationtoentertainourusers.Wewanttoincludeatleasttwogames,oneforchildrenandoneforadults.Wewilldecidewhichgametocreateandlaunchinruntime,basedonuserinput.AnAbstractFactorytakescareofthegamecreationpart.

    Let'sstartwiththekid'sgame.ItiscalledFrogWorld.Themainheroisafrogwhoenjoyseatingbugs.Everyheroneedsagoodname,andinourcasethenameisgivenbytheuserinruntime.Theinteract_with()methodisusedtodescribetheinteractionofthefrogwithanobstacle(forexample,bug,puzzle,andotherfrog)asfollows:

    classFrog:def__init__(self,name):self.name=name

    def__str__(self):returnself.name

    definteract_with(self,obstacle):print('{}theFrogencounters{}and{}!'.format(self,obstacle,obstacle.action()))

    TherecanbemanydifferentkindsofobstaclesbutforourexampleanobstaclecanonlybeaBug.Whenthefrogencountersabug,onlyoneactionissupported:iteatsit!

    classBug:

  • def__str__(self):return'abug'

    defaction(self):return'eatsit'

    TheFrogWorldclassisanAbstractFactory.Itsmainresponsibilitiesarecreatingthemaincharacterandtheobstacle(s)ofthegame.Keepingthecreationmethodsseparateandtheirnamesgeneric(forexample,make_character(),make_obstacle())allowsustodynamicallychangetheactivefactory(andthereforetheactivegame)withoutanycodechanges.Inastaticallytypedlanguage,theAbstractFactorywouldbeanabstractclass/interfacewithemptymethods,butinPythonthisisnotrequiredbecausethetypesarecheckedinruntime[Eckel08,page195],[j.mp/ginstromdp]asfollows:

    classFrogWorld:def__init__(self,name):print(self)self.player_name=namedef__str__(self):return'\n\n\t------FrogWorld-------'

    defmake_character(self):returnFrog(self.player_name)

    defmake_obstacle(self):returnBug()

    TheWizardWorldgameissimilar.Theonlydifferencesarethatthewizardbattlesagainstmonsterslikeorksinsteadofeatingbugs!

    classWizard:def__init__(self,name):self.name=name

    def__str__(self):returnself.name

    definteract_with(self,obstacle):print('{}theWizardbattlesagainst{}and{}!'.format(self,obstacle,obstacle.action()))

    classOrk:def__str__(self):return'anevilork'

    defaction(self):return'killsit'

    classWizardWorld:def__init__(self,name):print(self)self.player_name=name

    def__str__(self):

    http://j.mp/ginstromdp

  • return'\n\n\t------WizardWorld-------'

    defmake_character(self):returnWizard(self.player_name)

    defmake_obstacle(self):returnOrk()

    TheGameEnvironmentisthemainentrypointofourgame.Itacceptsfactoryasaninput,andusesittocreatetheworldofthegame.Theplay()methodinitiatestheinteractionbetweenthecreatedheroandtheobstacleasfollows:

    classGameEnvironment:def__init__(self,factory):self.hero=factory.make_character()self.obstacle=factory.make_obstacle()

    defplay(self):self.hero.interact_with(self.obstacle)

    Thevalidate_age()functionpromptstheusertogiveavalidage.Iftheageisnotvalid,itreturnsatuplewiththefirstelementsettoFalse.Iftheageisfine,thefirstelementofthetupleissettoTrueandthat'sthecasewhereweactuallycareaboutthesecondelementofthetuple,whichistheagegivenbytheuserasfollows:

    defvalidate_age(name):try:age=input('Welcome{}.Howoldareyou?'.format(name))age=int(age)exceptValueErroraserr:print("Age{}isinvalid,pleasetryagain...".format(age))return(False,age)return(True,age)

    Lastbutnotleastcomesthemain()function.Itasksfortheuser'snameandage,anddecideswhichgameshouldbeplayedbytheageoftheuserasfollows:

    defmain():name=input("Hello.What'syourname?")valid_input=Falsewhilenotvalid_input:valid_input,age=validate_age(name)game=FrogWorldifage<18elseWizardWorldenvironment=GameEnvironment(game(name))environment.play()

    AndthecompletecodeoftheAbstractFactoryimplementation(abstract_factory.py)isgivenasfollows:

    classFrog:def__init__(self,name):self.name=name

  • def__str__(self):returnself.name

    definteract_with(self,obstacle):print('{}theFrogencounters{}and{}!'.format(self,obstacle,obstacle.action()))

    classBug:def__str__(self):return'abug'

    defaction(self):return'eatsit'

    classFrogWorld:def__init__(self,name):print(self)self.player_name=namedef__str__(self):return'\n\n\t------FrogWorld-------'

    defmake_character(self):returnFrog(self.player_name)

    defmake_obstacle(self):returnBug()

    classWizard:def__init__(self,name):self.name=name

    def__str__(self):returnself.namedefinteract_with(self,obstacle):print('{}theWizardbattlesagainst{}and{}!'.format(self,obstacle,obstacle.action()))

    classOrk:def__str__(self):return'anevilork'

    defaction(self):return'killsit'

    classWizardWorld:def__init__(self,name):print(self)self.player_name=name

    def__str__(self):return'\n\n\t------WizardWorld-------'

    defmake_character(self):returnWizard(self.player_name)

    defmake_obstacle(self):returnOrk()

  • classGameEnvironment:def__init__(self,factory):self.hero=factory.make_character()self.obstacle=factory.make_obstacle()

    defplay(self):self.hero.interact_with(self.obstacle)

    defvalidate_age(name):try:age=input('Welcome{}.Howoldareyou?'.format(name))age=int(age)exceptValueErroraserr:print("Age{}isinvalid,pleasetryagain...".format(age))return(False,age)return(True,age)

    defmain():name=input("Hello.What'syourname?")valid_input=Falsewhilenotvalid_input:valid_input,age=validate_age(name)game=FrogWorldifage<18elseWizardWorldenvironment=GameEnvironment(game(name))environment.play()

    if__name__=='__main__':main()

    Asampleoutputofthisprogramisasfollows:

    >>>python3abstract_factory.pyHello.What'syourname?NickWelcomeNick.Howoldareyou?17------FrogWorld-------NicktheFrogencountersabugandeatsit!

    Tryextendingthegametomakeitmorecomplete.Youcangoasfarasyouwant:manyobstacles,manyenemies,andwhateverelseyoulike.

  • SummaryInthischapter,wehaveseenhowtousetheFactoryMethodandtheAbstractFactorydesignpatterns.Bothpatternsareusedwhenwewantto(a)trackanobjectcreation,(b)decoupleanobjectcreationfromanobjectusage,oreven(c)improvetheperformanceandresourceusageofanapplication.Case(c)wasnotdemonstratedinthechapter.Youmightconsideritasagoodexercise.

    TheFactoryMethoddesignpatternisimplementedasasinglefunctionthatdoesn'tbelongtoanyclass,andisresponsibleforthecreationofasinglekindofobject(ashape,aconnectionpoint,andsoon).WesawhowtheFactoryMethodrelatestotoyconstruction,mentionedhowitisusedbyDjangoforcreatingdifferentformfields,anddiscussedotherpossibleusecasesforit.Asanexample,weimplementedaFactoryMethodthatprovidesaccesstotheXMLandJSONfiles.

    TheAbstractFactorydesignpatternisimplementedasanumberofFactoryMethodsthatbelongtoasingleclassandareusedtocreateafamilyofrelatedobjects(thepartsofacar,theenvironmentofagame,andsoforth).WementionedhowtheAbstractFactoryisrelatedwithcarmanufacturing,howthedjango_factoryDjangopackagemakesuseofittocreatecleantests,andcoveredtheusecasesofit.TheimplementationoftheAbstractFactoryisamini-gamethatshowshowwecanusemanyrelatedfactoriesinasingleclass.

    Inthenextchapter,wewilltalkabouttheBuilderpattern,whichisanothercreationalpatternthatcanbeusedforfine-controllingthecreationofcomplexobjects.

  • Chapter2.TheBuilderPatternImaginethatwewanttocreateanobjectthatiscomposedofmultiplepartsandthecompositionneedstobedonestepbystep.Theobjectisnotcompleteunlessallitspartsarefullycreated.That'swheretheBuilderdesignpatterncanhelpus.TheBuilderpatternseparatestheconstructionofacomplexobjectfromitsrepresentation.Bykeepingtheconstructionseparatefromtherepresentation,thesameconstructioncanbeusedtocreateseveraldifferentrepresentations[GOF95,page110],[j.mp/builderpat].

    ApracticalexamplecanhelpusunderstandwhatthepurposeoftheBuilderpatternis.SupposethatwewanttocreateanHTMLpagegenerator,thebasicstructure(constructionpart)ofanHTMLpageisalwaysthesame:itbeginswithandfinisheswith;insidetheHTMLsectionaretheandelements,insidetheheadsectionaretheandelements,andsoforth.Buttherepresentationofthepagecandiffer.Eachpagehasitsowntitle,itsownheadings,anddifferentcontents.Moreover,thepageisusuallybuiltinsteps:onefunctionaddsthetitle,anotheraddsthemainheading,anotherthefooter,andsoon.Onlyafterthewholestructureofapageiscompletecanitbeshowntotheclientusingafinalrenderfunction.WecantakeitevenfurtherandextendtheHTMLgeneratorsothatitcangeneratetotallydifferentHTMLpages.Onepagemightcontaintables,anotherpagemightcontainimagegalleries,yetanotherpagecontainsthecontactform,andsoon.

    TheHTMLpagegenerationproblemcanbesolvedusingtheBuilderpattern.Inthispattern,therearetwomainparticipants:thebuilderandthedirector.Thebuilderisresponsibleforcreatingthevariouspartsofthecomplexobject.IntheHTMLexample,thesepartsarethetitle,heading,body,andthefooterofthepage.Thedirectorcontrolsthebuildingprocessusingabuilderinstance.TheHTMLexamplemeansforcallingthebuilder'sfunctionsforsettingthetitle,theheading,andsoon.UsingadifferentbuilderinstanceallowsustocreateadifferentHTMLpagewithouttouchinganycodeofthedirector.

    Areal-lifeexampleTheBuilderdesignpatternisusedinfast-foodrestaurants.Thesameprocedureisalwaysusedtoprepareaburgerandthepackaging(boxandpaperbag),eveniftherearemanydifferentkindsofburgers(classic,cheeseburger,andmore)anddifferentpackages(small-sizedbox,medium-sizedbox,andsoforth).Thedifferencebetweenaclassicburgerandacheeseburgerisintherepresentation,andnotintheconstructionprocedure.Thedirectoristhecashierwhogivesinstructionsaboutwhatneedstobepreparedtothecrew,andthebuilderisthepersonfromthecrewthattakescareofthespecificorder.Thefollowingfigureprovidedbywww.sourcemaking.comshowsaUnifiedModelingLanguage(UML)sequencediagramofthecommunicationthattakesplacebetweenthecustomer(client),thecashier(director),andthecrew(builder)whenakid'smenuisordered[j.mp/builderpat].

    http://j.mp/builderpathttp://www.sourcemaking.comhttp://j.mp/builderpat

  • AsoftwareexampleTheHTMLexamplethatwasmentionedatthebeginningofthechapterisactuallyusedbydjango-widgy,athird-partytreeeditorforDjangothatcanbeusedasaContentManagementSystem(CMS).Thedjango-widgyeditorcontainsapagebuilderthatcanbeusedforcreatingHTMLpageswithdifferentlayouts[j.mp/widgypb].

    Thedjango-query-builderlibraryisanotherthird-partyDjangolibrarythatreliesontheBuilderpattern.Thedjango-query-builderlibrarycanbeusedforbuildingSQLqueriesdynamically.Usingthis,wecancontrolallaspectsofaqueryandcreateadifferentrangeofqueries,fromsimpletoverycomplex[j.mp/djangowidgy].

    http://j.mp/widgypbhttp://j.mp/djangowidgy

  • UsecasesWeusetheBuilderpatternwhenweknowthatanobjectmustbecreatedinmultiplesteps,anddifferentrepresentationsofthesameconstructionarerequired.Theserequirementsexistinmanyapplicationssuchaspagegenerators(liketheHTMLpagegeneratormentionedinthischapter),documentconverters[GOF95,page110],andUserInterface(UI)formcreators[j.mp/pipbuild].

    SomeresourcesmentionthattheBuilderpatterncanalsobeusedasasolutiontothetelescopicconstructorproblem[j.mp/wikibuilder].Thetelescopicconstructorproblemoccurswhenweareforcedtocreateanewconstructorforsupportingdifferentwaysofcreatinganobject.Theproblemisthatweendupwithmanyconstructorsandlongparameterlists,whicharehardtomanage.Anexampleofthetelescopicconstructorislistedatthestackoverflowwebsite[j.mp/sobuilder].Fortunately,thisproblemdoesnotexistinPython,becauseitcanbesolvedinatleasttwoways:

    Withnamedparameters[j.mp/sobuipython]Withargumentlistunpacking[j.mp/arglistpy]

    Atthispoint,thedistinctionbetweentheBuilderpatternandtheFactorypatternmightnotbeveryclear.ThemaindifferenceisthataFactorypatterncreatesanobjectinasinglestep,whereasaBuilderpatterncreatesanobjectinmultiplesteps,andalmostalwaysthroughtheuseofadirector.SometargetedimplementationsoftheBuilderpatternlikeJava'sStringBuilderbypasstheuseofadirector,butthat'stheexceptiontotherule.

    AnotherdifferenceisthatwhileaFactorypatternreturnsacreatedobjectimmediately,intheBuilderpatterntheclientcodeexplicitlyasksthedirectortoreturnthefinalobjectwhenitneedsit[GOF95,page113],[j.mp/builderpat].

    ThenewcomputeranalogymighthelptodistinguishbetweenaBuilderpatternandaFactorypattern.Assumethatyouwanttobuyanewcomputer.Ifyoudecidetobuyaspecificpreconfiguredcomputermodel,forexample,thelatestApple1.4GHzMacmini,youusetheFactorypattern.Allthehardwarespecificationsarealreadypredefinedbythemanufacturer,whoknowswhattodowithoutconsultingyou.Themanufacturertypicallyreceivesjustasingleinstruction.Code-wise,thiswouldlooklikethefollowing(apple-factory.py):

    MINI14='1.4GHzMacmini'

    classAppleFactory:classMacMini14:def__init__(self):self.memory=4#ingigabytesself.hdd=500#ingigabytesself.gpu='IntelHDGraphics5000'

    def__str__(self):info=('Model:{}'.format(MINI14),'Memory:{}GB'.format(self.memory),'HardDisk:{}GB'.format(self.hdd),

    http://j.mp/pipbuildhttp://j.mp/wikibuilderhttp://j.mp/sobuilderhttp://j.mp/sobuipythonhttp://j.mp/arglistpyhttp://j.mp/builderpat

  • 'GraphicsCard:{}'.format(self.gpu))return'\n'.join(info)

    defbuild_computer(self,model):if(model==MINI14):returnself.MacMini14()else:print("Idon'tknowhowtobuild{}".format(model))

    if__name__=='__main__':afac=AppleFactory()mac_mini=afac.build_computer(MINI14)print(mac_mini)

    NoteNoticethenestedMacMini14class.Thisisaneatwayofforbiddingthedirectinstantiationofaclass.

    AnotheroptionisbuyingacustomPC.Inthiscase,youusetheBuilderpattern.Youarethedirectorthatgivesorderstothemanufacturer(builder)aboutyouridealcomputerspecifications.Code-wise,thislookslikethefollowing(computer-builder.py):

    classComputer:def__init__(self,serial_number):self.serial=serial_numberself.memory=None#ingigabytesself.hdd=None#ingigabytesself.gpu=None

    def__str__(self):info=('Memory:{}GB'.format(self.memory),'HardDisk:{}GB'.format(self.hdd),'GraphicsCard:{}'.format(self.gpu))return'\n'.join(info)

    classComputerBuilder:def__init__(self):self.computer=Computer('AG23385193')

    defconfigure_memory(self,amount):self.computer.memory=amount

    defconfigure_hdd(self,amount):self.computer.hdd=amount

    defconfigure_gpu(self,gpu_model):self.computer.gpu=gpu_model

    classHardwareEngineer:def__init__(self):self.builder=None

    defconstruct_computer(self,memory,hdd,gpu):self.builder=ComputerBuilder()

  • [stepforstepin(self.builder.configure_memory(memory),self.builder.configure_hdd(hdd),self.builder.configure_gpu(gpu))]

    @propertydefcomputer(self):returnself.builder.computer

    defmain():engineer=HardwareEngineer()engineer.construct_computer(hdd=500,memory=8,gpu='GeForceGTX650Ti')computer=engineer.computerprint(computer)

    if__name__=='__main__':main()

    ThebasicchangesaretheintroductionofabuilderComputerBuilder,adirectorHardwareEngineer,andthestep-by-stepconstructionofacomputer,whichnowsupportsdifferentconfigurations(noticethatmemory,hdd,andgpuareparametersandnotpreconfigured).Whatdoweneedtodoifwewanttosupporttheconstructionoftablets?Implementthisasanexercise.

    Youmightalsowanttochangethecomputerserial_numberintosomethingthatisdifferentforeachcomputer,becauseasitisnowitmeansthatallcomputerswillhavethesameserialnumber(whichisimpractical).

  • ImplementationLet'sseehowwecanusetheBuilderdesignpatterntomakeapizzaorderingapplication.Thepizzaexampleisparticularlyinterestingbecauseapizzaispreparedinstepsthatshouldfollowaspecificorder.Toaddthesauce,youfirstneedtopreparethedough.Toaddthetopping,youfirstneedtoaddthesauce.Andyoucan'tstartbakingthepizzaunlessboththesauceandthetoppingareplacedonthedough.Moreover,eachpizzausuallyrequiresadifferentbakingtime,dependingonthethicknessofitsdoughandthetoppingused.

    WestartwithimportingtherequiredmodulesanddeclaringafewEnumparameters[j.mp/pytenum]plusaconstantthatareusedmanytimesintheapplication.TheSTEP_DELAYconstantisusedtoaddatimedelaybetweenthedifferentstepsofpreparingapizza(preparethedough,addthesauce,andsoon)asfollows:

    fromenumimportEnum

    PizzaProgress=Enum('PizzaProgress','queuedpreparationbakingready')PizzaDough=Enum('PizzaDough','thinthick')PizzaSauce=Enum('PizzaSauce','tomatocreme_fraiche')PizzaTopping=Enum('PizzaTopping','mozzarelladouble_mozzarellabaconhammushroomsred_onionoregano')STEP_DELAY=3#insecondsforthesakeoftheexample

    Ourendproductisapizza,whichisdescribedbythePizzaclass.WhenusingtheBuilderpattern,theendproductdoesnothavemanyresponsibilities,sinceitisnotsupposedtobeinstantiateddirectly.Abuildercreatesaninstanceoftheendproductandmakessurethatitisproperlyprepared.That'swhythePizzaclassissominimal.Itbasicallyinitializesalldatatosanedefaultvalues.Anexceptionistheprepare_dough()method.Theprepare_dough()methodisdefinedinthePizzaclassinsteadofabuilderfortworeasons:

    ToclarifythefactthattheendproductistypicallyminimaldoesnotmeanthatyoushouldneverassignitanyresponsibilitiesTopromotecodereusethroughcomposition[GOF95,page32]

    classPizza:def__init__(self,name):self.name=nameself.dough=Noneself.sauce=Noneself.topping=[]

    def__str__(self):returnself.name

    defprepare_dough(self,dough):self.dough=doughprint('preparingthe{}doughofyour{}...'.format(self.dough.name,self))time.sleep(STEP_DELAY)

    http://j.mp/pytenum

  • print('donewiththe{}dough'.format(self.dough.name))

    Therearetwobuilders:oneforcreatingamargaritapizza(MargaritaBuilder)andanotherforcreatingacreamybaconpizza(CreamyBaconBuilder).EachbuildercreatesaPizzainstanceandcontainsmethodsthatfollowthepizza-makingprocedure:prepare_dough(),add_sauce(),add_topping(),andbake().Tobeprecise,prepare_dough()isjustawrappertotheprepare_dough()methodofthePizzaclass.Noticehoweachbuildertakescareofallthepizza-specificdetails.Forexample,thetoppingofthemargaritapizzaisdoublemozzarellaandoregano,whilethetoppingofthecreamybaconpizzaismozzarella,bacon,ham,mushrooms,redonion,andoreganoasfollows:

    classMargaritaBuilder:def__init__(self):self.pizza=Pizza('margarita')self.progress=PizzaProgress.queuedself.baking_time=5#insecondsforthesakeoftheexample

    defprepare_dough(self):self.progress=PizzaProgress.preparationself.pizza.prepare_dough(PizzaDough.thin)

    defadd_sauce(self):print('addingthetomatosaucetoyourmargarita...')self.pizza.sauce=PizzaSauce.tomatotime.sleep(STEP_DELAY)print('donewiththetomatosauce')

    defadd_topping(self):print('addingthetopping(doublemozzarella,oregano)toyourmargarita')self.pizza.topping.append([iforiin(PizzaTopping.double_mozzarella,PizzaTopping.oregano)])time.sleep(STEP_DELAY)print('donewiththetopping(doublemozzarella,oregano)')

    defbake(self):self.progress=PizzaProgress.bakingprint('bakingyourmargaritafor{}seconds'.format(self.baking_time))time.sleep(self.baking_time)self.progress=PizzaProgress.readyprint('yourmargaritaisready')

    classCreamyBaconBuilder:def__init__(self):self.pizza=Pizza('creamybacon')self.progress=PizzaProgress.queuedself.baking_time=7#insecondsforthesakeoftheexample

    defprepare_dough(self):self.progress=PizzaProgress.preparationself.pizza.prepare_dough(PizzaDough.thick)

    defadd_sauce(self):print('addingthecrèmefraîchesaucetoyourcreamybacon')

  • self.pizza.sauce=PizzaSauce.creme_fraichetime.sleep(STEP_DELAY)print('donewiththecrèmefraîchesauce')

    defadd_topping(self):print('addingthetopping(mozzarella,bacon,ham,mushrooms,redonion,oregano)toyourcreamybacon')self.pizza.topping.append([tfortin(PizzaTopping.mozzarella,PizzaTopping.bacon,PizzaTopping.ham,PizzaTopping.mushrooms,PizzaTopping.red_onion,PizzaTopping.oregano)])time.sleep(STEP_DELAY)print('donewiththetopping(mozzarella,bacon,ham,mushrooms,redonion,oregano)')

    defbake(self):self.progress=PizzaProgress.bakingprint('bakingyourcreamybaconfor{}seconds'.format(self.baking_time))time.sleep(self.baking_time)self.progress=PizzaProgress.readyprint('yourcreamybaconisready')

    Thedirectorinthisexampleisthewaiter.ThecoreoftheWaiterclassistheconstruct_pizza()method,whichacceptsabuilderasaparameterandexecutesallthepizzapreparationstepsintherightorder.Choosingtheappropriatebuilder,whichcanevenbedoneinruntime,givesustheabilitytocreatedifferentpizzastyleswithoutmodifyinganycodeofthedirector(Waiter).TheWaiterclassalsocontainsthepizza()method,whichreturnstheendproduct(preparedpizza)asavariabletothecallerasfollows:

    classWaiter:def__init__(self):self.builder=None

    defconstruct_pizza(self,builder):self.builder=builder[step()forstepin(builder.prepare_dough,builder.add_sauce,builder.add_topping,builder.bake)]

    @propertydefpizza(self):returnself.builder.pizza

    Thevalidate_style()functionissimilartothevalidate_age()functionasdescribedinChapter1,TheFactoryPattern.Itisusedtomakesurethattheusergivesvalidinput,whichinthiscaseisacharacterthatismappedtoapizzabuilder.ThemcharacterusestheMargaritaBuilderclassandtheccharacterusestheCreamyBaconBuilderclass.Thesemappingsareinthebuilderparameter.Atupleisreturned,withthefirstelementsettoTrueiftheinputisvalid,orFalseifitisinvalidasfollows:

    defvalidate_style(builders):try:

  • pizza_style=input('Whatpizzawouldyoulike,[m]argaritaor[c]reamybacon?')builder=builders[pizza_style]()valid_input=TrueexceptKeyErroraserr:print('Sorry,onlymargarita(keym)andcreamybacon(keyc)areavailable')return(False,None)return(True,builder)

    Thelastpartisthemain()function.Themain()functioncontainsacodeforinstantiatingapizzabuilder.ThepizzabuilderisthenusedbytheWaiterdirectorforpreparingthepizza.Thecreatedpizzacanbedeliveredtotheclientatanylaterpoint:

    defmain():builders=dict(m=MargaritaBuilder,c=CreamyBaconBuilder)valid_input=Falsewhilenotvalid_input:valid_input,builder=validate_style(builders)print()waiter=Waiter()waiter.construct_pizza(builder)pizza=waiter.pizzaprint()print('Enjoyyour{}!'.format(pizza))

    Toputallthesethingstogether,here'sthecompletecodeofthisexample(builder.py):

    fromenumimportEnumimporttime

    PizzaProgress=Enum('PizzaProgress','queuedpreparationbakingready')PizzaDough=Enum('PizzaDough','thinthick')PizzaSauce=Enum('PizzaSauce','tomatocreme_fraiche')PizzaTopping=Enum('PizzaTopping','mozzarelladouble_mozzarellabaconhammushroomsred_onionoregano')STEP_DELAY=3#insecondsforthesakeoftheexample

    classPizza:def__init__(self,name):self.name=nameself.dough=Noneself.sauce=Noneself.topping=[]

    def__str__(self):returnself.name

    defprepare_dough(self,dough):self.dough=doughprint('preparingthe{}doughofyour{}...'.format(self.dough.name,self))time.sleep(STEP_DELAY)print('donewiththe{}dough'.format(self.dough.name))

  • classMargaritaBuilder:def__init__(self):self.pizza=Pizza('margarita')self.progress=PizzaProgress.queuedself.baking_time=5#insecondsforthesakeoftheexample

    defprepare_dough(self):self.progress=PizzaProgress.preparationself.pizza.prepare_dough(PizzaDough.thin)

    defadd_sauce(self):print('addingthetomatosaucetoyourmargarita...')self.pizza.sauce=PizzaSauce.tomatotime.sleep(STEP_DELAY)print('donewiththetomatosauce')

    defadd_topping(self):print('addingthetopping(doublemozzarella,oregano)toyourmargarita')self.pizza.topping.append([iforiin(PizzaTopping.double_mozzarella,PizzaTopping.oregano)])time.sleep(STEP_DELAY)print('donewiththetopping(doublemozzarrella,oregano)')

    defbake(self):self.progress=PizzaProgress.bakingprint('bakingyourmargaritafor{}seconds'.format(self.baking_time))time.sleep(self.baking_time)self.progress=PizzaProgress.readyprint('yourmargaritaisready')

    classCreamyBaconBuilder:def__init__(self):self.pizza=Pizza('creamybacon')self.progress=PizzaProgress.queuedself.baking_time=7#insecondsforthesakeoftheexample

    defprepare_dough(self):self.progress=PizzaProgress.preparationself.pizza.prepare_dough(PizzaDough.thick)

    defadd_sauce(self):print('addingthecrèmefraîchesaucetoyourcreamybacon')self.pizza.sauce=PizzaSauce.creme_fraichetime.sleep(STEP_DELAY)print('donewiththecrèmefraîchesauce')

    defadd_topping(self):print('addingthetopping(mozzarella,bacon,ham,mushrooms,redonion,oregano)toyourcreamybacon')self.pizza.topping.append([tfortin(PizzaTopping.mozzarella,PizzaTopping.bacon,PizzaTopping.ham,PizzaTopping.mushrooms,PizzaTopping.red_onion,PizzaTopping.oregano)])time.sleep(STEP_DELAY)print('donewiththetopping(mozzarella,bacon,ham,mushrooms,redonion,oregano)')

  • defbake(self):self.progress=PizzaProgress.bakingprint('bakingyourcreamybaconfor{}seconds'.format(self.baking_time))time.sleep(self.baking_time)self.progress=PizzaProgress.readyprint('yourcreamybaconisready')

    classWaiter:def__init__(self):self.builder=None

    defconstruct_pizza(self,builder):self.builder=builder[step()forstepin(builder.prepare_dough,builder.add_sauce,builder.add_topping,builder.bake)]

    @propertydefpizza(self):returnself.builder.pizza

    defvalidate_style(builders):try:pizza_style=input('Whatpizzawouldyoulike,[m]argaritaor[c]reamybacon?')builder=builders[pizza_style]()valid_input=TrueexceptKeyErroraserr:print('Sorry,onlymargarita(keym)andcreamybacon(keyc)areavailable')return(False,None)return(True,builder)

    defmain():builders=dict(m=MargaritaBuilder,c=CreamyBaconBuilder)valid_input=Falsewhilenotvalid_input:valid_input,builder=validate_style(builders)print()waiter=Waiter()waiter.construct_pizza(builder)pizza=waiter.pizzaprint()print('Enjoyyour{}!'.format(pizza))

    if__name__=='__main__':main()

    Asampleoutputofthisexampleisasfollows:

    >>>python3builder.pyWhatpizzawouldyoulike,[m]argaritaor[c]reamybacon?rSorry,onlymargarita(keym)andcreamybacon(keyc)areavailableWhatpizzawouldyoulike,[m]argaritaor[c]reamybacon?m

    preparingthethindoughofyourmargarita...donewiththethindoughaddingthetomatosaucetoyourmargarita...

  • donewiththetomatosauceaddingthetopping(doublemozzarella,oregano)toyourmargaritadonewiththetopping(doublemozzarella,oregano)bakingyourmargaritafor5secondsyourmargaritaisready

    Enjoyyourmargarita!

    Supportingonlytwopizzatypesisashame.ImplementaHawaiianpizzabuilder.Considerusinginheritanceafterthinkingabouttheadvantagesanddisadvantages.ChecktheingredientsofatypicalHawaiianpizzaanddecidewhichclassyouneedtoextend:MargaritaBuilderorCreamyBaconBuilder?Perhapsboth[j.mp/pymulti]?

    Inthebook,EffectiveJava(2ndedition),JoshuaBlochdescribesaninterestingvariationoftheBuilderpatternwherecallstobuildermethodsarechained.Thisisaccomplishedbydefiningthebuilderitselfasaninnerclassandreturningitselffromeachofthesetter-likemethodsonit.Thebuild()methodreturnsthefinalobject.ThispatterniscalledtheFluentBuilder.Here'saPythonimplementation,whichwaskindlyprovidedbyareviewerofthebook:

    classPizza:def__init__(self,builder):self.garlic=builder.garlicself.extra_cheese=builder.extra_cheese

    def__str__(self):garlic='yes'ifself.garlicelse'no'cheese='yes'ifself.extra_cheeseelse'no'info=('Garlic:{}'.format(garlic),'Extracheese:{}'.format(cheese))return'\n'.join(info)

    classPizzaBuilder:def__init__(self):self.extra_cheese=Falseself.garlic=False

    defadd_garlic(self):self.garlic=Truereturnself

    defadd_extra_cheese(self):self.extra_cheese=Truereturnself

    defbuild(self):returnPizza(self)

    if__name__=='__main__':pizza=Pizza.PizzaBuilder().add_garlic().add_extra_cheese().build()print(pizza)

    AdaptthepizzaexampletomakeuseoftheFluentBuilderpattern.Whichversionofthetwodoyouprefer?Whataretheprosandconsofeachversion?

    http://j.mp/pymulti

  • SummaryInthischapter,wehaveseenhowtousetheBuilderdesignpattern.WeusetheBuilderpatternforcreatinganobjectinsituationswhereusingtheFactorypattern(eitheraFactoryMethodoranAbstractFactory)isnotagoodoption.ABuilderpatternisusuallyabettercandidatethanaFactorypatternwhen:

    Wewanttocreateacomplexobject(anobjectcomposedofmanypartsandcreatedindifferentstepsthatmightneedtofollowaspecificorder).Differentrepresentationsofanobjectarerequired,andwewanttokeeptheconstructionofanobjectdecoupledfromitsrepresentationWewanttocreateanobjectatonepointintimebutaccessitatalaterpoint

    WesawhowtheBuilderpatternisusedinfast-foodrestaurantsforpreparingmeals,andhowtwothird-partyDjangopackages,django-widgyanddjango-query-builder,useitforgeneratingHTMLpagesanddynamicSQLqueries,respectively.WefocusedonthedifferencesbetweenaBuilderpatternandaFactorypattern,andgaveapreconfigured(Factory)versuscustomer(Builder)computerorderanalogytoclarifythem.

    Intheimplementationpart,wehaveseenhowtocreateapizzaorderingapplication,whichhaspreparationdependencies.Therearemanyrecommendedinterestingexercisesinthischapter,includingimplementingaFluentBuilder.

    Inthenextchapter,youwilllearnaboutthelastcreationaldesignpatterncoveredinthisbook:thePrototypepattern,whichisusedforcloninganobject.

  • Chapter3.ThePrototypePatternSometimes,weneedtocreateanexactcopyofanobject.Forinstance,assumethatyouwanttocreateanapplicationforstoring,sharing,andediting(suchasmodifying,addingnotes,andremoving)culinaryrecipes.UserBobfindsacakerecipeandaftermakingafewmodificationshethinksthathiscakeisdelicious,andhewantstoshareitwithhisfriend,Alice.Butwhatdoessharingarecipemean?IfBobwantstodosomefurtherexperimentationwithhisrecipeaftersharingitwithAlice,willthenewchangesalsobevisibleinAlice'srecipe?CanBobkeeptwocopiesofthecakerecipe?Hisdeliciouscakerecipeshouldremainunaffectedbyanychangesmadeintheexperimentalcakerecipe.

    Suchproblemscanbesolvedbyallowingtheuserstohavemorethanoneindependentcopyofthesamerecipe.Eachcopyiscalledaclone,becauseitisanexactcopyoftheoriginalobjectataspecificpointintime.Thetimeaspectisimportant,sinceitaffectswhattheclonecontains.Forexample,ifBobsharesthecakerecipewithAlicebeforemakinghisownimprovementstoachieveperfection,AlicewillneverbeabletobakeherownversionofthedeliciouscakethatBobcreated!ShewillonlybeabletobaketheoriginalcakerecipefoundbyBob.

    Notethedifferencebetweenacopyandareference.Ifwehavetworeferencestothesamecakerecipe,whateverchangesBobmakestotherecipewillbevisibletoAlice'sversionoftherecipe,andviceversa.WhatwewantisbothBobandAlicetohavetheirowncopy,sothattheycanmakeindependentchangeswithoutaffectingeachother'srecipe.Bobactuallyneedstwocopiesofthecakerecipe:thedeliciousversionandtheexperimentalversion.

    Thedifferencebetweenareferenceandacopyisshowninthefollowingfigure:

    Ontheleftpart,wecanseetworeferences.BothAliceandBobrefertothesamerecipe,whichessentiallymeansthattheyshareitandallmodificationsarevisiblebyboth.Ontherightpart,wecanseetwodifferentcopiesofthesamerecipe.Inthiscase,independentmodificationsareallowedandthechangesofAlicedonotaffectthechangesofBob,andviceversa.

    ThePrototypedesignpatternhelpsuswithcreatingobjectclones.Initssimplestversion,thePrototypepatternisjustaclone()functionthatacceptsanobjectasaninputparameterand

  • returnsacloneofit.InPython,thiscanbedoneusingthecopy.deepcopy()function.Let'sseeanexample.Inthefollowingcode(fileclone.py),therearetwoclasses,AandB.AistheparentclassandBisthederivedclass.Inthemainpart,wecreateaninstanceofclassBb,andusedeepcopy()tocreateacloneofbnamedc.Theresultisthatallthemembersofthehierarchy(atthepointoftimethecloninghappens)arecopiedintheclonec.Asaninterestingexercise,youcantryusingdeepcopy()withcompositioninsteadofinheritancewhichisshowninthefollowingcode:

    importcopy

    classA:def__init__(self):self.x=18self.msg='Hello'

    classB(A):def__init__(self):A.__init__(self)self.y=34

    def__str__(self):return'{},{},{}'.format(self.x,self.msg,self.y)

    if__name__=='__main__':b=B()c=copy.deepcopy(b)print([str(i)foriin(b,c)])print([iforiin(b,c)])

    Whenexecutingclone.pyonmycomputer,Igetthefollowing:

    >>>python3clone.py['18,Hello,34','18,Hello,34'][,]

    Althoughyouroutputofthesecondlinewillmostlikelynotbethesameasmine,what'simportantistonoticethatthetwoobjectsresideintwodifferentmemoryaddresses(the0x...part).Thismeansthatthetwoobjectsaretwoindependentcopies.

    IntheImplementationsection,laterinthischapter,wewillseehowtousecopy.deepcopy()withsomeextraboilerplatecodewrappedinaclass,forkeepingaregistryoftheobjectsthatarecloned.

    Areal-lifeexampleThePrototypedesignpatternisallaboutcloninganobject.Mitosis,theprocessinacelldivisionbywhichthenucleusdividesresultingintwonewnuclei,eachofwhichhasexactlythesamechromosomeandDNAcontentastheoriginalcell,isanexampleofbiologicalcloning[j.mp/mmitosis].

    http://j.mp/mmitosis

  • Thefollowingfigure,providedbywww.sourcemaking.com,showsanexampleofthemitoticdivisionofacell[j.mp/pprotpat]:

    Anotherpopularexampleof(artificial)cloningisDolly,thesheep[j.mp/wikidolly].

    http://www.sourcemaking.comhttp://j.mp/pprotpathttp://j.mp/wikidolly

  • AsoftwareexampleTherearemanyPythonapplicationsthatmakeuseofthePrototypepattern[j.mp/pythonprot],butitisalmostneverreferredtoasPrototypesincecloningobjectsisabuilt-infeatureofthelanguage.

    OneapplicationthatusesPrototypeistheVisualizationToolkit(VTK)[j.mp/pyvto].VTKisanopensourcecross-platformsystemfor3Dcomputergraphics,imageprocessing,andvisualization.VTKusesPrototypeforcreatingclonesofgeometricalelementssuchaspoints,lines,hexahedrons,andsoforth[j.mp/vtkcell].

    AnotherprojectthatusesPrototypeismusic21.Accordingtotheproject'spage,"music21isasetoftoolsforhelpingscholarsandotheractivelistenersanswerquestionsaboutmusicquicklyandsimply"[j.mp/pmusic21].Themusic21toolkitusesPrototypeforcopyingmusicalnotesandscores[j.mp/py21code].

    http://j.mp/pythonprothttp://j.mp/pyvtohttp://j.mp/vtkcellhttp://j.mp/pmusic21http://j.mp/py21code

  • UsecasesThePrototypepatternisusefulwhenwehaveanexistingobjectandwewanttocreateanexactcopyofit.Acopyofanobjectisusuallyrequiredwhenweknowthatpartsoftheobjectwillbemodifiedbutwewanttokeeptheoriginalobjectuntouched.Insuchcases,itdoesn'tmakesensetorecreatetheoriginalobjectfromscratch[j.mp/protpat].

    AnothercasewherePrototypecomesinhandyiswhenwewanttoduplicateacomplexobject.Byduplicatingacomplexobject,wecanthinkofanobjectthatispopulatedfromadatabaseandhasreferencestootherobjectsthatarealsopopulatedfromadatabase.Itisalotofefforttocreateanobjectclonebyqueryingthedatabase(s)multipletimesagain.UsingPrototypeforsuchcasesismoreconvenient.

    Sofar,wehavecoveredonlythereferenceversuscopyissue,butacopycanbefurtherdividedintoadeepcopyversusashallowcopy.Adeepcopyiswhatwehaveseensofar:alldataoftheoriginalobjectaresimplycopiedintheclone,withoutmakinganyexceptions.Ashallowcopyreliesonreferences.Wecanintroducedatasharing,andtechniqueslikecopy-on-writetoimprovetheperformance(suchasclonecreationtime)andthememoryusage.Usingshallowcopiesmightbeworthwhileiftheavailableresourcesarelimited(suchasembeddedsystems)orperformanceiscritical(suchashigh-performancecomputing).

    InPython,wecandoshallowcopiesusingthecopy.copy()function.QuotingtheofficialPythondocumentation,thedifferencesbetweenashallowcopy(copy.copy())andadeepcopy(copy.deepcopy())inPythonare[j.mp/py3copy]asfollows:

    "Ashallowcopyconstructsanewcompoundobjectandthen(totheextentpossible)insertsreferencesintoittotheobjectsfoundintheoriginal.Adeepcopyconstructsanewcompoundobjectandthen,recursively,insertscopiesintoitoftheobjectsfoundintheoriginal."

    Canyouthinkofanyexampleswhereusingshallowcopiesisbetterthanusingdeepcopies?

    http://j.mp/protpathttp://j.mp/py3copy

  • ImplementationInprogramming,itisnotuncommonforabooktobeavailableinmultipleeditions.Forexample,theclassictextbookonCprogrammingTheCProgrammingLanguagebyKernighanandRitchieisavailableintwoeditions.Thefirsteditionwaspublishedin1978.Atthattime,Cwasnotstandardized.Thesecondeditionofthebookwaspublished10yearslaterandcoversthestandard(ANSI)versionofC.Whatarethedifferencesbetweenthetwoeditions?Tomentionafew,theprice,thelength(numberofpages),andthepublicationdate.Buttherearealsomanysimilarities:theauthors,thepublishers,andthetags/keywordsthatdescribethebookareexactlythesame.Thisindicatesthatcreatinganewbookfromscratchisnotalwaysthebestapproach.Ifweknowthattherearemanysimilaritiesbetweentwobookeditions,wecanusecloningandmodifyonlythedifferentpartsofthenewedition.

    Let'sseehowwecanusethePrototypepatternforcreatinganapplicationthatshowsbookinformation.Webeginwiththerepresentationofabook.Apartfromtheusualinitialization,theBookclassdemonstratesaninterestingtechnique.Itshowshowwecanavoidthetelescopicconstructorproblem.Inthe__init__()method,onlythreeparametersarefixed:name,authors,andprice.Butclientscanpassmoreparametersintheformofkeywords(name=value)usingtherestvariable-lengthlist.Thelineself.__dict__.update(rest)addsthecontentsofresttotheinternaldictionaryoftheBookclasstomakethempartofit.

    Butthere'sacatch.Sincewedon'tknowallthenamesoftheaddedparameters,weneedtoaccesstheinternaldictformakinguseofthemin__str__().Andsincethecontentsofadictionarydonotfollowanyspecificorder,weuseanOrderedDicttoforceanorder;otherwise,everytimetheprogramisexecuted,differentoutputswillbeshown.Ofcourse,youshouldnottakemywordsforgranted.Asanexercise,removetheusageofOrderedDictandsorted()andruntheexampletoseeifI'mright:

    classBook:def__init__(self,name,authors,price,**rest):'''Examplesofrest:publisher,length,tags,publicationdate'''self.name=nameself.authors=authorsself.price=price#inUSdollarsself.__dict__.update(rest)

    def__str__(self):mylist=[]ordered=OrderedDict(sorted(self.__dict__.items()))foriinordered.keys():mylist.append('{}:{}'.format(i,ordered[i]))ifi=='price':mylist.append('$')mylist.append('\n')return''.join(mylist)

    ThePrototypeclassimplementsthePrototypedesignpattern.TheheartofthePrototypeclassistheclone()method,whichdoestheactualcloningusingthefamiliarcopy.deepcopy()

  • function.ButthePrototypeclassdoesabitmorethansupportingcloning.Itcontainstheregister()andunregister()methods,whichcanbeusedtokeeptrackoftheobjectsthatareclonedinadictionary.Notethatthisisjustaconvenience,andnotanecessity.

    Moreover,theclone()methodusesthesametrickthat__str__()usesintheBookclass,butthistimeforadifferentreason.Usingthevariable-lengthlistattr,wecanpassonlythevariablesthatreallyneedtobemodifiedwhencloninganobjectasfollows:

    classPrototype:def__init__(self):self.objects=dict()

    defregister(self,identifier,obj):self.objects[identifier]=obj

    defunregister(self,identifier):delself.objects[identifier]

    defclone(self,identifier,**attr):found=self.objects.get(identifier)ifnotfound:raiseValueError('Incorrectobjectidentifier:{}'.format(identifier))obj=copy.deepcopy(found)obj.__dict__.update(attr)returnobj

    Themain()functionshowsTheCProgrammingLanguagebookcloningexamplementionedatthebeginningofthissectioninpractice.Whencloningthefirsteditionofthebooktocreatethesecondedition,weonlyneedtopassthemodifiedvaluesoftheexistingparameters.Butwecanalsopassextraparameters.Inthiscase,editionisanewparameterthatwasnotneededinthefirstbookbutisusefulinformationfortheclone:

    defmain():b1=Book('TheCProgrammingLanguage',('BrianW.Kernighan','DennisM.Ritchie'),price=118,publisher='PrenticeHall',length=228,publication_date='1978-02-22',tags=('C','programming','algorithms','datastructures'))

    prototype=Prototype()cid='k&r-first'prototype.register(cid,b1)b2=prototype.clone(cid,name='TheCProgrammingLanguage(ANSI)',price=48.99,length=274,publication_date='1988-04-01',edition=2)

    foriin(b1,b2):print(i)print("IDb1:{}!=IDb2:{}".format(id(b1),id(b2)))

    Noticetheusageoftheid()functionwhichreturnsthememoryaddressofanobject.Whenwecloneanobjectusingadeepcopy,thememoryaddressesoftheclonemustbedifferentfromthememoryaddressesoftheoriginalobject.

  • Theprototype.pyfileisasfollows:

    importcopyfromcollectionsimportOrderedDict

    classBook:def__init__(self,name,authors,price,**rest):'''Examplesofrest:publisher,length,tags,publicationdate'''self.name=nameself.authors=authorsself.price=price#inUSdollarsself.__dict__.update(rest)

    def__str__(self):mylist=[]ordered=OrderedDict(sorted(self.__dict__.items()))foriinordered.keys():mylist.append('{}:{}'.format(i,ordered[i]))ifi=='price':mylist.append('$')mylist.append('\n')return''.join(mylist)

    classPrototype:def__init__(self):self.objects=dict()

    defregister(self,identifier,obj):self.objects[identifier]=obj

    defunregister(self,identifier):delself.objects[identifier]

    defclone(self,identifier,**attr):found=self.objects.get(identifier)ifnotfound:raiseValueError('Incorrectobjectidentifier:{}'.format(identifier))obj=copy.deepcopy(found)obj.__dict__.update(attr)returnobj

    defmain():b1=Book('TheCProgrammingLanguage',('BrianW.Kernighan','DennisM.Ritchie'),price=118,publisher='PrenticeHall',length=228,publication_date='1978-02-22',tags=('C','programming','algorithms','datastructures'))

    prototype=Prototype()cid='k&r-first'prototype.register(cid,b1)b2=prototype.clone(cid,name='TheCProgrammingLanguage(ANSI)',price=48.99,length=274,publication_date='1988-04-01',edition=2)

    foriin(b1,b2):print(i)print("IDb1:{}!=IDb2:{}".format(id(b1),id(b2)))

  • if__name__=='__main__':main()

    Theoutputofid()dependsonthecurrentmemoryallocationofthecomputerandyoushouldexpectittodifferoneveryexecutionofthisprogram.Butnomatterwhattheactualaddressesare,theyshouldnotbethesameinanychance.

    AsampleoutputwhenIexecutethisprogramonmymachineisasfollows:

    >>>python3prototype.pyauthors:('BrianW.Kernighan','DennisM.Ritchie')length:228name:TheCProgrammingLanguageprice:118$publication_date:1978-02-22publisher:PrenticeHalltags:('C','programming','algorithms','datastructures')

    authors:('BrianW.Kernighan','DennisM.Ritchie')edition:2length:274name:TheCProgrammingLanguage(ANSI)price:48.99$publication_date:1988-04-01publisher:PrenticeHalltags:('C','programming','algorithms','datastructures')

    IDb1:140004970829304!=IDb2:140004970829472

    Indeed,Prototypeworksasexpected.ThesecondeditionofTheCProgrammingLanguagebookreusesalltheinformationthatwassetinthefirstedition,andallthedifferencesthatwedefinedareonlyappliedtothesecondedition.Thefirsteditionremainsunaffected.Ourconfidencecanbeincreasedbylookingattheoutputoftheid()function:thetwoaddressesaredifferent.

    Asanexercise,youcancomeupwithyourownexampleofPrototype.Afewideasareasfollows:

    TherecipeexamplethatwasmentionedinthischapterThedatabase-populatedobjectthatwasmentionedinthischapterCopyinganimagesothatyoucanaddyourownmodificationswithouttouchingtheoriginal

  • SummaryInthischapter,wehaveseenhowtousethePrototypedesignpattern.Prototypeisusedforcreatingexactcopiesofobjects.Creatingacopyofanobjectcanactuallymeantwothings:

    Relyingonreferences,whichhappenswhenashallowcopyiscreatedDuplicatingeverything,whichhappenswhenadeepcopyiscreated

    Inthefirstcase,wewanttofocusonimprovingtheperformanceandthememoryusageofourapplicationbyintroducingdatasharingbetweenobjects.Butweneedtobecarefulaboutmodifyingdata,becauseallmodificationsarevisibletoallcopies.Shallowcopieswerenotintroducedinthischapter,butyoumightwanttoexperimentwiththem.

    Inthesecondcase,wewanttobeabletomakemodificationstoonecopywithoutaffectingtherest.That'susefulforcaseslikethecake-recipeexamplethatwehaveseen.Here,nodatasharingisdoneandsoweneedtobecarefulabouttheresourceconsumptionandtheoverheadthatisintroducedbyourclones.

    WeshowedasimpleexampleofadeepcopyingwhichinPythonisdoneusingthecopy.deepcopy()function.Wealsomentionedexamplesofcloningfoundinreallife,focusingonmitosis.

    ManysoftwareprojectsusePrototype,butinPythonitisnotmentionedassuchbecauseitisabuilt-infeature.AmongthemaretheVTK,whichusesPrototypeforcreatingclonesofgeometricalelements,andmusic21,whichusesitforduplicatingmusicalscoresandnotes.

    Finally,wediscussedtheusecasesofPrototypeandimplementedaprogramthatsupportscloningbookssothatallinformationthatdoesnotchangeinaneweditioncanbereused,butatthesametimemodifiedinformationcanbeupdatedandnewinformationcanbeadded.

    Prototypeisthelastcreationaldesignpatterncoveredinthisbook.ThenextchapterbeginswithAdapter,astructuraldesignpatternthatcanbeusedtomaketwoincompatiblesoftwareinterfacescompatible.

  • Chapter4.TheAdapterPatternStructuraldesignpatternsdealwiththerelationshipsbetweentheentities(suchasclassesandobjects)ofasystem.Astructuraldesignpatternfocusesonprovidingasimplewayofcomposingobjectsforcreatingnewfunctionality[GOF95,page155],[j.mp/structpat].

    Adapterisastructuraldesignpatternthathelpsusmaketwoincompatibleinterfacescompatible.First,let'sanswerwhatincompatibleinterfacesreallymean.Ifwehaveanoldcomponentandwewanttouseitinanewsystem,oranewcomponentthatwewanttouseinanoldsystem,thetwocanrarelycommunicatewithoutrequiringanycodechanges.Butchangingthecodeisnotalwayspossible,eitherbecausewedon'thaveaccesstoit(forexample,thecomponentisprovidedasanexternallibrary)orbecauseitisimpractical.Insuchcases,wecanwriteanextralayerthatmakesalltherequiredmodificationsforenablingthecommunicationbetweenthetwointerfaces.ThislayeriscalledtheAdapter.

    E-commercesystemsareknownexamples.Assumethatweuseane-commercesystemthatcontainsacalculate_total(order)function.Thefunctioncalculatesthetotalamountofanorder,butonlyinDanishKroner(DKK).Itisreasonableforourcustomerstoaskustoaddsupportformorepopularcurrencies,suchasUnitedStatesDollars(USD)andEuros(EUR).IfweownthesourcecodeofthesystemwecanextenditbyaddingnewfunctionsfordoingtheconversionsfromDKKtoUSDandfromDKKtoEUR.Butwhatifwedon'thaveaccesstothesourcecodeoftheapplicationbecauseitisprovidedtousonlyasanexternallibrary?Inthiscase,wecanstillusethelibrary(forexample,callitsmethods),butwecannotmodify/extendit.Thesolutionistowriteawrapper(alsoknownasAdapter)thatconvertsthedatafromthegivenDKKformattotheexpectedUSDorEURformat.

    TheAdapterpatternisnotusefulonlyfordataconversions.Ingeneral,ifyouwanttouseaninterfacethatexpectsfunction_a()butyouonlyhavefunction_b(),youcanuseanAdaptertoconvert(adapt)function_b()tofunction_a()[Eckel08,page207],[j.mp/adapterpat].Thisisnotonlytrueforfunctionsbutalsoforfunctionparameters.Anexampleisafunctionthatexpectstheparametersx,y,andzbutyouonlyhaveafunctionthatworkswiththeparametersxandyathand.WewillseehowtousetheAdapterpatternintheimplementationsection.

    Areal-lifeexampleProbablyallofususetheAdapterpatterneveryday,butinhardwareinsteadofsoftware.Ifyouhaveasmartphoneoratablet,youneedtousesomething(forexample,thelightningconnectorofaniPhone)withaUSBadapterforconnectingittoyourcomputer.IfyouaretravelingfrommostEuropeancountriestotheUK,youneedtouseaplugadapterforchargingyourlaptop.ThesameistrueifyouaretravelingfromEuropetoUSA,ortheotherwayaround.Adaptersareeverywhere!

    Thefollowingimage,courtesyofsourcemaking.com,showsseveralexamplesofhardwareadapters[j.mp/adapterpat]:

    http://j.mp/structpathttp://j.mp/adapterpathttp://sourcemaking.comhttp://j.mp/adapterpat

  • AsoftwareexampleGrokisaPythonframeworkthatrunsontopofZope3andfocusesonagiledevelopment.TheGrokframeworkusesAdaptersformakingitpossibleforexistingobjectstoconformtospecificAPIswithouttheneedtomodifythem[j.mp/grokada].

    ThePythonTraitspackagealsousestheAdapterpatternfortransforminganobjectthatdoesnotimplementofaspecificinterface(orsetofinterfaces)toanobjectthatdoes[j.mp/pytraitsad].

    http://j.mp/grokadahttp://j.mp/pytraitsad

  • UsecasesTheAdapterpatternisusedformakingthingsworkaftertheyhavebeenimplemented[j.mp/adapterpat].Usuallyoneofthetwoincompatibleinterfacesiseitherforeignorold/legacy.Iftheinterfaceisforeign,itmeansthatwehavenoaccesstothesourcecode.Ifitisolditisusuallyimpracticaltorefactorit.Wecantakeitevenfurtherandarguethatalteringtheimplementationofalegacycomponenttomeetourneedsisnotonlyimpractical,butitalsoviolatestheopen/closeprinciple[j.mp/adaptsimp].Theopen/closeprincipleisoneofthefundamentalprinciplesofObject-Orienteddesign(theOofSOLID).Itstatesthatasoftwareentityshouldbeopenforextension,butclosedformodification.Thatbasicallymeansthatweshouldbeabletoextendthebehaviorofanentitywithoutmakingsourcecodemodifications.Adapterrespectstheopen/closedprinciple[j.mp/openclosedp].

    Therefore,usinganAdapterformakingthingsworkaftertheyhavebeenimplementedisabetterapproachbecauseit:

    DoesnotrequireaccesstothesourcecodeoftheforeigninterfaceDoesnotviolatetheopen/closedprinciple

    http://j.mp/adapterpathttp://j.mp/adaptsimphttp://j.mp/openclosedp

  • ImplementationTherearemanywaysofimplementingtheAdapterdesignpatterninPython[Eckel08,page207].AllthetechniquesdemonstratedbyBruceEckeluseinheritance,butPythonprovidesanalternative,andinmyopinion,amoreidiomaticwayofimplementinganAdapter.Thealternativetechniqueshouldbefamiliartoyou,sinceitusestheinternaldictionaryofaclass,andwehaveseenhowtodothatinChapter3,ThePrototypePattern.

    Let'sbeginwiththewhatwehavepart.OurapplicationhasaComputerclassthatshowsbasicinformationaboutacomputer.Alltheclassesofthisexample,includingtheComputerclassareveryprimitive,becausewewanttofocusontheAdapterpatternandnotonhowtomakeaclassascompleteaspossible.

    classComputer:def__init__(self,name):self.name=name

    def__str__(self):return'the{}computer'.format(self.name)

    defexecute(self):return'executesaprogram'

    Inthiscase,theexecute()methodisthemainactionthatthecomputercanperform.Thismethodiscalledbytheclientcode.

    Nowwemovetothewhatwewantpart.Wedecidetoenrichourapplicationwithmorefunctionality,andluckily,wefindtwointerestingclassesimplementedintwodifferentlibrariesthatareunrelatedwithourapplication:SynthesizerandHuman.IntheSynthesizerclass,themainactionisperformedbytheplay()method.IntheHumanclass,itisperformedbythespeak()method.Toindicatethatthetwoclassesareexternal,weplacetheminaseparatemodule,asshown:

    classSynthesizer:def__init__(self,name):self.name=name

    def__str__(self):return'the{}synthesizer'.format(self.name)

    defplay(self):return'isplayinganelectronicsong'

    classHuman:def__init__(self,name):self.name=name

    def__str__(self):return'{}thehuman'.format(self.name)

    defspeak(self):

  • return'sayshello'

    Sofarsogood.But,wehaveaproblem.Theclientonlyknowshowtocalltheexecute()method,andithasnoideaaboutplay()orspeak().HowcanwemakethecodeworkwithoutchangingtheSynthesizerandHumanclasses?Adapterstotherescue!WecreateagenericAdapterclassthatallowsustoadaptanumberofobjectswithdifferentinterfaces,intooneunifiedinterface.Theobjargumentofthe__init__()methodistheobjectthatwewanttoadapt,andadapted_methodsisadictionarycontainingkey/valuepairsofmethodtheclientcalls/methodthatshouldbecalled.

    classAdapter:def__init__(self,obj,adapted_methods):self.obj=objself.__dict__.update(adapted_methods)

    def__str__(self):returnstr(self.obj)

    Let'sseehowwecanusetheAdapterpattern.Anobjectslistholdsalltheobjects.ThecompatibleobjectsthatbelongtotheComputerclassneednoadaptation.Wecanaddthemdirectlytothelist.Theincompatibleobjectsarenotaddeddirectly.TheyareadaptedusingtheAdapterclass.Theresultisthattheclientcodecancontinueusingtheknownexecute()methodonallobjectswithouttheneedtobeawareofanyinterfacedifferencesbetweentheusedclasses.

    defmain():objects=[Computer('Asus')]synth=Synthesizer('moog')objects.append(Adapter(synth,dict(execute=synth.play)))human=Human('Bob')objects.append(Adapter(human,dict(execute=human.speak)))

    foriinobjects:print('{}{}'.format(str(i),i.execute()))

    Let'sseethecompletecodeoftheAdapterpatternexample(filesexternal.pyandadapter.py)asfollows:

    classSynthesizer:def__init__(self,name):self.name=name

    def__str__(self):return'the{}synthesizer'.format(self.name)

    defplay(self):return'isplayinganelectronicsong'

    classHuman:def__init__(self,name):self.name=name

  • def__str__(self):return'{}thehuman'.format(self.name)

    defspeak(self):return'sayshello'

    fromexternalimportSynthesizer,Human

    classComputer:def__init__(self,name):self.name=name

    def__str__(self):return'the{}computer'.format(self.name)

    defexecute(self):return'executesaprogram'

    classAdapter:def__init__(self,obj,adapted_methods):self.obj=objself.__dict__.update(adapted_methods)

    def__str__(self):returnstr(self.obj)

    defmain():objects=[Computer('Asus')]synth=Synthesizer('moog')objects.append(Adapter(synth,dict(execute=synth.play)))human=Human('Bob')objects.append(Adapter(human,dict(execute=human.speak)))

    foriinobjects:print('{}{}'.format(str(i),i.execute()))

    if__name__=="__main__":main()

    Theoutputwhenexecutingtheexampleis:

    >>>python3adapter.pytheAsuscomputerexecutesaprogramthemoogsynthesizerisplayinganelectronicsongBobthehumansayshello

    WemanagedtomaketheHumanandSynthesizerclassescompatiblewiththeinterfaceexpectedbytheclient,withoutchangingtheirsourcecode.Thisisnice.

    Here'sachallengingexerciseforyou.Thereisaproblemwiththisimplementation.Whileallclasseshaveanameattribute,thefollowingcodefails:

    foriinobjects:print(i.name)

  • Firstofall,whydoesthiscodefail?Althoughthismakessensefromacodingpointofview,itdoesnotmakesenseatallfortheclientcodewhichshouldnotbeawareofdetailssuchaswhatisadaptedandwhatisnotadapted.Wejustwanttoprovideauniforminterface.Howcanwemakethiscodework?

    TipHint:Thinkofhowyoucandelegatethenon-adaptedpartstotheobjectcontainedintheAdapterclass.

  • SummaryThischaptercoveredtheAdapterdesignpattern.WeusetheAdapterpatternformakingtwo(ormore)incompatibleinterfacescompatible.Asamotivation,ane-commercesystemthatshouldsupportmultiplecurrencieswasmentioned.Weuseadapterseverydayforinterconnectingdevices,chargingthem,andsoon.

    Adaptermakesthingsworkaftertheyhavebeenimplemented.TheGrokPythonframeworkandtheTraitspackageusetheAdapterpatternforachievingAPIconformanceandinterfacecompatibility,respectively.Theopen/closeprincipleisstronglyconnectedwiththeseaspects.

    Intheimplementationsection,wesawhowtoachieveinterfaceconformanceusingtheAdapterpatternwithoutmodifyingthesourcecodeoftheincompatiblemodel.ThisisachievedthroughagenericAdapterclassthatdoestheworkforus.Althoughwecouldusesub-classing(inheritance)toimplementtheAdapterpatterninthetraditionalwayinPython,thistechniqueisagreatalternative.

    Inthenextchapter,wewillseehowwecanusetheDecoratorpatterntoextendthebehaviorofanobjectwithoutusingsub-classing.

  • Chapter5.TheDecoratorPatternWheneverwewanttoaddextrafunctionalitytoanobject,wehaveanumberofdifferentoptions.Wecan:

    Addthefunctionalitydirectlytotheclasstheobjectbelongsto,ifitmakessense(forexample,addanewmethod)UsecompositionUseinheritance

    Compositionshouldgenerallybepreferredoverinheritance,becauseinheritancemakescodereuseharder,it'sstatic,andappliestoanentireclassandallinstancesofit[GOF95,page31],[j.mp/decopat].

    Designpatternsofferusafourthoptionthatsupportsextendingthefunctionalityofanobjectdynamically(inruntime):Decorators.ADecoratorpatterncanaddresponsibilitiestoanobjectdynamically,andinatransparentmanner(withoutaffectingotherobjects)[GOF95,page196].

    Inmanyprogramminglanguages,theDecoratorpatternisimplementedusingsub-classing(inheritance)[GOF95,page198].InPython,wecan(andshould)usethebuilt-indecoratorfeature.APythondecoratorisaspecificchangetothesyntaxofPythonthatisusedforextendingthebehaviorofaclass,method,orfunctionwithoutusinginheritance.Intermsofimplementation,aPythondecoratorisacallable(function,method,class)thatacceptsafunctionobjectfinasinput,andreturnsanotherfunctionobjectfout[j.mp/conqdec