Dividir interfaces grandes

9

Estou usando uma interface grande com cerca de 50 métodos para acessar um banco de dados. A interface foi escrita por um colega meu. Nós discutimos isso:

Eu: 50 métodos é demais. É um cheiro de código.
Colega: O que devo fazer sobre isso? Você quer o acesso ao banco de dados - você o possui.
Eu: Sim, mas não é claro e dificilmente sustentável no futuro.
Colega: OK, você está certo, não é legal. Como deve ser a interface então?
Eu: Que tal 5 métodos que retornam objetos com 10 métodos cada?

Mmmh, mas não seria o mesmo? Isso realmente leva a mais clareza? Vale a pena o esforço?

De vez em quando, estou em uma situação em que quero uma interface e a primeira coisa que vem à mente é uma grande interface. Existe um padrão geral de design para isso?


Atualização (respondendo ao comentário de SJuan):

O "tipo de métodos": é uma interface para buscar dados de um banco de dados. Todos os métodos têm o formato (pseudocódigo)

List<Typename> createTablenameList()

Métodos e tabelas não estão exatamente em uma relação 1-1, a ênfase está mais no fato de que você sempre obtém algum tipo de lista que vem de um banco de dados.

TobiMcNamobi
fonte
12
Faltam informações relevantes (que tipo de métodos você possui). Enfim, meu palpite: se você dividir apenas por número, seu colega está certo, você não está melhorando nada. Um critério divisão possível seria pela "entidade" (quase o equivalente a uma mesa) devolvido (assim, uma UserDaoe uma CustomerDaoe uma ProductDao)
SJuan76
De fato, algumas tabelas estão semanticamente próximas de outras, formando "panelinhas". O mesmo acontece com os métodos.
TobiMcNamobi
É possível ver o código? Eu sei que ele tem 4 anos e você provavelmente já o corrigiu: D Mas eu adoraria pensar sobre esse problema. Eu resolvi algo assim antes.
clankill3r
@ clankill3r Na verdade, não tenho mais acesso à interface específica que me levou a postar a pergunta acima. O problema é mais geral. Imagine um banco de dados com cerca de 50 mesas e em cada mesa um método comoList<WeatherDataRecord> createWeatherDataTable() {db.open(); return db.select("*", "tbl_weatherData");}
TobiMcNamobi

Respostas:

16

Sim, 50 métodos é um cheiro de código, mas um cheiro de código significa dar uma segunda olhada nele, não que esteja automaticamente errado. Se todo cliente que usa essa classe precisar potencialmente de todos os 50 métodos, pode não haver um caso para dividi-la. No entanto, isso é improvável. Meu argumento é que dividir uma interface arbitrariamente pode ser pior do que não dividi-la.

Não há um único padrão para corrigi-lo, mas o princípio que descreve o estado desejado é o Princípio de Segregação de Interface (o 'I' no SOLID), que afirma que nenhum cliente deve ser forçado a depender dos métodos que não usa .

A descrição do ISP fornece uma dica sobre como corrigi-lo: veja o cliente . Frequentemente, apenas olhando para uma classe, parece que tudo pertence a um grupo, mas divisões claras surgem quando você olha para os clientes que usam essa classe. Sempre considere os clientes primeiro ao projetar uma interface.

A outra maneira de determinar se e onde uma interface deve ser dividida é fazendo uma segunda implementação. O que muitas vezes acaba acontecendo é que sua segunda implementação não precisa de muitos métodos, portanto esses claramente devem ser divididos em sua própria interface.

Karl Bielefeldt
fonte
Esta e a resposta de utnapistim são realmente ótimas.
TobiMcNamobi
13

Colega: OK, você está certo, não é legal. Como deve ser a interface então?

Eu: Que tal 5 métodos que retornam objetos com 10 métodos cada?

Esse não é um bom critério (na verdade, não há nenhum critério nessa declaração). Você pode agrupá-los (assumindo que seu aplicativo é um aplicativo de transações financeiras, para meus exemplos):

  • funcionalidade (inserções, atualizações, seleções, transações, metadados, esquema etc.)
  • entidade ( DAO do usuário , DAO do depósito etc.)
  • área de aplicação (transações financeiras, gerenciamento de usuários, totais etc.)
  • nível de abstração (todo o código de acesso à tabela é um módulo separado; todas as APIs selecionadas estão em sua própria hierarquia, o suporte à transação é separado, todo o código de conversão em um módulo, todo o código de validação em um módulo etc.)

Mmmh, mas não seria o mesmo? Isso realmente leva a mais clareza? Vale a pena o esforço?

Se você escolher os critérios certos, definitivamente. Se não, definitivamente não :).

Alguns exemplos:

  • procure nos objetos ADODB um exemplo simplista de primitivas OO (sua API do banco de dados provavelmente já oferece isso)

  • procure no modelo de dados Django ( https://docs.djangoproject.com/en/dev/topics/db/models/ ) uma idéia de modelo de dados com um alto nível de abstração (em C ++, você provavelmente precisará de um pouco mais de placa de caldeira código, mas é uma boa ideia). Essa implementação foi projetada com uma função de "modelo" em mente, dentro do padrão de design do MVC.

  • consulte a API do sqlite para obter uma idéia simples da API ( http://www.sqlite.org/c3ref/funclist.html ), consistindo apenas em primitivas funcionais (API C).

utnapistim
fonte
3

De vez em quando, estou em uma situação em que quero uma interface e a primeira coisa que vem à mente é uma grande interface. Existe um padrão geral de design para isso?

É um antipadrão de design chamado classe monolítica . Ter 50 métodos em uma classe ou interface é uma provável violação do SRP . A classe monolítica surge porque tenta ser tudo para todos.

O DCI aborda o inchaço do método. Essencialmente, as muitas responsabilidades de uma classe podem ser divididas em funções (transferidas para outras classes) que são relevantes apenas em determinados contextos. A aplicação de papéis pode ser alcançada de várias maneiras, incluindo mixins ou decoradores . Essa abordagem mantém as aulas focadas e enxutas.

Que tal 5 métodos que retornam objetos com 10 métodos cada?

Isso sugere instanciar todas as funções quando o próprio objeto é instanciado. Mas por que instanciar funções que talvez você não precise? Em vez disso, instancie uma função no contexto em que você realmente precisa.

Se você achar que a refatoração em relação ao DCI não é óbvia, pode optar por um padrão de visitante mais simples . Ele fornece um benefício semelhante sem enfatizar a criação de contextos de casos de uso.

EDIT: Meu pensamento sobre isso mudou um pouco. Eu forneci uma resposta alternativa.

Mario T. Lanza
fonte
1

Parece-me que todas as outras respostas estão erradas. O ponto é que uma interface deve definir idealmente um pedaço atômico de comportamento. Esse é o eu no SOLID.

Uma classe deve ter uma responsabilidade, mas isso ainda pode incluir vários comportamentos. Para manter um objeto típico de cliente de banco de dados, isso pode oferecer funcionalidade CRUD completa. Seriam quatro comportamentos: criar, ler, atualizar e excluir. Em um mundo puro do SOLID, o cliente do banco de dados não implementaria o IDatabaseClient, mas sim o ICreator, IReader, IUpdater e IDeleter.

Isso teria vários benefícios. Primeiro, apenas lendo a declaração da classe, aprenderíamos instantaneamente muito sobre a classe, as interfaces implementadas contam a história toda. Segundo, se o objeto do cliente fosse passado como argumento, agora um tem diferentes opções úteis. Pode ser passado como um IReader e pode-se ter certeza de que o receptor só poderá ler. Comportamentos diferentes podem ser testados separadamente.

No entanto, quando se trata de testar, a prática comum é aplicar apenas uma interface em uma classe que é uma réplica 1 para 1 da interface completa da classe. Se o que importa é o teste, isso pode ser uma abordagem válida. Ele permite que você faça manequins rapidamente. Mas quase nunca é o SOLID e realmente um abuso de interfaces para um propósito dedicado.

Então, sim, 50 métodos é um cheiro, mas depende da intenção e do objetivo, seja ruim ou não. Certamente não é o ideal.

Martin Maat
fonte
0

As camadas de acesso a dados tendem a ter muitos métodos anexados a uma classe. Se você já trabalhou com o Entity Framework ou outras ferramentas ORM, verá que elas geram centenas de métodos. Presumo que você e seu colega estejam implementando manualmente. Não é necessário um cheiro de código, mas não é bonito de se olhar. Sem conhecer seu domínio, é difícil dizer.

Roman Mik
fonte
Métodos ou propriedades?
Jeffo
0

Eu uso protocolos (chame-os de interfaces, se quiser) quase universalmente para todas as APIs com FP e OOP. (Lembre-se da Matrix? Não existem concretizações!) É claro que existem tipos concretos, mas no escopo de um programa todo tipo é pensado como algo que desempenha um papel em algum contexto.

Isso significa que objetos passados ​​por programas, funções, etc. podem ser vistos como entidades abstratas com conjuntos de comportamentos nomeados. O objeto pode ser pensado como desempenhando um papel que é algum conjunto de protocolos. Uma pessoa (tipo concreto) pode ser um homem, um pai, um marido, um amigo e um empregado, mas não consigo imaginar muitas funções que considerariam a entidade a soma de mais de duas delas.

Eu acho que é possível que um objeto complexo possa obedecer a vários protocolos diferentes, mas você ainda seria pressionado para obter uma API de 50 métodos. A maioria dos protocolos tem 1 ou 2 métodos, talvez 3, mas nunca 50! Qualquer entidade que possua 50 métodos deve ser agregada a vários componentes menores, cada um com suas próprias responsabilidades. A entidade em geral apresentaria uma interface mais simples que abstrai a soma total das APIs dentro dela.

Em vez de pensar em termos de objetos e métodos, comece a pensar em termos de abstrações e contratos e quais papéis um sujeito desempenha em algum contexto.

Mario T. Lanza
fonte