Abus de fermetures? Les violations des divers principes? Ou bien?
-
27-09-2019 - |
Question
Edit:. Plusieurs problèmes de syntaxe et de cohérence pour rendre le code un peu plus apparente et à ce que je suis en train de faire
J'ai un code qui ressemble à ceci:
SomeClass someClass;
var finalResult =
DoSomething(() =>
{
var result = SomeThingHappensHere();
someClass = result.Data;
return result;
})
.DoSomething(() => return SomeOtherThingHappensHere(someClass))
.DoSomething(() => return AndYetAnotherThing())
.DoSomething(() => return AndOneMoreThing(someClass))
.Result;
HandleTheFinalResultHere(finalResult);
lorsque la méthode de DoSomething
est une méthode d'extension, et il attend un Func passé en elle. Ainsi, chacun des appels de méthode dans chacune des déclarations du DoSomething => lambda un type de résultat.
est semblable à un Peut-être monade . Sauf au lieu de vérifier pour les valeurs nulles, je vérifie l'état de la classe de résultats et soit en appelant le Func qui a été adoptée dans DoSomething ou retourner le résultat précédent sans appeler le Func
la face i problème est que, faute d'avoir ce genre de composition dans mon code, mais je dois aussi être en mesure de transmettre des données à partir de l'un des résultats des appels composés à l'appel d'un autre, comme vous pouvez le voir avec le someClass
variable.
Ma question est pas si cela est techniquement correct ... Je sais que cela fonctionne, parce que je fais actuellement il. Ma question est de savoir si ou non est un abus de fermeture, ou la séparation de commande requête, ou d'autres principes ... et de demander ce que de meilleurs modèles, il y a pour gérer cette situation, parce que je suis assez sûr que je suis coincé dans un mode « brillant nouveau marteau » avec ce type de code, en ce moment.
La solution
Comme il a été noté, vous avez presque mis en place un Monad ici.
Votre code est un peu inélégante en ce que les lambdas ont des effets secondaires. Monades résoudre ce plus d'élégance.
Alors, pourquoi ne pas transformer votre code dans un Monad approprié?
Bonus: vous pouvez utiliser la syntaxe LINQ
Je vous présente:
LINQ aux résultats
Exemple:
var result =
from a in SomeThingHappensHere()
let someData = a.Data
from b in SomeOtherThingHappensHere(someData)
from c in AndYetAnotherThing()
from d in AndOneMoreThing(someData)
select d;
HandleTheFinalResultHere(result.Value);
LINQ aux résultats , ce premier SomeThingHappensHere
exécute. Si cela réussit, il obtient la valeur de la propriété Data
du résultat et exécute SomeOtherThingHappensHere
. Si cela réussit, il exécute AndYetAnotherThing
, et ainsi de suite.
Comme vous pouvez le voir, vous pouvez facilement les opérations de la chaîne et consultez résultats des opérations précédentes. Chaque opération sera exécutée un après l'autre, et l'exécution s'arrête lorsqu'une erreur est rencontrée.
Le from x in
bit chaque ligne est un peu bruyant, mais l'OMI rien de complexité comparable obtiendra plus lisible que cela!
Comment pouvons-nous faire ce travail?
Monads en C # se composent de trois parties:
-
type Quelque chose-de-T
-
Select
/SelectMany
méthodes d'extension pour elle, et -
une méthode pour convertir un T dans Quelque chose-de-T .
Tout ce que vous devez faire est de créer quelque chose qui ressemble à une monade, se sent comme une monade et les odeurs comme une monade, et tout fonctionne automagiquement.
Les types et méthodes pour LINQ aux résultats sont les suivantes.
Résultat
Une classe simple qui représente un résultat. Un résultat est soit une valeur de type T , ou une erreur. Un résultat peut être construit à partir d'un T ou à partir d'un Exception .
class Result<T>
{
private readonly Exception error;
private readonly T value;
public Result(Exception error)
{
if (error == null) throw new ArgumentNullException("error");
this.error = error;
}
public Result(T value) { this.value = value; }
public Exception Error
{
get { return this.error; }
}
public bool IsError
{
get { return this.error != null; }
}
public T Value
{
get
{
if (this.error != null) throw this.error;
return this.value;
}
}
}
Méthodes d'extension:
pour les méthodes mises en œuvre de Select
et SelectMany
. Les signatures de méthode sont données dans la spécification C #, donc tout ce que vous avez à vous soucier est leur mise en œuvre. Ceux-ci viennent tout naturellement si vous essayez de combiner de manière significative tous les arguments de la méthode.
static class ResultExtensions
{
public static Result<TResult> Select<TSource, TResult>(this Result<TSource> source, Func<TSource, TResult> selector)
{
if (source.IsError) return new Result<TResult>(source.Error);
return new Result<TResult>(selector(source.Value));
}
public static Result<TResult> SelectMany<TSource, TResult>(this Result<TSource> source, Func<TSource, Result<TResult>> selector)
{
if (source.IsError) return new Result<TResult>(source.Error);
return selector(source.Value);
}
public static Result<TResult> SelectMany<TSource, TIntermediate, TResult>(this Result<TSource> source, Func<TSource, Result<TIntermediate>> intermediateSelector, Func<TSource, TIntermediate, TResult> resultSelector)
{
if (source.IsError) return new Result<TResult>(source.Error);
var intermediate = intermediateSelector(source.Value);
if (intermediate.IsError) return new Result<TResult>(intermediate.Error);
return new Result<TResult>(resultSelector(source.Value, intermediate.Value));
}
}
Vous pouvez librement modifier le résultat
Autres conseils
Me semble que vous avez construit quelque chose de très semblable à une monade ici.
Vous pourriez faire une monade appropriée en faisant saisir votre délégué un Func<SomeClass, SomeClass>
, avoir un moyen de mettre en place la valeur SomeClass
initiale à passer, et ont DoSomething
passer la valeur de retour d'un comme paramètre de la prochaine - ce Plût à faire l'enchaînement explicite plutôt que de compter sur l'état partagé scope lexicalement.
La faiblesse de ce code est le couplage implicite entre les première et seconde lambdas. Je ne suis pas sûr de la meilleure façon de le corriger.