Modele de interfață extensie

Noile extensii din .Net 3.5 permit ca funcționalitățile să fie separate de interfețe.

De exemplu, în .Net 2.0

public interface IHaveChildren {
    string ParentType { get; }
    int ParentId { get; }

    List GetChildren()
}

Poate (în 3.5) să devină:

public interface IHaveChildren {
    string ParentType { get; }
    int ParentId { get; }
}

public static class HaveChildrenExtension {
    public static List GetChildren( this IHaveChildren ) {
        //logic to get children by parent type and id
        //shared for all classes implementing IHaveChildren 
    }
}

Acest lucru mi se pare a fi un mecanism mai bun pentru multe interfețe. Ei nu mai au nevoie de o bază abstractă pentru a partaja acest cod, iar funcțional codul funcționează la fel. Acest lucru ar face codul mai ușor de întreținut și mai ușor de testat.

Singurul dezavantaj fiind faptul că o implementare bazată pe abstracte poate fi virtuală, dar poate fi rezolvată (ar fi o metodă de ascundere să ascundă o metodă de extensie cu același nume? Ar fi cod confuz să facă acest lucru?)

Orice alte motive pentru a nu utiliza în mod regulat acest model?


Clarificare:

Da, văd că tendința prin metode de extensie este de a ajunge cu ei peste tot. Aș fi deosebit de atent la orice tip de valori .Net fără o evaluare foarte mare (cred că singura pe care o avem pe șir este un .SplitToDictionary() - similar cu . Split() , dar luând și un delimiter cheie-valoare)

Cred că există o întreagă dezbatere despre cele mai bune practici acolo ;-)

(De altfel: DannySmurf, PM-ul tău sună înfricoșător.)

Mă întreb în mod specific aici despre utilizarea metodelor de extensie unde anterior am avut metode de interfață.


Încerc să evit o mulțime de niveluri de clase de bază abstracte - clasele care implementează aceste modele au deja clase de bază. Cred că acest model ar putea fi mai ușor de întreținut și mai puțin cuplat decât adăugarea de ierarhii de obiecte suplimentare.

Este ceea ce MS au făcut pentru a fi numărabil și IQueryable pentru Linq?

0
fr hi bn

11 răspunsuri

Metodele de extindere ar trebui folosite doar ca: extensii. Orice cod esențial al structurii/designului sau operațiunea netrivială ar trebui pusă într-un obiect care este compus/moștenit dintr-o clasă sau o interfață.

Odată ce un alt obiect încearcă să folosească extensia, acestea nu vor vedea extensiile și ar putea fi nevoite să le reimplementeze/re-aducă din nou.

Înțelepciunea tradițională este că metodele de extindere ar trebui utilizate numai pentru:

  • clase de utilitate, după cum a menționat Vaibhav
  • extinderea API-urilor terțe părți sigilate
0
adăugat

Cred că cel mai bun lucru pe care metodele de extensie le înlocuiesc sunt toate clasele de utilități pe care le găsiți în fiecare proiect.

Cel puțin pentru moment, simt că orice altă utilizare a metodelor de extindere ar provoca confuzie la locul de muncă.

Cei doi biți.

0
adăugat

Ouch. Vă rugăm să nu extindeți interfețele.
O interfață este un contract curat pe care o clasă ar trebui să-l implementeze, iar folosirea claselor trebuie să fie limitată la ceea ce este în interfața de bază pentru ca aceasta să funcționeze corect.

De aceea, declarați întotdeauna interfața ca fiind tipul în locul clasei reale.

IInterface variable = new ImplementingClass();

Dreapta?

Dacă într-adevăr aveți nevoie de un contract cu unele funcționalități suplimentare, clasele abstracte sunt prietenii dvs.

0
adăugat

I needed to solve something similar: I wanted to have a List passed to the extensions function where IIDable is an interface that has a long getId() function. I tried using GetIds(this List bla) but the compiler didn't allow me to do so. I used templates instead and then type casted inside the function to the interface type. I needed this function for some linq to sql generated classes.

Sper ca asta ajuta :)

    public static List GetIds(this List original){
        List ret = new List();
        if (original == null)
            return ret;

        try
        {
            foreach (T t in original)
            {
                IIDable idable = (IIDable)t;
                ret.Add(idable.getId());
            }
            return ret;
        }
        catch (Exception)
        {
            throw new Exception("Class calling this extension must implement IIDable interface");
        }
0
adăugat

Rob Connery (Subsonic și MVC Storefront) a implementat un model asemănător cu modelul IRepository în aplicația Storefront. Nu este chiar modelul de mai sus, dar are unele asemănări.

Stratul de date returnează IQueryable, care permite stratului consumant să aplice expresia de filtrare și sortare în partea superioară a acestuia. Bonusul este în măsură să specificați o singură metodă GetProducts, de exemplu, și apoi să decideți corespunzător în stratul consumator modul în care doriți să sortați, să filtrați sau chiar să obțineți doar o anumită gamă de rezultate.

Nu este o abordare tradițională, dar foarte rece și cu siguranță un caz de DRY.

0
adăugat

Nu există nimic în neregulă cu extinderea interfețelor, de fapt așa funcționează LINQ pentru a adăuga metodele de extensie la clasele de colectare.

Acestea fiind spuse, ar trebui să faceți acest lucru doar în cazul în care trebuie să furnizați aceeași funcționalitate pentru toate clasele care implementează acea interfață și că această funcționalitate nu este (și probabil nu trebuie să fie) parte a implementării "oficiale" clase. Extinderea unei interfețe este de asemenea bună dacă nu este practic să scrieți o metodă de extensie pentru fiecare tip derivat care necesită noua funcționalitate.

0
adăugat

Văd că separarea funcționalității domeniului/modelului și a interfeței UI/vizualizării folosind metode de extensie este un lucru bun, mai ales că acestea pot locui în spații separate de nume.

De exemplu:

namespace Model
{
    class Person
    {
        public string Title { get; set; }
        public string FirstName { get; set; }
        public string Surname { get; set; }
    }
}

namespace View
{
    static class PersonExtensions
    {
        public static string FullName(this Model.Person p)
        {
            return p.Title + " " + p.FirstName + " " + p.Surname;
        }

        public static string FormalName(this Model.Person p)
        {
            return p.Title + " " + p.FirstName[0] + ". " + p.Surname;
        }
    }
}

Metodele de extindere a metodei pot fi utilizate în mod similar cu șabloanele de date XAML. Nu puteți accesa membrii privați/protejați din clasă, dar permite menținerea abstractizării datelor fără o dublare excesivă a codului în întreaga aplicație.

0
adăugat
Îmi place, de asemenea, această abordare zooba, totuși m-am îndreptat spre o clasă parțială, unde parțial a avut loc toate "extensiile".
adăugat autor Matthew Zielonka.co.uk, sursa

O singură problemă pe care o pot vedea este că, într-o companie mare, acest model ar putea permite ca acest cod să devină dificil (dacă nu imposibil) ca oricine să înțeleagă și să folosească. Dacă mai mulți dezvoltatori își adaugă în mod constant propriile metode la clasele existente, separate de clasele respective (și, Dumnezeu ne ajută pe toți, chiar și la clasele BCL), am putut vedea o bază de cod care să se rătăcească destul de repede.

Chiar și la propria mea slujbă, am putut vedea acest lucru, cu dorința PM-ului meu de a adăuga fiecare bit de cod la care lucrăm fie la UI, fie la stratul de acces la date, l-am putut vedea complet insistând pe adăugarea a 20 sau 30 de metode System.String care sunt legate doar tangențial de manipularea șirului.

0
adăugat

Văd o mulțime de oameni care pledează pentru utilizarea unei clase de bază pentru a împărtăși funcționalitatea comună. Fii atent cu asta - ar trebui să favorizezi compoziția asupra moștenirii. Moștenirea ar trebui utilizată numai pentru polimorfism, atunci când are sens din punct de vedere al modelării. Nu este un instrument bun pentru reutilizarea codului.

În ceea ce privește întrebarea: Fă-ți limitele atunci când faci acest lucru - de exemplu în codul afișat, folosind o metodă de extensie pentru a pune în aplicare GetChildren "sigilează" efectiv această implementare și nu permite niciun IHaveChildren impl să ofere propriile sale, dacă este necesar. Dacă acest lucru este OK, atunci nu mă deranjează metoda de extindere a metodei atât de mult. Nu este așezată în piatră și, de obicei, poate fi refactată cu ușurință când este nevoie de mai multă flexibilitate mai târziu.

Pentru o flexibilitate mai mare, utilizarea modelului de strategie poate fi preferabilă. Ceva asemănător cu:

public interface IHaveChildren 
{
    string ParentType { get; }
    int ParentId { get; }
}

public interface IChildIterator
{
    IEnumerable GetChildren();
}

public void DefaultChildIterator : IChildIterator
{
    private readonly IHaveChildren _parent;

    public DefaultChildIterator(IHaveChildren parent)
    {
        _parent = parent; 
    }

    public IEnumerable GetChildren() 
    { 
       //default child iterator impl
    }
}

public class Node : IHaveChildren, IChildIterator
{ 
   //*snip*

    public IEnumerable GetChildren()
    {
        return new DefaultChildIterator(this).GetChildren();
    }
}
0
adăugat

Cred că utilizarea judicioasă a metodelor de extensie pune interfețele într-o poziție mai echivalentă cu clasele de bază (abstractă).


Versioning. One advantage base classes have over interfaces is that you can easily add new virtual members in a later version, whereas adding members to an interface will break implementers built against the old version of the library. Instead, a new version of the interface with the new members needs to be created, and the library will have to work around or limit access to legacy objects only implementing the original interface.

Ca exemplu concret, prima versiune a unei biblioteci ar putea defini o interfață cum ar fi:

public interface INode {
  INode Root { get; }
  List GetChildren( );
}

Odată ce biblioteca a lansat, nu putem modifica interfața fără a întrerupe utilizatorii actuali. În schimb, în ​​următoarea versiune ar trebui să definim o nouă interfață pentru a adăuga funcționalitate suplimentară:

public interface IChildNode : INode {
  INode Parent { get; }
}

Cu toate acestea, numai utilizatorii noii biblioteci vor putea să implementeze noua interfață. Pentru a lucra cu codul vechi, trebuie să adaptăm vechea implementare, pe care o metodă de extindere o poate gestiona frumos:

public static class NodeExtensions {
  public INode GetParent( this INode node ) {
   //If the node implements the new interface, call it directly.
    var childNode = node as IChildNode;
    if( !object.ReferenceEquals( childNode, null ) )
      return childNode.Parent;

   //Otherwise, fall back on a default implementation.
    return FindParent( node, node.Root );
  }
}

Acum, toți utilizatorii noii biblioteci pot trata în mod identic atât implementările moștenite, cât și cele moderne.


Overloads. Another area where extension methods can be useful is in providing overloads for interface methods. You might have a method with several parameters to control its action, of which only the first one or two are important in the 90% case. Since C# does not allow setting default values for parameters, users either have to call the fully parameterized method every time, or every implementation must implement the trivial overloads for the core method.

În schimb, metodele de extensie pot fi folosite pentru a furniza implementări triviale de suprasarcină:

public interface ILongMethod {
  public bool LongMethod( string s, double d, int i, object o, ... );
}

...
public static LongMethodExtensions {
  public bool LongMethod( this ILongMethod lm, string s, double d ) {
    lm.LongMethod( s, d, 0, null );
  }
  ...
}


Please note that both of these cases are written in terms of the operations provided by the interfaces, and involve trivial or well-known default implementations. That said, you can only inherit from a class once, and the targeted use of extension methods can provide a valuable way to deal with some of the niceties provided by base classes that interfaces lack :)


Edit: A related post by Joe Duffy: Extension methods as default interface method implementations

0
adăugat

Putin mai mult.

Dacă mai multe interfețe au aceeași semnătura metodei de extensie, va trebui să convertiți în mod explicit apelantul la un tip de interfață și apoi să apelați metoda. De exemplu.

((IFirst)this).AmbigousMethod()
0
adăugat
De acord, deși, dacă trebuie să faceți acest lucru, vă sugerez că fie interfețele sunt prost proiectate, fie că clasa dvs. nu ar trebui să le implementeze pe amândouă.
adăugat autor Keith, sursa