Como melhor organizar os arquivos de classe e interface?


17

OK .. depois de toda a discussão, estou mudando minha pergunta um pouco para refletir melhor um exemplo concreto com o qual estou lidando.


Eu tenho duas classes ModelOnee ModelTwo, essas classes executam um tipo semelhante de funcionalidade, mas não são relacionadas umas às outras. No entanto eu tenho uma terceira classe CommonFuncque contém algumas funcionalidades pública que é implementado em ambos ModelOnee ModelTwo, tendo sido contabilizado como por DRY. Os dois modelos são instanciados dentro da ModelMainclasse (que em si é instanciada em um nível superior, etc. - mas estou parando nesse nível).

O contêiner de IoC que estou usando é o Microsoft Unity . Não pretendo ser especialista nisso, mas entendo que você registra uma tupla de interface e classe no contêiner e quando deseja uma classe concreta, solicita ao contêiner IoC qualquer objeto que corresponda a uma interface específica. Isso implica que, para cada objeto que eu quero instanciar do Unity, deve haver uma interface correspondente. Como cada uma das minhas classes executa funcionalidades diferentes (e não sobrepostas), isso significa que existe uma proporção de 1: 1 entre a interface e a classe 1 . No entanto, isso não significa que estou escrevendo servilmente uma interface para cada classe que escrevo.

Assim, em termos de código, termino com 2 :

public interface ICommonFunc 
{ 
}

public interface IModelOne 
{ 
   ICommonFunc Common { get; } 
   .. 
}

public interface IModelTwo
{ 
   ICommonFunc Common { get; } 
   .. 
}

public interface IModelMain 
{ 
  IModelOne One { get; } 
  IModelTwo Two { get; } 
  ..
}

public class CommonFunc : ICommonFunc { .. }

public class ModelOne : IModelOne { .. }

public class ModelTwo : IModelTwo { .. }

public class ModelMain : IModelMain { .. }

A questão é sobre como organizar minha solução. Devo manter a classe e a interface juntas? Ou devo manter classes e interfaces juntas? POR EXEMPLO:

Opção 1 - Organizada pelo nome da classe

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-Main
          |   |
          |   |-IModelMain.cs
          |   |-ModelMain.cs
          |
          |-One
          |   |
          |   |-IModelOne.cs
          |   |-ModelOne.cs
          |
          |-Two
              |
              |-IModelTwo.cs
              |-ModelTwo.cs
              |

Opção 2 - Organizado por funcionalidade (principalmente)

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-IModelMain.cs
          |-IModelOne.cs
          |-IModelTwo.cs
          |-ModelMain.cs
          |-ModelOne.cs
          |-ModelTwo.cs
          |

Opção 3 - Interface e implementação separadas

MySolution
  |
  |-MyProject
      |
      |-Interfaces
      |   |
      |   |-Models
      |   |   |
      |       |-Common
      |       |   |-ICommonFunc.cs
      |       |
      |       |-IModelMain.cs
      |       |-IModelOne.cs
      |       |-IModelTwo.cs
      |
      |-Classes
          | 
          |-Models
          |   |
              |-Common
              |   |-CommonFunc.cs
              |
              |-ModelMain.cs
              |-ModelOne.cs
              |-ModelTwo.cs
              |

Opção 4 - Levando o exemplo de funcionalidade adiante

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Components
          |   |
          |   |-Common
          |   |   |
          |   |   |-CommonFunc.cs
          |   |   |-ICommonFunc.cs
          |   |   
          |   |-IModelOne.cs
          |   |-IModelTwo.cs
          |   |-ModelOne.cs
          |   |-ModelTwo.cs
          |
          |-IModelMain.cs
          |-ModelMain.cs
          |

Eu meio que não gosto da opção 1 por causa do nome da classe no caminho. Mas como estou tendendo à proporção de 1: 1 por causa da minha escolha / uso de IoC (e isso pode ser discutível), isso tem vantagens em ver o relacionamento entre os arquivos.

A opção 2 é atraente para mim, mas agora turvo as águas entre ModelMainos submodelos e os modelos.

A opção 3 funciona para separar a definição da interface da implementação, mas agora tenho essas quebras artificiais nos nomes dos caminhos.

Opção 4. Peguei a Opção 2 e a aprimorei para separar os componentes do modelo pai.

Há boas razões para preferir um ao outro? Ou outros layouts em potencial que eu perdi?


1. Frank fez um comentário de que a proporção de 1: 1 remete aos dias C ++ dos arquivos .h e .cpp. Eu sei de onde ele está vindo. Meu entendimento da Unity parece me colocar nesse canto, mas também não tenho certeza de como sair dela se você também estiver seguindo o ditado de Program to an interface Mas isso é uma discussão para outro dia.

2. Deixei de fora os detalhes de cada construtor de objetos. É aqui que o contêiner de IoC injeta objetos, conforme necessário.


1
Ugh. Cheira que você tem uma relação interface / classe 1: 1. Qual contêiner de IoC você está usando? O Ninject fornece mecanismos que não exigem uma proporção de 1: 1.
precisa

1
@RubberDuck FWIW Estou usando o Unity. Não pretendo ser especialista nisso, mas se minhas aulas são bem projetadas com responsabilidades únicas, como não acabo com uma proporção de quase 1: 1?
26617 Peter M

Você precisa do IBase ou pode ser abstrato? Por que o IDerivedOne quando ele já implementa o IBase? Você deve depender do IBase, não derivado. Não sei sobre o Unity, mas outros contêineres IoC permitem que você faça uma injeção "sensível ao contexto". Basicamente, quando Client1precisa de um IBase, ele fornece um Derived1. Quando Client2precisa de um IBase, o IoC fornece um Derived2.
precisa

1
Bem, se a base é abstrata, então não há razão para ter uma interfacepor isso. Um interfaceé realmente apenas uma classe abstrata com todos os membros virtuais.
precisa

1
RubberDuck está correto. Interfaces supérfluas são simplesmente irritantes.
26517 Frank Hileman

Respostas:


4

Como uma interface é abstratamente semelhante a uma classe base, use a mesma lógica que você usaria para uma classe base. As classes que implementam uma interface estão intimamente relacionadas à interface.

Duvido que você prefira um diretório chamado "Classes Base"; a maioria dos desenvolvedores não gostaria disso, nem um diretório chamado "Interfaces". No c #, os diretórios também são espaços de nome por padrão, tornando-o duplamente confuso.

A melhor abordagem é pensar em como você dividiria as classes / interfaces se tivesse que colocar algumas em uma biblioteca separada e organizasse os namespaces / diretórios de maneira semelhante. As diretrizes de design da estrutura .net têm sugestões de namespace que podem ser úteis.


Estou familiarizado com o link que você forneceu, mas não tenho certeza de como ele se relaciona com a organização de arquivos. Enquanto o VS padroniza os nomes de caminho para o espaço para nome inicial, eu sei que essa é uma escolha arbitrária. Também atualizei o código 'sample' e as possibilidades de layout.
Peter M

@PeterM Não sei ao certo qual IDE você está usando, mas o Visual Studio usa os nomes de diretório para gerar automaticamente as declarações de namespace ao adicionar uma classe, por exemplo. Isso significa que, embora você possa ter diferenças entre nomes de diretório e nomes de namespace, é mais doloroso trabalhar dessa maneira. Então a maioria das pessoas não faz isso.
Frank Hileman

Estou usando o VS. E sim, eu sei dor. Ele recupera memórias do layout de arquivo Visual Source Safe.
Peter M

1
@PeterM Com relação à relação 1: 1 para relação de classe. Algumas ferramentas exigem isso. Eu vejo isso como um defeito da ferramenta - .net possui recursos de reflexão suficientes para não precisar dessas restrições em nenhuma dessas ferramentas.
27417 Frank Hileman

1

Eu adotei a segunda abordagem, com mais pastas / namespaces em projetos complexos, é claro. Isso significa que eu desassocio as interfaces das implementações concretas.

Em um projeto diferente, talvez você precise conhecer a definição de interface, mas não é necessário conhecer nenhuma classe concreta que a implemente - especialmente quando você usa um contêiner de IoC. Portanto, esses outros projetos precisam apenas fazer referência aos projetos de interface, não aos projetos de implementação. Isso pode manter as referências baixas e evitar problemas de referência circular.


Eu revisei meu exemplo de código e adicionei mais algumas opções
Peter M

1

Como sempre em qualquer questão de design, a primeira coisa a fazer é determinar quem são seus usuários. Como eles vão usar sua organização?

Eles são programadores que usarão suas aulas? Em seguida, separar as interfaces do código pode ser melhor.

Eles são mantenedores do seu código? Em seguida, mantenha as interfaces e as classes juntas, talvez seja melhor.

Sente-se e crie alguns casos de uso. Em seguida, a melhor organização pode aparecer diante de você.


-2

Eu acho que é melhor armazenar interfaces na biblioteca separada. Primeiro, esse tipo de design aumenta a dependência. É por isso que a implementação dessas interfaces pode ser feita por outra linguagem de programação.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.