Reduzindo clichê na classe que implementa interfaces através da composição

10

Eu tenho uma classe: Aque é um composto de um número de classes menores, B, Ce D.

B, C, E Dimplementar as interfaces IB, ICe, IDrespectivamente.

Uma vez que Asuporta todas as funcionalidades de B, Ce D, Aimplementa IB, ICe IDtambém, mas isso infelizmente leva a muito redirecionamento na implementação deA

Igual a:

interface IB
{
    int Foo {get;}
}

public class B : IB
{
    public int Foo {get {return 5;}}
}

interface IC
{
    void Bar();
}

public class C : IC
{
    public void Bar()
    {
    }
}

interface ID
{
    string Bash{get; set;}
}

public class D: ID
{
    public string Bash {get;set;}
}

class A : IB, IC, ID
{
    private B b = new B();
    private C c = new C();
    private D d = new D();

    public int Foo {get {return b.Foo}}

    public A(string bash)
    {
        Bash = bash;
    }

    public void Bar()
    {
        c.Bar();
    }

    public string Bash
    {
         get {return d.Bash;}
         set {d.Bash = value;}
    }
}

Existe uma maneira de se livrar desse redirecionamento padrão A? B, CE Dtoda a implementar a funcionalidade diferente, mas comum, e eu gosto disso Aimplementos IB, ICe IDporque isso significa que eu posso passar em Asi como uma dependência combinando essas interfaces, e não tem que expor os métodos auxiliares internos.

Nick Udell
fonte
Você poderia dizer um pouco sobre por que você precisa de uma classe A para implementar IB, IC, ID? Por que não apenas expor o BCD como público?
Esben Skov Pedersen
Eu acho que minha principal razão para preferir Aimplementar IB, ICe IDé mais sensato escrever do Person.Walk()que Person.WalkHelper.Walk()por exemplo.
precisa

Respostas:

7

O que você está procurando é chamado de mixins . Infelizmente, o C # não suporta nativamente esses.

Existem poucas soluções alternativas: uma , duas , três e muito mais.

Na verdade, eu realmente gosto do último. A ideia de usar a classe parcial gerada automaticamente para gerar o padrão é provavelmente a mais próxima possível da solução realmente boa:

[pMixins] é um plug-in do Visual Studio que verifica uma solução para classes parciais decoradas com atributos pMixin. Ao marcar sua classe como parcial, o [pMixins] pode criar um arquivo code-behind e adicionar membros adicionais à sua classe

Eufórico
fonte
2

Embora não reduza o padrão no próprio código, o Visual Studio 2015 agora vem com uma opção de refatoração para gerar automaticamente o padrão para você.

Para usar isso, primeiro crie sua interface IExamplee implementação Example. Em seguida, crie sua nova classe Compositee torne-a herdada IExample, mas não implemente a interface.

Adicione uma propriedade ou campo do tipo Exampleà sua Compositeclasse, abra o menu de ações rápidas no token IExampleno seu Compositearquivo de classe e selecione "Implementar interface através de 'Exemplo'", onde 'Exemplo' neste caso é o nome do campo ou propriedade.

Observe que, embora o Visual Studio gere e redirecione todos os métodos e propriedades definidos na interface para essa classe auxiliar, você não redirecionará eventos a partir do momento em que essa resposta foi postada.

Nick Udell
fonte
1

Sua turma está fazendo muito, é por isso que você tem que implementar tanto clichê. Você diz nos comentários:

parece mais sensato escrever Person.Walk () em oposição a Person.WalkHelper.Walk ()

Discordo. É provável que o ato de andar envolva muitas regras e não pertença à Personclasse - ele pertence a uma WalkingServiceclasse com um walk(IWalkable)método no qual Person implementa IWalkable.

Lembre-se sempre dos princípios do SOLID ao escrever código. Os dois que são mais aplicáveis ​​aqui são Separação de preocupações (extrair o código ambulante para uma classe separada) e Segregação de interface (dividir interfaces em tarefas / funções / habilidades específicas). Parece que você pode ter um pouco do I com suas múltiplas interfaces, mas está desfazendo todo o seu bom trabalho, fazendo uma classe implementá-las.

Stuart Leyland-Cole
fonte
No meu exemplo, a funcionalidade é implementada em classes separadas, e eu tenho uma classe composta por algumas delas como forma de agrupar o comportamento necessário. Nenhuma das implementações reais existe na minha classe maior, todas são tratadas pelos componentes menores. Talvez você esteja certo, e tornar públicas essas classes auxiliares para que elas possam interagir diretamente é o caminho a seguir.
precisa
4
Quanto à idéia de um walk(IWalkable)método, eu pessoalmente não gosto, porque os serviços de caminhada se tornam responsabilidade do chamador, e não da Pessoa, e a consistência não é garantida. Qualquer pessoa que chama deve saber qual serviço usar para garantir consistência, enquanto mantê-lo na classe Person significa que ele deve ser alterado manualmente para que o comportamento da caminhada seja diferente, enquanto ainda permite a inversão de dependência.
precisa
0

O que você está procurando é herança múltipla. No entanto, nem C # nem Java possuem.

Você pode estender B de A. Isso eliminará a necessidade de um campo privado para B, bem como a cola de redirecionamento. Mas você só pode fazer isso para um de B, C ou D.

Agora, se você pode fazer C herdar de B e D de C, você só precisa ter A estender D ...;) - só para deixar claro, porém, não estou incentivando isso neste exemplo, pois não há indicação por isso.

Em C ++, você seria capaz de herdar A de B, C e D.

Mas a herança múltipla dificulta a discussão dos programas e tem seus próprios problemas; Além disso, geralmente há uma maneira limpa de evitá-lo. Ainda assim, às vezes sem ele, é preciso fazer trocas de design entre o padrão repetitivo e como o modelo de objetos é exposto.

Parece que você está tentando misturar composição e herança. Se fosse C ++, você poderia usar uma solução pura de herança. Mas como não é, eu recomendo adotar a composição.

Erik Eidt
fonte
2
Você pode ter várias heranças usando interfaces em java e c #, assim como a operação fez em seu código de exemplo.
Robert Harvey
1
Alguns podem considerar a implementação de uma interface igual à herança múltipla (como em C ++). Outros discordariam, porque as interfaces não podem apresentar métodos de instância herdáveis, que é o que você teria se tivesse herança múltipla. Em C #, você não pode estender múltiplas classes base, portanto, você não pode herdar método de instância implementações de várias classes, que é por isso que você precisa de todo o clichê ...
Erik Eidt