Ienfâldich ferantwurdlikensprinsipe. Net sa ienfâldich as it liket

Ienfâldich ferantwurdlikensprinsipe. Net sa ienfâldich as it liket It prinsipe fan ien ferantwurdlikens, ek wol bekend as it prinsipe fan ienige ferantwurdlikens,
aka it prinsipe fan unifoarme fariabiliteit - in ekstreem glêde keardel om te begripen en sa'n nerveuze fraach by in programmeur ynterview.

Myn earste serieuze kunde mei dit prinsipe fûn plak oan it begjin fan it earste jier, doe't de jonge en griene waarden nei de bosk brocht om studinten fan larven te meitsjen - echte studinten.

Yn 'e bosk waarden wy ferdield yn groepen fan elk 8-9 persoanen en hienen in kompetysje - hokker groep soe it fluchst in flesse wodka drinke, op betingst dat de earste persoan fan 'e groep wodka yn in glês skinkt, de twadde it drinkt, en de tredde hat in snack. De ienheid dy't har operaasje foltôge hat, ferhuzet nei it ein fan 'e wachtrige fan' e groep.

It gefal dêr't de wachtrige grutte wie in mearfâldichheid fan trije wie in goede ymplemintaasje fan SRP.

Definysje 1. Single ferantwurdlikens.

De offisjele definysje fan it prinsipe fan ien ferantwurdlikens (SRP) stelt dat elke entiteit har eigen ferantwurdlikens en reden foar bestean hat, en it hat mar ien ferantwurdlikens.

Beskôgje it objekt "Drinker" (Tippler).
Om it SRP-prinsipe út te fieren, sille wy de ferantwurdlikheden ferdiele yn trije:

  • Ien giet (PourOperation)
  • Ien drinkt (DrinkUpOperaasje)
  • Men hat in hapke (TakeBiteOperation)

Elk fan 'e dielnimmers oan it proses is ferantwurdlik foar ien komponint fan it proses, dat is, hat ien atomêre ferantwurdlikens - drinken, pour of snack.

It drinkgat is op syn beurt in gevel foar dizze operaasjes:

сlass Tippler {
    //...
    void Act(){
        _pourOperation.Do() // налить
        _drinkUpOperation.Do() // выпить
        _takeBiteOperation.Do() // закусить
    }
}

Ienfâldich ferantwurdlikensprinsipe. Net sa ienfâldich as it liket

Wêrom?

De minsklike programmeur skriuwt koade foar de aap-minske, en de aap-minske is ûnopmerklik, dom en altyd haast. Hy kin sawat 3 - 7 termen tagelyk hâlde en begripe.
Yn it gefal fan in dronkaard binne d'r trije fan dizze termen. As wy de koade lykwols mei ien blêd skriuwe, dan sil it hannen, glêzen, fjochtsjen en einleaze arguminten oer polityk befetsje. En dit alles sil wêze yn it lichem fan ien metoade. Ik bin der wis fan dat jo hawwe sjoen sokke koade yn jo praktyk. Net de meast minsklike test foar de psyche.

Oan 'e oare kant is de aapman ûntworpen om objekten yn' e echte wrâld yn syn holle te simulearjen. Yn syn ferbylding kin er se byinoar triuwe, der nije objekten fan gearstelle en op deselde wize útinoar helje. Stel jo in âld model auto foar. Yn jo ferbylding kinne jo de doar iepenje, de doarbekleding losdraaie en dêr de meganismen foar finsterlift sjen, wêryn d'r gears sille wêze. Mar jo kinne net alle komponinten fan 'e masine tagelyk sjen, yn ien "listing". Teminsten kin de "aapman" net.

Dêrom, minsklike programmeurs ûntbine komplekse meganismen yn in set fan minder komplekse en wurkjende eleminten. It kin lykwols op ferskate wizen ôfbrutsen wurde: yn in protte âlde auto's giet it luchtkanaal yn 'e doar, en yn moderne auto's foarkomt in flater yn 'e slotelektronika de motor fan start, wat in probleem wêze kin by reparaasjes.

No, SRP is in prinsipe dat ferklearret HOE te ûntbinen, dat is, wêr't de skiedsline te tekenjen.

Hy seit dat it nedich is om te ûntbinen neffens it prinsipe fan ferdieling fan "ferantwurdlikens", dat is neffens de taken fan bepaalde objekten.

Ienfâldich ferantwurdlikensprinsipe. Net sa ienfâldich as it liket

Litte wy weromgean nei drinken en de foardielen dy't de aapman ûntfangt by ûntbining:

  • De koade is op elk nivo ekstreem dúdlik wurden
  • De koade kin skreaun wurde troch ferskate programmeurs tagelyk (elk skriuwt in apart elemint)
  • Automatisearre testen is ferienfâldige - hoe ienfâldiger it elemint, hoe makliker it is om te testen
  • De gearstalling fan 'e koade ferskynt - jo kinne ferfange DrinkUpOperaasje ta in operaasje wêrby't in dronkaard floeistof ûnder de tafel skinkt. Of ferfange de skineoperaasje troch in operaasje wêryn jo wyn en wetter of wodka en bier mingje. Ofhinklik fan saaklike easken, kinne jo alles dwaan sûnder de metoadekoade oan te raken Tippler.Act.
  • Fan dizze operaasjes kinne jo de glutton foldje (allinich mei TakeBitOperation), Alkoholysk (allinich gebrûk DrinkUpOperaasje rjocht út 'e flesse) en foldogge oan in protte oare saaklike easken.

(Oh, it liket derop dat dit al in OCP-prinsipe is, en ik haw de ferantwurdlikens fan dizze post skeind)

En, fansels, de neidielen:

  • Wy moatte mear soarten meitsje.
  • In dronkenman drinkt in pear oeren letter foar it earst as hy oars soe.

Definysje 2. Unified fariabiliteit.

Lit my ta, hearen! De drinkklasse hat ek ien ferantwurdlikens - it drinkt! En yn 't algemien is it wurd "ferantwurdlikens" in ekstreem vague konsept. Immen is ferantwurdlik foar it lot fan it minskdom, en immen is ferantwurdlik foar it ferheegjen fan de pinguins dy't by de peal omkeard waarden.

Litte wy twa ymplemintaasjes fan 'e drinker beskôgje. De earste, hjirboppe neamde, befettet trije klassen - pour, drink en snack.

De twadde is skreaun troch de "Forward and Only Forward" metodyk en befettet alle logika yn 'e metoade Handeling:

//Не тратьте время  на изучение этого класса. Лучше съешьте печеньку
сlass BrutTippler {
   //...
   void Act(){
        // наливаем
    if(!_hand.TryDischarge(from:_bottle, to:_glass, size:_glass.Capacity))
        throw new OverdrunkException();

    // выпиваем
    if(!_hand.TryDrink(from: _glass,  size: _glass.Capacity))
        throw new OverdrunkException();

    //Закусываем
    for(int i = 0; i< 3; i++){
        var food = _foodStore.TakeOrDefault();
        if(food==null)
            throw new FoodIsOverException();

        _hand.TryEat(food);
    }
   }
}

Beide fan dizze klassen, út it eachpunt fan in eksterne waarnimmer, sjogge krekt itselde en diele deselde ferantwurdlikens fan "drinken".

Betizing!

Dan geane wy ​​online en fine wy ​​in oare definysje fan SRP - it Single Changeability Principle.

SCP stelt dat "In module hat ien en ienige reden om te feroarjen". Dat is, "ferantwurdlikens is in reden foar feroaring."

(It liket derop dat de jonges dy't mei de orizjinele definysje kamen betrouwen wiene yn 'e telepatyske kapasiteiten fan' e aapman)

No falt alles op syn plak. Separate kinne wy ​​​​de prosedueres foar it jitten, drinken en snacken feroarje, mar yn 'e drinker sels kinne wy ​​​​allinich de folchoarder en gearstalling fan operaasjes feroarje, bygelyks troch de snack te ferpleatsen foardat jo drinke of it lêzen fan in toast tafoegje.

Yn 'e oanpak "Forward and Only Forward" wurdt alles wat feroare wurde kin allinich yn 'e metoade feroare Handeling. Dit kin lêsber en effektyf wêze as d'r net folle logika is en it komselden feroaret, mar faak einiget it yn skriklike metoaden fan elk 500 rigels, mei mear as-ferklearrings dan nedich foar Ruslân om mei te dwaan oan de NATO.

Definysje 3. Lokalisaasje fan feroarings.

Drinkers begripe faaks net wêrom't se wekker waarden yn in oar syn appartemint, of wêr't har mobile tillefoan is. It is tiid om detaillearre logboeken ta te foegjen.

Litte wy begjinne te loggen mei it gieproses:

class PourOperation: IOperation{
    PourOperation(ILogger log /*....*/){/*...*/}
    //...
    void Do(){
        _log.Log($"Before pour with {_hand} and {_bottle}");
        //Pour business logic ...
        _log.Log($"After pour with {_hand} and {_bottle}");
    }
}

Troch it yn te kapseljen PourOperation, Wy diene wiis út it eachpunt fan ferantwurdlikens en ynkapseling, mar no binne wy ​​betize mei it prinsipe fan fariabiliteit. Neist de operaasje sels, dy't feroarje kin, wurdt de logging sels ek feroare. Jo sille moatte skieden en meitsje in spesjale logger foar it gieten operaasje:

interface IPourLogger{
    void LogBefore(IHand, IBottle){}
    void LogAfter(IHand, IBottle){}
    void OnError(IHand, IBottle, Exception){}
}

class PourOperation: IOperation{
    PourOperation(IPourLogger log /*....*/){/*...*/}
    //...
    void Do(){
        _log.LogBefore(_hand, _bottle);
        try{
             //... business logic
             _log.LogAfter(_hand, _bottle");
        }
        catch(exception e){
            _log.OnError(_hand, _bottle, e)
        }
    }
}

Dat sil de sekuere lêzer fernimme LogAfter, LogBefore и OnFout kin ek yndividueel feroare wurde, en, nei analogy mei de foarige stappen, sil trije klassen oanmeitsje: PourLoggerBefore, PourLoggerAfter и PourErrorLogger.

En ûnthâlde dat der trije operaasjes foar in drinker, wy krije njoggen logging klassen. Dêrtroch bestiet de hiele drinkkring út 14 (!!!) klassen.

Hyperboal? Amper! In aapman mei in ûntbiningsgranaat sil de "pourer" opsplitse yn in karaf, in glês, opgietoperators, in wetterfoarsjenningstsjinst, in fysyk model fan 'e botsing fan molekulen, en foar it folgjende kertier sil hy besykje de ôfhinklikens te ûntsluten sûnder globale fariabelen. En leau my, hy sil net ophâlde.

It is op dit punt dat in protte komme ta de konklúzje dat SRP mearkes binne út rôze keninkriken, en gean fuort te spyljen noedels ...

... sûnder oait te learen oer it bestean fan in tredde definysje fan Srp:

"It ienige ferantwurdlikensprinsipe stelt dat dingen dy't lykje op feroaring moatte wurde opslein op ien plak". of "Wat feroaret tegearre moat op ien plak bewarre wurde"

Dat is, as wy de logging fan in operaasje feroarje, dan moatte wy it op ien plak feroarje.

Dit is in heul wichtich punt - om't alle ferklearrings fan SRP hjirboppe seine dat it nedich wie om de soarten te ferpletterjen wylst se waarden ferpletterd, dat is, se hawwe in "boppeste limyt" oplein oan 'e grutte fan it objekt, en no wy hawwe it al oer in "legere limyt" . Mei oare wurden, SRP fereasket net allinich "ferpletterjen by it ferpletterjen", mar ek om it net te oerdriuwen - "ferpletterje yninoar sletten dingen net". Dit is de grutte striid tusken Occam syn skearmes en de aap man!

Ienfâldich ferantwurdlikensprinsipe. Net sa ienfâldich as it liket

No moat de drinker better fiele. Neist it feit dat it net nedich is om de IPourLogger-logger yn trije klassen te splitsen, kinne wy ​​​​ek alle loggers yn ien type kombinearje:

class OperationLogger{
    public OperationLogger(string operationName){/*..*/}
    public void LogBefore(object[] args){/*...*/}       
    public void LogAfter(object[] args){/*..*/}
    public void LogError(object[] args, exception e){/*..*/}
}

En as wy in fjirde type operaasje tafoegje, dan is de logging dêrfoar al klear. En de koade fan 'e operaasjes sels is skjin en frij fan ynfrastruktuerlûd.

As gefolch hawwe wy 5 klassen foar it oplossen fan it drinkprobleem:

  • Giet operaasje
  • Drinking operaasje
  • Jamming operaasje
  • Logger
  • Drinker gevel

Elk fan harren is strikt ferantwurdlik foar ien funksjonaliteit en hat ien reden foar feroaring. Alle regels fergelykber mei feroaring lizze tichtby.

Foarbyld fan Real life

Wy hawwe ienris in tsjinst skreaun foar it automatysk registrearjen fan in b2b-kliïnt. En in GOD-metoade ferskynde foar 200 rigels fan ferlykbere ynhâld:

  • Gean nei 1C en meitsje in akkount
  • Gean mei dit akkount nei de betellingsmodule en meitsje it dêr
  • Kontrolearje dat in akkount mei sa'n akkount net oanmakke is op 'e haadtsjinner
  • Meitsje in nij account
  • Foegje de registraasjeresultaten ta yn 'e betellingsmodule en it nûmer 1c oan' e tsjinst foar registraasjeresultaten
  • Foegje akkountynformaasje ta oan dizze tabel
  • Meitsje in puntnûmer foar dizze klant yn 'e punttsjinst. Jou jo 1c-akkountnûmer troch nei dizze tsjinst.

En d'r wiene sawat 10 mear saaklike operaasjes op dizze list mei skriklike ferbining. Hast elkenien hie it akkountobjekt nedich. De punt-ID en klantnamme wiene nedich yn 'e helte fan' e oproppen.

Nei in oere fan refactoring, wy wienen by steat om te skieden de ynfrastruktuer koade en guon fan 'e nuânses fan wurkjen mei in akkount yn aparte metoaden / klassen. De God-metoade makke it makliker, mar d'r wiene 100 rigels koade oer dy't gewoan net untangled woene.

Pas nei in pear dagen waard dúdlik dat de essinsje fan dizze "lichtgewicht" metoade is in saaklike algoritme. En dat de oarspronklike beskriuwing fan de technyske spesifikaasjes wie frij kompleks. En it is it besykjen om dizze metoade yn stikken te brekken dy't de SRP sil skeine, en net oarsom.

Formalisme.

It is tiid om ús dronken mei rêst te litten. Droog dyn triennen - wy sille grif werom nei it ienris. Litte wy no de kennis út dit artikel formalisearje.

Formalisme 1. Definysje fan SRP

  1. Skied de eleminten sadat elk fan harren is ferantwurdlik foar ien ding.
  2. Ferantwurdlikens stiet foar "reden om te feroarjen." Dat is, elk elemint hat mar ien reden foar feroaring, yn termen fan saaklike logika.
  3. Potinsjele feroarings oan saaklike logika. moat wurde pleatst. Eleminten dy't syngroan feroarje moatte tichtby wêze.

Formalisme 2. Needsaaklike selstestkritearia.

Ik haw net genôch kritearia sjoen foar it ferfoljen fan de SRP. Mar der binne needsaaklike betingsten:

1) Freegje josels ôf wat dizze klasse / metoade / module / tsjinst docht. jo moatte it beäntwurdzje mei in ienfâldige definysje. ( Dankewol Brightori )

ferklearrings

Soms is it lykwols heul lestich om in ienfâldige definysje te finen

2) It reparearjen fan in brek of it tafoegjen fan in nije funksje hat ynfloed op in minimum oantal bestannen / klassen. Ideaal - ien.

ferklearrings

Sûnt ferantwurdlikens (foar in funksje of brek) is ynkapsele yn ien triem / klasse, do witst krekt wêr te sjen en wat te bewurkjen. Bygelyks: de funksje fan it feroarjen fan de útfier fan logging-operaasjes sil allinich de logger feroarje. D'r is net nedich om de rest fan 'e koade troch te rinnen.

In oar foarbyld is it tafoegjen fan in nije UI-kontrôle, fergelykber mei de foarige. As dit twingt jo te foegjen 10 ferskillende entiteiten en 15 ferskillende converters, It liket derop dat jo oerdriuwe.

3) As ferskate ûntwikkelders wurkje oan ferskate funksjes fan jo projekt, dan is de kâns op in fúzjekonflikt, dat is, de kâns dat deselde triem / klasse tagelyk troch ferskate ûntwikkelders feroare wurdt, minimaal.

ferklearrings

As, by it tafoegjen fan in nije operaasje "Pour wodka ûnder de tafel", jo moatte beynfloedzje de logger, de operaasje fan drinken en gieten, dan liket it derop dat de ferantwurdlikheden binne ferdield krom. Fansels is dit net altyd mooglik, mar wy moatte besykje dit sifer te ferminderjen.

4) As jo ​​in ferdúdlikjende fraach oer bedriuwslogika (fan in ûntwikkelder of behearder) frege wurde, geane jo strikt yn ien klasse/bestân en krije allinich ynformaasje dêrwei.

ferklearrings

Funksjes, regels of algoritmen wurde kompakt skreaun, elk op ien plak, en net ferspraat mei flaggen yn 'e koaderomte.

5) De nammejouwing is dúdlik.

ferklearrings

Us klasse of metoade is ferantwurdlik foar ien ding, en de ferantwurdlikens wurdt wjerspegele yn syn namme

AllManagersManagerService - nei alle gedachten in God klasse
LocalPayment - wierskynlik net

Formalisme 3. Occam-earste ûntwikkelingsmetodology.

Oan it begjin fan it ûntwerp wit de aapman net en fielt net alle subtiliteiten fan it probleem dat wurdt oplost en kin in flater meitsje. Jo kinne flaters meitsje op ferskate manieren:

  • Meitsje objekten te grut troch ferskate ferantwurdlikheden te fusearjen
  • Reframing troch it dielen fan ien ferantwurdlikens yn in protte ferskillende soarten
  • Ferkearde definiearje de grinzen fan ferantwurdlikens

It is wichtich om de regel te ûnthâlden: "it is better om in grutte flater te meitsjen," of "as jo net wis binne, split it net op." As jo ​​​​klasse bygelyks twa ferantwurdlikheden befettet, dan is it noch begryplik en kin it yn twa wurde splitst mei minimale feroarings oan 'e kliïntkoade. It gearstallen fan in glês út glêsskerven is meastentiids dreger fanwegen de kontekst dy't ferspraat is oer ferskate bestannen en it ûntbrekken fan needsaaklike ôfhinklikens yn 'e kliïntkoade.

It is tiid om it in dei te neamen

De omfang fan SRP is net beheind ta OOP en SOLID. It jildt foar metoaden, funksjes, klassen, modules, mikrotsjinsten en tsjinsten. It jildt foar sawol "figax-figax-and-prod" as "rocket-science" ûntwikkeling, wêrtroch't de wrâld oeral in bytsje better makket. As jo ​​der oer tinke, is dit hast it fûnemintele prinsipe fan alle engineering. Mechanyske technyk, kontrôlesystemen, en yndie alle komplekse systemen binne boud fan komponinten, en "ûnderfragmintaasje" ûntslacht ûntwerpers fan fleksibiliteit, "oerfragmintaasje" ûntslacht ûntwerpers fan effisjinsje, en ferkearde grinzen ûntnimme se fan reden en frede fan geast.

Ienfâldich ferantwurdlikensprinsipe. Net sa ienfâldich as it liket

SRP is net útfûn troch de natuer en is gjin diel fan eksakte wittenskip. It brekt út ús biologyske en psychologyske beheiningen. It is gewoan in manier om komplekse systemen te kontrolearjen en te ûntwikkeljen mei it aap-minske brein. Hy fertelt ús hoe't jo in systeem ûntbrekke. De orizjinele formulearring easke in flinke hoemannichte telepathy, mar ik hoopje dat dit artikel wat fan 'e smokescreen wisket.

Boarne: www.habr.com

Add a comment