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 ModelOne
e 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 CommonFunc
que contém algumas funcionalidades pública que é implementado em ambos ModelOne
e ModelTwo
, tendo sido contabilizado como por DRY
. Os dois modelos são instanciados dentro da ModelMain
classe (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 ModelMain
os 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.
fonte
Client1
precisa de umIBase
, ele fornece umDerived1
. QuandoClient2
precisa de umIBase
, o IoC fornece umDerived2
.interface
por isso. Uminterface
é realmente apenas uma classe abstrata com todos os membros virtuais.Respostas:
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.
fonte
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.
fonte
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ê.
fonte
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.
fonte