web-dev-qa-db-fra.com

Programmation orientée forme: Quand commencer à utiliser un cadre?

Je viens de regarder ce discours par Greg Young AVERTISSEMENT Les gens à embrasser: gardez-le simple stupide.

L'une des choses qu'il a suggéré est que de faire une programmation orientée forme, un fait non besoin d'un cadre.

Il commence par faire une contrainte forte: que toutes les méthodes en prennent un, et un seul, paramètre (bien qu'il se détend cela un peu plus tard en utilisant une application partielle ).

L'exemple qu'il donne est de définir une interface:

public interface IConsumes<T>
{
    void Consume(T message);
}

Si nous voulons émettre une commande:

public class Command
{
    public string SomeInformation;
    public int ID;

    public override string ToString()
    {
       return ID + " : " + SomeInformation + Environment.NewLine;
    }
}

La commande est mise en œuvre comme suit:

public class CommandService : IConsumes<Command>
{
    private IConsumes<Command> _next;

    public CommandService(IConsumes<Command> cmd = null)
    {
        _next = cmd;
    }
    public void Consume(Command message)
    {
       Console.WriteLine("Command complete!");
        if (_next != null)
            _next.Consume(message);
    }
}

Pour se connecter à la console, l'un puis il suffit d'implément:

public class Logger<T> : IConsumes<T>
{
    private readonly IConsumes<T> _next;

    public Logger(IConsumes<T> next)
    {
        _next = next;
    }
    public void Consume(T message)
    {
        Log(message);
        if (_next != null)
            _next.Consume(message);
    }

    private void Log(T message)
    {
        Console.WriteLine(message);
    }
}

Ensuite, la journalisation de la commande avant commande, le service de commande et la journalisation post-commande sont alors juste:

var log1 = new Logger<Command>(null);
var svr  = new CommandService(log);
var startOfChain = new Logger<Command>(svr);

et la commande est exécutée par:

var cmd = new Command();
startOfChain.Consume(cmd);

Pour ce faire, par exemple, postSharp , on annoterait le CommandService de cette façon:

public class CommandService : IConsumes<Command>
{
    [Trace]
    public void Consume(Command message)
    {
       Console.WriteLine("Command complete!");
    }
}

puis doit mettre en œuvre la journalisation dans une classe d'attribut quelque chose comme :

[Serializable]
public class TraceAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry( MethodExecutionArgs args )
    {
        Console.WriteLine(args.Method.Name + " : Entered!" );   
    }

    public override void OnSuccess( MethodExecutionArgs args )
    {
        Console.WriteLine(args.Method.Name + " : Exited!" );
    }

    public override void OnException( MethodExecutionArgs args )
    {
        Console.WriteLine(args.Method.Name + " : EX : " + args.Exception.Message );
    }
}

L'argument greg utilise est que la connexion de l'attribut à la mise en œuvre de l'attribut est "trop ​​magique" pour pouvoir expliquer ce qui se passe dans un développeur junior. L'exemple initial est tout "juste code" et facilement expliqué.

Ainsi, après cette accumulation plutôt longue, la question est la suivante: quand faites-vous passer de l'approche non-cadre de Greg pour utiliser quelque chose comme postSharp pour AOP?

22
Peter K.

Essaie-t-il d'écrire un cadre "droit au TDWTF" AOP? J'ai sérieusement je n'ai toujours pas eu d'indice ce que son point était. Dès que vous dites "toutes les méthodes doivent prendre exactement un paramètre", vous n'avez pas échoué? À ce stade, vous dites, d'accord, cela impose des contraintes sérieusement artificielles sur ma capacité à écrire des logiciels, abandonnons ceci maintenant auparavant, trois mois sur la ligne que nous disposons d'un code de cauchemar complet.

Et tu sais quoi? Vous pouvez écrire un cadre de journalisation basé sur l'attribut simple à base d'attributs tout simplement facilement avec mono.cecil . (Testant qu'il est légèrement plus compliqué, mais ...)

Oh et imo, si vous n'utilisez pas d'attributs, ce n'est pas un AOP. L'ensemble du point de faire la méthode de l'entrée/la sortie du code de journalisation à la phase postale du processeur est de sorte qu'il ne gâche pas vos fichiers de code et de ne pas avoir besoin d'y réfléchir lorsque vous refacteur votre code; que est son pouvoir.

TOUT GREG a démontré qu'il y a le conserver stupide paradigme stupide.

17
user23157

Mon Dieu, ce mec est intolérablement abrasif. J'aimerais que je viens de lire le code dans votre question au lieu de regarder cette conversation.

Je ne pense pas que j'utilise jamais cette approche si ce n'est que pour l'utilisation de AOP. Greg dit que c'est bon pour des situations simples. Voici ce que je ferais dans une situation simple:

public void DeactivateInventoryItem(CommandServices cs, Guid item, string reason)
{
    cs.Log.Write("Deactivated: {0} ({1})", item, reason);
    repo.Deactivate(item, reason);
}

Ouais, je l'ai fait, je me suis débarrassé de l'AOP entièrement! Pourquoi? Parce que vous n'avez pas besoin d'un AOP dans des situations simples.

D'un point de vue de la programmation fonctionnelle, permettant qu'un seul paramètre par fonction ne me fait pas vraiment peur. Néanmoins, ce n'est vraiment pas un design qui fonctionne bien avec C # - et allant contre les grains de votre langue pas KISS rien.

Je n'utiliserais que cette approche s'il était nécessaire de créer un modèle de commande pour commencer, par exemple si j'avais besoin d'une pile d'annulation ou si je travaillais avec commandes WPF .

Sinon, je voudrais simplement utiliser un cadre ou une réflexion. PostSharp fonctionne même dans Silverlight et Compact Cadre - alors ce qu'il appelle "magique" n'est vraiment pas magique du tout.

Je ne suis pas non plus d'accord avec éviter les cadres pour pouvoir expliquer les choses aux juniors. Ça ne les fait pas bien. Si Greg traite ses juniors la façon dont il suggère d'être traité, comme des idiots crânes épais, alors je soupçonne que ses développeurs seniors ne sont pas très grands non plus, car ils n'ont probablement pas eu la possibilité d'apprendre quoi que ce soit d'apprendre au cours de leur années junior.

8
Rei Miyasaka

J'ai fait une étude indépendante au collège sur AOP. En fait, j'ai écrit un papier sur une approche du modèle AOP avec un plug-in Eclipse. C'est en fait un peu non négligeant que je suppose. Les points clés sont 1) J'étais jeune et inexpérimenté et 2) je travaillais avec Aspectj. Je peux vous dire que la "magie" de la plupart des cadres AOP n'est pas si compliqué. J'ai effectivement travaillé sur un projet à peu près au même moment, essayant de faire l'approche de paramètres unique en utilisant une hache. IMO, l'approche de paramètres unique est vraiment un cadre et il est invasif. Même sur ce post, j'ai passé plus de temps à essayer de comprendre l'approche de paramètres unique que je n'ai examiné l'approche déclarative. Je vais ajouter une mise en garde que je n'ai pas regardé le film, de sorte que la "magie" de cette approche peut être dans l'utilisation d'applications partielles.

Je pense que Greg répondit votre question. Vous devez passer à cette approche lorsque vous pensez que vous êtes dans une situation où vous passez une quantité excessive de temps en expliquant les cadres de l'AOP à vos développeurs juniors. IMO, si vous êtes dans ce bateau, vous embauchez probablement les mauvais développeurs junior. Je ne crois pas que l'AOP nécessite une approche déclarative, mais pour moi, il est tout simplement beaucoup plus clair et non invasif d'une perspective de conception.

5
kakridge

Ce qui décrit Greg est absolument raisonnable. Et il y a une beauté y a-t-elle aussi. Le concept est applicable dans un autre paradigme que l'orientation de l'objet pure. C'est plus une approche procédurale ou une approche de conception orientée de flux. Donc, si vous travaillez avec un code hérité, il sera assez difficile d'appliquer ce concept car beaucoup de refactoring pourraient être nécessaires.

Je vais essayer de donner un autre exemple. Peut-être pas parfait mais j'espère que cela rend le point plus clair.

Nous avons donc un service de produit qui utilise un référentiel (dans ce cas, nous utiliserons un talon). Le service recevra une liste de produits.

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }

    public override string ToString() { return String.Format("{0}, {1}", Name, Price); }
}

public static class ProductService
{
    public static IEnumerable<Product> GetAllProducts(ProductRepositoryStub repository)
    {
        return repository.GetAll();
    }
}

public class ProductRepositoryStub
{
    public ProductRepositoryStub(string connStr) {}

    public IEnumerable<Product> GetAll()
    {
        return new List<Product>
        {
            new Product {Name = "Cd Player", Price = 49.99m},
            new Product {Name = "Yacht", Price = 2999999m }
        };
    }
}

Bien sûr, vous pouvez également transmettre une interface au service.

Ensuite, nous voulons montrer une liste de produits dans une vue. Par conséquent, nous avons besoin d'une interface

public interface Handles<T>
{
    void Handle(T message);
}

et une commande qui détient la liste des produits

public class ShowProductsCommand
{
    public IEnumerable<Product> Products { get; set; }
}

et la vue

public class View : Handles<ShowProductsCommand>
{
    public void Handle(ShowProductsCommand cmd)
    {
        cmd.Products.ToList().ForEach(x => Console.WriteLine(x.ToString()));
    }
}

Maintenant, nous avons besoin du code qui exécute tout cela. Ceci nous ferons dans une classe appelée application. La méthode RUN () est la méthode d'intégration contenant non ou au moins très peu de logique commerciale. Les dépendances sont injectées dans le constructeur comme méthodes.

public class Application
{
    private readonly Func<IEnumerable<Product>> _getAllProducts;
    private readonly Action<ShowProductsCommand> _showProducts;

    public Application(Func<IEnumerable<Product>> getAllProducts, Action<ShowProductsCommand> showProducts)
    {
        _getAllProducts = getAllProducts;
        _showProducts = showProducts;
    }

    public void Run()
    {
        var products = _getAllProducts();
        var cmd = new ShowProductsCommand { Products = products };
        _showProducts(cmd);
    }
}

Enfin, nous composons la demande dans la méthode principale.

static void Main(string[] args)
{
    // composition
    Func<IEnumerable<Product>> getAllProducts = () => ProductService.GetAllProducts(new ProductRepositoryStub(""));
    Action<ShowProductsCommand> showProducts = (x) => new View().Handle(x);
    var app = new Application(getAllProducts, showProducts);

    app.Run();
}

Maintenant, la chose cool est que nous pouvons ajouter des aspects tels que la journalisation ou la gestion des exceptions sans toucher le code existant et sans cadre ni annotation. Pour la gestion des exceptions par exception. Nous allons juste ajouter une nouvelle classe:

public class ExceptionHandler<T> : Handles<T>
{
    private readonly Handles<T> _next;

    public ExceptionHandler(Handles<T> next) { _next = next; }

    public void Handle(T message)
    {
        try
        {
            _next.Handle(message);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

Et puis nous le branchons ensemble pendant la composition au point d'entrée de l'application. Nous n'avons même pas besoin de toucher le code dans la classe d'application. Nous venons de remplacer une ligne:

Action<ShowProductsCommand> showProducts = (x) => new ExceptionHandler<ShowProductsCommand>(new View()).Handle(x);

Donc, pour reprendre: lorsque nous avons une conception orientée de flux, nous pouvons ajouter des aspects en ajoutant la fonctionnalité dans une nouvelle classe. Ensuite, nous devons changer une ligne dans la méthode de la composition et c'est tout.

Je pense donc qu'une réponse à votre question est que vous ne pouvez pas facilement passer d'une approche à l'autre, mais vous devez décider du type d'approche architecturale que vous irez dans votre projet.

Edit : En fait, je viens de réaliser que le modèle d'application partiel utilisé avec le service de produit rend les choses un peu plus compliquées. Nous devons envelopper une autre classe autour de la méthode du service de produits pour pouvoir également ajouter des aspects ici. Cela pourrait être quelque chose comme ça:

public class ProductQueries : Queries<IEnumerable<Product>>
{
    private readonly Func<IEnumerable<Product>> _query;

    public ProductQueries(Func<IEnumerable<Product>> query)
    {
        _query = query;
    }

    public IEnumerable<Product> Query()
    {
        return _query();
    }
}

public interface Queries<TResult>
{
    TResult Query();
}

La composition doit alors être changée comme ceci:

Func<IEnumerable<Product>> getAllProducts = () => ProductService.GetAllProducts(new ProductRepositoryStub(""));
Func<IEnumerable<Product>> queryAllProducts = new ProductQueries(getAllProducts).Query;
Action<ShowProductsCommand> showProducts = (x) => new ExceptionHandler<ShowProductsCommand>(new View()).Handle(x);
var app = new Application(queryAllProducts, showProducts);
4
leifbattermann

Sauf si vous manquez quelque chose que le code que vous avez montré est le modèle de conception de la chaîne de responsabilité qui est génial si vous devez signaler une série d'actions sur un objet (telles que des commandes suivant une série de manuteurs de commande) à Durée.

AOP utilisant PostSharp est bon si vous savez à la compilation, quel comportement vous souhaitez ajouter sera. Le tissage du code postSharp signifie à peu près des frais généraux d'exécution de zéro et maintient le code très propre (surtout lorsque vous commencez à utiliser des objets comme des aspects de multidiffusion). Je ne pense pas que l'utilisation de base de postsharp est particulièrement complexe à expliquer. L'inconvénient de postsharp est qu'il augmente les temps de compilation de la compilation.

J'utilise à la fois des techniques du code de production et, bien qu'il y ait des chevauchements dans l'endroit où ils peuvent être appliqués, je pense que pour la plupart des scénarios différents.

4
FinnNk

En ce qui concerne son alternative - été là, fait cela. Rien ne se compare à la lisibilité d'un attribut d'une ligne.

Donnez une courte conférence aux nouveaux gars les expliquant comment les choses fonctionnent dans AOP.

4
Danny Varod