Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
SoftwareDevelopment,OneStepataTime
ByDerickBailey
DerickBailey
DerickBaileyisaDeveloperAdvocateforKendoUI,adeveloper,speaker,trainer,screencasterandmuch
more.Hesbeenslingingcodesincethelate80sanddoingitprofessionallysincethemid90s.Thesedays,
DerickspendshistimeprimarilywritingJavaScriptwithbackendlanguagesofalltypes,includingRuby,
NodeJS,PHP,.NETandanythingelsehecangethishandson.
DerickblogsatLosTechies.com,producesscreencastsatWatchMeCode.net,tweetsas@derickbaileyand
providessupportandassistanceforJavaScript,BackboneJS,MarionetteJS,andmuchmorearoundthe
Web.
Thisarticlewaspublishedin:
Thisarticlewasfiledunder:
Mostprofessionalsoftwaredevelopersunderstandtheacademicdefinitionsofcoupling,cohesion,and
encapsulation.However,manydevelopersdonotunderstandhowtoachievethebenefitsoflowcoupling,
highcohesionandstrongencapsulation,asoutlinedinthisarticle.Fortunately,othershavecreatedstepping
stonesthatleadtothesegoals,
resultinginsoftwarethatiseasiertoread,easiertounderstandandeasiertochange.Inthisarticleseries,I
willdefinethreeoftheprimaryobjectorientedprinciplesandshowhowtoreachthemthroughthefive
S.O.L.I.D.designprinciples.
HaveyoueverplayedJenga?Itsthatgameofwoodenblocksthatarestackedontopofeachotherinrows
ofthree.InJengayoutrytopushorpullablockoutofthestackandplaceitontopofthestackwithout
knockingthestackover.Theplayerthatcausesthestacktofallloses.
HaveyoueverthoughtyouwereplayingagameofJengawhenyouwerewritingordebuggingsoftware?
Forexample,youmayneedtochangeonefieldononescreen.Youstudythestackofcode,youlookfor
thatlittlespaceoflightpeakingbetweentheclasses,andyoumakethechangeintheoneplacethatyou
thoughtwouldbesafe.Unfortunately,youdidntrealizethatthecodeyouwerechangingwasreferencedin
severalcriticalprocessesthroughsomestrangelevelsofindirection.Theresultingcrashofthesoftware
stackhasleftyoutheloser,cleaningupthemesswithyourbossbreathingdownyourneckaboutthe
customerbeingupsetabouttheirlostdata,andHowmanytimeshaveyoubeenthere?Icantevenbegin
tocounthowoftenitshappenedtome.
SoftwaredevelopmentdoesnothavetobelikeagameofJenga.
Thereisgoodnews,though:softwaredevelopmentdoesnothavetobelikeagameofJenga.Infact,
softwaredevelopmentshouldnotbelikeanygamewheretherearewinnersandlosers.Whatyouwant,
instead,isasustainablepaceofsoftwaredevelopmentwhereeveryonewins.Youwanttoensurethatyou
dontoverworkthedevelopers,thatyoudontpressuremanagerstosayjustgetitdone,andthecustomer
getsthesoftwaretheywantinatimeframetheyagreeto.
ASustainablePace
Whentryingtosetasustainablepaceinanyendeavor,youfirstneedtounderstandhowfaryouneedtogo.
Youalsoneedtoknowhowfastyouneedtogetthere.Forexample,ifyouwanttoruna50meterdash,you
shouldrunasfastasyoupossiblycanandthenpushyourselftotrytorunfaster.However,ifyouwantto
runan800meterraceyoushouldsetasomewhatslowerpace.Whenyoustarttalkingaboutsignificant
distanceslikeamarathon,thepacebecomessignificantlyslower.Forsuchadistance,youwanttoseta
pacethatyoucanmaintainthroughouttherace.
Insoftwaredevelopment,youcanthinkofthepaceyouneedtorunasthetimelineoftheprojectcombined
withtheexpectedfeaturesandfunctionality.
Insoftwaredevelopment,youcanthinkofthepaceyouneedtorunasthetimelineoftheprojectcombined
withtheexpectedfeaturesandfunctionality.Forshortertimeframes,youneedtorunfaster.However,you
alsohavetoconsiderhowmuchfunctionalityyoucanreasonablyaddtoyoursystem,givenashort
timeframe.Ifyouonlyneedafewfeaturesandyouneedtogetitdonequickly,youmayneedtosprint
towardthegoal.Ifyourcustomerexpectsyoutocramtoomanyfeaturesintoashorttimeframe,youhavea
higherlikelihoodofburningout.Imaginetryingtosprintforthedurationofamarathon,orevena1600
meterrace.Theprobabilityofsustainingthatpaceforthatdistanceisgoingtoapproachzeroasyou
continuemovingforward.Youneedtoworkwithyourmanagement,yourteam,andyourcustomerstoset
theexpectationsofhowfastyoucanrunforagivenperiod.
Assumingthatyouhaveareasonablenumberoffeaturesforagiventimeframe,younowhavetosetthe
paceforfeaturedevelopmentinyourdailyactivities.Objectorientedsoftwaredevelopmenthasvarious
principles,patternsandpracticesthathelpyouachievethesustainablepaceyouneed.Theprinciples
includecouplingtheextenttowhichthepartsofthesystemrelyoneachothercohesiontheextenttowhich
thepartsofasystemworktogetherforasinglepurposeandencapsulationtheextenttowhichthedetails
ofimplementationarehidden.Builtontopoftheseprinciplesarevariousdesignandimplementation
patternssuchasstrategy,command,bridge,etc.Whenyoucombinetheseprinciples,patternsand
practicestheywillhelpyoutocreatesystemsthatarewellfactored,easytounderstand,andeasyto
change.
TheObjectOrientedPrinciples
Ideally,youwanttoensurethatyoursystemshavelowcouplingandhighcohesion.Thesetwoprinciples
helpyoutocreatethebuildingblocksofasoftwaresystem.Youalsowanttoensurethatyouhaveself
containedbuildingblocksthatis,theyarewellencapsulated.Youdontwanttheconcernsofonebuilding
blockleakingintootherblocks.Ifyoucreatebuildingblocksthathavethecorrectsizeandshape,youcan
putthemtogetherinmeaningfulwaystosolveyourproblems.
Oftenitseemsthatdevelopersonlydiscusstheseprinciplesinacademicsettings.Mostuniversitieswith
degreesthatcoversoftwaredevelopmentprovideatleastacursoryintroductiontothem.However,many
softwaredevelopersseemtomisstheircorrectusage,causingmoreproblemsthantheysolve.Indeed,
developerscanveryeasilymisapplytheseprinciplesinthewrongplaceatthewrongtime.Toavoidthis
situation,youneedtounderstandhowcoupling,cohesion,andencapsulationcorrectlyplayintodeveloping
softwaresolutions.
LowCoupling
Couplinginsoftwaredevelopmentisdefinedasthedegreetowhichamodule,class,orotherconstruct,is
tieddirectlytoothers.Forexample,thedegreeofcouplingbetweentwoclassescanbeseenashow
dependentoneclassisontheother.Ifaclassisverytightlycoupled(or,hashighcoupling)tooneormore
classes,thenyoumustusealloftheclassesthatarecoupledwhenyouwanttouseonlyoneofthem.
Youcanreducecouplingbydefiningstandardconnectionsorinterfacesbetweentwopiecesofasystem.
Forexample,akeyandalockhaveadefinedinterfacebetweenthem.Thekeyhasacertainpatternof
ridgesandvalleys,andthelockhasacertainpatternofpinsandsprings.Whenyouplacetherightkeyin
thelock,itpushesthepinsintoapositionthatallowsthemechanismtolockorunlock.Ifyouplacethe
wrongkeyintothelock,thepinswillnotmoveintothecorrectpositionandthemechanismwontmove.
Insoftwaredevelopment,developersalsoworkwithstandardconnectionsandinterfacedefinitions.Object
orientedlanguagessuchasC++,C#,Java,andVisualBasichavesomeconstructsthatallowyoutodefine
thoseinterfacesimplicitlyandexplicitly.Whetheritsaclasspublicmethodsandproperties,anabstractbase
class,anexplicitinterfaceorotherformofabstraction,theseconstructsallowyoutodefinecommon
interactionpointsbetweenpartsofyoursystem.Withoutabstractiontodecouplethesecommonpointsof
interaction,youareleftwithpiecesofthesystemthatmustknowabouteachotherdirectly.Basically,this
meansyouarestuckwithakeythatisweldeddirectlytothepinsofalock,preventingyoufromremoving
thekey,andcompromisingthesecurityofthatlock.
ImaginethatyouareworkingwiththestructureinFigure1.Thesoftwareworksfine,atthemoment,and
youcanfixbugswhenyouneedto.Thenyourbosshandsyouanewrequirementandyourealizethatthe
modulehighlightedinredcanhandlemostoftherequirement.Youwouldliketoreusethatmodulebutyou
dontneedanyofthesurroundingmodulesforthisnewfeature.Whenyoutrytopulloutthemoduleinred,
though,youquicklyrealizethatyoullhavetobringseveralmoremoduleswithitduetothehighcoupling
betweenthismoduleandtheonessurroundingit.
Figure1:Atightlycoupledsystem.
Nowimagineaclassthathaszerocoupling.Thatis,theclassdependsonnothingelseandnothingelse
dependsonit.Whatbenefitdoesthatoffer?Forone,youcanusethatclassanywhereyouwantwithout
havingtoworryaboutdependenciescomingalongwithit.However,youessentiallyhaveauselessclass.
Withzerocouplingintheclass,youwontbeabletogetanyinformationintooroutofit.Trytocreateaclass
in.NETthatdoesnotrelyonanythingnotaninteger,notastring,notaConsoleorApplicationstatic
referencenoteventheimpliedobjectinheritanceofeveryconstructin.NET.Goaheadtryitseehow
usefulthatisinyoursystem.
Couplingisnotinherentlyevil.Ifyoudonthavesomeamountofcoupling,yoursoftwarewillnotdoanything
foryou.
Couplingisnotinherentlyevil.Ifyoudonthavesomeamountofcoupling,yoursoftwarewillnotdoanything
foryou.Youneedwellknownpointsofinteractionbetweenthepiecesofyoursystem.Withoutthem,you
cannotcreateasystemofpartsthatcanworktogether,soyoushouldnotstrivetoeliminatecoupling
entirely.Rather,yourgoalinsoftwaredevelopmentshouldbetoattainthecorrectlevelofcouplingwhile
creatingasystemthatisfunctional,understandable(readablebyhumans),andmaintainable.
HighCohesion
Cohesionistheextenttowhichtwoormorepartsofasystemarerelatedandhowtheyworktogetherto
createsomethingmorevaluablethantheindividualparts.Thinkoftheoldadage,Thewholeisgreaterthan
thesumoftheparts.
Peopleseekhighcohesioninsportsteams,forexample.Theywanttohaveateamofbasketballplayers
thatknowhowandwhentopasstheball,andhowandwhentoscore.Everyoneexpectstheindividual
playerstoplaytogetherasateamtoincreasethechancesoftheteamwinningthegame.Companiesalso
seekcohesionintheirprojectteamsatwork.Theyputdevelopersanduserinterfacedesignerstogether
withbusinessanalystsanddatabaseadministrators,alongwithotherrolesandresponsibilities.Theintentof
creatingteamsofcrossfunctionalskillsetsistousethestrengthsofeachteammembertocounterthe
weaknessesofothers.Youlikelyalsolookforcohesioninthetechnologyyouareusingandthesoftware
thatyouarewriting.Youprobablywantadatabasesystemthatconnectseasilytoyourprogramming
languageofchoice.Youalsowantauserinterfacetechnologythatmakesiteasytowireupthebusiness
logicanddataaccess.Cohesionisallaround.Youonlyneedtorecognizeitforwhatitis.
Insoftwaresystems,developerstalkabouthighlevelconcernsandlowlevelimplementationdetails.This
scaleofconcerncanhelpyouunderstandthemanyperspectivesofcohesionwithinyoursoftware.Howwell
dothelinesofcodeinamethodorfunctionworktogethertocreateasenseofpurpose?Howwelldothe
methodsandpropertiesofaclassworktogethertodefineaclassanditspurpose?Howwelldotheclasses
fittogethertocreatemodules?Howwelldothemodulesworktogethertocreatethelargerarchitectureof
thesystem?Understandingtheperspectivethatyouaredealingwithatanygiventimewillhelpyou
understandtheextenttowhichthepiecesarecohesive.
Understandingtheperspectivethatyouaredealingwithatanygiventimewillhelpyouunderstandthe
extenttowhichthepiecesarecohesive.
ExaminethepuzzlepictureofmysoninFigure2.Ifyouseparatealloftheindividualpieces,whatdoyou
have?Youhaveaseriesofpiecesthatprovideverylimitedvalueontheirown.Theintrinsicvalueofan
individualpieceisonlythatitcanbecombinedwithotherpieces.Imnotinterestedinplayingwithasingle
piece,though.Iwanttohaveenoughpiecestocompletethepuzzleinquestion.Iwantahighlycohesive
systemofpieces.
Figure2:Acohesivesetofparts.
Acompletepuzzlehasmuchmorevaluethanthealloftheindividualpieces.Knowingthatthepuzzlepieces
shouldcreateapictureofmysonalsoprovidesahigherlevelofvaluetomemorevaluethananyother
randompuzzle.Apuzzlewhereallofthepiecesareblack,orapuzzlethatshowsapictureofafield,willnot
inspirethefeelingsofloveinmethewayapictureofmysonwill.Thisdesiretocompletethepictureofmy
sonprovidesmotivationtonotonlyputthepuzzletogether,buttoputtherightpiecesintherightplaces.
Ifcohesivesystemssoftware,puzzles,orotherwisehavemultiplepartscomingtogethertocreatemore
valuethanthenindividualparts,itstandstoreasonthatyoucannotcreateahighlycohesivesystemoutof
large,allinonepieces.Forexample,asoftwaresystemcannotbecohesiveifitismadeupofexcessively
largegodclasses.Thesetypesofclassestendtohavetoomanyconcernswithinthemtocreateany
cohesionoutsideofthemselves.Asingleclassthatdoesalloftheactionsinthefollowingbulletlisthasfar
toomanyconcernstobecohesivewithanythingelse.
Loaddatafromafileordatabase
Processthedataintoastructure
Displaythedatatotheuser
Obtaininputfromtheuseronwhattodowiththedata
Performtheactionsrequestedbytheuser
Persistthechangesbacktothefileordatabase
Theprocesseslistedhereareverycommon.Manysoftwaresystemsusesomeformofthisbasicworkflow.
However,includingalloftheseprocessesinasingleclasswouldmakeitdifficulttocreateacohesive
system.Youmightseecohesionbetweenhighlevelprocessesbutyoulosetheabilitytocreatecohesionat
lowerlevelssuchasmethodsandclasses.
Tocreateamorecohesivesystemfromthehigherandlowerlevelperspectivesinthisexample,youcan
breakoutthevariousneedsintoseparateclasses.Youmightseparatetheuserinterfaceneeds,thedata
loadingandsavingneeds,andtheprocessingofthedata.Havingthesesmallerparts,eachwiththeirown
valueintheoverallprocess,givesyouamuchmorecohesivesystemfromthevariousperspectives.
Encapsulation
Mostdevelopersdefineencapsulationasinformationhiding.However,thisdefinitionoftenleadstoan
incompleteunderstandingandimplementationofencapsulation.Itseemsthatmanypeopleseetheword
informationandthinkdataorproperties.Basedonthistheytrytomakepublicpropertiesthatwraparound
privatefields,ormakepropertiesprivateinsteadofpublic.Thisperspective,unfortunately,missesoutona
tremendousopportunitythatencapsulationprovides:tohidenotjustdata,butprocessandlogicaswell.
Strongencapsulationisevidencedbytheabilityforadevelopertouseagivenclassormodulebyits
interface,alone.Thedeveloperdoesnot,andshouldnot,needtoknowtheimplementationspecificsofthe
classormodule.Figure3representsawellencapsulatedprocesswherethecallingobjects,representedby
blueboxes,donothavetoknowabouttheimplementationdetailoftheredbox.Theredboxshouldbefree
tochangeitsimplementationwithoutfearofbreakingthecodethatiscallingit,solongasthepublic
interfaceorthesemanticsofthatinterfacedonotchange.
Figure3:Encapsulation:hiddendataandprocessimplementation.
Encapsulationhelpsreduceduplicationofdataandprocessesinyoursystem.Whetheryouhaveabusiness
process,asinglepointofcommondata,oratechnicalorinfrastructureprocess,youshouldhaveoneand
onlyoneimplementationtorepresenttheiteminquestion.
Ifyouplantouseacircularsawtocutapieceofwood,youprobablydontcareabouthowthemotorinthat
sawworks.Youonlycarethatwhenyoupullthetrigger,thebladespinsatarapidrateandallowsyouto
easilycutthewood.
Insituationswhereyouneedtouseaprocessinmorethanonelocation,properencapsulationcombined
withlowcouplingwillhelptoensurethatyouhaveapartthatcancreatecohesioninthesystem.For
example,ifyouplantouseacircularsawtocutapieceofwood,youprobablydontcareabouthowthe
motorinthatsawworks.Youonlycarethatwhenyoupullthetrigger,thebladespinsatarapidrateand
allowsyoutoeasilycutthewood.Thesawhasencapsulatedtheprocessofcausingthebladetoturn
throughasimple,publicinterfacethehandlewiththetrigger.Additionally,thesawitselfcontainsotherforms
ofencapsulationsuchastheconnectionpointsbetweenthesawbladeandthemotor.Thisallowsyouto
replacethesawbladewithouthavingtoreconstructthemotor,thetriggermechanism,oranyotherpartof
thesaw.
ObjectOrientedPrinciplesinOurDaytoDayJobs
Developershearabouttheseandotherprinciplesofobjectorienteddevelopmentfairlyregularlyduringtheir
professionalcareer.Theserealworlddiscussionsoftencenteraroundhowtheprincipleswouldbeniceto
achieve,though,relegatingthemtotherealmsoftheivorytoweracademic.Whenitcomestotheeveryday
workofsoftwaredevelopment,itseemsthatmostdeveloperseitherdontunderstandhowtogettothese
principlesordontthinkitspossibleinareasonabletimeframe.However,theseprinciplesarenotjustfor
theacademics.Developersshouldapplythemtotheirdevelopmentefforts.Thequestionshouldchange
fromCanyouapplytheprinciples,here?toHowdoyoucorrectlyapplytheprinciples,here?
S.O.L.I.D.SteppingStones
Whenyoustartaskingthequestionofhow,itsalittlelikelookingatamarathonraceandwonderinghow
youendupatthefinishline.Obviously,foramarathonyouarriveatthefinishlinebyrunningonestepata
time.Softwaredevelopmentletsyoumoveonestepatatimetowardyourobjectorientedgoals,aswell.
Thestepsarecomposedofadditionalprinciplesandimplementationgoals,suchasthoseoutlinedinthe
SOLIDacronym:
S:SingleResponsibilityPrinciple(SRP)
O:OpenClosedPrinciple(OCP)
L:LiskovSubstitutionPrinciple(LSP)
I:InterfaceSegregationPrinciple(ISP)
D:DependencyInversionPrinciple(DIP)
OriginallycompiledbyRobertC.Martininthe1990s,theseprinciplesprovideaclearpathwayformoving
fromtightlycoupledcodewithpoorcohesionandlittleencapsulationtothedesiredresultsoflooselycoupled
code,operatingverycohesivelyandencapsulatingtherealneedsofthebusinessappropriately.
TheSingleResponsibilityPrinciplesaysthatclasses,modules,etc.,shouldhaveoneandonlyonereasonto
change.Thishelpstodrivecohesionintoasystemandcanbeusedasameasureofcouplingaswell.
TheOpenClosedPrincipleindicateshowasystemcanbeextendedbymodifyingthebehaviorofindividual
classesormodules,withouthavingtomodifytheclassormoduleitself.Thishelpsyoucreatewell
encapsulated,highlycohesivesystems.
TheLiskovSubstitutionPrinciplealsohelpswithencapsulationandcohesion.Thisprinciplesaysthatyou
shouldnotviolatetheintentorsemanticsoftheabstractionthatyouareinheritingfromorimplementing.
TheInterfaceSegregationPrinciplehelpstomakeyoursystemeasytounderstandanduse.Itsaysthatyou
shouldnotforceaclienttodependonaninterface(API)thattheclientdoesnotneed.Thishelpsyou
developwellencapsulated,cohesivesetofparts.
TheDependencyInversionPrinciplehelpsyoutounderstandhowtocorrectlybindyoursystemtogether.It
tellsyoutohaveyourimplementationdetaildependonthehigherlevelpolicyabstractions,andnottheother
wayaround.Thishelpsyoutomovetowardasystemthatiscoupledcorrectly,anddirectlyinfluencesthat
systemsencapsulationandcohesion.
Throughouttherestofthisarticle,Iwillwalkthroughascenarioofcreatingasoftwaresystem.Youwillsee
howthefiveSOLIDprinciplescanhelpyoutoachievestrongencapsulation,highcohesion,andlow
coupling.Youwillseehowyoucanstartwitha50metergetitdonenowdash,andendwithalongterm
marathonofupdatestothesystemsfunctionality.
SettingthePace:A50MeterDash
Tohelpunderstandhowyoucanachievethegoalofanobjectorientedsystemthroughtheuseofthe
SOLIDprinciples,Illwalkyouthroughasimplescenario,asolution,andtheresultingexpectations.
Scenario:EmailanErrorLog
Onedayattheoffice,yourmanagerwalksintoyourcubeandlookslikehishairisonfire.Heinformsyou
thathismanager,theCTO,justgotoffthephonewithaveryiratecustomer.Apparently,oneofyour
companyshostedapplicationsisthrowingexceptionsandpreventingthecustomerfrombeingableto
completetheirwork.
TheCTOhasinformedyourmanagerthatheneedsimmediateknowledgeoftheexceptionsbeingthrown
fromthissystem,andpersonallywantstoseeanemailinhisinboxforeveryexceptionthrown,untilthe
systemisfixed.Yourmanager,worriedaboutkeepinghisjob,nowwantsyoutocreateaquickanddirty
applicationthatallowsanetworkoperationspersontosendthecontentsofalogfiletotheCTO.
Furthermore,thisthinghastobeoutthedoorandinthehandsofthenetworkoperationspersonbefore
lunchacoupleofhoursfromnow.Usingarunninganalogy,youarenowengagedina50meterdash.You
needtocrankthiscodeoutanddeliveritasquicklyaspossible.
Solution:SelectaFileandSendIt
Afewhoursafterthatconversationwithyourmanager,youhaveproducedaverysimplesystemthatallows
theusertoselectafileandsendthecontentsofthatfileviaemail(Figure4).
Figure4:Applicationstructuretosendfilecontents.
Theimplementationofthisapplicationisverycrudebyyourownstandards:youcodedtheentireapplication
intheformscode,didnoofficialtesting,anddidthebareminimumofexceptionhandling(Listing1).
However,yougotthejobdone.
NewExpectation:AllErrorsEmailed
Aweekafteryouwrotethatquickanddirtyemailsendingapplication,yourbossisbackinyourcubetotalk
aboutitagain.Thistime,heinformsyouthatyourapplicationwasasmashingsuccessandtheCTOhas
mandatedthatallsystemssenderrorlogemailstoaspecialemailaddressforcollectingthem.TheCTO
wantsyou,specifically,tohandlethissinceyouroriginalapplicationwasreceivedsowell.
Figure5:Aclasswithmanyreasonstochange.
ResettingthePace
Asyourfirstassignment,afterhearingaboutthisnewmandatefromtheCTO,youwanttofigureoutwhat
logfilesthenetworkoperationspersonnelwillneedtosend,andhowtheywanttofacilitatethis.Aftersome
discussionwiththeoperationsgrouplead,youhaveagreedtoaddtwonewaspectsoffunctionalityofthe
system:
1.TheoperationspeoplewantanAPItocodeagainstforsomeoftheirautomationscripts.
2.TheyneedtoparsethecontentsofanXMLfiletomakeitalittlemorehumanreadable.
Youhavealsonegotiatedaslightlybettertimeframewiththenetworkoperationspeoplethanyourmanager
gaveyoufortheoriginalapplication.Theyhaveagreedtoadeliverydateofcloseofbusiness,tomorrow.
Withthisnewdeadlineandthenewrequirementsinmind,youdecidetosettleinforaslightlylongerrace
thantheoriginal50meterdash.Thecodeyoustartedwithwassufficientatthetime,butnowyouneedto
enhanceandextendit.Youcouldconsiderthisa100orpossiblya400meterraceatthispoint.Thegood
newsisthatyouknowhowtosetyourpaceaccordingtothesituationyoufindyourselfin.
SingleResponsibilityPrinciple
TheSingleResponsibilityPrinciplesaysthataclassshouldhaveone,andonlyone,reasontochange.
Thismayseemcounterintuitiveatfirst.Wouldntitbeeasiertosaythataclassshouldonlyhaveone
reasontoexist?Actually,noonereasontoexistcouldveryeasilybetakentoanextremethatwouldcause
moreharmthangood.Ifyoutakeittothatextremeandbuildclassesthathaveonereasontoexist,youmay
endupwithonlyonemethodperclass.Thiswouldcausealargesprawlofclassesforeventhemostsimple
ofprocesses,causingthesystemtobedifficulttounderstandanddifficulttochange.
Whenthebusinessperceptionandcontexthaschanged,thenyouhaveareasontochangetheclass.
Thereasonthataclassshouldhaveonereasontochange,insteadofonereasontoexist,isthebusiness
contextinwhichyouarebuildingthesystem.Eveniftwoconceptsarelogicallydifferent,thebusiness
contextinwhichtheyareneededmaynecessitatethembecomingoneandthesame.Thekeypointof
decidingwhenaclassshouldchangeisnotbasedonapurelylogicalseparationofconcepts,butratherthe
businesssperceptionoftheconcept.Whenthebusinessperceptionandcontexthaschanged,thenyou
haveareasontochangetheclass.Tounderstandwhatresponsibilitiesasingleclassshouldhave,youneed
tofirstunderstandwhatconceptshouldbeencapsulatedbythatclassandwhereyouexpectthe
implementationdetailsofthatconcepttochange.
Consideranengineinacar,forexample.Doyoucareabouttheinnerworkingoftheengine?Doyoucare
thatyouhaveaspecificsizeofpiston,camshaft,fuelinjector,etc?Or,doyouonlycarethattheengine
operatesasexpectedwhenyougetinthecar?Theanswer,ofcourse,dependsentirelyonthecontextin
whichyouneedtousetheengine.
Ifyouareamechanicworkinginanautoshop,youprobablycareabouttheinnerworkingsoftheengine.
Youneedtoknowthespecificmodel,thevariouspartsizes,andotherspecificationsoftheengine.Ifyou
donthavethisinformationavailable,youlikelycannotservicetheengineappropriately.However,ifyouare
anaverageeverydaypersonthatonlyneedstransportationfrompointAtopointB,youwilllikelynotneed
thatlevelofinformation.Thenotionoftheindividualpistons,sparkplugs,pulleys,belts,etc.,isalmost
meaninglesstoyou.Youonlycarethatthecaryouaredrivinghasanengineandthatitperformscorrectly.
TheengineexampledrivesstraighttotheheartoftheSingleResponsibilityPrinciple.Thecontextsofdriving
thecarvs.servicingtheengineprovidetwodifferentnotionsofwhatshouldandshouldnotbeasingle
conceptareasonforchange.Inthecontextofservicingtheengine,everyindividualpartneedstobe
separate.Youneedtocodethemassingleclassesandensuretheyarealluptotheirindividual
specifications.Inthecontextofdrivingacar,though,theengineisasingleconceptthatdoesnotneedtobe
brokendownanyfurther.YouwouldlikelyhaveasingleclasscalledEngine,inthiscase.Ineithercase,the
contexthasdeterminedwhattheappropriateseparationofresponsibilitiesis.
SeparatingtheEmailApplication
Aftersomequickanalysisofyourexistingapplicationscode,youdecidethatthenewrequirementsare
reallytwodistinctpointsofchange.FollowingtheSingleResponsibilityPrinciple,thesetwopointsshowyou
whereyouneedtoseparatetheexistingcodeintomultipleclasses(Figure6).
Figure6:Twopointsofchangeforyourapplication.
AnewEmailSenderobjectwillprovidetheabilityforthenetworkoperationspersonneltohaveanAPIto
codeagainst.Additionally,separatingouttheformatreadingfromtheformisnecessarytoallowtheformor
theAPItoreadthefileformat.
TosimplifytheAPIthatthenetworkoperationspeopleneed,youdecidetoputthefilereadingcodeintothe
emailsender(Listing2).Thiswillprovideasimpleenoughinterfaceandletyougetthefunctionalityoutthe
doorinatimelymanner.
Intheinterestoftimeandnotneglectingyourotherresponsibilities,youdecidetogoaheadandcreatea
singleFormatReaderclasstohandlebothofthefileformats.Thiscodeonlyneedstoknowifthecontents
arevalidXML.AquickhacktoloadthecontentsintoanXmlDocumentshouldbesufficientforthissmall
application.
stringmessageBody;
try
{
XmlDocumentxmlDoc=newXmlDocument();
xmlDoc.LoadXml(fileContents);
messageBody=xmlDoc.
SelectSingleNode("//email/body")
.InnerText;
}
catch(Exception)
{
messageBody=fileContents;
}
returnmessageBody;
ThelessontorememberinthisreleaseoftheapplicationisthattheSingleResponsibilityPrincipleisdriven
bytheneedsofthebusinesstoallowchange.Asinglereasontochangehelpsyouunderstandwhich
logicallyseparateconceptsshouldbegroupedtogetherbyconsideringthebusinessconceptandcontext,
insteadofthetechnicalconceptalone.
ExtensibilityandComingRequirements
AfewdaysafterdeliveringtheAPIsettoyournetworkoperationsdepartment,yourmanagerisbackinyour
cubewithgoodnewstheoperationspersonnellovewhatyouhavedoneforthem.TheAPIyoudelivered
wasverysimpleandtheywereabletogettheemailprocessupandrunninginnotimeatall.
Withthissuccessinmind,yourmanagerhasbeentryingtodrumupadditionalusesforyournew
application.Inthiseffort,hehasheardsomerumblingaboutneedingtosendlogmessagesfrommorethan
flatfilesorXMLfiles.Heexpectstheofficialrequestforfeaturestocomeinsoon,andwantsyoutogeta
headstartonbeingabletoextendtheapplicationinthismanner.
Giventheoperationsgroupcapabilitiestheywritesomecode,thoughitisusuallysomesortofscriptingyou
decidethattheyshouldbeabletoextendthesupportedfileformatswhenevertheyneedto.Afteraquick
discussionwiththeoperationspersonnel,theyagreeandappreciateyourconfidenceintheirabilities.From
thatdiscussionandthedirectionfromyourmanager,youdecidetomoveforwardontheabilitytoaddnew
fileformatsasneeded.
OpenClosedPrinciple
TheOpenClosedPrinciplesaysthataclassshouldbeopenforextension,butclosedformodification.In
otherwords,youshouldbeabletoeasilychangethebehavioroftheclassinquestionwithouthavingto
modifyit.
Thenexttimeyouareatahardwarestore,lookatthepowertools.Youwillnoticethatthereareawide
rangeofsawbladesthatcanattachtoasinglesaw.Onebladecomparedtoanothermaynotlookvery
differentatfirst,butacloserinspectionmayrevealsomesignificantdifferences.Somebladesare
constructedwithdifferentmetals,thenumberofteethoredgesmayvary,andthematerialthatisusedfor
theteethisoftendesignedforspecialpurposes.Nomatterwhatthedifference,though,ifyouarecomparing
twobladesthatattachtothesametypeofsaw,theywillhaveonethingincommon:howtheyattachtothe
sawtheinterfacebetweenthesawandtheblade.
Theindividualdifferencesofthebladesarewhatmakeeachtypeofbladeunique.Oneblademaycut
throughwoodextremelyquickly,butleavetheedgesrough.Anotherblademaycutwoodmoreslowlyand
leavetheedgessmooth.Stillothersmaybesuitedforcuttingmetalorothermaterials.Thewidevarietyof
blades,combinedwiththecommonmethodofattachingthemtothesaw,allowsyoutochangethebehavior
ofthesawwithouthavingtomodifythemechanicalportionofthesaw.
So,howdoyouallowaclasssbehaviortobemodifiedwithoutactuallymodifyingtheclass?Theansweris
surprisinglysimpleandthereareseveralmethodsfordoingthis.
Haveyoueverimplementedaninterfaceinaclassandthenpassedaninstanceofthatclassintoanother
object?PerhapsyouimplementedtheIPrincipalinterfaceforcustomsecurityneeds.Or,youmayhave
writtenyourowninterfacesuchastheclassicexampleofIAnimal,andimplementedaCatandaDogobject
fromthisinterface.Theubiquitousnatureofexplicitinterfacesin.NET,aswellasabstractbaseclasses,
delegates,andotherformsofabstraction,allprovidedifferentwaysofallowingcustombehaviortobe
suppliedtoexistingclassesandmodules.YoucanusedesignpatternssuchasStrategy,Template,State,
andotherstofacilitatethebehavioralchangesthroughtheuseofsuchabstractions.Therearestillother
patternsandabstractions,andothermethodsofinjectingbehaviorandalteringtheclassatruntime.
Chancesare,ifyouhavewrittenanapplicationthatrequiredevenasmallamountofflexibility,youhave
eitherprovidedacustombehaviorimplementationtoanexistingclass,orhavewrittenaclassthatrequired
acustombehaviortobesupplied.
RestructuringforOpenClosed
Giventheneedformultiple,unknownfiletypestobeparsed,youdecidetosupplyaninterfacethatcanbe
implementedbyanynumberofobjects,fromanynumberofthirdparties,includingthenetworkoperations
personnel.Inadditiontotheactualfileparsing,youwillneedtheinterfacetotellyouwhetherornotthe
specificimplementationcanhandlethecurrentfilecontents.Yourresultingapplicationstructurelooksmore
likeFigure7,withtheIFileFormatReaderinterfacedefinedasfollows:
Figure7:Enablingopen/closedviaIFileFormatReader.
publicinterfaceIFileFormatReader
{
boolCanHandle(stringfileContents);
stringGetMessageBody(stringfileContents);
}
Sinceyouknowthattherearemultiplefileformatsbeingreadnow,youalsodecidetomovetheexisting
codethatreadstheflatfileandXMLfileformatsintotwoseparateobjects.Theflatfilereadercanhandle
anynonbinarylogfile,soyoudecidethatthishandlerdoesnotneedtodetermineifitcanhandlethefile
contentssenttoit.Itonlyneedstosaythatitcanhandletheformat,andthensendtheoriginalcontentback
out.Yourewritetheimplementationoftheflatfileformatreaderasfollows:
classFlatFileFormatReader:IFileFormatReader
{
publicboolCanHandle(stringfileContents)
{
returntrue;
}
publicstringGetMessageBody(
stringfileContents)
{
returnfileContents;
}
}
TheXMLfileformatreaderwillcontainachecktoseeiftheXMLisvalid.TheGetMessageBodymethodwill
thenparsetheXMLforthecontent,asshowninListing3.
Next,youwanttointroducetheFileReaderServiceclass.ThiswillusethevariousIFileFormatReader
implementationsandiswherethebehavioralchangewilloccurwhenthevariousformatreadersare
supplied.
Tosupportanunknownnumberoffileformatreaders,youdecidetostorethelistofregisteredformat
readersinasimplecollection:
IList<IFileFormatReader>_formatReaders=new
List<IFileFormatReader>();
publicvoidRegisterFormatReader(
IFileFormatReaderfileFormatReader)
{
_formatReaders.Add(fileFormatReader);
}
TheRegisterFormatReadermethodallowsanycodethatcallstheFileReaderServiceAPItoregisteras
manyformatreadersastheyneed.Then,whenafileneedstobeparsed,acalltoaGetMessageBody
methodismade,passinginthecontentsofthefileasastring.Thismethodrunsthroughthelistof
registeredformatreadersandcheckstoseeifthecurrentonecanhandletheformat.Ifitcan,itcallsthe
GetMessageBodymethodofthereaderandreturnsthedata.
publicstringGetMessageBody(stringfileContents)
{
stringmessageBody=string.Empty;
foreach(IFileFormatReaderformatReader
in_formatReaders)
{
if(formatReader.CanHandle(fileContents))
{
messageBody=formatReader
.GetMessageBody(fileContents);
break;
}
}
returnmessageBody;
}
Atthispoint,ifthereisnoregisteredreaderthatcanhandlethefilecontents,anemptystringisreturned.
Yourealizethatyouneedtoaddadefaultfilereader.Theintentionistoensurethatalllogfilesarehandled,
regardlessofthecontent.Ifafilecantbehandledbyanyotherreader,youwillwanttoreturnallofthe
contentthroughtheflatfileformatreader.
ByaddingaseparateRegisterDefaultFileReadermethod,youcanensurethatonlyonedefaultexists.
Listing4showstheresultingGetMessageBodyimplementation.
Finally,youneedtoupdatetheusageoftheFormatReaderobjectinyourEmailSender.Youneedtoregister
boththeXMLfileformatreaderandtheflatfileformatreaderintheconstructoroftheemailsenderclass.
privatereadonlyFileReaderService
_fileReaderService=newFileReaderService();
publicEmailSender()
{
_fileReaderService.RegisterFormatReader(
newXmlFormatReader());
_fileReaderService.
RegisterDefaultFormatReader(
newFlatFileFormatReader());
}
HappyConsumersandMoreRequirements
AfewdaysafterreleasingthisversionoftheapplicationandAPI,youhearthattheoperationsgrouploves
yourIFileFormatReaderandtheextensibilityitbringstothetable.Theyhavesuccessfullyimplemented
severalformatreadersandareplanningonmore.
Ashorttimelater,anewrequestcomesinthatyouwerenotexpecting.Onesystemtheoperationsgroup
mustsupportlogsallofitserrorstoadatabase,notatextfile.Moreover,accordingtheoperations
personnel,theycannotwritecodethathitsthedatabaseinquestion.Apparently,thatsabovetheirpay
grade.Theyneedsomeoneonthedevelopmentstafftodoit,andareaskingforyourhelp.
ThemostchallengingpartofthisnewrequirementistheCTObeinginvolved,again.Duetothehighvisibility
ofthisprojectandthepotentialforlostrevenueiferrorsarenotproactivelycorrected,hewantsyour
applicationupdatedtosupportreadingfromthedatabase,immediately.Accordingtoyourmanager,when
theCTOsaysimmediatelyheusuallymeansbeforetheendoftheday.Itsonlyafewhoursbeforetheday
endsandyouvebeenrunningonverylittlesleepforthelastfewdays,butyouthinkyoucanbangouta
workingversionandgetittotheoperationsgroupintimetomaketheCTOhappy.
LiskovSubstitutionPrinciple
TheLiskovSubstitutionPrinciplesaysthatanobjectinheritingfromabaseclass,interface,orother
abstractionmustbesemanticallysubstitutablefortheoriginalabstraction.Eveniftheoriginalabstractionis
poorlynamed,theintentofthatabstractionshouldnotbechangedbythespecificimplementations.This
requiresasolidunderstandingofthecontextinwhichtheinterfacewasmeanttobeused.
Toillustratewhatasemanticviolationmaylooklikeincode,considerasquareandarectangle,asshownin
Figure8.Ifyouareconcernedwithcalculatingtheareaofaresultingrectangle,youwillneedaheight,a
widthandanareamethodthatreturnstheresultingcalculation.
Figure8:Semanticviolationsarenotalwayseasytosee.
publicclassRectangle
{
publicvirtualintHeight{get;set;}
publicvirtualintWidth{get;set;}
publicintArea()
{
returnHeight*Width;
}
}
Ingeometry,youknowthatallsquaresarerectangles.Youalsoknowthatnotallrectanglesaresquares.
Sinceasquareisarectangle,though,itseemsintuitivethatyoucouldcreatearectanglebaseclassand
havesquareinheritfromthat.Butwhathappenswhenyoutrytochangetheheightorwidthofasquare?
Theheightandwidthmustbethesameoryounolongerhaveasquare.Ifyoutrytoinheritfromrectangle
tocreateasquare,youendupchangingthesemanticsofheightandwidthtoaccountforthis.
publicclassSquare:Rectangle{
publicoverrideintHeight{
get{returnbase.Height;}
set{base.Height=value;
base.Width=value;
}
}
publicoverrideintWidth{
get{returnbase.Width;}
set{base.Width=value;
base.Height=value;
}
}
}
Whathappenswhenyouusearectanglebaseclassandasserttheareaofthatrectangle?Ifyouexpectthe
rectanglesareatobe20,youcansettherectanglesheightto5andwidthto4.Thiswillgiveyoutheresult
youexpect.
Rectanglerectangle=newRectangle();
rectangle.Height=4;
rectangle.Width=5;
AssertTheArea(rectangle);
privatevoidAssertTheArea(Rectanglerectangle)
{
intexpectedArea=20;
intactualArea=rectangle.Area();
Debug.Assert(expectedArea==actualArea);
}
WhatifyoudecidetopassasquareintotheAssertTheAreamethod,though?Themethodexpectstofindan
areaof20.Letstrytosetthesquaresheightto5.Youknowthatthiswillalsosetthesquareswidthto5.
Whenyoupassthatsquareintothemethod,whathappens?
Rectanglesquare=newSquare();
square.Height=5;
AssertTheArea(square);
privatevoidAssertTheArea(Rectanglerectangle)
{
intexpectedArea=20;
intactualArea=rectangle.Area();
Debug.Assert(expectedArea==actualArea);
}
Yougetthewrongresultbecause5x5is25,not20.Thatistoohigh,sonowtryaheightof4instead.You
knowthat4x4is16.Unfortunately,thatstoolow.Sothequestionis,howcanyouget20outofmultiplying
twointegers?Theansweris:youcant.
ThesquarerectangleissueillustratesaviolationoftheLiskovSubstitutionPrinciple.Youclearlyhavethe
wrongabstractiontorepresentbothasquareandarectangleforthisscenario.Thisisevidencedbythe
squareoverridingtheheightandwidthpropertiesoftherectangle,andchangingtheexpectedbehaviorofa
rectangle.
What,then,wouldacorrectabstractionbe?Inthiscase,youmaywanttouseasimpleShapeabstraction
andonlyprovideanAreamethod,asshowninListing5.Eachspecificimplementationsquareand
rectanglewouldthenprovidetheirowndataandimplementationforareaallowingyoutocreateadditional
shapessuchascircles,triangles,andothersthatyoudontyetneed.Bylimitingtheabstractiontoonlywhat
iscommonamongalloftheshapes,andensuringthatnoshapehasadifferentmeaningforareayoucan
helppreventLSPviolations.
AQuickandDirtyDatabaseReader
Afterfuellingupwithanotherenergydrinkandshakingoffthesleepyousodesperatelywant,youdiveinto
thecodeforthedatabasereader.Giventheshorttimeframe,youdecidetotakeashortcutandnot
introduceanewabstractionoranewmethodtotheAPI.Rather,youdecidetohardcodethebehaviorof
readingfromthedatabaseintotheapplication,facilitatedbytheuseofaspecialfileformatreader.Youknow
itsnotthebrightestmomentinyourcareer,butyoujustwanttogetitoutthedoorandgohomeforthe
night.Whatshouldhavebeenan800meterracehasnowbecomea50meterdashinyourmind.Listing6
showstheresult.
Youdelivertheworkingcodeontime,andmanagetomakeithomebeforefallingasleepwhiledriving.
Overall,youconsiderittobeasuccessfulday.
Thefollowingweek,youhearwordthatthenetworkoperationspersonnellikedtheabilitytoreadfroma
database.Infact,theylikeditsomuchthattheytoldanotherdepartmentaboutthisfeature.Whattheydidnt
know,though,wasthatthecodeyoudeliveredwaswrittenforoneveryspecificdatabaseanddidntactually
readtheconnectionstringfromafile.Youknewthatthesecurityguyswouldhaveyourheadifyoustored
therealconnectioninformationinaplaintextfile,soyouhardcodeditintothefilereaderservice.You
createdtheserver=contentofthefileasaplaceholdertoletyouknowthatyoushouldusethedatabase
connectionreader.
So,whenthenetworkoperationspersonnelgaveyourcodetotheotherdepartment,everyonestarted
wonderingwhytheotherdepartmentwasnowreadinglogfilesfromthenetworkoperationscenter.Alleyes
arenowlookingsquarelyatyou.
RevisitingtheDatabaseReader
Alloftheeyeslookingsquarelyatyouwerefromyourfriendsinthecompany,fortunately.Afterexplaining
thestressandsleeplessnessthatunderminedyourabilitytocodethatday,theyalllaughedandaskedwhen
youwouldhaveanewversionreadyforthem.Rememberingthatsprintingoutofthegateduringa
marathonraceislikelytocausethesameproblemsagain,youinformthemthatyoullneedadayortwoto
getthesituationsortedoutcorrectly.TheresnoimmediateneedorCTOputtingonthepressureatthis
point,soeveryoneagreestothegeneraltimelineandwaitspatientlywhileyouwork.
Afteraquickdiscussionwithsomecoworkers,yourealizethatyouhadchangedthesemanticsofthefile
formatreaderinterfaceandintroducedbehaviorthatwasincompatible.Afteralittlemorediscussion,you
endupwiththedesignrepresentedbyFigure9,andthechangeturnsouttobefairlysimple.
Figure9:RestructuringdatabasereadingtocorrecttheLSPviolation.
Byintroducingaseparatedatabasereaderservice,youcanremovethetypecheckingcodefromthefile
readerservice.
Byintroducingaseparatedatabasereaderservice,youcanremovethetypecheckingcodefromthefile
readerservice.Youcansetupthedatabasereadertoreadtherequiredconnectionstringfromthe
companystandardstorageforsensitivedata.Thatdecisionmakesthepeopleinnetworkoperations,
security,andtheotherdepartmentthatwantstousethecode,happy.
Next,youupdatetheUItoincludeaSendFromDatabasebuttonasshowninFigure10.Thisbuttoncalls
intothesameemailsenderobjectthatyouvebeenusingasthepublicAPI.However,theemailsendernow
hasaReadFromDatabasemethodalongwithaReadFromFilemethod.ThiskeepsthepublicAPI
centralizedwhilestillprovidingthefunctionalitythatthevariousdepartmentsneed.
Figure10:TheUIupdatedwiththeSendFromDatabasefunctionality.
publicclassEmailSender
{
publicvoidReadFile()
{/*...*/}
publicvoidSendEmail()
{/*...*/}
publicvoidReadDatabase()
{/*...*/}
}
Withthisnewlystructuredsysteminplace,youdeliverthesolutiontobothofthewaitingdepartments.Your
friendsarehappytohearthatyouvebeengettingmoresleepandthattheapplicationtheyvebeenwaiting
forisfinallydoneadayearlierthanpromised.
StillMoreUseforYourApplication
Shortlyafterdeliveringtheupdatedversionoftheapplicationwiththedatabasereadingcapabilities,another
departmentgetswindofitandtheywanttousetheAPI.Afteraquickconversationwiththemtofindoutif
yourapplicationiswhattheyreallyneed,youdelivertheworkingbits.Adaylater,oneofthedevelopersfrom
thatdepartmentstopsbyyourcubewithaconfusedlookonhisface.Aftersomequickchat,yourealizethat
hesconfusedbytheemailsenderobject.Itseemsthathedoesntunderstandwhytheresareadfrom
databaseandreadfromfilemethodonanobjectthatissupposedtosendemail.
InterfaceSegregationPrinciple
TheInterfaceSegregationPrinciplesaysthataclient(thecallingclassormodule)shouldnotbeforcedto
dependonaninterfacethatitdoesnotneed.Iftherearemultipleconcernsrepresentedbyaninterface,or
themethodsandpropertiesareunclear,thenitbecomesdifficulttoknowwhichmethodsshouldbecalled
when.Therefore,youshouldseparatetheinterfaceintologicalpieces,basedontheneedsofthe
consumers.
Toacertainextent,ISPcanbeconsideredasubset,ormorespecificformoftheSingleResponsibility
Principle.TheperspectiveshiftofISP,though,examinesthepublicAPIforagivenclassormodule.Looking
attheIHaveALotOfResponsibilitiesclassinFigure11,youcanseenotonlyasetofmethodsthatyou
shouldbreakintomultipleclassesforthesakeofSingleResponsibility,butaveryfatinterfacethatmay
confuseanyofthecallingclients.IfIwanttouseIHaveALotOfResponsibilities,doIneedtocalltheDo
methods?Ifso,doIneedtocallthembeforetheSomemethods?OrcanIjustcalltheSome
methods?Or???
Figure11:Aclasswithafatinterface.
Ratherthanforcingadevelopertoknowwhichmethodstheyshouldcalltofacilitatethefunctionality,you
shouldprovideaseparatedsetofinterfacesthatencapsulatetheprocessesinquestion,independently.This
helpstopreventconfusionandalsohelpstocutdownonsemanticcouplingtheideathatadeveloperhasto
knowthespecificimplementationoftheclasstouseitcorrectly.
ViolationsofISParenotjustfoundinsoftware,though.Mostprofessionalworkersinmodernsocietyhave
workedinanofficeatonetimeoranother.Dependingonthesizeoftheofficehowmanypeopleareworking
thereyouwilltypicallyfindoneormore,verylarge,allinonebusinessmachines.Thesemachinesprint,
copy,fax,scan&email,andotherfunctions.
Thinkbacktothelasttimeyouhadtouseoneoftheselarge,multifunctionmachines.Theytypicallyhave
controlpanelsthatinclude15to20buttons,anLCDdisplayofsomesort,andvariousotherformsofinput.
Thenumberoffunctionscombinedwiththenumberofinputoptionsoftencreatesaveryfrustratinguser
experience.Whatbuttonsorcontrolsdoyouneedtooperatethemachine?Howcanyouensurethatitis
goingtocreateaphotocopyofadocumentinsteadofscanningandemailingit?Doyouneedtoclearthe
currentsettings?Doyouneedtotypeinanumberofcopiesnow,orscanthedocumentfirstandthentellit
howmanycopiestoprint?Whataboutthebrightness,contrast,orpapersizeofyourcopy?Didthemachine
rememberthesettingsfromthelastuser,whowantedtoscanadocumentandemailittosomeone?The
numberofoptionsisoverwhelming.Theinstructionsaredifficulttofollowandyourenotalwayssurethatit
didwhatyouwant.Theworstoutcomeiswhenanerrormessagespopsupafterperformingwhatyou
believeisthecorrectsequenceofsteps.WhatdoesPCLOADLETTERmean,anyway?
Thelargenumberofcapabilitiesandoptionsthattheseallinonecopiersprovidemayoffersome
advantagestoanofficeenvironment.Theyprovidearelativelylowcost,highquality,documentcentricsetof
solutions.Unfortunately,theytypicallycomeatthecostofaconfusinginterface.(I,forone,havespenta
goodnumberofhourstryingtorememberhowtoscanandemailadocumenttomyselfvs.makinga
photocopyonthemachinesinmyoffice.)Ifmanufacturerswanttohavemachineswithsuchalargefeature
setprovidingvaluetosomanydifferentusers,theyshouldlookforwaystoseparatetheinterfaceintoeach
featuresothattheuserisnotleddownthewrongpath,orleddowntherightpathatthewrongtime.
SplittingUptheEmailSenderAPI
AfterthediscussionaboutyouremailsendersAPIbeingrolledupintoasingleclass,andtherealizationthat
noteveryoneneedstoreadfromadatabaseorafile,youdecidetoseparateouttheobjectsasshownin
Figure12.Whenyoudiveintothecodeagain,yourealizethatyoudonthavetomakemanychangesto
yoursystem.Thedatabasereaderserviceandthefilereaderservicearebothobjectsontheirown,already.
Therealchangethatyouneedtomakeistonotcallthemdirectlyfromtheemailsender.
Figure12:SeparatingthevariousserviceAPIsbyresponsibility.
TheresultingEmailSenderclassissignificantlysmaller.Additionally,youwereabletomakethemessage
bodyaparameteroftheSendEmailmethod.Thisallowsforanyclientoftheemailsendertoprovideany
messagebodytheyneed,regardlessofthesource.
publicclassEmailSender
{
publicvoidSendEmail(stringmessageBody)
{
SmtpClientclient=//new...
client.Credentials=//new...
MailAddressfrom=//new...
MailAddressto=//new...
MailMessagemsg=//new...
msg.Body=messageBody;
msg.Subject=//...
client.Send(msg);
}
Thefilereaderserviceanddatabasereaderserviceclassesneedednochangesthistimearound.You
simplymovedtheircallsintothecodebehindoftheformforyourapplication(Listing7).However,theother
departmentsthatareusingtheAPIneedtoknowthattheyareresponsibleforcallingthedatabasereader
andfilereader,directly.
Aftersomediscussionwiththeotherdepartments,therewasalittlebitofgrumblingabouthowtheythought
thenewAPIsetwasmoredifficulttouse.Theyagreedthatasmallsetofdocumentationonhowtousethe
APIwouldbesufficientfortheirneeds,though.Andhonestly,youfeelalittlemoresecureknowingthatyou
willhavesomedocumentationforthegrowingAPI.
TryingtoSettleinforaMarathon
Afterdeliveringthisversionofthesystem,includingthenewdocumentationthatyouwrotefortheAPIset,
youdecidetostepbackforaminuteandtakestockofyoursystem.Whatyouseeisrathersurprisingatthis
point.Youhaveagrowingnumberofclassesandalotoffunctionalitycomparedtotheoriginalstartingpoint
ofreadingaflattextfileandemailingit.Giventheattentionthatyouvereceivedforthisworkandthe
amountoffunctionalitythatyouhavebuiltin,youdecidetoaskyourmanagerforsomeofficialproject
support.Ratherthanworkingthiscodebaseonthesideofyourotherresponsibilities,youwouldliketohave
adedicatedprojectforthissystem.Thiswouldhelpprovidelongtermsupportforwhatisnowamission
criticalpartofthecompany.
Figure13:Adependency.
Yourmanager,havingreceivednothingbutpraiseforyourhardworkanddedicationfromeveryone
includingtheCTOhappilysaysyes.Hethenasksforatimelinetocompletethenextversion.Youlethim
knowthatyoullneedtothinkaboutthatforabit,butoffhand,youexpectthecodetochangewhennew
featuresareneeded,primarily.
Onyourwaybacktoyourdesk,anothercoworkerstopsyouandwantstodiscussthisproject.Heshearda
lotaboutwhatyouhavebeendoingandlikesthedirectionthatyouhavebeentakingthecode.Afterafew
minutesofdiscussion,yourcoworkerletsyouknowthathewantstousethisproject,butisntinterestedin
thecurrentformatreadersoremailsender.Hesmostlyinterestedinthegeneralprocessandwantstoknow
ifhecanreusevariouspartsofthesystemwithouthavingtobringallofyourspecificimplementationsalong.
Thecurrentobjectsandinterfacesprovidemostofwhatyourcoworkerwants.However,yourealizethatthe
bigpictureoftheprocessisstillhardcodedandtightlycoupledinthecurrentapplication.Yourcoworker
mentionsanideathatrevolvesaroundahigherlevelprocessprovidinganabstractionthatlowerlevel
detailscanimplement.Thispiquesyourinterestandyousitdownatyourdesk,readytotacklethisnext
challenge.
TheDependencyInversionPrinciple
TheDependencyInversionPrinciplehastwoparts:
1.Highlevelmodulesshouldnotdependonlowlevelmodules.Bothshoulddependonabstractions.
2.Abstractionsshouldnotdependupondetails.Detailsshoulddependuponabstractions.
Thinkbacktothelasttimeyouwantedtoturnonalamptohelplightanareaofaroom.Didyouhavetocut
aholeinthewall,digaroundforelectricalwires,stripthembare,andsolderthelampdirectlyintothewiring
ofthehouse?
Thinkbacktothelasttimeyouwantedtoturnonalamptohelplightanareaofaroom.Didyouhavetocut
aholeinthewall,digaroundforelectricalwires,stripthembare,andsolderthelampdirectlyintothewiring
ofthehouse?Ofcoursenot(atleast,Ihopenot!)Theelectricaloutletprovidesastandardinterfaceforsuch
anoccasion.Noone,inmostoftheindustrializedworld,wouldexpecttosolderalampdirectlyintothe
electricalwiringofthebuilding.Additionally,nooneexpectstoonlybeabletopluginalamp,toanoutlet.
Weexpecttopluginlamps,computers,televisions,vacuumsandotherdevices.Thestandard,120volt,60
hertzpoweroutlethasbecomeaubiquitouspartofsocietyintheUnitedStates.
Thesameprinciplealsoappliesinsoftwaredevelopment.Ratherthanworkingwithasetofclassesthatare
hardwired(tightlycoupled)toeachother,youwanttoworkwithastandardinterface.Furthermore,you
wanttoensurethatyoucanreplacetheimplementationwithoutviolatingtheexpectationsofthatinterface,
accordingtoLSP.So,ifyoureworkingwithaninterfaceandyouwanttobeabletoreplaceit,thenyouneed
toensurethatyouareonlyworkingwiththeinterfaceandneverwithaconcreteimplementation.Thatis,the
codethatreliesontheinterfaceshouldonlyeverknowabouttheinterface.Itshouldnotknowaboutanyof
thespecificclassesthatimplementtheinterface.
Policy,Detail,andAbstractionOwnership
AnotherwaytothinkaboutDIPistosaythatpolicy(highlevel)shouldnotdependondetail
(implementation),butdetailshoulddependonpolicy.Thehigherlevelpolicyshoulddefineanabstraction
thatitwillcalloutto,wheresomedetailimplementationexecutestherequestedaction.Thisperspectivecan
helptoillustratewhythisisthedependencyinversionprincipleandnotjustadependencyabstraction
principle.
Asanexampleofwhydetaildependingonpolicyisaninversionofthedependency,lookatthecodeyou
wroteintotheFormatReaderService.Theformatreaderserviceisthepolicy.Itdefineswhatthe
IFileFormatReaderinterfaceshoulddotheexpectedbehaviorofthosemethods.Thisallowsyoutobe
concernedwiththepolicyitself,bydefininghowtheformatreaderserviceworkswithoutregardforthe
implementationdetailoftheindividualformatreaders.Theformatreaders,then,aredependentonthe
abstractionprovidedbythereaderservice.Boththeserviceandindividualformatreaders,intheend,are
dependentontheabstractionoftheformatreaderinterface.
CorrectingCouplingbyInvertingDependencies
Youknowthatitsnotreasonableforaclasstohavezerodependenciestohavezerocoupling.Youwould
nothaveausablesetofclassesifyouhadzerocoupling.However,youalsoknowthatyouwanttoreduce
directcouplingwheneverpossible.Youwanttodecoupleyoursystemsothatyoucanchangeindividual
pieceswithouthavingtochangeanythingmorethantheindividualpiece.TheDependencyInversion
Principleisthekeytothisgoal.Bydependingonlyonanabstractionsuchasaninterfaceorbaseclass,you
cancorrectthecouplingofthevariouspartsofthesystem.Thisallowsyoutorecomposethesystemwith
differentimplementations.
Youwouldnothaveausablesetofclassesifyouhadzerocoupling.
Considerasetofclassesthatneedtobeinstantiatedintothecorrecthierarchysothatyoucangetthe
functionalityneeded.Itseasytohavethehighestlevelclasstheonethatyouwanttocallinstantiatethe
classatthenextleveldown,andhavethatclassinstantiateitsnextleveldown,andsoon.Figure14
representsastandardobjectgraphwherethehigherlevelobjectthepolicyisdependentonandcoupled
directlytothelowerlevelobjectthedetail.
Figure14:Policycoupledtodetail.
Thiscreatesthenecessaryhierarchybutcouplestheclassestogether,directly.Youwouldnotbeabletouse
FoowithoutbringingBaralongwithit.
Ifyouwanttodecoupletheseclasses,youcaneasilyintroduceaninterfaceforFootodependonandBarto
implement.Figure15illustratesasimpleIBarinterfacethatyoucancreatefromthepublicAPIoftheBar
class.
Figure15:Decouplingwithabstraction.
Inthisscenario,youcandecoupletheimplementationofBarfromtheuseofitinFoobyintroducingthe
interface.However,youveonlydecoupledtheimplementationbyseparatingtheinterfacefromit.You
haventinvertedthedependencystructureyetandyouhaventcorrectedallofthecouplingproblemsinthis
setup.
WhathappenswhenyouwanttochangeBarinthisscenario?Dependingonthechangeyouwanttomake,
youcouldhavearipplingeffectthatcausesyoutochangetheIBarinterface.FoodependsontheIBar
interface,soyoumustchangetheimplementationofFooaswell.Youmayhavedecoupledthe
implementationofBar,butyouhaveleftFoodependentonchangestoBar.Thatis,thePolicyisstill
dependentontheDetail.
IfyouwanttoinvertthedependencystructureandhavetheDetailbecomedependentonthePolicy,then
youmustfirstchangeyourperspective.Thedeveloperworkingwiththissystemmustunderstandthatyou
shouldnotmerelyabstracttheimplementationawayfromtheinterface.Yes,thisseparationisnecessary,
butitisnotsufficient.YoumustunderstandwhoownstheabstractionthePolicy,ortheDetail.
TheDependencyInversionPrinciplesaysthatDetailshouldbedependentonPolicy.Thismeansthatyou
shouldhavethePolicydefineandowntheabstractionthatthedetailimplements.IntheFoo>IBar>Bar
scenario,youneedtotreatIBaraspartofFooandnotjustawrapperaroundBar.Nothingmayhave
changedstructurally,buttheperspectiveofownershiphasshifted,asillustratedbyFigure16.
Figure16:Policyownstheabstraction.Detaildependsonpolicy.
IfFooownstheIBarabstraction,youcanplacethesetwoconstructsinapackagethatisindependentof
Bar.Youcanputthemintotheirownnamespace,theirownassembly,etc.Thiscangreatlyincreasethe
illustrationofwhatclassormoduleisdependentontheother.IfyouseethatAssemblyAcontainsFooand
IBar,andAssemblyBprovidestheimplementationofIBar,itiseasiertoseethatthedetailofBaris
dependentonthepolicydefinedbyFoo.
Whenyouhavethedependencystructureinvertedcorrectly,therippleeffectofchangingthepolicyand/or
detailisnowcorrectaswell.WhenyouchangetheimplementationofBar,youarenolongerseeingan
upwardrippleofchanges.ThisisduetoBarbeingrequiredtoconformtotheabstractionprovidedbyFoo
thedetailisnolongerdictatingchangestothepolicy.Then,whenyouchangetheneedsofFoo,causinga
changeintheIBarinterface,younowhavechangesthatrippledownthestructure.Barthedetailwillbe
forcedtochangebasedonthepolicychanging.
DecouplingandInvertingtheEmailSendingSystemDependencies
LookingthroughyourcodebaseyouseethattheIFileFormatReaderisalreadyaninstanceofDependency
Inversion.TheFormatReaderServiceclassownsthedefinitionoftheformatreaderinterface.Iftheneedsof
theformatreaderservicechanges,youwilllikelyseeripplesofchangedownintotheindividualformat
readers.However,ifanindividualfileformatreaderchanges,youwillnotlikelyseechangesrippleupinto
theformatreaderservice.Thismakesyouwonderwhereelseyoucaninvertthesystemsdependencies.
Thefirstthingyouwanttodoisdecouplethelogicofgettingthelogmessage,andsendingitasanemail,
fromtheform.Youdontmindthereferencestothetworeaderservicesandtheemailsender,buthaving
theexplicitknowledgeofwhattocallwhenisalittlequestionableinyourmind.Yourecognizethatthe
processisactuallyduplicatedintheform:onceforsendingfromafile,andonceforsendingfroma
database.Andthenyourememberalltheotherdepartmentsthatareusingthisaswell,andstarttowonder
justhowmuchduplicationoftheprocessreallyexists.Additionally,someofyourfriendshavebeentalking
aboutunittestingrecently.Theysaythatyoushouldensuretherealprocesslogicthatyouaretestingis
encapsulatedintoobjectsthatdonthavereferencestoexternalsystems.
Withallofthisinmind,youdecidetocreateanobjectcalledProcessingService.Afterafewminutesof
movingcodearoundtotryandconsolidatetheprocess,yourealizethatyoudontwanttheprocessing
servicetobecoupleddirectlytothedatabasereaderorfilereaderservices.Withanadditionalmomentof
thinking,yourecognizeapatternbetweenthetwo:theGetMessageBodymethod.Usingthismethodas
thebasis,youcreateanewinterfacedcalledIMessageInfoRetrieverandhaveboththedatabasereaderand
filereaderservicesimplementthat.
publicinterfaceIMessageInfoRetriever
{stringGetMessageBody();}
publicclassFileReaderService:
IMessageInfoRetriever
{
publicstringGetMessageBody(){/*...*/}
}
publicclassDatabaseReaderService:
IMessageInfoRetriever
{
publicstringGetMessageBody(){/*...*/}
}
Thisinterfaceallowsyoutoprovideanyimplementationyouneedtotheprocessingservice.Youthenset
youreyesontheemailservice,whichiscurrentlydirectlycoupledtotheprocessingservice.Asimple
IEmailServiceinterfacesolvesthat,though.Figure17showstheresultingstructure.
Figure17:Invertingthedependenciesoftheprocessingserviceandfilereaderservice.
Passingthemessageinforetrieverandemailserviceinterfacesintotheprocessingserviceensuresthatyou
haveaninstanceofwhateverclassimplementsthoseinterfaces,withouthavingtoknowaboutthespecific
instancetypes.YoucanseetheendresultofthisendeavorinListing8.
AnotherDay,AnotherDelivery
Youcoworkermeetsthedeliveryofthisversionwithgreatenthusiasm.Hesexcitedaboutthepossibilitiesof
usingtheprocessingserviceandprovidinghisownemailserviceimplementation,formatreaders,etc.It
seemsthatyouhavemadeyetanothersuccessfuldeliveryofthiseverevolvingsolutionandyougladly
acceptyourcoworkersthanks.
Headingbacktoyourdesk,yourealizehowhighyourconfidenceinthiscodebase,andyourabilityto
continueimprovingit,actuallyis.Thisleadsyoutowonderwhatthenextchangerequestwillbeandwhat
newprinciplesandpracticesyoucanassimilateintoyourskillset.Itisoflittleconcern,though.Whateverthe
task,forwhoevermakestherequest,youarecertainofyourabilitytomeettheirneeds.
SummaryofYourSOLIDRace
WhenyoufirstsetoutonthequesttosatisfyyourCTO,youhadnoideathattheresultingracewould
changesomanytimesorsorapidly.Youstartedatapacetowina50meterdash.Soonafter,youslowed
thepacedownfora400meterrace.Youthencontinuedtochangethepaceasyoucontinuedtoadd
featuresandfunctionality,realizingtheneedtorestructuretheapplicationforlongtermmaintenanceand
enhancements.Thejourney,nowatthepointwhereyouaresettledinforthemarathonofalongterm
project,hasbroughtyoufromasystemrepresentedbyFigure18,toasystemrepresentedbyFigure19.
Figure18:Theoriginalstructure.
Figure19:Thenewstructure.
Thedifferencebetweenthesetwostructuresisstaggeringandlookingatthemsidebysideforamoment,
youwonderwhatyouveactuallygainedwiththissprawlofobjectsandinterfaces.Youquicklydismissthe
senseofuncertainty,though,asyourememberthenumerousadvantages.
BenefitsoftheNewSystemStructure
Withasetofclassesthataresmallandeachprovidingavaluablefunction,youareabletoconstructalarger
systemthathasmorevaluethanjusttheindividualparts.ItslikeworkingwithaboxofLEGObuilding
blocks.Eachindividualblockmaybevaluable,buttheycancreatesomethingmuchmorevaluablewhen
stackedtogethercorrectly.
Thissystemisalsoeasilydissectible,andthevariouspartsareeasilyreplaceable.Youcanchangeoutthe
specificimplementationsoftheemailservice,themessageinforetrieval,andotherpartsofthesystem.You
canaccomplishallofthesechangeswithoutworryingaboutadverselyaffectingthepartsofthesystemthat
youarenottouching.
Theabilitytosegmentthesystem,bybothhorizontallayersandverticalslices,givesyoutheabilitytofocus
intoasinglepointinthesystem.
Theabilitytosegmentthesystem,bybothhorizontallayersandverticalslices,givesyoutheabilitytofocus
intoasinglepointinthesystem.Thisallowsyoutoassignvariouspartsofthesystemtodifferentteam
members,withmoreconfidenceofthesystemnotfallingapartlikeagameofJenga.
AchievingLowCoupling
Byabstractingmanyoftheimplementationneedsintovariousinterfacesandintroducingtheconceptsof
OCPandDIP,youcancreateasystemthathasverylowcoupling.Youcantakemanyoftheseindividual
piecesoutofthissystemwithlittletonospaghettimesstrailingafterthem.Separatingthevariousconcerns
intothevariousobjectimplementationsalsohelpstoensurethatyoucanchangethesystemsbehavioras
needed,withverylittlemodificationtotheoverallsystemjustupdatetheonepiecethatcontainsthe
behaviorinquestion.
AchievingHighCohesion
YoucangethighercohesionwithacombinationoflowcouplingandSRPyoucanstackalotofsmallpieces
togetherlikebuildingblockstocreatesomethinglargerandmorecomplex.Anyoftheseindividualpieces
maynotrepresentmuchfunctionalityorbehavior,butthen,anindividualpuzzlepieceisntmuchfuntouse
withouttheotherpieces.Onceseparated,DIPallowsyoutotiethevariousblocksbacktogetherby
dependingonanabstractionandallowingthatabstractiontobefulfilledwithdifferentimplementations.This
createsasystemthatismuchgreaterthanthemeresumofitsparts.
AchievingStrongEncapsulation
LSP,DIP,andSRPallworkhandinhandtocreateencapsulation.Youcanencapsulatethebehavioral
implementationsinmanyindividualobjects,preventingthemfromleakingintoeachother.Youcanensure
thatthedependenciesonthosebehaviorsareencapsulatedbehindanappropriateinterface.Atthesame
time,youdothenecessaryduediligencetoensurethatyouarentviolatinganyoftheindividual
abstractionssemanticsorpurpose,accordingtoLSP.Thishelpsensurethatyoucanproperlyreplacethe
implementationasneeded.
ANewUtilityBelt
Intheend,thesteppingstonesoftheSOLIDprinciplesoffernewinsightintoobjectorientedsoftware
development.Theprinciplesthatyouoncethoughtwerefortheacademicsarenowasetoftoolsthatyou
canreadilygrasp.
OnlineResources
InadditiontosomesimpleGooglesearches(oryoursearchengineofchoice),thereareanumberof
popularresourcesfortheSOLIDprinciples.BobMartinhasmadehisoriginalarticlesavailable,amongalist
ofothergreatprinciples.ThereareseveralresourcesavailablethroughtheLosTechies.comcommunity,as
well.
UncleBobsPrinciplesofOODhttp://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
PablosTopicoftheMonthhttp://www.lostechies.com/blogs/chad_myers/archive/2008/03/07/pablostopic
ofthemonthmarchsolidprinciples.aspx
PablosSOLIDSoftwareDevelopmentebookhttp://www.lostechies.com/content/pablo_ebook.aspx
SingleResponsibility
Ifaclasshasmorethanoneresponsibility,thentheresponsibilitiesbecomecoupled.Changestoone
responsibilitymayimpairorinhibittheclassabilitytomeettheothers.Thiskindofcouplingleadstofragile
designsthatbreakinunexpectedwayswhenchanged.
RobertC.Martin
OpenClosed
Modulesthatconformtotheopen/closedprinciplehavetwoprimaryattributes:
TheyareOpenforExtension.Thismeansthatthebehaviorofthemodulecanbeextended.Thatis,youcan
makethemodulebehaveinnewanddifferentwaysastherequirementsoftheapplicationchange,orto
meettheneedsofnewapplications.
TheyareClosedforModification.Thesourcecodeofsuchamoduleisinviolate.Nooneisallowedtomake
sourcecodechangestoit.
RobertC.Martin
LiskovSubstitution
Ifforeachobjecto1oftypeSthereisanobjecto2oftypeTsuchthatforallprogramsPdefinedintermsof
T,thebehaviorofPisunchangedwheno1issubstitutedforo2thenSisasubtypeofT.
BarbaraLiskov
InterfaceSegregation
Thisprincipledealswiththedisadvantagesoffatinterfaces.Classesthathavefatinterfacesareclasses
whoseinterfacesarenotcohesive.Inotherwords,theinterfacesoftheclasscanbebrokenupintogroups
ofmemberfunctions.Eachgroupservesadifferentsetofclients.Thussomeclientsuseonegroupof
memberfunctions,andotherclientsusetheothergroups.
RobertMartin
DependencyInversion
Whatisitthatmakesadesignrigid,fragileandimmobile?Itistheinterdependenceofthemoduleswithin
thatdesign.Adesignisrigidifitcannotbeeasilychanged.Suchrigidityisduetothefactthatasingle
changetoheavilyinterdependentsoftwarebeginsacascadeofchangesindependentmodules.
RobertMartin
Books
BobMartinandhisson,Micah,havereleasedtwobooksthatdescribetheSOLIDprinciplesingreatdetail,
withindifferentcontexts.Youcanfindbothofthesebooksavailablethroughvariousbooksellers.
AgileSoftwareDevelopmentPrinciples,Patterns,andPracticesAgilePrinciples,Patterns,and
PracticesinC#
Listing1:Methodtosendemailfromfilecontents
privatevoidSend_Click(objectsender,EventArgse)
{
try
{
Output.Text=string.Empty;
FileInfofile=newFileInfo(_file);
StreamReaderrdr=file.OpenText();
stringmessageBody=rdr.ReadToEnd();
rdr.Dispose();
SmtpClientclient=newSmtpClient("some.mail.server.com");
client.Credentials=newNetworkCredential(
"someuser","somepassword");
MailAddressfrom=newMailAddress("me@myserver.com");
MailAddressto=newMailAddress("your.cto@yourcompany.com");
MailMessagemsg=newMailMessage(from,to);
msg.Body=messageBody;
msg.Subject="Testmessage";
client.Send(msg);
Output.Text="Sentemailwithbody:"+
Environment.NewLine+messageBody;
}
catch(Exceptionex)
{
Output.Text=ex.ToString();
}
}
Listing2:TheEmailSenderobject
publicclassEmailSender
{
publicstringMessageBody{get;set;}
publicstringFileName{get;set;}
publicvoidReadFile()
{
FileInfofile=newFileInfo(FileName);
StreamReaderrdr=file.OpenText();
stringfileContents=rdr.ReadToEnd();
rdr.Dispose();
FormatReaderformatReader=newFormatReader();
MessageBody=
formatReader.GetMessageBody(fileContents);
}
publicvoidSendEmail()
{
SmtpClientclient=newSmtpClient("some.mail.server.com");
client.Credentials=newNetworkCredential(
"someuser","somepassword");
MailAddressfrom=newMailAddress("me@myserver.com");
MailAddressto=newMailAddress("error.logs@yourcompany.com");
MailMessagemsg=newMailMessage(from,to);
msg.Body=messageBody;
msg.Subject="Testmessage";
client.Send(msg);
}
}
Listing3:ThenewXMLFileFormatReaderobject
publicclassXmlFileFormatReader:IFileFormatReader
{
XmlDocument_xmlDoc;
publicboolCanHandle(stringfileContents)
{
boolcanHandle;
try
{
_xmlDoc=newXmlDocument();
_xmlDoc.LoadXml(fileContents);
canHandle=true;
}
catch(Exception)
{
canHandle=false;
}
returncanHandle;
}
publicstringGetMessageBody(stringfileContents)
{
stringmessageBody=_xmlDoc.SelectSingleNode("//email/body")
.InnerText;
returnmessageBody;
}
}
Listing4:GetMessageBodywithdefaultfileformatreader
privateIFileFormatReader_defaultFormatReader;
publicvoidRegisterDefaultFormatReader(
IFileFormatReaderdefaultFormatReader)
{
_defaultFormatReader=defaultFormatReader;
}
publicstringGetMessageBody(stringfileContents)
{
stringmessageBody=string.Empty;
boolwasHandled=false;
foreach(IFileFormatReaderformatReaderin_formatReaders)
{
if(formatReader.CanHandle(fileContents))
{
messageBody=formatReader.GetMessageBody(fileContents);
wasHandled=true;
}
}
if(!wasHandled)
{
messageBody=_defaultFormatReader
.GetMessageBody(fileContents);
}
returnmessageBody;
}
Listing5:Acorrectabstractionforasquareandrectangle
publicinterfaceShape
{
intArea();
}
publicclassRectangle:Shape
{
publicvirtualintHeight{get;set;}
publicvirtualintWidth{get;set;}
publicintArea(){returnHeight*Width;}
}
publicclassSquare:Shape
{
publicintLengthOfSides{get;set;}
publicintArea(){returnLengthOfSides^2;}
}
publicclassDoStuff
{
publicvoidDoTheRectangleStuff()
{
Rectanglerectangle=newRectangle();
rectangle.Height=4;
rectangle.Width=5;
AssertTheArea(rectangle,20);
publicvoidDoTheSquareStuff()
{
Squaresquare=newSquare();
square.LengthOfSides=4;
AssertTheArea(square,16);
}
privatevoidAssertTheArea(Shapeshape,intexpectedArea)
{
intactualArea=shape.Area();
Debug.Assert(expectedArea==actualArea);
}
}
Listing6:ViolatingLSPwithspecifictypechecks
publicclassDatabaseConnectionReader:IFileFormatReader
{
publicboolCanHandle(stringfileContents)
{
boolcanHandle=false;
if(fileContents.StartsWith("server="))
canHandle=true;
returncanHandle;
}
publicstringGetMessageBody(stringfileContents)
{
thrownewNotImplementedException("Needtoreadfromthe
database!Notfromthisinterface!");
}
}
publicclassFileReaderService
{
//...
publicstringGetMessageBody(stringfileContents)
{
//...
foreach(IFileFormatReaderformatReaderin_formatReaders)
{
//...
if(formatReader.GetType()
.Equals(typeof(DatabaseConnectionReader)))
{
messageBody=//GETTHELOGINFOFROMADATABASE,HERE.
}
else
{
messageBody=formatReader.GetMessageBody(fileContents);
}
//...
}
//...
}
//...
}
Listing7:UsingThenewAPIsetinyourapplication
privatevoidSendFromFile_Click(objectsender,EventArgse)
{
try
{
Output.Text=string.Empty;
FileReaderServicefileReaderService=
newFileReaderService();
fileReaderService.RegisterFormatReader(
newXmlFormatReader());
fileReaderService.RegisterDefaultFormatReader(
newFlatFileFormatReader());
stringmessageBody=fileReaderService
.GetMessageBodyFromFile(_fileName);
EmailSenderemailSender=newEmailSender();
emailSender.SendEmail(messageBody);
Output.Text="Sentemailwithbody:"+
Environment.NewLine+messageBody;
}
catch(Exceptionex)
{
Output.Text=ex.ToString();
}
}
privatevoidSendFromDatabase_Click(objectsender,EventArgse)
{
try
{
Output.Text=string.Empty;
DatabaseReaderServicedatabaseReaderService=
newDatabaseReaderService();
stringmessageBody=databaseReaderService.GetMessageBody();
EmailSenderemailSender=newEmailSender();
emailSender.SendEmail(messageBody);
Output.Text="Sentemailwithbody:"+
Environment.NewLine+messageBody;
}
catch(Exceptionex)
{
Output.Text=ex.ToString();
}
}
Listing8:TheProcessingServiceclass
publicclassProcessingService
{
privatereadonlyIEmailSender_emailSender;
privatereadonlyIMessageInfoRetriever_messageInfoRetriever;
publicProcessingService(IEmailSenderemailSender,
IMessageInfoRetrievermessageInfoRetriever)
{
_emailSender=emailSender;
_messageInfoRetriever=messageInfoRetriever;
}
publicstringSendMessage()
{
stringmessageBody=_messageInfoRetriever.GetMessageBody();
_emailSender.SendEmail(messageBody);
return"SendEmailWithBody:"+messageBody;
}
Likewhatyoujustreadandwantmore?