Eu tenho esta função de API:
public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d,
string e, string f, out Guid code)
Eu não gosto disso Porque a ordem dos parâmetros se torna desnecessariamente significativa. Torna-se mais difícil adicionar novos campos. É mais difícil ver o que está sendo repassado. É mais difícil refatorar o método em partes menores, porque isso cria outra sobrecarga na passagem de todos os parâmetros nas sub-funções. O código é mais difícil de ler.
Tive a idéia mais óbvia: ter um objeto encapsulando os dados e repassá-los ao invés de passar cada parâmetro um por um. Aqui está o que eu vim com:
public class DoSomeActionParameters
{
public string A;
public string B;
public DateTime C;
public OtherEnum D;
public string E;
public string F;
}
Isso reduziu minha declaração da API para:
public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)
Agradável. Parece muito inocente, mas na verdade introduzimos uma grande mudança: introduzimos a mutabilidade. Porque o que estávamos fazendo anteriormente era realmente passar um objeto imutável anônimo: parâmetros de função na pilha. Agora criamos uma nova classe que é muito mutável. Criamos a capacidade de manipular o estado do chamador . Isso é péssimo. Agora eu quero meu objeto imutável, o que faço?
public class DoSomeActionParameters
{
public string A { get; private set; }
public string B { get; private set; }
public DateTime C { get; private set; }
public OtherEnum D { get; private set; }
public string E { get; private set; }
public string F { get; private set; }
public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d,
string e, string f)
{
this.A = a;
this.B = b;
// ... tears erased the text here
}
}
Como você pode ver, eu realmente recriei meu problema original: muitos parâmetros. É óbvio que esse não é o caminho a seguir. O que eu vou fazer? A última opção para alcançar essa imutabilidade é usar uma estrutura "somente leitura" como esta:
public struct DoSomeActionParameters
{
public readonly string A;
public readonly string B;
public readonly DateTime C;
public readonly OtherEnum D;
public readonly string E;
public readonly string F;
}
Isso nos permite evitar construtores com muitos parâmetros e obter imutabilidade. Na verdade, ele corrige todos os problemas (ordenação de parâmetros etc.). Ainda:
- Todos (incluindo FXCop e Jon Skeet) concordam que a exposição de campos públicos é ruim .
- Eric Lippert e colaboradores afirmam que confiar em campos somente para imutabilidade é uma mentira .
Foi quando fiquei confuso e decidi escrever esta pergunta: Qual é a maneira mais direta em C # de evitar o problema de "muitos parâmetros" sem introduzir mutabilidade? É possível usar uma estrutura somente leitura para esse fim e ainda não ter um design de API ruim?
ESCLARECIMENTOS:
- Por favor, assuma que não há violação do princípio de responsabilidade única. No meu caso original, a função apenas grava parâmetros fornecidos em um único registro de banco de dados.
- Não estou procurando uma solução específica para a função especificada. Estou buscando uma abordagem generalizada para esses problemas. Estou especificamente interessado em resolver o problema de "muitos parâmetros" sem introduzir mutabilidade ou um design terrível.
ATUALIZAR
As respostas fornecidas aqui têm diferentes vantagens / desvantagens. Portanto, eu gostaria de converter isso em um wiki da comunidade. Acho que cada resposta com amostra de código e Prós / Contras seria um bom guia para problemas semelhantes no futuro. Agora estou tentando descobrir como fazê-lo.
DoSomeActionParameters
é um objeto descartável, que será descartado após a chamada do método.Respostas:
Um estilo adotado nas estruturas é geralmente como agrupar parâmetros relacionados em classes relacionadas (mas mais uma vez problemático com mutabilidade):
fonte
Use uma combinação da API do estilo do construtor e do idioma específico do domínio - Interface Fluente. A API é um pouco mais detalhada, mas com o intellisense é muito rápido digitar e fácil de entender.
fonte
DoSomeAction
era um método dela.O que você tem lá é uma indicação bastante certa de que a classe em questão está violando o Princípio de Responsabilidade Única, porque possui muitas dependências. Procure maneiras de refatorar essas dependências em agrupamentos de dependências de fachada .
fonte
Basta alterar sua estrutura de dados de parâmetro de a
class
parastruct
e você estará pronto.O método agora terá sua própria cópia da estrutura. As alterações feitas na variável de argumento não podem ser observadas pelo método, e as alterações feitas pelo método na variável não podem ser observadas pelo chamador. O isolamento é alcançado sem imutabilidade.
Prós:
Contras:
fonte
Que tal criar uma classe de construtor dentro de sua classe de dados. A classe de dados terá todos os configuradores como privados e somente o construtor poderá configurá-los.
fonte
DoSomeActionParameters.Builder
instância pode ser usada para criar e configurar exatamente umaDoSomeActionParameters
instância. Após chamarBuild()
as alterações subseqüentes nasBuilder
propriedades da, continuará modificando as propriedades da instância originalDoSomeActionParameters
e as chamadas subseqüentes paraBuild()
continuarão retornando a mesmaDoSomeActionParameters
instância. Deveria mesmo serpublic DoSomeActionParameters Build() { var oldObj = obj; obj = new DoSomeActionParameters(); return oldObj; }
.Eu não sou um programador de C #, mas acredito que o C # suporte argumentos nomeados: (o F # faz e o C # é amplamente compatível com esse tipo de coisa) Ele faz: http://msdn.microsoft.com/en-us/library/dd264739 .aspx # Y342
Assim, chamar seu código original se torna:
isso não ocupa mais espaço / pensamento de que o seu objeto é criado e possui todos os benefícios, do fato de você não ter alterado o que está acontecendo no sistema não-subjacente. Você nem precisa recodificar nada para indicar que os argumentos são nomeados
Edit: aqui está um artigo que eu encontrei sobre ele. http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ Eu devo mencionar que o C # 4.0 suporta argumentos nomeados, o 3.0 não
fonte
Por que não fazer apenas uma interface que imponha imutabilidade (ou seja, apenas getters)?
É essencialmente sua primeira solução, mas você força a função a usar a interface para acessar o parâmetro.
e a declaração da função se torna:
Prós:
struct
soluçãoContras:
DoSomeActionParameters
é uma classe que pode ser mapeada paraIDoSomeActionParameters
fonte
IDoSomeActionParameters
e deseja capturar os valores nela não tem como saber se manter a referência será suficiente ou se deve copiar os valores para outro objeto. Interfaces legíveis são úteis em alguns contextos, mas não como um meio de tornar as coisas imutáveis.Sei que essa é uma pergunta antiga, mas pensei em entrar na minha sugestão, pois tinha que resolver o mesmo problema. Agora, é certo que meu problema era um pouco diferente do seu, pois eu tinha o requisito adicional de não desejar que os usuários pudessem construir esse objeto eles mesmos (toda a hidratação dos dados veio do banco de dados, para que eu pudesse isolar toda a construção internamente). Isso me permitiu usar um construtor privado e o seguinte padrão;
fonte
Você pode usar uma abordagem no estilo Builder, embora, dependendo da complexidade do seu
DoSomeAction
método, este possa ser um pouco pesado. Algo nesse sentido:fonte
Além da resposta do manji - você também pode dividir uma operação em várias menores. Comparar:
e
Para quem não conhece o POSIX, a criação do filho pode ser tão fácil quanto:
Cada opção de design representa uma troca sobre quais operações ela pode realizar.
PS. Sim - é semelhante ao construtor - somente no sentido inverso (ou seja, no lado chamado em vez de no chamador). Pode ou não ser melhor do que o construtor nesse caso específico.
fonte
aqui está um pouco diferente de Mikeys, mas o que estou tentando fazer é tornar a coisa toda o mínimo possível para escrever
O DoSomeActionParameters é imutável como pode e não pode ser criado diretamente, pois seu construtor padrão é privado
O inicializador não é imutável, mas apenas um transporte
O uso tira proveito do inicializador no Inicializador (se você entender meu desvio). E eu posso ter padrões no construtor padrão do Inicializador.
Os parâmetros serão opcionais aqui, se você quiser que alguns sejam necessários, você pode colocá-los no construtor padrão Initializer
E a validação pode ir no método Create
Então agora parece
Ainda um pouco maluco, eu sei, mas vou tentar mesmo assim
Editar: mover o método create para uma estática no objeto de parâmetros e adicionar um delegado que passa no inicializador retira parte da chamada da chamada
Portanto, a ligação agora se parece com isso
fonte
Use a estrutura, mas em vez de campos públicos, tenha propriedades públicas:
Jon e FXCop ficarão satisfeitos porque você está expondo propriedades, não campos.
Eric ficará satisfeito porque, ao usar propriedades, você pode garantir que o valor seja definido apenas uma vez.
Um objeto semi-imutável (o valor pode ser definido, mas não alterado). Funciona para tipos de valor e referência.
fonte
this
são muito mais más que os campos públicos. Não sei por que você acha que apenas definir o valor uma vez torna as coisas "corretas"; se alguém começar com uma instância padrão de uma estrutura, os problemas associados àsthis
propriedades -mutating ainda existirão.Uma variante da resposta de Samuel que usei no meu projeto quando tive o mesmo problema:
E para usar:
No meu caso, os parâmetros foram intencionalmente modificáveis, porque os métodos setter não permitiram todas as combinações possíveis e apenas expuseram combinações comuns deles. Isso porque alguns dos meus parâmetros eram bastante complexos e os métodos de escrita para todos os casos possíveis teriam sido difíceis e desnecessários (combinações malucas raramente são usadas).
fonte
DoMagic
contrato possui no entanto que ele não modificará o objeto. Mas acho que não protege contra modificações acidentais.