Pergunta

As novas extensões do .Net 3.5 permitem que a funcionalidade seja separada das interfaces.

Por exemplo em .Net 2.0

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

    List<IChild> GetChildren()
}

Pode (em 3.5) tornar-se:

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

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

Este me parece ser um mecanismo melhor para muitas interfaces.Eles não precisam mais de uma base abstrata para compartilhar esse código e, funcionalmente, o código funciona da mesma forma.Isso poderia tornar o código mais sustentável e mais fácil de testar.

A única desvantagem é que uma implementação de bases abstratas pode ser virtual, mas isso pode ser contornado (um método de instância ocultaria um método de extensão com o mesmo nome?seria um código confuso fazer isso?)

Algum outro motivo para não usar esse padrão regularmente?


Esclarecimento:

Sim, vejo que a tendência dos métodos de extensão é acabar com eles em todos os lugares.Eu seria particularmente cuidadoso ao ter qualquer tipo de valor .Net sem muita revisão por pares (acho que o único que temos em uma string é um .SplitToDictionary() - igual a .Split() mas também usando um delimitador de valor-chave)

Acho que há todo um debate sobre melhores práticas aí ;-)

(Aliás:DannySmurf, seu PM parece assustador.)

Estou perguntando especificamente aqui sobre o uso de métodos de extensão onde anteriormente tínhamos métodos de interface.


Estou tentando evitar muitos níveis de classes base abstratas - a maioria das classes que implementam esses modelos já possuem classes base.Acho que esse modelo poderia ser mais sustentável e menos acoplado do que adicionar mais hierarquias de objetos.

Foi isso que a MS fez com IEnumerable e IQueryable para Linq?

Foi útil?

Solução

Acho que o uso criterioso de métodos de extensão coloca as interfaces em uma posição mais equivalente às classes base (abstratas).


Versionamento. Uma vantagem que as classes base têm sobre as interfaces é que você pode facilmente adicionar novos membros virtuais em uma versão posterior, enquanto adicionar membros a uma interface interromperá os implementadores construídos na versão antiga da biblioteca.Em vez disso, uma nova versão da interface com os novos membros precisa ser criada, e a biblioteca terá que contornar ou limitar o acesso a objetos legados que implementam apenas a interface original.

Como exemplo concreto, a primeira versão de uma biblioteca pode definir uma interface como esta:

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

Depois que a biblioteca for lançada, não poderemos modificar a interface sem interromper os usuários atuais.Em vez disso, na próxima versão precisaríamos definir uma nova interface para adicionar funcionalidades adicionais:

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

Porém, apenas os usuários da nova biblioteca poderão implementar a nova interface.Para trabalhar com código legado, precisamos adaptar a implementação antiga, que um método de extensão pode lidar perfeitamente:

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 );
  }
}

Agora todos os usuários da nova biblioteca podem tratar as implementações legadas e modernas de forma idêntica.


Sobrecargas. Outra área onde os métodos de extensão podem ser úteis é no fornecimento de sobrecargas para métodos de interface.Você pode ter um método com vários parâmetros para controlar sua ação, dos quais apenas o primeiro ou dois são importantes no caso de 90%.Como o C# não permite definir valores padrão para parâmetros, os usuários precisam chamar o método totalmente parametrizado todas as vezes ou cada implementação deve implementar as sobrecargas triviais para o método principal.

Em vez disso, métodos de extensão podem ser usados ​​para fornecer implementações triviais de sobrecarga:

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 );
  }
  ...
}


Observe que ambos os casos são escritos em termos de operações fornecidas pelas interfaces e envolvem implementações padrão triviais ou bem conhecidas.Dito isto, você só pode herdar de uma classe uma vez, e o uso direcionado de métodos de extensão pode fornecer uma maneira valiosa de lidar com algumas das sutilezas fornecidas pelas classes base que faltam às interfaces :)


Editar: Uma postagem relacionada de Joe Duffy: Métodos de extensão como implementações de métodos de interface padrão

Outras dicas

Os métodos de extensão devem ser usados ​​exatamente assim:extensões.Qualquer código crucial relacionado à estrutura/design ou operação não trivial deve ser colocado em um objeto que seja composto/herdado de uma classe ou interface.

Depois que outro objeto tentar usar o estendido, ele não verá as extensões e poderá ter que reimplementá-las/rereferencia-las novamente.

A sabedoria tradicional é que os métodos de extensão só devem ser usados ​​para:

  • classes utilitárias, como Vaibhav mencionou
  • estendendo APIs de terceiros seladas

Acho que a melhor coisa que os métodos de extensão substituem são todas aquelas classes utilitárias que você encontra em cada projeto.

Pelo menos por enquanto, sinto que qualquer outro uso de métodos de extensão causaria confusão no local de trabalho.

Meus dois pedaços.

Vejo separar a funcionalidade de domínio/modelo e UI/visualização usando métodos de extensão como uma coisa boa, especialmente porque eles podem residir em namespaces separados.

Por exemplo:

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;
        }
    }
}

Dessa forma, os métodos de extensão podem ser usados ​​de forma semelhante aos modelos de dados XAML.Você não pode acessar membros privados/protegidos da classe, mas permite que a abstração de dados seja mantida sem duplicação excessiva de código em todo o aplicativo.

Não há nada de errado em estender interfaces; na verdade, é assim que o LINQ funciona para adicionar os métodos de extensão às classes da coleção.

Dito isto, você realmente só deve fazer isso no caso em que precisa fornecer a mesma funcionalidade em todas as classes que implementam essa interface e essa funcionalidade não é (e provavelmente não deveria ser) parte da implementação "oficial" de qualquer derivado Aulas.Estender uma interface também é bom se for impraticável escrever um método de extensão para cada tipo derivado possível que requer a nova funcionalidade.

Vejo muitas pessoas defendendo o uso de uma classe base para compartilhar funcionalidades comuns.Tenha cuidado com isso – você deve favorecer a composição em vez da herança.A herança só deve ser usada para polimorfismo, quando fizer sentido do ponto de vista da modelagem.Não é uma boa ferramenta para reutilização de código.

Quanto à pergunta:Esteja ciente das limitações ao fazer isso - por exemplo, no código mostrado, usar um método de extensão para implementar GetChildren efetivamente 'sela' essa implementação e não permite que qualquer impl IHaveChildren forneça o seu próprio, se necessário.Se estiver tudo bem, então não me importo muito com a abordagem do método de extensão.Ele não é imutável e geralmente pode ser facilmente refatorado quando mais flexibilidade for necessária posteriormente.

Para maior flexibilidade, pode ser preferível usar o padrão de estratégia.Algo como:

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

public interface IChildIterator
{
    IEnumerable<IChild> GetChildren();
}

public void DefaultChildIterator : IChildIterator
{
    private readonly IHaveChildren _parent;

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

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

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

    public IEnumerable<IChild> GetChildren()
    {
        return new DefaultChildIterator(this).GetChildren();
    }
}

Um pouquinho mais.

Se várias interfaces tiverem a mesma assinatura de método de extensão, você precisará converter explicitamente o chamador em um tipo de interface e depois chamar o método.Por exemplo.

((IFirst)this).AmbigousMethod()

Ai.Por favor, não estenda interfaces.
Uma interface é um contrato limpo que uma classe deve implementar, e o uso dessas classes deve fique restrito ao que está na interface principal para que isso funcione corretamente.

É por isso que você sempre declara a interface como o tipo em vez da classe real.

IInterface variable = new ImplementingClass();

Certo?

Se você realmente precisa de um contrato com alguma funcionalidade adicional, as classes abstratas são suas amigas.

Rob Connery (Subsonic e MVC Storefront) implementou um padrão semelhante ao IRepository em seu aplicativo Storefront.Não é exatamente o padrão acima, mas compartilha algumas semelhanças.

A camada de dados retorna IQueryable, que permite que a camada de consumo aplique expressões de filtragem e classificação além disso.O bônus é poder especificar um único método GetProducts, por exemplo, e então decidir apropriadamente na camada de consumo como deseja essa classificação, filtragem ou até mesmo apenas uma determinada faixa de resultados.

Não é uma abordagem tradicional, mas muito legal e definitivamente um caso de DRY.

Um problema que posso ver é que, em uma grande empresa, esse padrão pode permitir que o código se torne difícil (se não impossível) para qualquer pessoa entender e usar.Se vários desenvolvedores estiverem constantemente adicionando seus próprios métodos às classes existentes, separados dessas classes (e, que Deus nos ajude, até mesmo das classes BCL), eu poderia ver uma base de código ficando fora de controle rapidamente.

Mesmo no meu próprio trabalho, eu pude ver isso acontecendo, com o desejo do meu PM de adicionar cada pedaço de código em que trabalhamos à UI ou à camada de acesso a dados, eu pude vê-lo insistindo totalmente em 20 ou 30 métodos sendo adicionados a System.String que estão relacionados apenas tangencialmente ao tratamento de strings.

Eu precisava resolver algo semelhante:Eu queria que um List<IIDable> fosse passado para a função de extensões, onde IIDable é uma interface que possui uma função getId() longa.Tentei usar GetIds(this List<IIDable> bla) mas o compilador não me permitiu fazer isso.Em vez disso, usei modelos e digitei convertido dentro da função para o tipo de interface.Eu precisava dessa função para algumas classes geradas por linq to sql.

Eu espero que isso ajude :)

    public static List<long> GetIds<T>(this List<T> original){
        List<long> ret = new List<long>();
        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");
        }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top