Atenție, contractele de clasă adevărate pot diferi de cele formale

Atenție, contractele de clasă adevărate pot diferi de cele formale +8

  • 24.11.15 01:35 •
  • FocuriShadul •
  • # 271533
  • Habrahabr •
  • 16 •
  • 7600

- la fel ca Forbes, doar mai bine.







Pe scurt, în acest articol ne vom concentra pe statul de moștenire Liska, diferența NotifyCollectionChangedAction.Reset contracte în versiuni de .NET Framework 4 și .NET Framework 4.5. și care dintre aceste două contracte este adevărat și care este greșit.



Conform principiului lui Liskov. clasa de moștenire trebuie să moștenească contractul de clasă de bază (cu capacitatea de a adăuga propriile sale specificități care nu contravin contractului inițial).

Voi da un exemplu. Imaginați-vă că metoda Add List este virtuală. Dacă creați un moștenitor din listă<>, apoi metoda de adăugare în ea trebuie să adauge exact un element în colecție. Dacă un element este adăugat numai atunci când o anumită condiție este îndeplinită sau elementul și copia sa sunt adăugate, atunci codul de utilizator care se așteaptă ca numărul de adăugare să crească cu exact una după apel să devină inoperant. Comportamentul claselor moștenite ar trebui să fie așteptat pentru codul care utilizează o variabilă a tipului de bază.

Acum, să ne imaginăm ce aveați de gând să utilizați în codul dvs. de listă<>. Judecând după numele Add și parametri (un element), metoda trebuie să adauge un element în colecție. Ați folosit foaia de mai multe ori și sunteți siguri că este așa. Puteți să întrebați pe colegul dvs. și nu va ezita să confirmați că este așa. Dar să ne imaginăm pentru o clipă că mergeți la msdn, vedeți documentația și acolo este scris că Add doar "schimbă colecția originală", adică face orice. În acest caz, vom numi acest contract, care este tipic pentru clasa de bază, și pe care toate se bazează, este adevărat. iar cea descrisă în documentație este formală.

La crearea unui moștenitor de clasă, vă bazați pe comportamentul formal, mai degrabă decât un adevărat, în mod oficial nu se va rupe nimic, dar de fapt a crea o bombă care poate crea vreodată pentru cineva (probabil, nu pentru tine) probleme.

Un exemplu de discrepanță dintre un contract formal și un contract adevărat este NotifyCollectionChangedAction.Reset. Înainte de versiunea 4.5, Resetarea a însemnat că conținutul colecției sa schimbat foarte mult. Ce înseamnă "puternic"? Pentru cineva, adăugarea celor trei elemente este o schimbare puternică, dar pentru unii, nu există.

În general, resetarea înseamnă "colecția sa schimbat așa cum doriți". Începând cu versiunea 4.5, Resetarea a început să însemne curățarea colecției. Unii ar putea crede că această schimbare a fost făcută în zadar, pentru că se rupe compatibilitatea cu versiunile anterioare, dar aș spune că tipii bine făcut - au observat la timp că șansele reale cu contract formal, și prompt corectat greșeala lui. Utilizând ObservatorulCollecția, puteți găsi Reset numai dacă obiectul a fost numit Ștergere (). Programatorii care lucrează în mod regulat cu ObservableCollection sunt obișnuiți cu acest lucru și consideră că aceasta este norma. "Când se poate întâlni Reset?" - îi întrebați, iar ei, fără ezitare, vor răspunde: "Când a fost numit Clear!". În mod firesc, ei intuitiv cred că acest comportament, de facto să devină standard, ar trebui să fie păstrat în moștenitori. Prin urmare, documentația ar trebui să spună că Resetarea este un semn de curățare a colecției.

Pentru a rezuma: în cazul în care punerea în aplicare a moștenitorului, se sprijini pe contractul cel mai specific și specific între formal și adevărat. Dacă utilizați o clasă, aplecați pe contractul cel mai puțin specific între formal și adevărat.

Folosind Resetare, considera că poate însemna orice. Moștenind ObservatorulCollecția, considerați că Resetare înseamnă eliminarea colecției.

P.S. Dacă sunteți interesat în opinia mea, pe Reset, cred că dezvoltatorii de clasa ObservableCollection ar trebui să părăsească resetată și contractul în forma în care este în prezent (colectare de curățare semn), dar se adaugă la elementul de transfer, de semnalizare, care Colecția a fost modificată după cum doriți și nu ar fi fost utilizată în originalul ObservationCollection. Faptul este că singurul element de enumerare care semnalează faptul că mai multe elemente ale colecției s-au schimbat este Resetare, elementele rămase ale enumerării semnalează o schimbare în elementul unității. Odată, pentru a atinge o viteză acceptabilă, un programator a trebuit să schimbe mai întâi câteva elemente din colecție și apoi să trimită exact un semnal despre schimbarea colecției. Și nu avea de ales decât să semnaleze o schimbare a colecției în moștenitorul său de la ObservatorCollection prin Reset, pentru lipsa altor alternative.

Deci, cred că schimbarea documentației a rezolvat o problemă, dar în același timp a creat un altul (pentru soluția căruia trebuie să adăugați un element suplimentar la enumerare). Elementele rezervate amuzante, dar uneori neutilizate pot fi utile.

În general, NotifyCollectionChangedEventArgs conține proprietățile NewItems și OldItems, care pot conține unul sau mai multe obiecte. Deci, declarația este singurul element de enumerare care semnalează faptul că mai multe elemente ale colecției s-au schimbat - această Resetare nu este, în general, adevărată. Adăugați și eliminați pot semna, de asemenea, mai multe modificări în același mod. Și acesta este doar contractul adevărat, iar în msdn există un aspect formal: în mod evident, aceste acțiuni pot fi doar pentru un singur element.







Dar, în practică, .Net Framework nu conține componente care să funcționeze corect cu (NewItems.Count> 1) și (OldItems.Count> 1). În cel mai bun caz, o excepție este aruncată, în cel mai rău caz - toate elementele, cu excepția primelor, sunt pur și simplu ignorate.

Aceasta nu înseamnă că nu pot folosi această funcție în componentele mele, nu? Deoarece aceste proprietăți există și sunt corect populate atunci când se creează o instanță a clasei NotifyCollectionChangedEventArgs, această posibilitate a fost furnizată în mod explicit.

Adăugați și eliminați pot semna, de asemenea, mai multe modificări în același mod.
Lucrați cu ObserverableCollection, ați practicat cel puțin o dată acest eveniment? Puteți da un exemplu?
ObserverableCollection nu are metoda AddRange. De fiecare dată când se numește metoda Add (care ia exact un parametru ca parametru), se declanșează un eveniment, care are întotdeauna exact un element în NewItems. Deci, pentru NotifyCollectionChangedAction.Add, ceea ce este scris în msdn este adevărat și în practică.

Pot să moștenesc cu ușurință de la ObservantCollecție<> și adăugați metoda AddRange și chiar RemoveRange, ceea ce fac în practică și fac, dacă am nevoie de ea. În plus, îmi pot implementa colecția (de exemplu, ObservableHashSet<>), utilizând interfața INotifyCollectionChanged. În această implementare, pot fi utilizate și metodele AddRange și RemoveRange. Codul care citează sau ideea este în principiu clar?

Sfatul oferit în articol cu ​​privire la alegerea a două contracte este relevant, indiferent dacă un contract este așteptări intuitive, sau ambele contracte sunt formale. Din nou, în cazul în care implementați moștenitorul, se bazează pe contractul cel mai specific și mai specific dintre cele două.
Un contract formal din .NET Framework 4 spune că Add înseamnă "Unul sau mai multe articole au fost adăugate la colecție." Contractul formal din .NET Framework 4.5 pentru Add este "Un element a fost adăugat la colecție".
Făcând așa, după cum spuneți, rupeți contractul oficial de la versiunea 4.5.
Încălcarea contractului, care este o așteptare intuitivă - un subiect subtil și holivar, însă încălcarea unui contract oficial este un fapt incontestabil. Vă recomandăm să utilizați contractul cu cele mai multe restricții atunci când moșteniți. Adăugarea unui singur element și în utilizare - cu cele mai mici restricții, adică adăugând mai multe elemente.

performanță prăbușită (-1)
dar a legalizat creativitatea dezvoltatorilor care nu reușesc să citească specificația (+100500)
Sunt de acord, tocmai am scris asta
Deci, cred că schimbarea documentației a rezolvat o problemă, dar în același timp a creat un altul (pentru soluția căruia trebuie să adăugați un element suplimentar la enumerare)

Și toate problemele nu sunt legate de un anumit "contract real", ci de bug-uri vechi în implementare.
În orice situație de neînțeles, este mai ușor să nu dai vina pe toți artiștii finali. Cu toate acestea, să ne gândim, de ce interpreții finali au implementat exact "controlul curbei de la livrarea WPF"? Poate că s-au ofensat de întreaga lume sadică, care a făcut în mod deliberat o greșeală? Sună nebun. Sau poate că așteptările lor intuitive despre comportamentul lui ObservableCollection diferă de ceea ce este scris în msdn. Pare plauzibil. Deci, există o problemă - așteptările intuitive ale majorității dezvoltatorilor nu coincid cu descrierea din documentație. Ce decizii pot exista? Ei bine, cel mai simplu mod nu este de a atribui responsabililor pentru rezolvarea problemei artiștilor finali. Ca, baieti, memoreaza intregul msdn si repeta-l in mod regulat. Va ajuta să scapi de bug-uri? Cred că nu va ajuta. Opțiunea Doi - pentru a face așteptările intuitive ale majorității programatorilor se potrivesc descrierii din documentația. Acesta este modul în care evaluez cazul cu modificări în documentație. Dacă există critici constructive - voi fi bucuros să aud opinia altcuiva. Expresia este „toate prostiile de mai sus pentru a da vina interpreții finale, crap bug-uri“ nu sunt foarte asemănătoare cu critici constructive, deoarece nu dezvăluie un gând, dar de ce este un nonsens, de ce „toate problemele care nu sunt cu genul de“ contract de reale „“

Voi explica un pic. Cadru 4 NET spune că Adaugă înseamnă că unul sau mai multe elemente adăugate la colecție, și dezvoltatorii, așteptările intuitive aparent ghidate ObservableCollection format, ceea ce înseamnă adăugarea unui element Adăugare. Și dacă în documentație sa spus că Add înseamnă adăugarea unui element, atunci nimeni nu ar încerca să implementeze colecția în așa fel încât Add să adauge adăugarea mai multor elemente și nu ar fi întâmpinat probleme. De asemenea, ar fi bine să specificați în documentație că Resetarea înseamnă eliminarea colecției și adăugarea unui element care semnalizează o schimbare arbitrară a colecției. Coincidența așteptărilor intuitive și a cerințelor formale reduce probabilitatea de eroare. În acest caz, aparent, documentația formală a fost scrisă pentru interfața abstractă, iar așteptările intuitive s-au format pe baza unei anumite clase.

De asemenea, ar fi bine să specificați în documentație că Resetarea înseamnă eliminarea colecției și adăugarea unui element care semnalizează o schimbare arbitrară a colecției.
Este și mai rău. Adăugarea unui element în enum public împiedică compatibilitatea înapoi direct prin cod.
Și dacă în documentație sa spus că Add înseamnă adăugarea unui element, atunci nimeni nu ar încerca să implementeze colecțiile în așa fel încât Add să însemne adăugarea mai multor elemente și să nu se confrunte cu probleme.
"Dezvoltatorii intuitivi" ar scrie încă în felul lor, nu citesc documentația prin definiție.

Nu contează - rezultatul este important.
Și dacă motivul nu este important, atunci cum, întrebați, învățați din greșelile altor persoane? Cum să nu facem greșeli similare în viitor? Unul din scopurile articolului meu este de a avertiza cititorii de astfel de erori. Dacă pentru dvs. toate acestea nu contează - bine, drept, puteți ignora în continuare așteptările intuitive ale dezvoltatorilor, va rămâne pe conștiința dumneavoastră.
De fapt, întrebarea mea a fost, de ce ai spune că „toate problemele care nu sunt cu genul de“ contract de reale „“, si ca raspuns am auzit o grămadă de emoții cu privire la încălcarea compatibilitatea și WPF evaluările dezvoltatorilor de competență. Desigur, aveți toate drepturile la emoții și mulțumiți că le-ați împărtășit, dar discuția despre subiect în esență ar fi mult mai plăcută și utilă.

"Dezvoltatorii intuitivi" ar scrie încă în felul lor, nu citesc documentația prin definiție. Provocările intuitive se formează nu aleatoriu, ci se bazează pe comportamentul unei anumite clase. Am crezut că am descris acest proces într-un anumit detaliu, am citat chiar și două exemple. Cred că mă însoți regulat. În ultimul articol m-ai întrebat de 5 ori despre scurgerile de memorie și evenimentele slabe și ți-am răspuns de 5 ori: [1]. [2]. [3]. [4]. [5]. În acest articol, încercați să transformați totul cu susul în jos.

Ei bine, în cazul în care, de fapt, că nu ar fi nici o diferență între așteptările intuitive și documentele necesare sau clase care implementeaza interfata pentru a adăuga metode AddRange, RemoveRange, sau, din nou, pentru a scrie documente care pot fi adăugate doar un singur element.







Trimiteți-le prietenilor: