Design Patterns in Swift: A Different Approach to Coding ...englishonlineclub.com/pdf/Design...
Transcript of Design Patterns in Swift: A Different Approach to Coding ...englishonlineclub.com/pdf/Design...
Contents
DesignPatternsinSwiftStoryShopCopyrightHalfTitlePrefaceIntroductionPartOne-SOLID1)SOLID-SingleResponsibilityPrinciple(SRP)2)SOLID-OpenClosedPrinciple(OCP)3)SOLID-LiskovSubstitutionPrinciple(LSP)4)SOLID-InterfaceSegregationPrinciple(ISP)5)SOLID-DependencyInversionPrinciple(DIP)PartTwo-Creational6)Creational-FactoryDesignPattern8)Creational-PrototypeDesignPattern9)Creational-SingletonDesignPatternPartThree-Structural10)Structural-AdapterDesignPattern11)Structural-BridgeDesignPattern12)Structural-CompositeDesignPattern13)Structural-DecoratorDesignPattern14)Structural-FacadeDesignPattern15)Structural-FlyWeightDesignPattern16)Structural-ProxyDesignPatternPartFour-Behavioural17)Behavioural-ChainofResponsibilityDesignPattern18)Behavioural-StrategyDesignPattern20)Behavioural-IteratorDesignPattern21)Behavioural-InterpreterDesignPattern22)Behavioural-MediatorDesignPattern23)Behavioural-MementoDesignPattern24)Behavioural-NullObjectDesignPattern25)Behavioural-ObserverDesignPattern26)Behavioural-StateDesignPattern27)Behavioural-TemplateDesignPattern
28)Behavioural-VisitorDesignPatternFinalnote:
DesignPatternsinSwift
VamshiKrishna
(Non)FictionVortex™
JointheStorywithourgroundbreakingmobileapp,
StoryShop.Weareredefiningdigitalnarrative.
DesignPatternsinSwiftVamshiKrishna
ElectronicEditionCopyright©2018byFictionVortex.Allrightsreserved.PrintEditionCopyright©2018byFictionVortex.Allrightsreserved.
Nopartofthispublicationmaybereproduced,storedinaretrievalsystem,ortransmittedinanywaybyanymeans,electronic,mechanical,photocopy,recordingorotherwisewithoutthepriorpermissionoftheauthorexceptasprovidedbyUSAcopyrightlaw.
ISBN:978-1-947655-16-4PublishedbyFictionVortex,Inc.(FVPress)Nampa,ID83651http://www.fictionvortex.com
CoverbyTomPatchin
PublishedintheUnitedStatesofAmerica
DesignPatternsinSwift
VamshiKrishna
Preface
DesignPatterns-Icameacrossthistermforthefirsttimeinmylifeinajobinterview(mindyou,IwasalmosttwoyearsintoiOSDevelopmentthen).Igotbackhomeandgoogledaboutthem.Itwasembarrassingandinterestingatthesametime,asIfoundoutthatIhadbeenusingafewofthosepatternswithoutactuallyrealisingthatIwasdoingso.
Iforgotaboutthatincidentafterseveraldaysandgotbacktomywork.Fastforwardtwoyears,IwasbackatjobsearchingandthistimeIdecidedtolearnindepthaboutdesignpatterns.AsaseasonediOSDeveloper,IwasinitiallylookingtolearntheconceptsthroughexamplescodedinSwiftlanguage.
Surprisingly,IcouldnotfindasinglebookorblogdiscussingdesignpatternsindetailintheSwiftlanguage.TherearetonsofbooksandblogsdiscussingtheminJava,C#,PHP,etc.
ForsomeonewhocodesinSwift,itonlytakesalittleefforttounderstandJava.So,IstartedlearningthedesignpatternsfromdifferentsourcesavailableontheInternetthroughJavaexamples.Forpractice,IstartedputtingupafewexamplesinSwiftforeachofthedesignpatterns.
Personally,cricketissomethingthatIunderstandinandout.Icanalmostrelateanythingunderthesuntoasituationincricket(okay,that’sabitofanexaggeration).
So,Idecided,insteadofusingdifferentcontextsforeachofthedesignpatternexamples,IwouldbeusingcricketingtermsforalltheexamplesIwouldbecoding.Ibelievecricketisaverysimplegame,andevenforthosewhodonotfollowthegame,itshouldnotbeabigefforttorelatetothecricketingterms.
That’swhenIdecidedinsteadofjustlettingthecoderesideonmyMac,Iwouldputalittlemoreefforttotakeittobookform.That’showthisbookwasborn,andIamsureyourunderstandingondesignpatternswillbeenhancedbythetimeyoufinishreadingthisbook.
Iwouldsuggestyoucodetheexamples(notcopy-paste,buttypeeachandeverylineofthecode)inyourXcodeplaygroundandseetheresultsforyourself.Thenimagineascenariowhereyouwouldapplysuchadesignpattern,andcodeanexampleforyourself.
Ibelievethat’showcodingislearned.
Happylearning.
Introduction
Wikipediasays,“Insoftwareengineering,asoftwaredesignpatternisageneral,reusablesolutiontoacommonlyoccurringproblemwithinagivencontextinsoftwaredesign”.
Inageneralsense,designpatternscanbestatedasbestpracticesthatwereimplementedonarepetitivebasistosolvesimilarproblems,butthatarefoundindifferentcontexts.
Designpatternsarenotfinisheddesignsthatcanbedirectlytransformedintocode.Butthetemplatescanhelpasabridgebetweenthelevelsofaprogrammingparadigmandconcretealgorithm.Thesetemplatescanalsobeusedtosolveaspecifictypeofproblemthatcanoccurindifferentprogrammingsituations.
ThepopularityofdesignpatternscameaboutafterbeingformalisedinthebookDesignPatterns:ElementsofReusableObject-OrientedSoftwarebytheso-calledGangofFour.
DesignpatternsareverypopularinJavaandC#,buttheycanbeappliedtoallobjectorientedlanguages.Theyareuniversallyrelevantbecausewearelivinginaworldwhereobjectorientedparadigmsareusedonadailybasis.Objectorienteddesignpatternsmainlyshowstheinteractionsandrelationshipsbetweenclassesorobjects.
Interestingly,mostdevelopershavebeenusingdesignpatterns(atleastafewofthem)formanyyearswithoutactuallyrealisingthattheyaredoingso.
Designpatternsonabroadlevelcanbedividedintofourtypes,namely:
1. SOLIDDesignPrinciples2. Creational3. Structural4. Behavioural
SOLIDdesignprinciples,introducedbyRobertC.Martin,arerelevantacrossalloftheremainingthreedesignpatterns.SOLIDisanacronymforthesetoffivedesignprinciplesintendedtomakesoftwaredesignsmoreunderstandable,flexible,andmaintainable.It’sbettertobeawareofthembeforemovingontotheotherpatterns.
Prerequisites:
1. BasicunderstandingofSwift2. Goodunderstandingofobjectorientedprogramming
Contents:
1. SOLID-SingleResponsibilityPrinciple2. SOLID-OpenClosedPrinciple3. SOLID-LiskovSubstitutionPrinciple4. SOLID-InterfaceSegregationPrinciple5. SOLID-DependencyInversionPrinciple6. Creational-FactoryDesignPattern7. Creational-BuilderDesignPattern8. Creational-PrototypeDesignPattern9. Creational-SingletonDesignPattern10. Structural-AdapterDesignPattern11. Structural-BridgeDesignPattern12. Structural-CompositeDesignPattern13. Structural-DecoratorDesignPattern14. Structural-FacadeDesignPattern15. Structural-FlyWeightDesignPattern
16. Structural-ProxyDesignPattern17. Behavioural-ChainofResponsibilityDesignPattern18. Behavioural-StrategyDesignPattern19. Behavioural-CommandDesignPattern20. Behavioural-IteratorDesignPattern21. Behavioural-InterpreterDesignPattern22. Behavioural-MediatorDesignPattern23. Behavioural-MementoDesignPattern24. Behavioural-NullObjectDesignPattern25. Behavioural-ObserverDesignPattern26. Behavioural-StateDesignPattern27. Behavioural-TemplateDesignPattern28. Behavioural-VisitorDesignPattern
PartOne:SOLID
1)SOLID-SingleResponsibilityPrinciple(SRP)Definition:
Singleresponsibilityprinciplesaysaclassshouldhaveone,andonlyone,reasontochange.Everyclassshouldberesponsibleforasinglepartofthefunctionality,andthatresponsibilityshouldbeentirelyencapsulatedbytheclass.Thismakesyoursoftwareeasiertoimplementandpreventsunexpectedside-effectsoffuturechanges.
Usage:
Letusdesignanimaginaryoperationsystemforacrickettournament.Forthesakeofsimplicity,let’shavetwomajoroperations:1)ATeamRegisterclass,whichhelpsforcheckinginandcheckingoutcricketers.2)ATeamConveyanceclass,whichisusedtodroptheplayersfromhoteltostadiumandtopickthemupfromthestadiumafterthematchisover.
Everyclassisassigneditsownresponsibilityandtheywillberesponsibleonlyforthataction.
importUIKitimportFoundation
classTeamRegister:CustomStringConvertible{
varteamMembers=[String]()varmemberCount=0funccheckInGuest(_name:String)->Int{
memberCount+=1teamMembers.append("\(memberCount)-\(name)")returnmemberCount-1}funccheckOutGuest(_index:Int){teamMembers.remove(at:index)}vardescription:String{returnteamMembers.joined(separator:"\n")}}
TeamRegisterclassconformstoCustomStringConvertible.Ithastwovariablesdefined,anarraynamedteamMembersoftypeStringandmemberCountoftypeInteger.
Wealsodefinetwomethods.checkInGuestmethodtakestheguestnameasaparameteroftypeStringandappendstheguesttoteamMembersarrayandreturnsarraycount.
checkOutGuesttakesindexoftypeIntegerasaparameterandremovestheguestfromregister.
classTeamConveyance{functakePlayersToStadium(_teamRegister:TeamRegister){print("Takingplayers\n\(teamRegister.description)\ntotheStadium")}funcdropPlayersBackAtHotel(){print("DroppingalltheplayersbackatHotel")}}
TeamConveyanceclasshastwomajorresponsibilities.takePlayersToStadiumtakesaparameteroftypeTeamRegisteranddropsalltheplayersatthestadium.
dropPlayersBackAtHotelgetsbackalltheplayerstothehotelafterthematchisover.Itisnotconcernedaboutanythingelse.
Letusnowwriteafunctioncalledmainandseethecodeinaction.
funcmain(){letteamRegister=TeamRegister()letplayer1=teamRegister.checkInGuest("PlayerOne")letplayer2=teamRegister.checkInGuest("PlayerTwo")print(teamRegister)}
main()
WetakeaninstanceofTeamRegisterclassandcheckinacoupleofguestspassingtheirnamesasparameters.
OutputintheXcodeconsole:
1-PlayerOne2-PlayerTwo
Letusnowcheckoutaguestandaddonemoreguesttotheteam.Changethemainfunctionto:
funcmain(){letteamRegister=TeamRegister()letplayer1=teamRegister.checkInGuest("PlayerOne")letplayer2=teamRegister.checkInGuest("PlayerTwo")print(teamRegister)teamRegister.checkOutGuest(1)print("------------------------------------")print(teamRegister)letplayer3=teamRegister.checkInGuest("PlayerThree")
print("------------------------------------")print(teamRegister)}
main()
Wecheckedout‘PlayerTwo’andthencheckedinanotherguestnamed‘PlayerThree’.
OutputintheXcodeconsole:
1-PlayerOne2-PlayerTwo------------------------------------1-PlayerOne------------------------------------1-PlayerOne3-PlayerThree
Nowchangethemainmethodtothefollowing:
funcmain(){letteamRegister=TeamRegister()letplayer1=teamRegister.checkInGuest("PlayerOne")letplayer2=teamRegister.checkInGuest("PlayerTwo")print(teamRegister)teamRegister.checkOutGuest(1)print("------------------------------------")print(teamRegister)letplayer3=teamRegister.checkInGuest("PlayerThree")
print("------------------------------------")print(teamRegister)print("------------------------------------")letteamBus=TeamConveyance()
teamBus.takePlayersToStadium(teamRegister)print("-------MatchOver----------")teamBus.dropPlayersBackAtHotel()}
main()
WearetakinganinstanceofTeamConveyancetodropplayersatthestadiumandgetthembacktothehotelafterthematchisover.
OutputintheXcodeconsole:
1-PlayerOne2-PlayerTwo------------------------------------1-PlayerOne------------------------------------1-PlayerOne3-PlayerThree------------------------------------Takingplayers1-PlayerOne3-PlayerThreetotheStadium-------MatchOver----------DroppingalltheplayersbackatHotel
2)SOLID-OpenClosedPrinciple(OCP)Definition:
Openclosedprinciplesaysoneshouldbeabletoextendaclassbehaviourwithoutmodifyingit.Thisprincipleisthefoundationforbuildingcodethatismaintainableandreusable.
AnyclassfollowingOCPshouldfulfilltwocriteria:
1. 1) Openforextension:Thisensuresthattheclassbehaviourcanbeextended.Inarealworldscenario,requirementskeepchanging,andinorderforustobeabletoaccommodatethosechanges,classesshouldbeopenforextensionsothattheycanbehaveinanewway.
1. 2) Closedformodification:Codeinsidetheclassiswritteninsuchawaythatnooneisallowedtomodifytheexistingcodeunderanycircumstances.
Usage:
Letusconsideranexamplewherewehaveanarrayofcricketers’profiles,whereeachentityhasthenameofacricketer,histeam,andhisspecialisationastheattributes.Nowwewanttobuildasystemwheretheclientcanapplyfiltersonthedatabasedondifferentcriterialiketeam,roleoftheplayer,etc.LetusseehowwecanuseOCPtobuildthis:
importFoundationimportUIKit
enumTeam{caseindiacaseaustraliacasepakistancaseengland}
enumRole{casebatsmancasebowlercaseallrounder}
Enumerationisadatatypethatallowsustodefinealistofpossiblevalues.Wedefineenumsfortheavailablenamesoftheteamsandrolesofthecricketers.
classCricketer{varname:Stringvarteam:Teamvarrole:Roleinit(_name:String,_team:Team,_role:Role){self.name=nameself.team=teamself.role=role}}
WethendefineaclasscalledCricketer,whichtakesthreeparametersduringitsinitialisation:nameoftypeString,teamoftypeTeam,androleoftypeRole.
Now,assumeoneoftheclientrequirementsistoprovideafilterofcricketersbasedontheirteam.
classCricketerFilter{
funcfilterByTeam(_cricketers:[Cricketer],_team:Team)->[Cricketer]{varfilteredResults=[Cricketer]()foritemincricketers{ifitem.team==team{filteredResults.append(item)}}returnfilteredResults}
}
WewriteaclasscalledCricketerFilteranddefineamethodfilterByTeamtofiltertheplayerprofilesbasedontheirteam.IttakesanarrayoftypeCricketerandteamoftypeTeamasparametersandreturnsafilteredarrayoftypeCricketer.
Foreachcricketerinthegivenarray,wecheckifhisteamisthesameasthatofthegiventeamforthefilter,thenaddhimtothefilteredarray.Letusseethiscodeinaction.AddthebelowcodeafterCricketerFilterclass.
funcmain(){letdhoni=Cricketer("Dhoni",.india,.batsman)letkohli=Cricketer("Kohli",.india,.batsman)letmaxi=Cricketer("Maxwell",.australia,.allrounder)letsmith=Cricketer("Smith",.australia,.batsman)letsymo=Cricketer("Symonds",.australia,.allrounder)letbroad=Cricketer("Broad",.england,.bowler)letali=Cricketer("Ali",.pakistan,.batsman)letstokes=Cricketer("Stokes",.england,.allrounder)letcricketers=[dhoni,kohli,maxi,broad,ali,stokes,smith,symo]print("IndianCricketers")letcricketerFilter=CricketerFilter()foritemincricketerFilter.filterByTeam(cricketers,.india){print("\(item.name)belongstoIndianTeam")}}
main()
OutputintheXcodeconsole:
IndianCricketersDhonibelongstoIndianTeamKohlibelongstoIndianTeam
Assume,afterafewdays,wegotanewrequirementtobeabletofilterbyroleofthecricketerandthentofilterbybothteamandroleatonce.OurCricketerFilterclasswouldlooksomethinglikethis:
classCricketerFilter{
funcfilterByRole(_cricketers:[Cricketer],_role:Role)->[Cricketer]{varfilteredResults=[Cricketer]()foritemincricketers{ifitem.role==role{filteredResults.append(item)}}returnfilteredResults}
funcfilterByTeam(_cricketers:[Cricketer],_team:Team)->[Cricketer]{varfilteredResults=[Cricketer]()foritemincricketers{ifitem.team==team{filteredResults.append(item)}}returnfilteredResults}funcfilterByRoleAndTeam(_cricketers:[Cricketer],_role:Role,_team:Team)->[Cricketer]{varfilteredResults=[Cricketer]()foritemincricketers{ifitem.role==role&&item.team==team{filteredResults.append(item)
}}returnfilteredResults}
}
ThislogicisquitesimilartofilterByTeammethod,exceptwithfilterByRole,wecheckiftheplayer’sroleisthesameasthatofthegivenrole.ForfilterByRoleAndTeammethod,weuseANDstatementtocheckifthegivenconditionismet.
TheOCPstatesthatclassesshouldbeclosedformodificationandopenforextension.But,hereweareclearlybreakingthisprinciple.LetusseehowthesameusecasecanbeservedwiththehelpofOCP.
//ConditionsprotocolCondition{associatedtypeTfuncisConditionMet(_item:T)->Bool}
WebeginbydefiningaprotocolcalledCondition,whichbasicallychecksifaparticularitemsatisfiessomecriteria.WehaveafunctioncalledisConditionMet,whichtakesanitemofgenerictypeTandreturnsabooleanindicatingwhethertheitemmeetsthegivencriteria.
protocolFilter{associatedtypeTfuncfilter<Cond:Condition>(_items:[T],_cond:Cond)->[T]whereCond.T==T;}
WethendefineaprotocolnamedFilterthathasafunctioncalledfilter,whichtakesanarrayofitemsofgenerictypeTandaconditionoftypeConditionasparametersandreturnsthefilteredarray.
WenowusetheabovegenerictypeFiltertowriteconditionsforroleandteam.
classRoleCondition:Condition{typealiasT=Cricketerletrole:Roleinit(_role:Role){self.role=role}funcisConditionMet(_item:Cricketer)->Bool{returnitem.role==role}}
classTeamCondition:Condition{typealiasT=Cricketerletteam:Teaminit(_team:Team){self.team=team}funcisConditionMet(_item:Cricketer)->Bool{returnitem.team==team}}
Ineachofthemethods,wewritethelogicofisConditionMetprotocolmethodtoseeiftheitemmeetsthecriteriaandreturnsaboolean.
classOCPCricketFilter:Filter{typealiasT=Cricketerfuncfilter<Cond:Condition>(_items:[Cricketer],_cond:Cond)->[T]whereCond.T==T{
varfilteredItems=[Cricketer]()foriinitems{ifcond.isConditionMet(i){filteredItems.append(i)}}returnfilteredItems}}
NowwedefineabrandnewfiltercalledOCPCricketFilter,usageofwhichdoesnotviolateOCP.WetakeitemsoftypeCricketer,checkfortheconditionoftypeCondition,andreturnthefilteredarray.
Letusnowseethecodeinaction.Changethemainmethodtothefollowing:
funcmain(){letdhoni=Cricketer("Dhoni",.india,.batsman)letkohli=Cricketer("Kohli",.india,.batsman)letmaxi=Cricketer("Maxwell",.australia,.allrounder)letsmith=Cricketer("Smith",.australia,.batsman)letsymo=Cricketer("Symonds",.australia,.allrounder)letbroad=Cricketer("Broad",.england,.bowler)letali=Cricketer("Ali",.pakistan,.batsman)letstokes=Cricketer("Stokes",.england,.allrounder)letcricketers=[dhoni,kohli,maxi,broad,ali,stokes,smith,symo]
letocpFilter=OCPCricketFilter()
print("EnglandCricketers")foriteminocpFilter.filter(cricketers,TeamCondition(.england)){print("\(item.name)belongstoEnglishTeam")}}
WetakeaninstanceofOCPFilterandjustpasstheteamnameparameterto
TeamCondition.
OutputintheXcodeconsole:
EnglandCricketersBroadbelongstoEnglishTeamStokesbelongstoEnglishTeam
Inasimilarway,withoutmodifyinganyexistingclasses,wecanextendtheOCPCricketFilterclasstoasmanyfiltersasweneed.NowwewillseehowwecanwriteafilterforANDcondition(roleandteamforexample):
classAndCondition<T,CondA:Condition,CondB:Condition>:ConditionwhereT==CondA.T,T==CondB.T{letfirst:CondAletsecond:CondBinit(_first:CondA,_second:CondB){self.first=firstself.second=second}funcisConditionMet(_item:T)->Bool{returnfirst.isConditionMet(item)&&second.isConditionMet(item)}}
Thisisverymuchsimilartootherfilters.Theonlychangeisthatittakestwoconditionsasargumentsforitsinitialisation.
Changethemainmethodtothebelowcode:
funcmain(){letdhoni=Cricketer("Dhoni",.india,.batsman)letkohli=Cricketer("Kohli",.india,.batsman)
letmaxi=Cricketer("Maxwell",.australia,.allrounder)letsmith=Cricketer("Smith",.australia,.batsman)letsymo=Cricketer("Symonds",.australia,.allrounder)letbroad=Cricketer("Broad",.england,.bowler)letali=Cricketer("Ali",.pakistan,.batsman)letstokes=Cricketer("Stokes",.england,.allrounder)letcricketers=[dhoni,kohli,maxi,broad,ali,stokes,smith,symo]
letocpFilter=OCPCricketFilter()
print("AustralianAllrounders")foriteminocpFilter.filter(cricketers,AndCondition(TeamCondition(.australia),RoleCondition(.allrounder))){print("\(item.name)belongstoAustraliaTeamandisanAllrounder")}}
OutputintheXcodeconsole:
AustralianAllroundersMaxwellbelongstoAustraliaTeamandisanAllrounderSymondsbelongstoAustraliaTeamandisanAllrounder
Wecanwritennumberoffilterswithoutmodifyinganyexistingclasses.AllwehavetodoisextendtheFilterclass.
3)SOLID-LiskovSubstitutionPrinciple(LSP)Definition:
Liskovsubstitutionprinciple,namedafterBarbaraLiskov,statesthatoneshouldalwaysbeabletosubstituteabasetypeforasubtype.LSPisawayofensuringthatinheritanceisusedcorrectly.Ifamoduleisusingabaseclass,thenthereferencetothebaseclasscanbereplacedwithaderivedclasswithoutaffectingthefunctionalityofthemodule.
Usage:
LetusunderstandLSP’susagewithasimpleexample.
importUIKitimportFoundation
protocolCricketer{funccanBat()funccanBowl()funccanField()}
WedefineaprotocolcalledCricketer,whichimplementsthreemethodsofcanBat,canBowl,andcanField.
classAllRounder:Cricketer{funccanBat(){print("Icanbat")}
funccanBowl(){print("Icanbowl")}funccanField(){print("Icanfield")}}
WethendefineaclasscalledAllRounder,conformingtoCricketerprotocol.Anall-rounderincricketissomeonewhocanbat,bowl,andfield.
classBatsman:Cricketer{funccanBat(){print("Icanbat")}funccanBowl(){print("Icannotbowl")}funccanField(){print("Icanfield")}}
WethendefineaclasscalledBatsman,conformingtoCricketerprotocol.ThisisaviolationofLSP,asabatsmanisacricketerbutcannotuseCricketerprotocolbecausehecannotbowl.LetusnowseehowwecanuseLSPinthisscenario:
protocolCricketer{funccanBat()funccanField()}
classBatsman:Cricketer{funccanBat(){print("Icanbat")}
funccanField(){print("Icanfield")}}
WechangetheCricketerprotocolandnowmaketheBatsmanclassconformtoCricketerprotocol.
classBatsmanWhoCanBowl:Cricketer{
funccanBat(){print("Icanbat")}funccanField(){print("Icanfield")}funccanBowl(){print("Icanbowl")}
}
classAllRounder:BatsmanWhoCanBowl{}
WethendefineanewclassnamedBatsmanWhoCanBowlwithsuperclassasCricketeranddefinetheextramethodofcanBowlinthisclass.
4)SOLID-InterfaceSegregationPrinciple(ISP)Definition:
TheonlymottoofInterfacesegregationprincipleisthattheclientsshouldnotbeforcedtoimplementinterfacestheydon’tuse.Clientsshouldnothavethedependencyontheinterfacesthattheydonotuse.
Usage:
Letusassumewearebuildingascreendisplayformobile,tablet,anddesktopinterfacesofanappthatisusedtodisplaylivescoresofacricketmatch.
WewillseehowthiscanbeachievedwithoutusingISPandthenusingISP.
importUIKitimportFoundation
//BeforeISPprotocolMatchSummaryDisplay{funcshowLiveScore()funcshowCommentary()funcshowLiveTwitterFeed()funcshowSmartStats()}
WedefineaprotocolnamedMatchSummaryDisplay,whichhasfourmethodstoshowlivescore,commentary,twitterfeedaboutthematch,andstatisticsoftheplayers.
enumNoScreenEstate:Error
{casedoesNotShowLiveTwitterFeedcasedoesNotShowSmartStats}
extensionNoScreenEstate:LocalizedError{publicvarerrorDescription:String?{switchself{case.doesNotShowLiveTwitterFeed:returnNSLocalizedString("NoScreenEstatetoshowLiveTwitterFeed",comment:"Error")case.doesNotShowSmartStats:returnNSLocalizedString("NoScreenEstatetoshowSmartStats",comment:"Error")}}}
Bydefault,wewanttoshowthelivescoreandcommentaryonalltypesofdeviceslikemobile,tablet,anddesktop.Showingtwitterfeedandstatisticsareoptional,dependingonthescreenestateavailableonthedevice.So,wedefineanenumcalledNoScreenEstatewithtwopossiblecases.Wealsowriteanextensiontoitjusttomaketheerrordescriptionsmoreclear.
classDesktopDisplay:MatchSummaryDisplay{funcshowLiveScore(){print("ShowingLiveScoreOnDesktop")}funcshowCommentary(){print("ShowingCommentaryOnDesktop")}funcshowLiveTwitterFeed(){print("ShowingLiveTwitterFeedOnDesktop")}funcshowSmartStats(){print("ShowingSmartStatsOnDesktop")
}}
WestarttheinterfacedesignbydefiningaclasscalledDesktopDisplayconformingtoMatchSummaryDisplay.Adesktophasenoughscreenspaceavailable,andweshowalltheavailabledatatotheuser.
classTabletDisplay:MatchSummaryDisplay{funcshowLiveScore(){print("ShowingLiveScoreOnTablet")}funcshowCommentary(){print("ShowingCommentaryOnTablet")}funcshowLiveTwitterFeed(){print("ShowingLiveTwitterFeedOnTablet")}funcshowSmartStats(){do{leterror:Error=NoScreenEstate.doesNotShowSmartStatsprint(error.localizedDescription)throwerror}catch{}}}
WethendefineanotherclasscalledTabletDisplayconformingtoMatchSummaryDisplay.Asthescreensizeofatabletislesswhencomparedtoadesktop,wedonotshowsmartstatsonthetabletdisplay.WethrowanerrorinshowSmartStatsmethod.
classMobileDisplay:MatchSummaryDisplay{funcshowLiveScore(){print("ShowingLiveScoreOnMobile")
}funcshowCommentary(){print("ShowingCommentaryOnMobile")}funcshowLiveTwitterFeed(){do{leterror:Error=NoScreenEstate.doesNotShowLiveTwitterFeedprint(error.localizedDescription)throwerror}catch{}}funcshowSmartStats(){do{leterror:Error=NoScreenEstate.doesNotShowSmartStatsprint(error.localizedDescription)throwerror}catch{}}}
WethendefineanotherclasscalledMobileDisplayconformingtoMatchSummaryDisplay.Asthescreensizeofmobileisevensmallerwhencomparedtodesktopandtablet,wedonotshowsmartstatsandtwitterfeedonmobiledisplay.WethrowanerrorinshowLiveTwitterFeedandshowSmartStatsmethods.
Asyoucansee,thisapproachviolatesISPbecauseTabletDisplayandMobileDisplayareforcedtoimplementmethodstheyarenotusing.Let’sseehowwecanuseISPinthisscenario.
//FollowingISP
protocolLiveScoreDisplay{funcshowLiveScore()funcshowCommentary()}
protocolTwitterFeedDisplay{funcshowLiveTwitterFeed()}
protocolSmartStatsDisplay{funcshowSmartStats()}
HerewedefineaprotocolnamedLiveScoreDisplay,whichismandatoryforallthescreensizesofthedevices.ThenwedefinedifferentprotocolscalledTwitterFeedDisplayandSmartStatsDisplaysothatonlythedeviceswithenoughscreensizescanconformtorequiredprotocols.
classISPMobileDisplay:LiveScoreDisplay{funcshowLiveScore(){print("ShowingLiveScoreOnMobile")}funcshowCommentary(){print("ShowingCommentaryOnMobile")}}
WedefineaclasscalledISPMobileDisplay,whichconformsonlytoLiveScoreDisplay.Wedon’thavetoforcetheclasstoimplementanyunwantedmethods.
classISPTabletDisplay:LiveScoreDisplay,TwitterFeedDisplay{funcshowLiveScore(){print("ShowingLiveScoreOnTablet")}funcshowCommentary(){
print("ShowingCommentaryOnTablet")}funcshowLiveTwitterFeed(){print("ShowingLiveTwitterFeedOnTablet")}}
WethendefineaclasscalledISPTabletDisplay,whichconformstoTwitterFeedDisplayalongwithLiveScoreDisplay.
Wecandefinedesktopinterfaceasfollows:
classISPDesktopDisplay:LiveScoreDisplay,TwitterFeedDisplay,SmartStatsDisplay{funcshowLiveScore(){print("ShowingLiveScoreOnDesktop")}funcshowCommentary(){print("ShowingCommentaryOnDesktop")}funcshowLiveTwitterFeed(){print("ShowingLiveTwitterFeedOnDesktop")}funcshowSmartStats(){print("ShowingSmartStatsOnDesktop")}}
Wecanobservethat,inalltheabovethreeclasses,wearenotforcinganyclasstoimplementamethodthattheydonotuse.WeachievedISPbydefiningmultipleprotocols.
5)SOLID-DependencyInversionPrinciple(DIP)Definition:
Inshort,Dependencyinversionprinciplesaystodependonabstractions,notonconcretions.High-levelmodulesshouldnotdependuponlow-levelmodules.Bothshoulddependuponabstractions.
Abstractionsshouldnotdependupondetails.Detailsshoulddependuponabstractions.Bydependingonhigher-levelabstractions,wecaneasilychangeoneinstancewithanotherinstanceinordertochangethebehaviour.DIPincreasesthereusabilityandflexibilityofourcode.
Usage:
Letusassumewearedesigningasmallsystemwherewewanttolistfromthedatabaseallthewicketstakenbyabowlerinhiscricketingcareer.
importFoundationimportUIKit
enumWicketsColumn{casewicketTakenBycasewicketGivenTo}
classCricketer{varname=""init(_name:String){self.name=name
}}
WedefineanenumcalledWicketsColumnwithalistoftwopossiblecases.WethendefineaclasscalledCricketerthattakestheparameterofnameoftypeStringforitsinitialisation.
protocolWicketsTallyBrowser{funcreturnAllWicketsTakenByBowler(_name:String)->[Cricketer]}
WedefineaprotocolnamedWicketsTallyBrowser,whichhasafunctiontoreturnallthewicketstakenbyagivenbowlerasanarrayoftypeCricketer.
Wewillnowdefineaclass,whichstoresrelationshipbetweenbowlersandbatsmen.
classWicketsTally:WicketsTallyBrowser{//LowLevelvarwickets=[(Cricketer,WicketsColumn,Cricketer)]()funcaddToTally(_bowler:Cricketer,_batsman:Cricketer){wickets.append((bowler,.wicketTakenBy,batsman))wickets.append((batsman,.wicketGivenTo,bowler))}funcreturnAllWicketsTakenByBowler(_name:String)->[Cricketer]{returnwickets.filter({$0.name==name&&$1==WicketsColumn.wicketTakenBy&&$2!=nil}).map({$2})}}
WedefineaclasscalledWicketsTallyconformingtoWicketsTallyBrowserprotocol.Ithasavariablecalledwickets,whichisanarrayoftupleswhereeachofthetupleshasthreeattributes:oneeachoftypeCricketer,WicketsColumn,andCricketer,inthatorder.
ThenwedefineamethodcalledaddToTally,whichtakesparametersofbowlerandbatsmanoftypeCricketer.ItappendsthesametothewicketsarraybutwithdifferentrelationshipsavailablefromWicketsColumnenum.
InthedefinitionofprotocolmethodreturnAllWicketsTakenByBowler,wefilterthewicketsarraybycomparingfirstattributeoftupletothenameofthegivenbowler.
classPlayerStats{//HighLevelinit(_wicketsTally:WicketsTally){letwickets=wicketsTally.wicketsforwinwicketswherew.0.name=="BrettLee"&&w.1==.wicketTakenBy{print("BrettLeehasawicketof\(w.2.name)")}}}
WenowdefineaclasscalledPlayerStats,whereweusethelogicwritteninWicketsTallyclasstoreturnallthewicketstakenbyaparticularbowler.
Letusnowwriteamainmethodtoseethiscodeinaction.
funcmain(){letbowler=Cricketer("BrettLee")letbatsman1=Cricketer("Sachin")letbatsman2=Cricketer("Dhoni")letbatsman3=Cricketer("Dravid")letwicketsTally=WicketsTally()wicketsTally.addToTally(bowler,batsman1)wicketsTally.addToTally(bowler,batsman2)wicketsTally.addToTally(bowler,batsman3)let_=PlayerStats(wicketsTally)}
OutputintheXcodeconsole:
BrettLeehasawicketofSachinBrettLeehasawicketofDhoniBrettLeehasawicketofDravid
TheissuewiththeaboveapproachisitsviolationofDIP(itstatesthatthehigh-levelmodulesshouldnotdirectlydependonlow-levelmodules),asourPlayerStatsclassdependsuponwicketsarrayofWicketsTallyclass.Itshouldbedeclaredasaprivatevariablesothatnootherclasscanmanipulatethedatadirectly.
LetusnowchangetheWicketsTallyclassthisway:
classWicketsTally:WicketsTallyBrowser{//LowLevelprivatevarwickets=[(Cricketer,WicketsColumn,Cricketer)]()funcaddToTally(_bowler:Cricketer,_batsman:Cricketer){wickets.append((bowler,.wicketTakenBy,batsman))wickets.append((batsman,.wicketGivenTo,bowler))}funcreturnAllWicketsTakenByBowler(_name:String)->[Cricketer]{returnwickets.filter({$0.name==name&&$1==WicketsColumn.wicketTakenBy&&$2!=nil}).map({$2})}}
NowchangethePlayerStatsclassto:
classPlayerStats{//HighLevelinit(_browser:WicketsTallyBrowser){forwinbrowser.returnAllWicketsTakenByBowler("BrettLee"){print("BrettLeehasawicketof\(w.name)")}}}
Herewecanobservethat,insteadofdirectlydependingonwicketsarrayfromWicketsTally,PlayerStatsisdependentonabstractionfromWicketsTallyBrowser.OutputintheXcodeconsoleremainsthesamebutwearenowadheringtoDIP.
OutputintheXcodeconsole:
BrettLeehasawicketofSachinBrettLeehasawicketofDhoniBrettLeehasawicketofDravid
PartTwo:Creational
6)Creational-FactoryDesignPatternDefinition:
FactorydesignpatternisalsoknownasVirtualConstructor.Itisacreationaldesignpatternthatdefinesanabstractclassforcreatingobjectsinsuperclassbutallowsthesubclassestodecidewhichclasstoinstantiate.
Usage:
AssumethereisaBowlingMachinethatdeliversRedCricketBalls(usedforTestCricket)andWhiteCricketBalls(usedforLimitedOversCricket)basedonuserinput.
importUIKit
protocolCricketBall{funchitMe()}
AnyclassconformingtoCricketBallmustimplementhitMemethod.
classRedBall:CricketBall{funchitMe(){print("ThisballisgoodforTestCricket")}}
classWhiteBall:CricketBall{funchitMe(){print("ThisballisgoodforLimitedOversCricket")
}}
Letusstartdefiningfactoriesnow.
protocolCricketBallFactory{
init()funcdeliverTheBall(_speed:Int)->CricketBall}
FactoriesconformingtoCricketBallFactorymustimplementdeliverTheBall.Weshouldalsogivesomeinputlikethespeedatwhichwewanttheballtobedelivered.
Now,movingoutofabstractclassescreatingobjects,westartdefiningsubclassesforobjectcreation.
classRedBallFactory{funcdeliverTheBall(_speed:Int)->CricketBall{print("ReleasingRedBallat\(speed)speed")returnRedBall()}}
classWhiteBallFactory{funcdeliverTheBall(_speed:Int)->CricketBall{print("ReleasingWhiteBallat\(speed)speed")returnWhiteBall()}}
Herewearedefiningtwofactoriestodeliverdifferentcoloursofballs.Weinputthespeedoftheballandgetared/whiteballinreturn.
It’stimewegotothemachineandgiveaninputtodelivertheballs.
classBowlingMachine{enumAvailableBall:String{
caseredBall="RedBall"casewhiteBall="WhiteBall"staticletall=[redBall,whiteBall]}internalvarfactories=[AvailableBall:CricketBallFactory]()internalvarnamedFactories=[(String,CricketBallFactory)]()init(){forballinAvailableBall.all{lettype=NSClassFromString("FactoryDesignPattern.\(ball.rawValue)Factory")letfactory=(typeas!CricketBallFactory.Type).init()factories[ball]=factorynamedFactories.append((ball.rawValue,factory))}}funcsetTheBall()->CricketBall{foriin0..<namedFactories.count{lettuple=namedFactories[i]print("\(i):\(tuple.0)")}letinput=Int(readLine()!)!returnnamedFactories[input].1.deliverTheBall(120)}}
WedefineaclasscalledBowlingMachine.WehaveanenumofavailableballswithredBallandwhiteBallastheoptions.Thenwehaveanarrayofalltheavailableballs.
Wehaveaninternalvariablecalledfactories,whichisadictionarywithkeyastheAvailableDrinkandvalueasCricketBallFactory.ThenwedefineavariablecallednamedFactories,whichisalistoftupleswhereeachentryhasthenameofthefactoryandtheinstanceofthefactory.
Intheinitialisermethod,weinitialisethefactory.Foreachballinavailableballs,wegetthetypefromactualclass.ThenweconstructthefactorybytakingthetypeandcastingitasaCricketBallFactoryandinitialisingit.Thenweappendeachfactorytothearrayoffactories.
Wethendefineafunctionthatasksustosettheballandreturnsacricketball.Foreachfactory,weprintouttheindexandthenameofthefactory.Thenbasedontheinputenteredbytheuser,wereturncricketBallatgivenspeed.
Let’snowdefineafunctioncalledmainandseethecodeinaction.
funcmain(){letbowlingMachine=BowlingMachine()print(bowlingMachine.namedFactories.count)letball=bowlingMachine.setTheBall()ball.hitMe()}
main()
HereweinitialisetheBowlingMachineandsettheball.ThenwecallthehitMemethodontheinstanceofeachballtheuserinputs.
OutputintheXcodeconsole:
2AvailableBalls0:RedBall1:WhiteBall
Ifwechoose0,weprint‘ReleasingRedBallat20speed’.Ifwechoose1,weprint‘ReleasingWhiteBallat20speed’.
Summary:
Whenyouareinasituationwhereaclassdoesnotknowwhatsubclasseswillberequiredtocreate,orwhenaclasswantsitssubclassestospecifytheobjectstobecreated,goforFactorydesignpattern.
7)Creational-BuilderDesignPattern:
Definition:
Builderisacreationaldesignpatternthathelpsinpiecewiseconstructionofcomplexobjectsavoidingtoomanyinitialiserarguments.Itletsusproducedifferenttypesandrepresentationsofanobjectusingthesameprocessofbuilding.
Thispatternprimarilyinvolvesthreetypes:
Product-complexobjecttobecreatedBuilder-handlesthecreationofproductDirector-acceptsinputsandcoordinateswiththebuilder
Usage:
Letusassumewearecreatingacricketteamthatconsistsofacaptain,batsmen,andbowlers.WewillseehowwecanuseBuilderpatterninthiscontext.
WestartwiththeProductpartfirst.
importUIKit
//MARK:-ProductpublicstructCricketTeam{publicletcaptain:Captainpublicletbatsmen:Batsmenpublicletbowlers:Bowlers}
extensionCricketTeam:CustomStringConvertible{publicvardescription:String{return"Teamwithcaptain\(captain.rawValue)"}}
WefirstdefineCricketTeam,whichhaspropertiesforcaptain,batsmen,and
bowlers.Onceateamisset,weshouldn’tbeabletochangeitscomposition.WealsomakeCricketTeamconformtoCustomStringConvertible.
publicenumCaptain:String{caseDhonicaseKohlicaseRahane}
WedeclareCaptainasenum.Eachteamcanhaveonlyonecaptain.
publicstructBatsmen:OptionSet{publicstaticlettopOrderBatsman=Batsmen(rawValue:1<<0)publicstaticletmiddleOrderBatsman=Batsmen(rawValue:1<<1)publicstaticletlowerOrderBatsman=Batsmen(rawValue:1<<2)publicletrawValue:Intpublicinit(rawValue:Int){self.rawValue=rawValue}}
publicstructBowlers:OptionSet{publicstaticletfastBowler=Bowlers(rawValue:1<<0)publicstaticletmediumPaceBowler=Bowlers(rawValue:1<<1)publicstaticletspinBowler=Bowlers(rawValue:1<<2)publicletrawValue:Intpublicinit(rawValue:Int){self.rawValue=rawValue}}
WedefineBatsmenandBowlersasOptionSet.Thisallowsustotrydifferentcombinationsofbatsmentogether,likeateamwithtwotopOrderBatsmanandonemiddleOrderBatsman.SamewithBowlerswherewecanchooseacombinationoffastBowler,mediumPaceBowler,andaspinBowlerfortheteam.
AddthefollowingcodetomakeBuilder:
//MARK:-BuilderpublicclassCricketTeamBuilder{publicenumError:Swift.Error{casealreadyTaken}publicprivate(set)varcaptain:Captain=.Dhonipublicprivate(set)varbatsmen:Batsmen=[]publicprivate(set)varbowlers:Bowlers=[]privatevarsoldOutCaptains:[Captain]=[.Dhoni]publicfuncaddBatsman(_batsman:Batsmen){batsmen.insert(batsman)}publicfuncremoveBatsman(_batsman:Batsmen){batsmen.remove(batsman)}publicfuncaddBowler(_bowler:Bowlers){bowlers.insert(bowler)}publicfuncremoveBowler(_bowler:Bowlers){bowlers.remove(bowler)}
publicfuncpickCaptain(_captain:Captain)throws{guardisAvailable(captain)else{throwError.alreadyTaken}self.captain=captain}publicfuncisAvailable(_captain:Captain)->Bool{return!soldOutCaptains.contains(captain)}
publicfuncmakeTeam()->CricketTeam{returnCricketTeam(captain:captain,batsmen:batsmen,bowlers:bowlers)}
}
Wedeclarepropertiesforcaptain,batsmen,andbowlers.Thesearedeclaredasvarsothatwecanchangetheteam’scompositionbasedontherequirement.Weareusingprivate(set)foreachtoensureonlyCricketTeamBuildercansetthemdirectly.
Sinceeachpropertyisdeclaredprivate,weneedtoprovidepublicmethodstochangethem.WedefinedmethodslikeaddBatsman,removeBatsman,addBowler,removeBowler,etc.,forthepurposeofbuildingtheteam.
Wehaveaninterestingthingtonotehere.Everyteambydefaultshouldhaveacaptain.AssumeyouarestartingateamwithDhoniascaptain.WhatifsomeotherteamtriestochooseDhoniascaptaintoo?WeshouldthrowsomeerrorusingthearrayofsoldOutCaptains.WechecktheavailabilityofthecaptainsviaisAvailablemethod.
WearedonewiththeBuilder.Now,let’sbuildourDirector.
//MARK:-Director/MakerpublicclassTeamOwner{publicfunccreateTeam1()throws->CricketTeam{letteamBuilder=CricketTeamBuilder()tryteamBuilder.pickCaptain(.Kohli)teamBuilder.addBatsman(.topOrderBatsman)teamBuilder.addBowler([.fastBowler,.spinBowler])returnteamBuilder.makeTeam()}
publicfunccreateTeam2()throws->CricketTeam{letteamBuilder=CricketTeamBuilder()tryteamBuilder.pickCaptain(.Dhoni)teamBuilder.addBatsman([.topOrderBatsman,.lowerOrderBatsman])teamBuilder.addBowler([.mediumPaceBowler,.spinBowler])
returnteamBuilder.makeTeam()}
}
WehaveaclasscalledTeamOwner,whobuildstheirteamsfromtheavailableoptions.EachteamisbuilttakinganinstanceofCricketTeamBuilder,pickingupacaptainandarraysofdifferenttypesofbatsmenandbowlers.
Now,let’sdefineafunctioncalledmaintoseethecodeinaction.
funcmain(){letowner=TeamOwner()ifletteam=try?owner.createTeam1(){print("Hello!"+team.description)}}
main()
WetrytousemethodcreateTeam1withcaptainasKohli.
OutputintheXcodeconsole:
Hello!TeamwithcaptainKohli
Now,changethemain()tothefollowing:
funcmain(){letowner=TeamOwner()ifletteam=try?owner.createTeam1(){print("Hello!"+team.description)}ifletteam=try?owner.createTeam2(){print("Hello!"+team.description)}else{print("Sorry!Captainalreadytaken")
}}
main()
AfterTeam1,wearetryingtocreateaTeam2withthehelpofcreateTeam2()withDhoniascaptain.ButDhoniisalreadytakenandwethrowtheerror.
OutputintheXcodeconsole:
Hello!TeamwithcaptainKohliSorry!Captainalreadytaken
Summary:
Ifyouaretryingtousethesamecodeforbuildingdifferentproductstoisolatethecomplexconstructioncodefrombusinesslogic,Builderdesignpatternfitsthebest.
Also,becarefulwhenyourproductdoesnotrequiremultipleparametersforinitialisationorconstruction.Inthisinstanceit’sadvisedtostayawayfromBuilderpattern.
8)Creational-PrototypeDesignPatternDefinition:
Prototypeisacreationaldesignpatternusedtoproducenewobjectsthathaveveryfewdifferences.Aprototypeisbasicallyatemplateofanyobjectbeforetheactualobjectisconstructed.ThePrototypepatterndelegatesacloningprocesstoobjectsthemselves.
Usage:
Letusconsiderasimpleusecasewherewewanttocreatetheprofileoftwocricketers,includingtheirnameandacustomprofilethatincludesrunsscoredandwicketstaken.
importUIKit
classProfile:CustomStringConvertible{varrunsScored:IntvarwicketsTaken:Intinit(_runsScored:Int,_wicketsTaken:Int){self.runsScored=runsScoredself.wicketsTaken=wicketsTaken}vardescription:String{return"\(runsScored)RunsScored&\(wicketsTaken)WicketsTaken"}}
First,wecreateaProfileclassthatconformstoCustomStringConvertible.Ithastwoproperties,runsScoredandwicketsTakenoftypeint.Ittakesthesameparametersduringitsinitialisation.
ThenwedefineaCricketerclassthatconformstoCustomStringConvertible.Ithastwoproperties,nameoftypeStringandprofileofcustomtypeProfile,whichwejustcreated.
classCricketer:CustomStringConvertible{varname:Stringvarprofile:Profileinit(_name:String,_profile:Profile){self.name=nameself.profile=profile}vardescription:String{return"\(name):Profile:\(profile)"}}
Letusnowwriteafunctioncalledmaintoseethethingsinaction.
funcmain(){letprofile=Profile(1200,123)letbhuvi=Cricketer("Bhuvi",profile)print(bhuvi.description)}
main()
IntheXcodeconsoleitprints:
Bhuvi:Profile:1200RunsScored&123WicketsTaken
Nowweneedtotalkaboutcopyingtheobjects.
Justbeforeprintstatementinthemainfunction,addthefollowinglines:
varishant=bhuviishant.name="Ishant"print(ishant.description)
IntheXcodeconsoleitprints:
Ishant:Profile:1200RunsScored&123WicketsTakenIshant:Profile:1200RunsScored&123WicketsTaken
Thisisbecauseweareonlycopyingthereferences.
Nowaddthislinejustbeforeprintingishant’sdescription:
ishant.profile.runsScored=600
IntheXcodeconsoleitprints:
Ishant:Profile:600RunsScored&123WicketsTakenIshant:Profile:600RunsScored&123WicketsTaken
Nowweneedtomakesurebhuviandishantactuallyrefertodifferentobjects.
Here,weusetheconceptofDeepCopy.Whenwedeepcopyobjects,thesystemwillcopyreferences,andeachcopiedreferencewillbepointingtoitsowncopiedmemoryobject.LetusnowseehowtoimplementDeepCopyinterfaceforourusecase.
protocolDeepCopy{funccreateDeepCopy()->Self}
First,wecreateaDeepCopyprotocolthatdefinesafunctioncalledcreateDeepCopyreturningself.
ThenmaketheclassesProfileandCricketerconformtoDeepCopyprotocol.Classesnowlooklikethis:
classProfile:CustomStringConvertible,DeepCopy{
varrunsScored:IntvarwicketsTaken:Intinit(_runsScored:Int,_wicketsTaken:Int){self.runsScored=runsScoredself.wicketsTaken=wicketsTaken}vardescription:String{return"\(runsScored)RunsScored&\(wicketsTaken)WicketsTaken"}funccreateDeepCopy()->Self{returndeepCopyImplementation()}privatefuncdeepCopyImplementation<T>()->T{returnProfile(runsScored,wicketsTaken)as!T}}
WehaveaprivatemethodcalleddeepCopyImplementation,whichisgenericandabletofigureoutthetypecorrectly.Ithasatypeparameter‘T’,whichisactuallygoingtobeinferred(wedon’tprovidethistypeparameteranywhere)andareturntypeof‘T’.WereturnaProfileobjectandforcecastittoT.
Cricketerclassnowlookslikethis:
classCricketer:CustomStringConvertible,DeepCopy{varname:Stringvarprofile:Profileinit(_name:String,_profile:Profile){self.name=nameself.profile=profile}vardescription:String{return"\(name):Profile:\(profile)"
}funccreateDeepCopy()->Self{returndeepCopyImplementation()}privatefuncdeepCopyImplementation<T>()->T{returnCricketer(name,profile)as!T}}
Letusdefineourmainmethodasbelowandseetheresults:
funcmain(){letprofile=Profile(1200,123)letbhuvi=Cricketer("Bhuvi",profile)letishant=bhuvi.createDeepCopy()ishant.name="Ishant"ishant.profile=bhuvi.profile.createDeepCopy()ishant.profile.wicketsTaken=140print(bhuvi.description)print(ishant.description)}
main()
OutputintheXcodeconsole:
Bhuvi:Profile:1200RunsScored&123WicketsTakenIshant:Profile:1200RunsScored&140WicketsTaken
Wecanseethatbhuviandishantaretwodifferentobjectsnow,andthisishowDeepCopyisimplemented.
Summary:
Whenyouareinasituationtocloneobjectswithoutcouplingtotheirconcreteclasses,youcanoptforPrototypedesignpattern,whichalsohelpsinreducing
repetitiveinitialisationcode.
9)Creational-SingletonDesignPatternWhendiscussingwhichpatternstodrop,wefoundthatwestilllovethemall(Notreally-IaminfavourofdroppingSingleton.Itsusageisalmostalwaysadesignsmell)-ErichGamma(oneoftheGangFour)
Adesignpatterneveryonelovestohate.Isitbecauseitisactuallybadorisitbecauseofitsabusebythedevelopers?Let’ssee.
Definition:
Singletonisacreationaldesignpatternthatprovidesuswithoneofthebestwaystocreateanobject.Thispatternensuresaclasshasonlyoneinstanceandprovidesaglobalaccesstoitsothattheobjectcanbeusedbyalltheotherclasses.
Usage:
LetustakethecaseofanAPIthatreturnssomeJSONresponse,whichwhenparsedlookslikethis:
["Sachin":1,"Sehwag":2,"Dravid":3,"Kohli":4,"Yuvraj":5,"Dhoni":6,"Jadeja":7,"Ashwin":8,"Zaheer":9,"Bhuvi":10,"Bumrah":11]
Thisdatastructureisanarraywhereeachobjectisakey-valuepair.KeyrepresentsthenameofIndianCricketerandValuerepresentsthepositionatwhichthecricketerbats.
WewouldneedonlyoneinstanceoftheSingletonDatabaseclassinordertosavethisdatatoourdatabase.Thereisnopointininitialisingdatabaseclassmorethanonce,asitwouldjustwastememory.Ourcodelookslikethis:
importUIKitclassSingletonDatabase{vardataSource=["Sachin":1,"Sehwag":2,"Dravid":3,"Kohli":4,"Yuvraj":5,"Dhoni":6,"Jadeja":7,"Ashwin":8,"Zaheer":9,"Bhuvi":10,"Bumrah":11]
varcricketers=[String:Int]()
staticletinstance=SingletonDatabase()staticvarinstanceCount=0
privateinit(){print("Initialisingthesingleton")type(of:self).instanceCount+=1fordataElementindataSource{cricketers[dataElement.key]=dataElement.value}}
}
Wefirstmakeaprivateinitialiserthatdoesnottakeanyarguments.Andthat’sthesimplestwaytocreateonobject.Asitisprivate,noonecanmakeanotherinstanceoftheclass.
ButhowdoweletsomeoneaccesstheSingletonDatabase?That’swheretheSingletonpatterncomesintoplay.
WeinitialiseastaticvariablewiththeonlyinstanceofSingletonDatabaseclass.Makingitstaticrestrictstheabilitytocreatemultipleinstancesoftheclass.
NowweaddthedatacomingfromtheAPIcalltoourarrayofcricketers.That’sit!Wehaveourdatabaseready.
Now,howdoessomeonehaveaccesstothisdatabase?Assumewewanttoknowthepositionatwhichacricketerbats.Wewriteafunctionforthatjustaftertheprivateinit()methodinSingletonDatabaseclass.
funcgetRunsScoredByCricketer(name:String)->Int{ifletposition=cricketers[name]{print("\(name)batsatnumber\(position)forIndianCrikcetTeam")returncricketers[name]!}
print("Cricketerwithname\(name)notfound")return0}
Thismethodisstraightforward.Ittakesthenameofthecricketerasanargumentandreturnshispositionintheline-up.
Inorderforustoaccessthisclassatsomepointinourcode,wewriteitthisway:
funcmain(){letsingleton=SingletonDatabase.instancesingleton.getRunsScoredByCricketer(name:"Sachin")}
Verysimpleandshort.Wecreateavariablenamedsingleton,whichhelpsusinaccessingallthefunctionsinourSingletonDatabaseclass.
Nowrunthemain()method.
main()
OutputintheXcodeconsole:
InitialisingthesingletonSachinbatsatnumber1forIndianCricketTeam
Changethenameparameterto“Sach”andtheoutputis:
InitialisingthesingletonCricketerwithnameSachnotfound
WehavenotyetdiscussedthevariablenamedinstanceCountinourprivateinit()
method.WecanusethisvariabletoshowthatthereisonlyoneinstanceoftheSingletonDatabaseclass.
Changethemainmethodthisway:
funcmain(){letsingleton1=SingletonDatabase.instanceprint(SingletonDatabase.instanceCount)letsingleton2=SingletonDatabase.instanceprint(SingletonDatabase.instanceCount)
}
OutputintheXcodeconsole:
Initialisingthesingleton11
Instancecountremains1eventhoughweinitialisedtheclassmorethanonce.
Addingthecodesnippetforanotherself-explanatoryexamplehere,whichwouldenhanceyourunderstanding:
importUIKit
classPlayerRating:CustomStringConvertible{privatestaticvar_nameOfThePlayer=""privatestaticvar_ratingForThePlayer=0
varnameOfThePlayer:String{get{returntype(of:self)._nameOfThePlayer}set(value){type(of:self)._nameOfThePlayer=value}}
varratingForThePlayer:Int{get{returntype(of:self)._ratingForThePlayer}set(value){type(of:self)._ratingForThePlayer=value}
}
vardescription:String{return"\(nameOfThePlayer)hasgotaratingof\(ratingForThePlayer)"}}
funcmain(){letplayerRating1=PlayerRating()playerRating1.nameOfThePlayer="Dhoni"playerRating1.ratingForThePlayer=8
letplayerRating2=PlayerRating()playerRating2.ratingForThePlayer=7
print(playerRating1)print(playerRating2)}main()
OutputintheXcodeconsole:
Dhonihasgotaratingof7Dhonihasgotaratingof7
Summary:
WeshoulduseSingletonpatternonlywhenwehaveascenarioforcingustouseasingleinstanceofanobjectatmultipleplaces.
PartThree:Structural
10)Structural-AdapterDesignPatternDefinition:
Adapterisastructuraldesignpatternthatconvertstheinterfaceofaclassintoanotherinterfaceclientsexpect.Thisallowsclasseswithincompatibleinterfacestocollaborate.
Usage:
SupposeyouhaveaTestBatsmanclasswithfieldWell()andmakeRuns()methods.AndalsoaT20BatsmanclasswithbatAggressively()method.
Let’sassumethatyouareshortonT20BatsmanobjectsandyouwouldliketouseTestBatsmanobjectsintheirplace.TestBatsmenhavesomesimilarfunctionalitybutimplementadifferentinterface(theycanbatbutcannotbatinthewayneededforaT20match),sowecan’tusethemdirectly.
WewillusetheAdapterpattern.HereourclientwouldbeT20BatsmanandadapteewouldbeTestBatsman.
Letusnowwritecode:
importUIKit
protocolTestBatsman{funcmakeRuns()funcfieldWell()}
AsimpleprotocolnamedTestBatsmandefiningtwomethods,makeRunsand
fieldWell.
classBatsman1:TestBatsman{funcmakeRuns(){print("IcanbatwellbutonlyatStrikeRateof80")}funcfieldWell(){print("Icanfieldwell")}}
WedefineaBatsman1classconformingtoTestBatsmanprotocol.Thistypeofbatsmancanmakerunsatastrikerateof80.
protocolT20Batsman{funcbatAggressively()}
WehaveonemoreprotocolnamedT20Batsman,whichdefinesbatAggressivelymethod.
classBatsman2:T20Batsman{funcbatAggressively(){print("IneedtobatwellataStrikeRateofmorethan130")}}
WedefineaBatsman2classconformingtoT20Batsmanprotocol.Thistypeofbatsmancanmakerunsatastrikerateof130.
Nowconsideringoursituation,weneedtomakeanadapterinsuchawaythatTestBatsmancanfittobeaT20Batsman.
classTestBatsmanAdapter:T20Batsman{lettestBatsman:TestBatsmaninit(_testBatsman:TestBatsman){self.testBatsman=testBatsman}
funcbatAggressively(){testBatsman.makeRuns()}}
WewriteaclassnamedTestBatsmanAdapter,whosesuperclassisT20Batsman.IthasapropertyoftypeTestBatsmanandittakesanobjectoftypeTestBatsmanforitsinitialisation.ItisthisobjectwhichwemakeadaptabletobatAggressivelymethodbycallingmakeRunsmethod.
OutputintheXcodeconsole:
TestBatsmanIcanfieldwellIcanbatwellbutonlyatStrikeRateof80T20BatsmanIneedtobatwellataStrikeRateofmorethan130TestBatsmanAdapterIcanbatwellbutonlyatStrikeRateof80
Summary:
Whenyouareinasituationwhereyouhaveanobjectthatshouldbeabletodothesametaskbutinlotsofdifferentways,andyoudonotwanttoexposethealgorithm'simplementationdetailstootherclasses,optforAdapterdesignpattern.
11)Structural-BridgeDesignPatternDefinition:Bridgeisastructuraldesignpatternthatletsusconnectcomponentstogetherthroughabstraction.Itenablestheseparationofimplementationhierarchyfrominterfacehierarchyandimprovestheextensibility.Usage:LetussupposethatwehaveaprotocolnamedBatsman,whosemainfunctionistomakerunsforhisteam.
importFoundationimportUIKit
protocolBatsman{funcmakeRuns(_numberOfBalls:Int)}
makeRunstakesaparameternamednumberOfBallsoftypeInt.
LetusnowdefinethreedifferentclassesofbatsmenconformingtoBatsmanprotocol.
classTestBatsman:Batsman{funcmakeRuns(_numberOfBalls:Int){print("IamaTestBatsmanandIscore\(0.6*Double(numberOfBalls))runsin\(numberOfBalls)balls")}}
classODIBatsman:Batsman{funcmakeRuns(_numberOfBalls:Int){print("IamaODIBatsmanandIscore\(1*Double(numberOfBalls))runsin\(numberOfBalls)balls")}}
classT20IBatsman:Batsman{funcmakeRuns(_numberOfBalls:Int){print("IamaT20BatsmanandIscore\(1.4*Double(numberOfBalls))runsin\(numberOfBalls)balls")}}
Wehavethreetypesofbatsmenwiththeonlydifferencebetweenthembeingthenumberofrunstheyscoreinagivennumberofballs.LetusnowdefineaprotocolPlayer,whosemainfunctionistoplay.
protocolPlayer{funcplay()}
WenowdefineaCricketerclassconformingtoPlayerprotocol.
classCricketer:Player{varnumberOfBalls:Intvarbatsman:Batsmaninit(_batsman:Batsman,_numberOfBalls:Int){self.batsman=batsmanself.numberOfBalls=numberOfBalls}funcplay(){
batsman.makeRuns(numberOfBalls)}}
Cricketerclasstakestwoparametersduringitsinitialisation,oneoftypeBatsmanandtheotheroftypeInt.ThisiswherewearebridgingbetweenBatsmanclassandPlayerclassbycallingmakeRunsmethodofbatsmanintheplaymethod.
Letusnowdefineourmainfunctionandseehowthisdesignpatternworks.
funcmain(){lettestBatsman=TestBatsman()letodiBatsman=ODIBatsman()lett20Batsman=T20IBatsman()letcricketer1=Cricketer(testBatsman,20)letcricketer2=Cricketer(odiBatsman,20)letcricketer3=Cricketer(t20Batsman,20)cricketer1.play()cricketer2.play()cricketer3.play()}
main()OutputintheXcodeconsole:
IamaTestBatsmanandIscore12.0runsin20ballsIamaODIBatsmanandIscore20.0runsin20ballsIamaT20BatsmanandIscore28.0runsin20balls
Summary:
Whenyouareinasituationwhereyouhavetochangetheimplementationobjectinsidetheabstraction,andwhenyouneedtoextendaclassinseveral
independentdimensions,Bridgedesignpatternservesthebest.
12)Structural-CompositeDesignPatternDefinition:
Compositeisastructuraldesignpatternthatletsuscomposeobjectsintotreestructuresandallowsclientstoworkwiththesestructuresasiftheywereindividualobjects.Compositionletsusmakecompoundobjects.
Usage:
Assumewearebuildingatreestructureofacricketteamwhereeachentitycontainsname,role,andgradeofcontractasattributes.Let’sseehowwecanuseCompositedesignpatterntobuildsuchasystem.
importUIKitimportFoundation
classCricketTeamMember:CustomStringConvertible{varname:Stringvarrole:Stringvargrade:StringvarteamMembers:[CricketTeamMember]init(name:String,role:String,grade:String){self.name=nameself.role=roleself.grade=gradeself.teamMembers=[CricketTeamMember]()}
funcaddMember(member:CricketTeamMember){teamMembers.append(member)}funcremoveMember(member:CricketTeamMember){teamMembers.append(member)}funcgetListOfTeamMembers()->[CricketTeamMember]{returnteamMembers}vardescription:String{letdemo="\(name)\(role)\(grade)"returndemo}}
Let’sstartwithdefiningaclasscalledCricketTeamMemberconformingtoCustomStringConvertible.IthasfourpropertieslikenameoftypeString,roleoftypeString,gradeoftypeString,andanarrayofteamMembersoftypeCricketTeamMember.Ittakesthreeparametersforitsinitialisation:name,role,andgradeoftypeString.
WedefineafunctioncalledaddMember,whichtakesaCricketTeamMemberobjectasparameterandappendsittotheteamMembersarray.
WehaveafunctionnamedremoveMember,whichtakesaCricketTeamMemberobjectasparameterandremovesitfromteamMembersarray.
WehaveanotherfunctioncalledgetListOfTeamMembers,whichreturnslistofteammembers.
LetusnowdefinemainfunctionandseehowtheCompositepatterncanbeusedtodefineatreestructure.
funcmain(){
//1
letheadCoach=CricketTeamMember(name:"HeadCoach",role:"HeadCoach",grade:"A")letcaptain=CricketTeamMember(name:"TeamCaptain",role:"Captain",grade:"B")letbowlingCoach=CricketTeamMember(name:"BowlingCoach",role:"Coach",grade:"B")letbattingCoach=CricketTeamMember(name:"BattingCoach",role:"Coach",grade:"B")letfieldingCoach=CricketTeamMember(name:"FieldingCoach",role:"Coach",grade:"B")letasstBowlingCoach=CricketTeamMember(name:"ABoC1",role:"AsstCoach",grade:"C")letasstBattingCoach=CricketTeamMember(name:"ABaC1",role:"AsstCoach",grade:"C")letasstFieldingCoach=CricketTeamMember(name:"ABfC1",role:"AsstCoach",grade:"C")letteamMember1=CricketTeamMember(name:"TM1",role:"Player",grade:"B")letteamMember2=CricketTeamMember(name:"TM2",role:"Player",grade:"B")//2
headCoach.addMember(member:captain)headCoach.addMember(member:bowlingCoach)headCoach.addMember(member:battingCoach)headCoach.addMember(member:fieldingCoach)captain.addMember(member:teamMember1)captain.addMember(member:teamMember2)bowlingCoach.addMember(member:asstBowlingCoach)battingCoach.addMember(member:asstBattingCoach)fieldingCoach.addMember(member:asstFieldingCoach)
//3
print(headCoach.description)formemberinheadCoach.getListOfTeamMembers(){print(member.description)formemberinmember.getListOfTeamMembers(){print(member.description)}}}
main()
Let’sreadthismethodstep-by-stepnow.
1. 1) HerewedefinedifferentteammembersusingtheinstanceofCricketTeamMember.WecanseedifferentroleslikeHeadCoach,TeamCaptain,BowlingCoach,etc.
1. 2) Wethenstartformingtreesbyaddingallthecaptainsandcoachesunderheadcoach,addingteammembersunderteamcaptain,etc.
1. 3) Herewestartprintingthetrees.InitiallyweprintthedescriptionofHeadCoachandthenweloopthroughalltheteammembersaddedunderhimandprinttheirdescriptionstoo.
OutputintheXcodeconsole:
HeadCoachHeadCoachATeamCaptainCaptainBTM1PlayerBTM2PlayerBBowlingCoachCoachBABoC1AsstCoachCBattingCoachCoachB
ABaC1AsstCoachCFieldingCoachCoachBABfC1AsstCoachC
Summary:
Whenyouareinasituationtosimplifythecodeattheclient’sendthathastointeractwithacomplextreestructure,thengoforCompositedesignpattern.Inotherwords,itshouldbeusedwhenclientsneedtoignorethedifferencebetweencompositionsofobjectsandindividualobjects.
13)Structural-DecoratorDesignPatternDefinition:
Decoratorisastructuraldesignpatternthatletsusaddnewbehaviourtotheobjectswithoutalteringtheclassitself.Ithelpsusinkeepingthenewfunctionalitiesseparatewithouthavingtorewriteexistingcode.
Usage:
AssumewearecheckingifaplayerisfitforplayingT20gameofcricketasabowlerorbatsmanorbothornone,basedonhisbattingandbowlingstatistics.LetusseehowDecoratordesignpatterncanhelpushere.
importUIKitimportFoundation
classT20Batsman{varstrikeRate:Int=0funcmakeRuns()->String{return(strikeRate>130)?"FitforT20TeamasBatsman":"TooslowBatsmanforT20Team"}}
WewriteaclasscalledT20BatsmanwithapropertycalledstrikeRateoftypeInt.IthasafunctiondefinedmakeRuns,whichtellsusifthebatsmanisfitforT20teambasedonhisstrikeRate.Ifthestrikerateismorethan130,heisfitasT20
batsman,otherwiseheistooslowforthegame.
classT20Bowler{vareconomyRate:Float=0funcbowlEconomically()->String{return(economyRate<8.0)?"FitforT20TeamasBowler":"TooexpensiveasaBowler"}}
WethendefineaclasscalledT20BowlerwithapropertycalledeconomyRateoftypeFloat.IthasafunctiondefinedcalledbowlEconomically,whichtellsusifthebowlerisfitforT20teambasedonhiseconomyRate.Iftheeconomyrateislessthan8.0,heisfitasT20bowler,otherwiseheistooexpensiveasabowlerforthegame.
classT20AllRounder:CustomStringConvertible{privatevar_strikeRate:Int=0privatevar_economyRate:Float=0privatelett20Batsman=T20Batsman()privatelett20Bowler=T20Bowler()funcmakeRuns()->String{returnt20Batsman.makeRuns()}funcbowlEconomically()->String{returnt20Bowler.bowlEconomically()}varstrikeRate:Int{get{return_strikeRate}set(value){t20Batsman.strikeRate=value
_strikeRate=value}}vareconomyRate:Float{get{return_economyRate}set(value){t20Bowler.economyRate=value_economyRate=value}}vardescription:String{ift20Batsman.strikeRate>130&&t20Bowler.economyRate<8{return"FitasT20AllRounder"}else{varbuffer=""buffer+=t20Batsman.makeRuns()buffer+="&"+t20Bowler.bowlEconomically()returnbuffer}}}
WenowdefineaclassforT20AllRounderconformingtoCustomStringConvertible.Allrounderissomeoneincricketwhocanbatandbowlreasonablywell.Ithasfourprivatevariables,strikeRateandeconomyRateoftypeIntandFloat,alongwithtwomorevariablesoftypeT20BatsmanandT20Bowler.
Thisallroundershouldbeabletomakerunsandbowlwell.Ithastwofunctionsdefined:
1. 1) makeRuns:HereweusetheinstanceofT20BatsmanvariabletocallthemakeRunsmethodandseeifheisfitasT20Batsmanbasedondefinedcriteriaforstrikerate.
1. 2) bowlEconomically:HereweusetheinstanceofT20BowlervariabletocallthebowlEconomicallymethodandseeifheisfitasT20Bowlerbasedondefinedcriteriaforeconomyrate.
Inthefuture,ifwewanttochangetheconditionsforbatsmenorbowlerorboth,wedonothavetodisturbthecodewrittenforallrounderclass.JustchangingthecodeinT20BatsmanandT20Bowlerclasseswillbeenough.
Letusnowwriteamainfunctiontoseethecodeinaction.
funcmain(){lett20AllRounder=T20AllRounder()t20AllRounder.strikeRate=120t20AllRounder.economyRate=7print(t20AllRounder.description)
}
main()
WetakeaninstanceofT20AllRounderclassandfeedinthestrikeRateandeconomyRateandseeifacertainplayerisfitornot.
OutputintheXcodeconsole:
TooslowBatsmanforT20Team&FitforT20TeamasBowler
KeepchangingtheinputsforstrikeRateandeconomyRateandseeiftheplayerisfitforT20gameofcricket.
t20AllRounder.strikeRate=150t20AllRounder.economyRate=7
Prints:FitasT20AllRounder
t20AllRounder.strikeRate=150t20AllRounder.economyRate=9
Prints:FitforT20TeamasBatsman&TooexpensiveasaBowler
t20AllRounder.strikeRate=120t20AllRounder.economyRate=9
Prints:TooslowBatsmanforT20Team&TooexpensiveasaBowler
Summary:
Ifyouareinasituationwhereyouarelookingforsomethingmoreflexiblethanclassinheritanceandneedtoedit/updatebehavioursatruntime,thenDecoratordesignpatternservesyoubetter.
14)Structural-FacadeDesignPatternDefinition:
Facadeisastructuraldesignpatternthatletsusexposeseveralpatternsthroughasingle,easy-to-useinterface.Facadedefinesahigher-levelinterfacethatmakesthesubsystemeasiertousebywrappingacomplicatedsubsystemwithasimplerinterface.
Usage:
Assumewearebuildinganimaginaryplayerauctionsystemforaprivatecricketleague.Anyteamwithanidandanamecanbuyplayerswhohaveanid,roleintheteam,andaprice.Let’swritesomecodeforthis:
importUIKitimportFoundation
//TeamrepresentsanobjectthatcanbuyaplayerpublicstructTeam{publicletteamId:StringpublicvarteamName:String}
publicstructPlayer{publicletplayerId:StringpublicvarprimaryRole:Stringpublicvarprice:Double}
WedefineTeamstructthatholdsthepropertiesofteamIdandteamNameasString.ThenthereisanotherstructforPlayerthatholdsplayerId,primaryRoleasStringandpriceasDouble.
//AnySwifttypethatconformstheHashableprotocolmustalsoconformtheEquatableprotocol.BecauseHashableprotocolisinheritedfromEquatableprotocol.
extensionTeam:Hashable{publicvarhashValue:Int{returnteamId.hashValue}
publicstaticfunc==(lhs:Team,rhs:Team)->Bool{returnlhs.teamId==rhs.teamId}
}
extensionPlayer:Hashable{publicvarhashValue:Int{returnplayerId.hashValue}publicstaticfunc==(lhs:Player,rhs:Player)->Bool{returnlhs.playerId==rhs.playerId}}
Wewriteacoupleofextensions,oneforTeamandoneforPlayer,eachconformingtoHashableprotocol.Whenweconformtoahashableprotocol,wemusthaveahashValueproperty.
HashableisatypethathashashValueintheformofanintegerthatcanbecomparedacrossdifferenttypes.WegetthehashValueasteamId.hashValue.
AppledefinesEquatableasatypethatcanbecomparedforvalueequality,which
ispartoftheworkingdefinitionforahashableprotocol.
WethenusemandatorymethodrelatedtoHashableprotocolthatcomparesthetypeandcheckstoseeiftheyareequal.
publicclassAvailablePlayersList{publicvaravailablePlayers:[Player:Int]=[:]publicinit(availablePlayers:[Player:Int]){self.availablePlayers=availablePlayers}}
publicclassSoldPlayersList{publicvarsoldPlayers:[Team:[Player]]=[:]}
WedefineaclasscalledAvailablePlayersList.IthasavariablenamedavailablePlayersoftypeDictionary.
ThenwehaveanotherclasscalledSoldPlayerList,whichhasavariablenamedsoldPlayersthatbasicallymaintainsalistofplayersboughtbyacertainteam.
Nowwedefineourfacadewiththehelpoftheclassesdefinedabove!
publicclassAuctionFacade{publicletavailablePlayersList:AvailablePlayersListpublicletsoldPlayersList:SoldPlayersListpublicinit(availablePlayersList:AvailablePlayersList,soldPlayersList:SoldPlayersList){self.availablePlayersList=availablePlayersListself.soldPlayersList=soldPlayersList}publicfuncbuyAPlayer(forplayer:Player,byteam:Team){
print("Readytobuy\(player.primaryRole)withid'\(player.playerId)'-'\(team.teamName)'")letcount=availablePlayersList.availablePlayers[player,default:0]guardcount>0else{print("'\(player.primaryRole)'issoldout")return}availablePlayersList.availablePlayers[player]=count-1varsoldOuts=soldPlayersList.soldPlayers[team,default:[]]soldOuts.append(player)soldPlayersList.soldPlayers[team]=soldOutsprint("\(player.primaryRole)with\(player.playerId)"+"boughtby'\(team.teamName)'")}}
AuctionFacadetakestwoparametersduringitsinitialisation,oneoftypeAvailablePlayersListandoneoftypeSoldPlayerList.WethendefineapublicmethodbuyAplayer.
Whenaplayerisbought,thecountforthattypeofplayerisreducedbyoneinavailablePlayerList.ThesameplayerisappendedtothelistofsoldPlayersList.
Let’snowwriteamainfunctiontoseeourfacadeinaction.
funcmain(){letbowler1=Player(playerId:"12345",primaryRole:"Bowler",price:123)letbatsman1=Player(playerId:"12365",primaryRole:"Batsman",price:152)letavailablePlayerList=AvailablePlayersList(availablePlayers:[bowler1:3,batsman1:45])letauctionFacade=AuctionFacade(availablePlayersList:availablePlayerList,
soldPlayersList:SoldPlayersList())letteam1=Team(teamId:"XYZ-123",teamName:"Sydney")auctionFacade.buyAPlayer(for:bowler1,by:team1)}
main()
Wedefinebowler1andbatsman1asPlayertypeobjects.WetheninitialiseAvailablePlayerListwith3bowler1typePlayersand45batsman1typePlayers.
WethentakeaninstanceofAuctionFacadeandprovideavailablePlayerListandinstanceofSoldPlayerListasparameters.
OutputintheXcodeconsole:
ReadytobuyBowlerwithid'12345'-'Sydney'Bowlerwith12345boughtby'Sydney'
Summary:
Whenyouwanttoprovideasimpleinterfacetoacomplexsubsystemandhaveasingleinterfacefortraversingdifferentdatastructures,Facadedesignpatternsworksthebest.
15)Structural-FlyWeightDesignPatternDefinition:
FlyWeightisastructuraldesignpatternthathelpsinavoidingredundancywhilestoringdata.IthelpsfitmoreobjectsintheavailableamountofRAMbyreusingalreadyexistingsimilarkindsofobjectsbystoringthemandcreatinganewobjectwhennomatchingobjectisfound.
Assumeyouarestoringfirstandlastnamesinmemory.Whentherearemanypeoplewithidenticalfirstandlastnames,thereisnopointinstoringthemagainandagainasanewentity.InsteadweusesomethinglikeFlyWeightdesignpatterntosavethestoragespace.
Usage:
Letusconsiderasituationwherewearestoringplayerprofileswhereeachentityconsistsofplayer’snameoftypeStringandtheteamsheplayedforoftypeStringarrayasattributes.
WewritethecodewithoutusingFlyWeightdesignpatternandcheckthememoryoccupied.
importUIKitimportFoundation
classPlayerProfile{varfullName:StringvarteamsPlayedFor:[String]init(_fullName:String,_teamsPlayedFor:[String]){
self.fullName=fullNameself.teamsPlayedFor=teamsPlayedFor}varcharCount:Int{varcount=0forteaminteamsPlayedFor{count+=team.utf8.count}count+=fullName.utf8.countreturncount}}
WedefineaclasscalledPlayerProfile,whichtakesfullNameoftypeStringandteamsPlayedForoftypeStringarrayasparametersduringitsinitialisation.
WethendefineavariablecalledcharCount,whichisanindicatorofthememoryoccupied.Letuswriteourmainfunctionandcheckthecharactercount.
funcmain(){letdhoni=PlayerProfile("MahendraDhoni",["India,Chennai"])letkohli=PlayerProfile("ViratKohli",["India,Bangalore"])letyuvi=PlayerProfile("YuvrajSingh",["India,Punjab"])print("Totalnumberofcharsused:",dhoni.charCount+kohli.charCount+yuvi.charCount)}
main()
WedefineafewinstancesofPlayerProfilebypassingtheplayers’namesandtheirteamsasparameters.ThenweusethecharCountpropertyonalltheinstancesandprintittotheconsole.
OutputintheXcodeconsole:
Totalnumberofcharsused:82
LetusnowuseFlyWeightdesignpatternforthesameusecase.
classPlayerProfileOptimised{staticvarstringsArray=[String]()privatevargenericNames=[Int]()init(_fullName:String,_teamsPlayedFor:[String]){funcgetOrAdd(_s:String)->Int{ifletidx=type(of:self).stringsArray.index(of:s){returnidx}else{type(of:self).stringsArray.append(s)returntype(of:self).stringsArray.count-1}}genericNames=fullName.components(separatedBy:"").map{getOrAdd($0)}forteaminteamsPlayedFor{genericNames=team.components(separatedBy:"").map{getOrAdd($0)}}}staticvarcharCount:Int{returnstringsArray.map{$0.utf8.count}.reduce(0,+)}}
WedefineaclasscalledPlayerProfileOptimised.HerewedefineastaticvariablecalledstringsArray,whichstoresdifferentstringsthatmayormaynotberepeated.Wethendefineanon-staticvariablecalledgenericNames,whichis
goingtokeepanarrayofindices.
Ininitialisationmethod,wehaveaninnerfunctioncalledgetOrAdd,whichtakesastringasaparameterandreturnstheindexofthestringinstringsArrayifalreadyexisting,orreturnstheindexbyaddingittothestringsArrayarrayatthetailend.
WetheninitialisethegenericNamesarraybytakingthefullname,splittingitintoacomponentseparatedbyspace,andmappingitbycallingthefunctiongetOrAddwithaparameter.ThisletsusgetgenericNamestobeinitialisedtoasetofindicesthatcorrespondtothestringsinsidethestringsArrayarray.
Letusnowwriteamainfunctionandcheckthecharactercount:
funcmain(){letdhoni1=PlayerProfileOptimised("MahendraDhoni",["India,Chennai"])letkohli1=PlayerProfileOptimised("ViratKohli",["India,Bangalore"])letyuvi1=PlayerProfileOptimised("YuvrajSingh",["India,Punjab"])print("Totalnumberofcharsused:",PlayerProfileOptimised.charCount)}
main()
OutputintheXcodeconsole:
Totalnumberofcharsused:63
Forthesamedata,thenumberofcharactersreducedsignificantly.That’showFlyWeightdesignpatterncanbeusedforefficientstorageofdata.
Summary:
Whenyouareinasituationtostoredatathatmightcontainasignificantamountofduplicatedata,youcanuseFlyWeightdesignpattern.ThishelpsinreducingtheusageofavailableRAM.
16)Structural-ProxyDesignPatternDefinition:
Talkingrealworldterms,yourdebitcardisaproxyofyourbankaccount.It’snotrealmoney,itbutcanbesubstitutedformoneywhenyouwanttobuysomething.
Proxyisastructuraldesignpatternthatuseswrapperclassestocreateastand-inforarealresource.Itisalsocalledsurrogate,handle,andwrapper.Proxyisusedtocoverthemainobject’scomplexlogicfromtheclientusingit.
Usage:
Assumewearedesigningasmallsoftwaretofilterapplicantsforthepositionofheadcoachofacricketteam.Theclientonlypassesthenumberofyearsoftheapplicant’sexperience,andweneedtowritealogictosayiftheapplicantisfitfortheroleornot,withoutdisturbingtheclient.
LetusseehowwecanuseProxydesignpatternhere:
importUIKitimportFoundation
protocolCoach{funcmentorTheTeam()}
classCricketCoach:Coach{
funcmentorTheTeam(){print("MentoringtheCricketTeam")}}
WedefineaprotocolcalledCoach,whosemainjobistomentortheteam.ThenwedefineaclasscalledCricketCoachconformingtoCoachprotocol.
classCoachApplicant{varnumberOfYearsOfExperience:Intinit(numberOfYearsOfExperience:Int){self.numberOfYearsOfExperience=numberOfYearsOfExperience}}
WewriteaclasscalledCoachApplicant,whichtakesnumberOfYearsOfExperienceoftypeIntasparameterduringitsinitialisation.
NowwewriteaproxyconformingtoCoachprotocoltodefinethelogicinordertofilterapplicants.
classCricketCoachProxy:Coach{privateletcricketCoach=CricketCoach()privateletcoachApplicant:CoachApplicantinit(coachApplicant:CoachApplicant){self.coachApplicant=coachApplicant}funcmentorTheTeam(){ifcoachApplicant.numberOfYearsOfExperience>=8{cricketCoach.mentorTheTeam()
}else{print("Notenoughexperiencetocoachtheteam")}}}
Ithastwoprivatevariables,oneoftypeCricketCoachandoneoftypeCoachApplicant.InmentorTheTeammethod,wedefinethelogic.Iftheexperienceofthecoachapplicantismorethan8years,heisthrough,otherwiseheisrejected.
Letusnowwriteourmainmethod:
funcmain(){letcoach:Coach=CricketCoachProxy(coachApplicant:CoachApplicant(numberOfYearsOfExperience:8))coach.mentorTheTeam()}
main()
OutputintheXcodeconsole:
MentoringtheCricketTeam
Keepchangingtheexperienceparameterandchecktheoutput.
funcmain(){letcoach:Coach=CricketCoachProxy(coachApplicant:CoachApplicant(numberOfYearsOfExperience:5))coach.mentorTheTeam()}
main()
OutputintheXcodeconsole:
Notenoughexperiencetocoachtheteam
Inthefuture,ifwewanttochangethecriteriafrom8yearsto10yearsor6years,wedonothavetochangeanycodeattheclient’send.Wecanjustchangethelogicintheproxyandthingswillworkjustfine.
Summary:
Whenyouwanttocreateawrapperaroundamainobjecttohideitscomplexityfromtheclient,Proxydesignpatternsuitsthebest.Italsohelpsindelayingtheobject’sinitialisationsothatyoucanloadtheobjectsonlywhenitisneeded.
PartFour:Behavioural
17)Behavioural-ChainofResponsibilityDesignPattern
Definition:
ChainofResponsibilityisabehaviouraldesignpatternthatallowsustoavoidcouplingthesenderofarequesttoitsreceiverbygivingmultipleobjectsachancetohandletherequest.
Usage:
Assumewearebuildingasmallcricketvideogamewherewechoosetheplayercharacterswiththeirdefaultskills.Butthenwealsogiveprovisiontoaddskillboosterstotheplayercharactersasgamersgainsomecredits.LetusseehowwecanuseChainofResponsibilitytodesignsuchasystem.
importFoundation
classCricketer:CustomStringConvertible{varname:StringvarbattingSkillRating:IntvarbowlingSkillRating:IntvarfieldingSkillRating:Intinit(_name:String,_battingSkillRating:Int,_bowlingSkillRating:Int,_fieldingSkillRating:Int){self.name=nameself.battingSkillRating=battingSkillRatingself.bowlingSkillRating=bowlingSkillRatingself.fieldingSkillRating=fieldingSkillRating}
vardescription:String{return"Cricketer:\(name)withbattingRating:\(battingSkillRating),bowlingRating:\(bowlingSkillRating),fieldingRating:\(fieldingSkillRating)"}}
WedefineaclasscalledCricketerconformingtoCustomStringConvertible.DuringitsinitialisationittakesparametersofnameoftypeString,battingSkillRatingoftypeInt,bowlingSkillRatingoftypeInt,andfieldingSkillRatingoftypeInt.
classSkillBooster{letcricketer:CricketervarskillBooster:SkillBooster?init(_cricketer:Cricketer){self.cricketer=cricketer}funcaddBooster(_booster:SkillBooster){ifskillBooster!=nil{skillBooster!.addBooster(booster)}else{skillBooster=booster}}funcplayTheGame(){skillBooster?.playTheGame()}}
WethendefineaclasscalledSkillBooster,whichismeanttobeabaseclassfordifferenttypesofboosters.IttakesaparameteroftypeCricketerduringitsinitialisation.WealsodefineanoptionalprivatevariableoftypeSkillBooster.Ithastwomethodsdefined,addBoosterandplayTheGame.addBoostertakesaparameteroftypeSkillBoosterandaddsittoexistingboostersafternilcheck.Otherwise,skillBoosterisassignedthevalueofincomingbooster.
classBattingSkillBooster:SkillBooster{overridefuncplayTheGame(){print("AddingHookShotto\(cricketer.name)'sBatting")cricketer.battingSkillRating+=1super.playTheGame()}}
classBowlingSkillBooster:SkillBooster{overridefuncplayTheGame(){print("AddingReverseSwingto\(cricketer.name)'sBowling")cricketer.bowlingSkillRating+=1super.playTheGame()}}
classFieldingSkillBooster:SkillBooster{overridefuncplayTheGame(){print("AddingDiveCatchesto\(cricketer.name)'sFielding")cricketer.fieldingSkillRating+=1super.playTheGame()}}
WedefinedifferenttypesofskillboosterswithSkillBoosterasthebaseclass.Inalltheboosters,weoverridethefunctionplayTheGameandimprovethecorrespondingskillratingoftheplayercharacterby1.
classNoSkillBooster:SkillBooster{overridefuncplayTheGame(){print("Noboostersavailablehere")//don'tcallsuper}}
Wealsodefineadummyskillboosterjusttomakethegamemoreinteresting.
Letusnowwriteamainfunctiontoseethecodeinaction.
funcmain(){letdhoni=Cricketer("Dhoni",6,3,7)print(dhoni)}
main()
OutputintheXcodeconsole:
Cricketer:DhoniwithbattingRating:6,bowlingRating:3,fieldingRating:7
Nowchangethemainmethodtothefollowing:
funcmain(){letdhoni=Cricketer("Dhoni",6,3,7)print(dhoni)letskillBooster=SkillBooster(dhoni)print("AddingBattingBoostertoDhoni")skillBooster.addBooster(BattingSkillBooster(dhoni))skillBooster.playTheGame()print(dhoni.description)}
main()
OutputintheXcodeconsole:
Cricketer:DhoniwithbattingRating:6,bowlingRating:3,fieldingRating:7AddingBattingBoostertoDhoniAddingHookShottoDhoni'sBattingCricketer:DhoniwithbattingRating:7,bowlingRating:3,fieldingRating:7
WeaddBattingSkillBoostertoobjectdhonioftypeCricketer.Wecanchecktheoutputintheconsoleforanimprovedratingondhoni’sbattingskills.
Changethemainmethodtothefollowingandobservetheconsole:
funcmain(){letdhoni=Cricketer("Dhoni",6,3,7)letskillBooster=SkillBooster(dhoni)print("AddingBattingBoostertoDhoni")skillBooster.addBooster(BattingSkillBooster(dhoni))print("AddingBowlingBoostertoDhoni")skillBooster.addBooster(BowlingSkillBooster(dhoni))skillBooster.playTheGame()print(dhoni.description)}
main()
OutputintheXcodeconsole:
AddingBattingBoostertoDhoniAddingBowlingBoostertoDhoniAddingHookShottoDhoni'sBattingAddingReverseSwingtoDhoni'sBowlingCricketer:DhoniwithbattingRating:7,bowlingRating:4,fieldingRating:7
Summary:
UsetheChainofResponsibilitypatternwhenyoucanconceptualizeyourprogramasachainmadeupoflinks,whereeachlinkcaneitherhandlearequestorpassitupthechain.Itcanmodifyanexistingbehaviourbyoverridinganexistingmethodusinginheritance.
18)Behavioural-StrategyDesignPatternDefinition:
Strategyisabehaviouraldesignpatternthatletsyoudefineasetofencapsulatedalgorithmsandenablesselectingoneofthematruntime.Animportantpointtoobserveisthatthesealgorithmimplementationsareinterchangeable.Inotherwords,strategyletsthealgorithmvaryindependentlyfromtheclientsthatuseit.
Usage:
ConsideranexampleofaBowlingMachinethatreleasesballsofdifferentcoloursbasedontheinputofspeedspecifiedbytheuser.Assumewehavethreedifferentspeeds:slow,medium,andfast,whichcorrespondstoyellow,green,andredcolouredballsrespectively.
importUIKit
enumCricketBall:String{caseslow="Yellow"casemedium="Green"casefast="Red"}
WenowdefineaprotocolReleaseCricketBallStrategy,whichhaspropertiesofspeedandthetypeofcricketball,andwhichalsodefinesamethodtoreleasetheball.
protocolReleaseCricketBallStrategy{varspeed:String{getset}varcricketBall:CricketBall{getset}
funcreleaseBall()->String}
Wenowdefinethreenewclasses,oneeachforfast,medium,andslowballstrategies.
EachoftheclassesconformtoReleaseCricketBallStrategyprotocol.Forthesakeofsimplicity,wedefinespeedasastringwhichcanbeFast,Medium,orSlow.Eachoftheclasseshasaninitialiserthatdoesnottakeanyextraarguments.
releaseBallmethodreturnsastringimplyingthatitsimplementationreleasesaballwithspecifiedproperties.
classFastBallStrategy:ReleaseCricketBallStrategy{
varspeed="Fast"varcricketBall=CricketBall.fastinit(){}
funcreleaseBall()->String{return"Released\(speed)ballwithcolour\(cricketBall.rawValue)"}}
classMediumBallStrategy:ReleaseCricketBallStrategy{varspeed="Medium"varcricketBall=CricketBall.mediuminit(){}
funcreleaseBall()->String{return"Released\(speed)ballwithcolour\(cricketBall.rawValue)"}}
classSlowBallStrategy:ReleaseCricketBallStrategy{varspeed="Slow"varcricketBall=CricketBall.slowinit(){}
funcreleaseBall()->String{return"Released\(speed)ballwithcolour\(cricketBall.rawValue)"}}
Now,wedefineaBowlingMachineclass,whichcanbeinitialisedatruntimebypassinganargumentofthetypeofstrategy.WemakeitconformtoCustomStringConvertible.
classBowlingMachine:CustomStringConvertible{privatevarreleaseCricketBallStrategy:ReleaseCricketBallStrategyprivatevarreturnString=""init(whatStrategy:ReleaseCricketBallStrategy){self.releaseCricketBallStrategy=whatStrategyreturnString=releaseCricketBallStrategy.releaseBall()}vardescription:String{returnreturnString}}
It’snowtimetoplaywithourbowlingmachine.Wedefineamethodnamedmain,whereweinitialiseBowlingMachineclasswithdifferenttypesofstrategies.
funcmain(){varbowlingMachine=BowlingMachine(whatStrategy:FastBallStrategy())print(bowlingMachine.description)
bowlingMachine=BowlingMachine(whatStrategy:SlowBallStrategy())print(bowlingMachine.description)
bowlingMachine=BowlingMachine(whatStrategy:MediumBallStrategy())print(bowlingMachine.description)
}
Now,runthemain()method.
main()
OutputintheXcodeconsole:
ReleasedFastballwithcolourRedReleasedSlowballwithcolourYellowReleasedMediumballwithcolourGreen
Summary:
Strategypatternallowsustodefineasetofrelatedalgorithmsandallowstheclienttochooseanyofthealgorithmsatruntime.Itallowsustoaddanewalgorithmwithoutmodifyingexistingalgorithms.
19)Behavioural-CommandDesignPattern:
Definition:
ThedefinitionofCommandprovidedintheoriginalGangofFourbookonDesignPatternsstates:
Encapsulatearequestasanobject,therebylettingyouparameterizeclientswithdifferentrequests,queueorlogrequests,andsupportundoableoperations.
Commandisabehaviouraldesignpatternthatdecouplestheobjectinvokingtheoperationfromtheobjectthatknowshowtoperformit.Itallowsustoturnrequestsintostand-aloneobjectsbyprovidingrequestobjectswithallthenecessaryinformationfortheactiontobetaken.Usage:Letusconsideradecisionreviewsysteminacricketmatchwheretheon-fieldumpireisnotsureifabatsmanisoutornot.ThisumpirethenaskstheTVumpiretocheckTVreplaysandmakeadecision.TheTVumpirethencommandstheTVoperatortoshowOUT/NOTOUTonthescreen,dependinguponthedecisionmade.Let’sseehowwecandesignsuchasystemwiththehelpofCommanddesignpattern.importUIKit
importFoundation
protocolCommand{funcexecute()}
WedefineaprotocolnamedCommandwithafunctionnamedexecute.classScreenDisplay{privatevarshowOutOnDisplay=falsefuncisBatsmanOut(){showOutOnDisplay=trueprint("BatsmanisOUT")}funcisBatsmanNotOut(){showOutOnDisplay=falseprint("BatsmanisNOTOUT")}
}
WethendefineaclasscalledScreenDisplay,whichisusedtodisplaythedecisionmadebytheTVumpire.IthasaprivatevariablenamedshowOutOnDisplay,whichisinitialisedtofalse.Ithastwomethodsdefined.Basedontheboolproperty,thesemethodsshowbatsmanOUT/NOTOUTonthescreen.classBatsmanOutCommand:Command{varscreenDisplay:ScreenDisplayinit(_screenDisplay:ScreenDisplay){self.screenDisplay=screenDisplay}funcexecute(){screenDisplay.isBatsmanOut()}}
classBatsmanNotOutCommand:Command{varscreenDisplay:ScreenDisplayinit(_screenDisplay:ScreenDisplay){self.screenDisplay=screenDisplay}funcexecute(){screenDisplay.isBatsmanNotOut()}}
Wethenwritetwoclasses,BatsmanOutCommandandBatsmanNotOutCommand,conformingtoCommandprotocol.BoththeseclassestakeaparameteroftypeScreenDisplayduringtheirinitialisation.ThenwewritethedefinitionofexecutemethodbycallingisBatsmanOut/isBatsmanNotOutonScreenDisplayobject.
classDisplaySwitch{varcommand:Commandinit(_command:Command){self.command=command}funcpressSwitch(){command.execute()}}
Finally,wewriteaclasscalledDisplaySwitch,whichtakesanobjectoftypeCommandforitsinitialisation.WedefineamethodcalledpressSwitch,whichimplementstheexecutemethodonCommandobject.Letuswriteourmainmethodandseeourcodeinaction.funcmain(){letscreenDisplay=ScreenDisplay()letoutCommand=BatsmanOutCommand(screenDisplay)letnotOutCommand=BatsmanNotOutCommand(screenDisplay)
letdisplaySwitchForOut=DisplaySwitch(outCommand)displaySwitchForOut.pressSwitch()letdisplaySwitchForNotOut=DisplaySwitch(notOutCommand)displaySwitchForNotOut.pressSwitch()}
main()
WetakeaninstanceofScreenDisplayandpassittoBatsmanOutCommandandBatsmanNotOutCommandfortheirinitialisations.
Then,basedonthedecisionmadebytheTVumpire,weinitialiseDisplaySwitchusingoutCommand/notOutCommandastheparameters.
OutputintheXcodeconsole:
BatsmanisOUTBatsmanisNOTOUT
Summary:
Whenyouwanttoencapsulatethelogicaldetailsofanoperationinaseparateentityanddefinespecificinstructionsforapplyingthecommand,Commanddesignpatternservesyouthebest.Italsohelpsincreatingcompositecommands.
20)Behavioural-IteratorDesignPatternDefinition:
Iterationincodingisacorefunctionalityofvariousdatastructures.Aniteratorfacilitatesthetraversalofadatastructure.Iteratorisabehaviouraldesignpatternthatisusedtosequentiallyaccesstheelementsofanaggregateobjectwithoutexposingitsunderlyingimplementation.
Usage:
Assumewearemakingalistoftopcricketersinacurrentlot,whichincludestheirnameandteamname.Wewillnowseehowtouseaniteratortotraversethroughthelistandprinttheprofileofeachcricketer.
Letuswritesomecodenow:
importFoundationstructCricketer{letname:Stringletteam:String}
WedefineastructnamedCricketer,whichstoresnameandteamasStringproperties.
structCricketers{letcricketers:[Cricketer]}
WedefineanotherstructnamedCricketers,whichstorescricketersarrayof
customtypeCricketer.
structCricketersIterator:IteratorProtocol{privatevarcurrent=0privateletcricketers:[Cricketer]init(_cricketers:[Cricketer]){self.cricketers=cricketers}mutatingfuncnext()->Cricketer?{defer{current+=1}ifcricketers.count>current{returncricketers[current]}else{returnnil}}}
extensionCricketers:Sequence{funcmakeIterator()->CricketersIterator{returnCricketersIterator(cricketers)}}
Thisiswherethemagichappens.WedefineastructnamedCricketersIteratorconformingtoIteratorProtocol.ThenwewriteanextensionforCricketersthatconformstoSequenceprotocol.
Applesays,
“TheIteratorProtocolprotocolistightlylinkedwiththeSequenceprotocol.Sequencesprovideaccesstotheirelementsbycreatinganiterator,whichkeepstrackofitsiterationprocessandreturnsoneelementatatimeasitadvances
throughthesequence.Wheneveryouuseafor-inloopwithanarray,set,oranyothercollectionorsequence,you’reusingthattype’siterator.Swiftusesasequence’sorcollection’siteratorinternallytoenablethefor-inlooplanguageconstruct.Usingasequence’siteratordirectlygivesyouaccesstothesameelementsinthesameorderasiteratingoverthatsequenceusingafor-inloop.”
Backtoourcode,wedefinedtwoprivateproperties,currentoftypeInt(withdefaultvalueof0)andanarraycricketersoftypeCricketer.
WedefineamethodnextthatreturnsanobjectoftypeCricketer(noticetheoptional-wemaynothaveanyelementleftinthearrayafterwereachthelastelement).
Letusnowwriteourmainmethod:
funcmain(){letcricketers=Cricketers(cricketers:[Cricketer(name:"Kohli",team:"India"),Cricketer(name:"Steve",team:"Australia"),Cricketer(name:"Kane",team:"Kiwis"),Cricketer(name:"Root",team:"England")])forcrickincricketers{print(crick)}}
main()
OutputintheXcodeconsole:
Cricketer(name:"Kohli",team:"India")Cricketer(name:"Steve",team:"Australia")Cricketer(name:"Kane",team:"Kiwis")Cricketer(name:"Root",team:"England")
Addingthecodesnippetforanotherself-explanatoryexamplehere,whichwouldenhanceyourunderstanding:
importFoundation
classCricketer:Sequence{vartotalRunsScored=[Int](repeating:0,count:3)privatelet_testRuns=0privatelet_ODIRuns=1privatelet_t20Runs=2vartestRuns:Int{get{returntotalRunsScored[_testRuns]}set(value){totalRunsScored[_testRuns]=value}}varODIRuns:Int{get{returntotalRunsScored[_ODIRuns]}set(value){totalRunsScored[_ODIRuns]=value}}vart20Runs:Int{get{returntotalRunsScored[_t20Runs]}set(value){totalRunsScored[_t20Runs]=value}}vartotalRuns:Int{returntotalRunsScored.reduce(0,+)}subscript(index:Int)->Int{get{returntotalRunsScored[index]}set(value){totalRunsScored[index]=value}}funcmakeIterator()->IndexingIterator<Array<Int>>{
returnIndexingIterator(_elements:totalRunsScored)}}
funcmain(){letcricketer=Cricketer()cricketer.testRuns=1200cricketer.ODIRuns=1800cricketer.t20Runs=600
print("TotalRunsScored=\(cricketer.totalRuns)")forsincricketer{print(s)}}
main()
OutputintheXcodeconsole:
TotalRunsScored=360012001800600
Summary:
Whenyouareinasituationwhereyouwanttohidethecomplexityofadatastructurefromclientsandhaveasingleinterfacefortraversingthedatastructures,Iteratordesignpatternservesyouthebest.
21)Behavioural-InterpreterDesignPatternDefinition:
Interpretersarepresenteverywherearoundus.Indesignpatterns,interpreterisacomponentthatprocessesstructuredtextdata.Itfallsunderbehaviouraldesignpattern.
Usage:
Letususeinterpreterdesignpatterntogetawaytoprinttheelementsofacollectionobjectinsequentialmanner.
importUIKitimportFoundation
protocolInterpreter{funchasNext()->Boolfuncnext()->String}
protocolContainer{funcgetInterpreter()->Interpreter}
WedefinetwoprotocolsnamedInterpreterandContainer.Interpreterhastwofunctionstocheckifthenextelementispresentinarrayaftereveryiterationandtoreturntheelement(ifpresent).Containerhasamethodtoreturntheinterpreter.
classNameRepo:Container{letnames=["India","Australia","England","NewZealand"]funcgetInterpreter()->Interpreter{returnNameInterpreter(names)}}
WethendefineaclasscalledNameRepoconformingtoContainerprotocol.Inourmainfunction,weuseiteratortoprintallthevaluespresentinthenamesarray.
privateclassNameInterpreter:Interpreter{varindex=-1varnames=[String]()init(_names:[String]){self.names=names}funchasNext()->Bool{ifindex<names.count{returntrue}returnfalse}funcnext()->String{ifself.hasNext(){index=index+1returnnames[index]}else{return""}}}
WedefineaclasscalledNameIteratorconformingtoIteratorprotocol.IttakesaparameteroftypeStringarrayduringitsinitialisation.
Letuswriteamainfunctiontoseethecodeinaction.
funcmain(){letnr=NameRepo()letinterpreter=NameInterpreter(nr.names)for_innr.names{interpreter.hasNext()print(interpreter.next())}}
main()
OutputintheXcodeconsole:
IndiaAustraliaEnglandNewZealand
Summary:
UsetheInterpreterpatternwhenthereisalanguagetointerpret,andyoucanrepresentstatementsinthelanguageasabstractsyntaxtrees.
22)Behavioural-MediatorDesignPatternDefinition:Mediatorisabehaviouraldesignpatternthatletsusdefineacomponentthatencapsulatesrelationshipsbetweenasetofcomponents(thatabsolutelymakesnosensetohavedirectreferencestooneanother)tomakethemindependentofeachother.Mediatorpatternpreventsdirectcommunicationbetweenindividualcomponentsbysendingrequeststoacentralcomponentthatknowswheretoredirectthoserequests.Usage:LetusassumewearedesigningaTVumpiredecisionreviewsystemforacricketmatch.Whenanon-fieldumpiredoesnothaveenoughevidencetoruleabatsmanout,hesendstherequesttotheTVumpirewhotakesalookatthereplaysandsendsacommandtothemonitoroperatorwhodisplaysthefinaldecisiononthebigscreen.Let’sseehowwecanuseMediatordesignpatterntodesignsuchadecisionreviewsystem.importUIKitimportFoundation
protocolCommand{funcdisplayStatus()}
WewriteaprotocolCommandthatdefinesafunctioncalleddisplayStatus.
protocolRemoteUmpire{funcregisterTVDisplay(tvDisplay:TVDisplay)funcregisterTVOperator(tvOperator:TVOperator)funcisDecisionMade()->BoolfuncsetDecisionStatus(status:Bool)
}
WewriteanotherprotocolcalledRemoteUmpirethatdefinesahandfuloffunctionalities.
1. 1) Theremoteumpire(alsocalledTVumpire)hastoregisterforTVdisplaybypassingaparameteroftypeTVDisplay(tobedefined).
2. 2) Theremoteumpire(alsocalledTVumpire)hastoregisterforTVoperatorbypassingaparameteroftypeTVOperator(tobedefined).
3. 3) AfunctionnamedisDecisionMade,whichreturnsaboolean.4. 4) Anotherfunctiontosetthestatusofdecisionbypassingaboolean
argument.
classTVOperator:Command{vartvUmpire:TVUmpireinit(_tvUmpire:TVUmpire){self.tvUmpire=tvUmpire}funcdisplayStatus(){iftvUmpire.isDecisionMade(){print("DecisionMadeandBatsmaninOUT")tvUmpire.setDecisionStatus(status:true)}else{print("DecisionPending")}}funcgetReady(){print("ReadytoDisplayDecision")}}
WedefineaclasscalledTVOperatorconformingtoCommandprotocol.IttakesanobjectoftypeTVUmpire(tobedefined)duringitsinitialisation.
IthasamethodnameddisplayStatus,whichbasedontheTVumpire’sdecision,displaysabatsmanoutornotonthebigscreeninthestadium.
classTVDisplay:Command{vartvUmpire:TVUmpireinit(_tvUmpire:TVUmpire){self.tvUmpire=tvUmpiretvUmpire.setDecisionStatus(status:true)}
funcdisplayStatus(){print("DecisionmadeandpermissiongrantedtodisplaythedecisiononTVDisplay")tvUmpire.setDecisionStatus(status:true)}}
WedefineaclasscalledTVDisplayconformingtoCommandprotocol.ThisclassalsotakesanobjectoftypeTVUmpire(tobedefined)foritsinitialisation.ItsmainfunctionalityistodisplaythestatusofthedecisionbasedontheinputgivenbytheTVumpire.
classTVUmpire:RemoteUmpire{privatevartvOperator:TVOperator?privatevartvDisplay:TVDisplay?privatevardecisionMade:Bool?funcregisterTVDisplay(tvDisplay:TVDisplay){self.tvDisplay=tvDisplay}funcregisterTVOperator(tvOperator:TVOperator){self.tvOperator=tvOperator}funcisDecisionMade()->Bool{returndecisionMade!}
funcsetDecisionStatus(status:Bool){self.decisionMade=status}}
WethendefineourmostimportantclassnamedTVUmpireconformingtoRemoteUmpireprotocol.Ithasthreeprivateoptionalvariablesdefined:tvOperatoroftypeTVOperator,tvDisplayoftypeTVDisplay,andabooleannameddecisonMade.
ItregistersforTVdisplaybyassigningitspropertyoftvDisplaytoparameteroftypeTVDisplayfromthemethodregisterTVDisplay.
ItregistersforTVoperatorbyassigningitspropertyoftvOperatortoparameteroftypeTVOperatorfromthemethodregisterTVOperator.
Let’snowwriteourmainfunctionandseehowtheabovecodecomesintoaction.
funcmain(){lettvUmpire=TVUmpire()lettvDisplayAtGround=TVDisplay(tvUmpire)lettvOperatorAtGround=TVOperator(tvUmpire)tvUmpire.registerTVDisplay(tvDisplay:tvDisplayAtGround)tvUmpire.registerTVOperator(tvOperator:tvOperatorAtGround)tvOperatorAtGround.getReady()tvDisplayAtGround.displayStatus()tvOperatorAtGround.displayStatus()}
main()
WetakeaninstanceofTVUmpireandpassthesameinstancetoinitialiseTVDisplayandTVOperator.
TVUmpirethenregistersforTVDisplayandTVUmpirebypassinginstancesofTVDisplayandTVUmpirerespectively.
OncetheTVumpirehasmadehisdecision,TVoperatoronthegroundgetsreadytodisplaythestatusofthedecisionaccordinglyonthedisplayattheground.
OutputintheXcodeconsole:ReadytoDisplayDecisionDecisionmadeandpermissiongrantedtodisplaythedecisiononTVDisplayDecisionMadeandBatsmaninOUT
Summary:
UseaMediatordesignpatternwhenthecomplexityoftheobjectcommunicationbeginstohinderobjectreusability.Mediatorengagesintwo-waycommunicationwithitsconnectedcomponents.
23)Behavioural-MementoDesignPatternDefinition:
Mementoisabehaviouraldesignpatternthatletsussavethesnapshotsoftheobject’sinternalstateateverypointoftimewithoutexposingitsinternalstructure.Thishelpsusinrollingbacktothestatewhenthesnapshotwastaken.
Usage:
Assumeweareaddingthestatsofacricketer(numberofrunsscored)yearbyyearinourprogram,andatsomepointwewanttotracebacktoayearinthepastandcheckhisstatsupuntilthatpointintime.
Let’sdefineaMementoclass,whichtakesanargumentofnumberofrunsscoredinitsinitialisation.
importUIKitclassMemento{letnumberOfRunsScored:Intinit(_numberOfRunsScored:Int){self.numberOfRunsScored=numberOfRunsScored}}
Let’sassumeanimaginaryhardwarenamedStatsHolder,whichdisplaysthestats.ItconformstoCustomStringConvertibleprotocol.Ittakesanargumentofnumberofrunsscoredinitsinitialisation.
classStatsHolder:CustomStringConvertible{
privatevarnumberOfRunsScored:Intprivatevarsnapshots:[Memento]=[]privatevarcurrentIndex=0
init(_numberOfRunsScored:Int){self.numberOfRunsScored=numberOfRunsScoredsnapshots.append(Memento(numberOfRunsScored))}vardescription:String{return"TotalRunsscored=\(numberOfRunsScored)"}}
Allthepropertiesaredeclaredprivate,aswedonotwanttoexposetheinternalstructureofourhardwaretotheclient.
WemaintainanarrayofsnapshotsoftypeMementosothatwecanrestorepaststatsjustbypassingamemento.Whentheclassisinitialised,currentIndexstartsatzero.
Wenowaddafunctionnamed‘addStatsToHolder’,whichtakesnumberofrunsasparameterandreturnsusasnapshotoftypeMemento.
funcaddStatsToHolder(_runsToBeAdded:Int)->Memento{numberOfRunsScored+=runsToBeAddedletsnapshot=Memento(runsToBeAdded)snapshots.append(snapshot)currentIndex+=1returnsnapshot}
WekeepappendingthesnapshotofMementoinitialisedtothearrayandincrementthecurrentIndexby1.
WeneedafunctionthatletsusrestoreapaststatbypassingaparameteroftypeMemento.
funcrestoreToPastStat(_memento:Memento?){ifletsnap=memento{numberOfRunsScored=snap.numberOfRunsScoredsnapshots.append(snap)currentIndex=snapshots.count-1}}
NotethatmementoparameterisoptionalbecauseforthecurrentIndexvalueof0,wedonothaveanythingtorestoreto.WechangethenumberOfRunsScoredtothevalueofsnapshot.WeappendthesnapshotofparametertoourarrayanddecrementthecurrentIndexby1.
Now,weneedmethodstoundoandredostats.
funcundoAStat()->Memento?{ifcurrentIndex>0{currentIndex-=1letsnap=snapshots[currentIndex]numberOfRunsScored=snap.numberOfRunsScoredreturnsnap}returnnil}funcredoAStat()->Memento?{ifcurrentIndex+1<snapshots.count{currentIndex+=1letsnap=snapshots[currentIndex]numberOfRunsScored=snap.numberOfRunsScoredreturnsnap}returnnil}
NotethatthereturnedMementoisoptional,aswemaynothaveanythingtoundoforthefirstadditionofthestatandnothingtoredoafterthefinaladditionofthestat.Inthesecases,wereturnanil.
InundoAStatmethod,wecheckifthecurrentIndex>0.Ifno,wereturnnil.Ifyes,wedecrementthecurrentIndexby1andgetthesnapshotatcurrentIndex.WethenchangethenumberOfRunsScoredtothevaluepresentinthesnapshot.
InredoAStatmethod,wecheckifthecurrentIndexislessthanthecountofsnapshotsdecrementedby1.Ifno,wereturnnil.Ifyes,weincrementthecurrentIndexby1andgetthesnapshotatthecurrentIndex.WethenchangethenumberOfRunsScoredtothevaluepresentinthesnapshot.
WearedonewithoursetupofMementodesignpattern.Let’sseehowweimplementthispattern.
funcmain(){letstatsHolder=StatsHolder(1200)//1200isthefirststat(numberofruns)weaddtostatsholderletstat1=statsHolder.addStatsToHolder(1400)letstat2=statsHolder.addStatsToHolder(700)print("a-",statsHolder)//undoTopmostoperationstatsHolder.undoAStat()print("b-",statsHolder)//undoTopmostoperationstatsHolder.undoAStat()print("c-",statsHolder)//restoreToStat2statsHolder.redoAStat()print("d-",statsHolder)//Thereisnomemento/snapshotwhentheStatsHolderisinitialised}
Inthemainmethod,weinitialisetheStatsHolderbypassing1200runsasparameter.WethenaddacoupleofstatsbyusingaddStatsToHoldermethodbypassing1400and700runsasparameters.
WethenundothelasttwostatadditionsbyusingundoAStatmethodon
statsHolder.WethenredothelastoperationbyusingredoAStatmethodonstatsHolder.
Nowrunthemain()method.
main()
OutputintheXcodeconsole:
a-TotalRunsscored=3300b-TotalRunsscored=1400c-TotalRunsscored=1200d-TotalRunsscored=1400
Initially,weaddedthreestats,whichtakesthetotalto3300.Thenweundooneoperation,whichtakesthetotalto1400(subtracting700).Onemoreundotakesustotheinitialstateof1200.Weredothelastundooperation,whichagaintakesusbackto1400.
Summary:
Ifyourapplicationdemandstosavecheckpointsastheuserprogressesthroughtheapp,goforMementodesignpattern.Thishelpsinrestoringthecheckpointsatalaterpointoftime.
24)Behavioural-NullObjectDesignPatternDefinition:
InObjectOrientedProgramming,nullisanobjectthathasnoreferencedvalue.WhenanobjectAtriestouseanobjectB,objectAassumesthatobjectBisnotnil.WhenthereisnooptionoftellingAnottouseinstanceofBwhenithasnovalue,NullObjectdesignpatterncomesintoplay.NullObjectisabehaviouraldesignpatternthatsimplifiestheuseofundefineddependencies.
Usage:
Let’sseehowthisdesignpatterncanbeusedincode.
Assumewehaveacricketmatchhappeningatastadiumandusersreceiveliveupdatesofthescoreontheirdevices(iPadoriPhone).Bydefault,weshowthescoreontheinterface.ButoniPad,alongwiththelivescore,wealsoshowbowlersandbatsmanstats,asthereisavailablescreenestate.ButthesameinterfacelookscongestedonaniPhonedisplay.So,werefrainourselvesfromshowingbatsmanandbowlerstatsforiPhonedisplay.
importFoundation
protocolLog{funcbowlerStatsFromCurrentMatch(_stats:String)funcbatsmenStatsFromCurrentMatch(_stats:String)}
WehaveaprotocolnamedLogthatdefinestwomethodstoshowbowlersandbatsmenstatsfromthecurrentmatch,whichtakesstatsasinputinString
format.
classStatsDisplayLog:Log{funcbowlerStatsFromCurrentMatch(_stats:String){print(stats)}funcbatsmenStatsFromCurrentMatch(_stats:String){print(stats)}}
classNoDisplayStatsLog:Log{funcbowlerStatsFromCurrentMatch(_stats:String){}funcbatsmenStatsFromCurrentMatch(_stats:String){}}
Wenowdefinetwoclasses,StatsDisplayLogandNoDisplayStatsLog,bothconformingtoLogprotocol.Theonlydifferenceistheimplementationofthesemethodsintheclasses,whichisverystraightforward.WeshowthestatsinStatsDisplayLoganddonotshowanystatsinNoDisplayStatsLog.
classUserInterface{varlog:LogvarrunsScored=0varwicketsTaken=0
init(_log:Log){self.log=log}
funcwicketTaken(){wicketsTaken+=1log.bowlerStatsFromCurrentMatch("TotalWickets:\(wicketsTaken)")}
funcrunsScored(numberOFRunsScored:Int){runsScored+=numberOFRunsScoredlog.batsmenStatsFromCurrentMatch("TotalRuns:\(runsScored)")}}
WedefineaclasscalledUserInterface,whichtakescareofthelogicbehindwhattodisplaytotheusersontheirdevices.ThisclasstakesaparameteroftypeLogduringitsinitialisation.Therearetwomethodstoupdatethenumberofwicketstakenandnumberofrunsscored,andwhenaneventhappensinthematch.WiththehelpofinstanceofLogclass,thesestatsareshownontheinterface.Let’snowwriteafunctioncalledmain.
funcmain(){letipadLog=StatsDisplayLog()letiPAdUserInterface=UserInterface(ipadLog)iPAdUserInterface.runsScored(numberOFRunsScored:4)iPAdUserInterface.runsScored(numberOFRunsScored:3)iPAdUserInterface.wicketTaken()}
main()OutputintheXcodeconsole:
TotalRuns:4TotalRuns:7TotalWickets:1ThisiswhatauserseesonhisiPad,aswearetakinganinstanceofStatsDisplayLogforiPadinterface.Now,addthebelowcodetothemainfunction:
letiPhoneLog=NoDisplayStatsLog()letiPhoneUserInterface=UserInterface(iPhoneLog)iPhoneUserInterface.runsScored(numberOFRunsScored:6)iPhoneUserInterface.runsScored(numberOFRunsScored:2)
WecanobservethatthereisnochangeintheoutputintheXcodeconsole.ThisisbecauseweareusinganinstanceofNoDisplayStatsLog,whichisusedforaniPhoneinterface.
Summary:
NullObjectdesignpatterncanbeusedinsituationswhererealobjectsarereplacedbynullobjectswhentheobjectisexpectedtodonothing.
25)Behavioural-ObserverDesignPatternDefinition:
Observerdesignpatternisusedwhenwewantanobject(calledobservable),whichmaintainsalistofobjects(calledobservers),tonotifythemwhenobservabledoessomethingoritspropertieschangeorsomeexternalchangeoccurs.Theprocessofnotifyingisdonethrougheventsgeneratedbyobservable.
Usage:
importFoundation
protocolInvocable:class{funcinvoke(_data:Any)}
publicprotocolDisposable{funcdispose()}
publicclassEvent<T>{publictypealiasEventHandler=(T)->()vareventHandlers=[Invocable]()publicfuncraise(_data:T)
{forhandlerineventHandlers{handler.invoke(data)}}publicfuncaddHandler<U:AnyObject>(target:U,handler:@escaping(U)->EventHandler)->Disposable{letsubscription=Subscription(target:target,handler:handler,event:self)eventHandlers.append(subscription)returnsubscription}}
classSubscription<T:AnyObject,U>:Invocable,Disposable{weakvartarget:T?lethandler:(T)->(U)->()letevent:Event<U>init(target:T?,handler:@escaping(T)->(U)->(),event:Event<U>){self.target=targetself.handler=handlerself.event=event}funcinvoke(_data:Any){iflett=target{handler(t)(dataas!U)}}funcdispose()
{event.eventHandlers=event.eventHandlers.filter{$0asAnyObject?!==self}}}
Thisishowwedefineobserversandobservables.YoucanfeelfreetocopyandpastethiswithoutanymodificationstouseObserverdesignpatterns.Iwillnotbediscussingitindetail,asthemainintentionistolearnaboutdesignpatterns.
Now,assumeacricketmatchishappening.Thereisascoreboardontheground,whichisupdatedwheneveranyeventhappens(runhit/batsmanout/fieldertakingcatch,etc).Forthesakeofsimplicity,letusonlytalkaboutthebattingteammakingrunsevent.
Letusdefinescoreboardclass.
classScoreBoardInTheGround{letbatsmenHitRun=Event<Int>()init(){}funcupdateScore(){}}
ScoreBoardInTheGroundcanbeinitialisedwithoutanyarguments.IthasaneventbatsmenHitRunandamethodtoupdatethescore.Thisisourobservable,whichbroadcastseventswheneverbatsmanhitsruns.
Whenthescoreboardonthegroundisupdated,thesameupdatehastoreachtheserversofamobileapp,wheremillionsofpeoplefollowthematchupdates.Therearemanysuchservers,whicharecalledobservers,andtheyneedtoknowwheneverthestateofScoreBoardInTheGroundchanges.Letusdefineourobservableclass.
classScoreUpdateInServers{init(){letscoreBoard=ScoreBoardInTheGround()
letsubscriber=scoreBoard.batsmenHitRun.addHandler(target:self,handler:ScoreUpdateInServers.showScoreInApp)//simualtebatsmanhittingrunsinthegroundscoreBoard.batsmenHitRun.raise(6)//getridofthedescriptionsubscriber.dispose()}funcshowScoreInApp(score:Int){print("ScoreNowis:\(score)runs")}}
WhenScoreUpdateInServersisinitialised,wetakeaninstanceofScoreBoardInTheGroundandaddasubscribertobatsmanHitRuneventwiththehelpofahandler.
Thenwearesimulatinganeventofabatsmanhittingsixrunsinthematchandthescoreboardonthegroundneedstobroadcastthiseventtotheservers.Thenwealsogetridofthesubscriptionwiththehelpofadisposefunction.
Serversofthemobileapphaveamethodtoshowthescoreinthedisplay,whichtakesthescoreastheinputparameter.Forthesakeofsimplicity,wejustprintthescoreinthismethod.
Now,addthebelowcodesnippettoseethecodeinaction.
funcmain(){letdummy=ScoreUpdateInServers()}
main()
OutputintheXcodeconsole:
ScoreNowis:6runs
Summary:
Ifyouwanttosubscribe/unsubscribetoobjectsdynamically,Observerdesignpatternisthebestpossibleway.Notethefactthatsubscribersarenotifiedinrandomorder.
26)Behavioural-StateDesignPatternDefinition:
Stateisabehaviouraldesignpatternthatisusedtoalterthebehaviourofanobjectasitsinternalstatechanges.Theobjectwillappeartochangeitsclass.
Usage:
Let’sassumeaplayerauctionisgoingonforsomeprivatecricketleague.Aplayeriseitherinunsoldstateorsoldstatewiththenameoftheteamattachedtohim.WenowseehowStatedesignpatterncanbeusedinthiscontext.
importUIKit
protocolState{funcisSold(playerAuction:PlayerAuction)->BoolfuncwhichTeam(playerAuction:PlayerAuction)->String?}
WehaveaprotocolnamedState,whichdefinestwomethods:
1. 1) isSoldtakesanobjectoftypePlayerAuction(tobedefined)andreturnstrue/false.
2. 2) whichTeamtakesanobjectoftypePlayerAuction(tobedefined)andreturnsanoptional(asanunsoldplayerdoesnothaveanyteamassociatedwithhim).
Wethendefinestatesinwhichaplayerobjectcanbein.
classIsUnsoldState:State{funcisSold(playerAuction:PlayerAuction)->Bool{returnfalse}funcwhichTeam(playerAuction:PlayerAuction)->String?{returnnil}}
classIsSoldState:State{letteamName:Stringinit(teamName:String){self.teamName=teamName}funcisSold(playerAuction:PlayerAuction)->Bool{returntrue}funcwhichTeam(playerAuction:PlayerAuction)->String?{returnteamName}}
1. 1) FirstoneisIsUnsoldStateanditconformstoStateprotocol.ItreturnsfalsewhencalledisSoldmethodandanilwhencalledwhichTeam.
2. 2) SecondoneisIsSoldStateanditconformstoStateprotocol.IttakestheargumentofteamNameoftypeStringforitsinitialisation.ItreturnstruewhencalledisSoldmethodandanilwhencalledwhichTeam.
Wenowdefinethemostimportantclass,PlayerAuction:
classPlayerAuction{privatevarstate:State=IsUnsoldState()varisSold:Bool{get{returnstate.isSold(playerAuction:self)
}}varteamName:String?{get{returnstate.whichTeam(playerAuction:self)}}funcchangeStateToSold(teamName:String){state=IsSoldState(teamName:teamName)}funcchangeStateToUnSold(){state=IsUnsoldState()}}
IthasaprivatevariableoftypeState,whichhasadefaultvalueofIsUnsoldState.Wethengetandsettwovariables,isSoldandteamName,withthehelpofstatepropertyandpassinganargumentoftypeself.
Wethendefinetwomethods:
1. 1) changeStateToSold,whichtakesanargumentoftypeString,andthestateofplayerobjectischangedtoIsSoldStatebypassingtheargument.
2. 2) changeStateToUnsold,whereplayer’sstateischangedtoinstanceofIsUnsoldState.
Letusnowwriteamainmethodtoseethisdesignpatterninaction:
funcmain(){letplayerAuction=PlayerAuction()print(playerAuction.isSold,playerAuction.teamName)playerAuction.changeStateToSold(teamName:"ChennaiSuperKings")
print(playerAuction.isSold,playerAuction.teamName!)}
main()
WestartwithtakinganinstanceofclassPlayerAuction.ThenchangethedefaultstatusofunsoldtosoldstatebypassingateamnameChennaiSuperKings.
OutputintheXcodeconsole:
falseniltrueChennaiSuperKings
Fortheunsoldstate,isSoldreturnsafalseandwhichTeamreturnsnil.Asthestateischanged,isSoldreturnstrueandwhichTeamreturnsChennaiSuperKings.
Summary:
Whenyouareinasituationwherethebehaviourofanobjectshouldbeinfluencedbyitsstate,thenumberofstatesisbig,andthestaterelatedcodechangesfrequently,Statedesignpatternservesyourpurpose.
27)Behavioural-TemplateDesignPatternDefinition:
Templateisabehaviouraldesignpatternthathelpstodividealgorithmsintocommonpartsandspecificsthroughinheritance.Insimplewords,baseclassdeclaresalgorithm‘placeholders’andderivedclasseswritetheconcreteimplementationofplaceholders(oralgorithms).
Usage:
Letusconsiderasituationwherewearebuildingatemplateforacricketteam.Themainmottoistodecidetheteam’scompositionbasedonthepitchandweatherconditions(howmanybatsmentoplay,howmanybowlerstoplay,howmanyfastbowlers,howmanyspinners,etc).Asingletemplateforallthepitchconditionswillnothelp.
LetusseehowwecanuseTemplatedesignpatternforthesameusecase.
importUIKitimportFoundation
classTeamTemplate{
funcbuildTeam(){pickBatsmen()pickBowlers()pickAllRounders()pickWicketKeeper()print("\nTeamSetForthematch")}
funcpickBatsmen(){}funcpickBowlers(){}funcpickAllRounders(){}privatefuncpickWicketKeeper(){print("OnlyoneWKavailableandheispickedbydefault")}}
WedefineabaseclasscalledTeamTemplate,whichwilllaterbeinheritedbyotherclasses.
WedefineafunctioncalledpickWicketKeeperanddeclareitprivatebecausethereisonlyoneWicketKeeperavailableinthesquad,andheisintheplayingteambydefault.Noonehastheauthoritytochangeit.
WealsodefineemptyfunctionscalledpickBatsmen,pickBowlers,andpickAllRounders,whoseimplementationsaredefinedinthesubclassesthroughinheritance.
WenowdefinethreedifferentclasseswithTeamTemplateasBaseClasswherewewritetheconcreteimplementationofteamBuildingmethod.
classSeamingPitchTeamTemplate:TeamTemplate{overridefuncpickBatsmen(){print("Picking6batsmen")}overridefuncpickBowlers(){print("Picking3FastBowlers")
}overridefuncpickAllRounders(){print("Picking1PaceAllRounder")}}
classSpinPitchTeamTemplate:TeamTemplate{overridefuncpickBatsmen(){print("Picking5Batsmen")}overridefuncpickBowlers(){print("Picking2FastBowlersand2Spinners")}overridefuncpickAllRounders(){print("Picking2SpinAllRounder")}}
classBattingPitchTeamTemplate:TeamTemplate{overridefuncpickBatsmen(){print("Picking7Batsmen")}overridefuncpickBowlers(){print("Picking2FastBowlersand1Spinners")}overridefuncpickAllRounders(){print("Picking1BattingAllRounder")}}
WedefinethreetemplatesnamedSeamingPitchTeamTemplate,SpinPitchTeamTemplate,andBattingPitchTeamTemplate,withdifferentmethoddefinitionsforpickBatsmen,pickBowlers,andpickAllRounders.
Letusnowwriteourmainmethodandseethecodeinaction.
funcmain(){varfinalTeam:TeamTemplate=SeamingPitchTeamTemplate()finalTeam.buildTeam()}
main()
AssumewearepickingateamforseamfriendlypitchandwetakeaninstanceofSeamingPitchTeamTemplateandcallthebuildTeammethod.
OutputintheXcodeconsole:
Picking6batsmenPicking3FastBowlersPicking1PaceAllRounderOnlyoneWKavailableandheispickedbydefault
TeamSetForthematch
Now,wechangetheteamcompositionsbytakinginstancesofothertemplatesandobservetheoutput.
funcmain(){varfinalTeam:TeamTemplate=SeamingPitchTeamTemplate()finalTeam.buildTeam()print("\n***PitchChanged***\n")finalTeam=SpinPitchTeamTemplate()finalTeam.buildTeam()}
main()
OutputintheXcodeconsole:
Picking6batsmenPicking3FastBowlersPicking1PaceAllRounderOnlyoneWKavailableandheispickedbydefault
TeamSetForthematch
***PitchChanged***
Picking5BatsmenPicking2FastBowlersand2SpinnersPicking2SpinAllRounderOnlyoneWKavailableandheispickedbydefault
TeamSetForthematch
Nowchangethemainmethodto:
funcmain(){varfinalTeam:TeamTemplate=SeamingPitchTeamTemplate()finalTeam.buildTeam()print("\n***PitchChanged***\n")finalTeam=SpinPitchTeamTemplate()finalTeam.buildTeam()print("\n***PitchChanged***\n")finalTeam=BattingPitchTeamTemplate()finalTeam.buildTeam()}
main()
OutputintheXcodeconsole:
Picking6batsmenPicking3FastBowlersPicking1PaceAllRounder
OnlyoneWKavailableandheispickedbydefault
TeamSetForthematch
***PitchChanged***
Picking5BatsmenPicking2FastBowlersand2SpinnersPicking2SpinAllRounderOnlyoneWKavailableandheispickedbydefault
TeamSetForthematch
***PitchChanged***
Picking7BatsmenPicking2FastBowlersand1SpinnersPicking1BattingAllRounderOnlyoneWKavailableandheispickedbydefault
TeamSetForthematch
Summary:
Whenyouareinasituationtobuildatemplate/baseclass,whichisopenforextensionbutclosedformodification,orinsimplewords,subclassesshouldbeabletoextendthebasealgorithmwithoutalteringitsstructure,Templatedesignpatternsuitsthebest.
28)Behavioural-VisitorDesignPatternDefinition:
Visitorisabehaviouraldesignpatternthatletsusdefineanewoperationwithoutchangingtheclassesoftheobjectsonwhichitoperates.Weuseitwhenwedonotwanttokeepmodifyingeveryclassinthehierarchy.
Usage:
Considerasituationwherewearedesigningacheck-outcounterinashopthatsellscricketaccessories.Itoffersdiscountsonselectivebrandsandselectiveitems.LetusseehowwecanuseVisitorpatterntodesignsuchasystem.
importFoundationimportUIKit
protocolCricketAccessory{funcaccept(counter:CheckoutCounter)->Int}
WedefineaprotocolcalledCricketAccessorywithafunctioncalledaccept,whichtakesaparameteroftypeCheckoutCounter(tobedefined)andreturnsaninteger.
classCricketBat:CricketAccessory{privatevarprice:Doubleprivatevarbrand:Stringinit(_price:Double,_brand:String){
self.price=priceself.brand=brand}publicfuncgetPrice()->Double{returnprice}publicfuncgetBrand()->String{returnbrand}funcaccept(counter:CheckoutCounter)->Int{returncounter.moveToCounter(bat:self)}}
WethendefineaclasscalledCricketBatconformingtoCricketAccessoryprotocol.Ithastwoprivatevariablesdefined:priceoftypeDoubleandbrandoftypeString.WealsodefinetwopublicmethodscalledgetPriceandgetBrandtoreturnpriceandbrandofthebatrespectively.
classCricketBall:CricketAccessory{
privatevartype:Stringprivatevarprice:Double
init(_type:String,_price:Double){self.type=typeself.price=price
}
publicfuncgetType()->String{returntype}
publicfuncgetPrice()->Double{returnprice}
funcaccept(counter:CheckoutCounter)->Int{returncounter.moveToCounter(ball:self)}
}
WedefineanotherclasscalledCricketBallconformingtoCricketAccessoryprotocol.ThisisverymuchsimilartoCricketBatclass.
protocolCheckoutCounter{funcmoveToCounter(bat:CricketBat)->IntfuncmoveToCounter(ball:CricketBall)->Int}
WedefineaprotocolcalledCheckoutCounter,whichhastwomethodswiththesamenamebutdifferswhenitcomestoparametertypes.classCashCounter:CheckoutCounter{funcmoveToCounter(bat:CricketBat)->Int{varcost:Int=0ifbat.getBrand()=="MRF"{cost=Int(0.9*bat.getPrice())}else{cost=Int(bat.getPrice())}print("Batbrand:\(bat.getBrand())andpriceis:\(cost)")returncost}
funcmoveToCounter(ball:CricketBall)->Int{print("BallType:\(ball.getType())andpriceis:\(ball.getPrice())")returnInt(ball.getPrice())}}
WenowdefineaclasscalledCashCounterconformingtoCheckoutCounter.Wecanseethat,foracricketbatofbrandMRF,wegiveadiscountof10%.Inthefuture,ifwewanttoaddanynewbrandsorremovediscountsonexisting
brands,wecanmakeallthechangesherewithnochangesrequiredattheclientend.Letusnowwriteamainfunctiontoseethecodeinaction.funcmain(){print("Main")funcfinalPriceCalculation(accessories:[CricketAccessory])->Int{varcheckout=CashCounter()varcost=0foriteminaccessories{cost+=item.accept(counter:checkout)}print("Totalcartvalue:\(cost)")returncost}varcartItems=[CricketAccessory]()letmrfBat=CricketBat(2000,"MRF")letbrittaniaBat=CricketBat(1500,"Brittania")lettennisBall=CricketBall("Tennis",120)letleatherBall=CricketBall("Leather",200)cartItems.append(mrfBat)cartItems.append(brittaniaBat)cartItems.append(tennisBall)cartItems.append(leatherBall)
varcost=finalPriceCalculation(accessories:cartItems)print("CheckedOutwithBillAmount:\(cost)")}
main()
WedefineafunctioncalledfinalPriceCalculation,whichtakesanarrayoftypeCricketAccessoryandreturnsthefinalcartvalue.
OutputintheXcodeconsole:
MainBatbrand:MRFandpriceis:1800Batbrand:Brittaniaandpriceis:1500
BallType:Tennisandpriceis:120.0BallType:Leatherandpriceis:200.0Totalcartvalue:3620CheckedOutwithBillAmount:3620
YoucanseetheMRFbatischeckedoutatdiscountedprice.
LetusnowchangetheCashCounterclasstothefollowing:
classCashCounter:CheckoutCounter{funcmoveToCounter(bat:CricketBat)->Int{varcost:Int=0ifbat.getBrand()=="Brittania"{cost=Int(0.8*bat.getPrice())}else{cost=Int(bat.getPrice())}print("Batbrand:\(bat.getBrand())andpriceis:\(cost)")returncost}
funcmoveToCounter(ball:CricketBall)->Int{print("BallType:\(ball.getType())andpriceis:\(ball.getPrice())")returnInt(ball.getPrice())}}
Now,weareremovingthediscountonMRFandgivinga20%discountonBrittaniabats.
ThesamemainmethodgivesadifferentoutputintheXcodeconsole:
MainBatbrand:MRFandpriceis:2000Batbrand:Brittaniaandpriceis:1200BallType:Tennisandpriceis:120.0BallType:Leatherandpriceis:200.0Totalcartvalue:3520
CheckedOutwithBillAmount:3520
Summary:
Whenyouareinasituationwhereyoumightwanttoaddanewactionandhavethatnewactionentirelydefinedwithinoneofthevisitorclassesratherthanspreadoutacrossmultipleclasses,Visitordesignpatternservesyouthebest.
Finalnote:
Finalnote:
Itisnotnecessarytolearnallthepatternsandtheirapplicationsbyheart.Themainintentionistoidentifytheusecasesandproblems,whichthedesignpatternsaremeanttoaddress.Thenapplyingaspecificdesignpatternisjustamatterofusingtherighttoolattherighttimefortherightjob.It'sthejobthatmustbeidentifiedandunderstoodbeforethetoolcanbechosen.
HappyCoding!!!