Ich habe gerade diesen Vortrag von Greg Young gesehen, der die Leute zu KISS warnt: Keep It Simple Stupid.
Eines der Dinge , schlug er vor, dass die aspektorientierte Programmierung zu tun, man hat nicht einen Rahmen braucht .
Er beginnt mit einer starken Einschränkung: Alle Methoden verwenden nur einen einzigen Parameter ( dies wird jedoch etwas später durch teilweise Anwendung gelockert ).
Das Beispiel, das er gibt, besteht darin, eine Schnittstelle zu definieren:
public interface IConsumes<T>
{
void Consume(T message);
}
Wenn wir einen Befehl ausgeben wollen:
public class Command
{
public string SomeInformation;
public int ID;
public override string ToString()
{
return ID + " : " + SomeInformation + Environment.NewLine;
}
}
Der Befehl wird implementiert als:
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);
}
}
Um sich an der Konsole anzumelden, muss man nur Folgendes implementieren:
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);
}
}
Dann sind die Protokollierung vor dem Befehl, der Befehlsservice und die Protokollierung nach dem Befehl einfach:
var log1 = new Logger<Command>(null);
var svr = new CommandService(log);
var startOfChain = new Logger<Command>(svr);
und der Befehl wird ausgeführt von:
var cmd = new Command();
startOfChain.Consume(cmd);
Um dies zum Beispiel in PostSharp zu tun , würde man dies folgendermaßen kommentieren CommandService
:
public class CommandService : IConsumes<Command>
{
[Trace]
public void Consume(Command message)
{
Console.WriteLine("Command complete!");
}
}
Und dann müssen Sie die Protokollierung in einer Attributklasse implementieren, wie etwa:
[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 );
}
}
Das Argument, das Greg verwendet, ist, dass die Verbindung vom Attribut zur Implementierung des Attributs "zu magisch" ist, um erklären zu können, was mit einem Junior-Entwickler geschieht. Das erste Beispiel ist alles "nur Code" und leicht zu erklären.
Nach diesem langwierigen Aufbau lautet die Frage also: Wann stellen Sie den Wechsel von Gregs Nicht-Framework-Ansatz auf die Verwendung von PostSharp für AOP um?
quelle
IConsumes
Teile miteinander verbindet. Anstatt externes XML oder eine Fluent-Schnittstelle verwenden zu müssen - noch eine andere Sache zum Lernen. Man könnte argumentieren, dass diese Methodik auch "eine andere Sache zu lernen" ist.Antworten:
Versucht er, ein AOP-Framework "Straight to TDWTF" zu schreiben? Ich habe immer noch keine Ahnung, worum es ihm ging. Sobald Sie sagen "Alle Methoden müssen genau einen Parameter annehmen", sind Sie gescheitert, nicht wahr? Zu diesem Zeitpunkt sagen Sie, OK, dies bringt einige ernsthafte künstliche Einschränkungen für meine Fähigkeit zum Schreiben von Software mit sich. Lassen Sie uns dies jetzt, drei Monate später, fallen. Wir haben eine komplette Albtraum-Codebasis, mit der wir arbeiten können.
Und weisst du was? Mit Mono.Cecil können Sie ganz einfach ein einfaches, attributgesteuertes, IL-basiertes Protokollierungsframework schreiben . (Testen ist etwas komplizierter, aber ...)
Oh und IMO, wenn Sie keine Attribute verwenden, ist es nicht AOP. Der Sinn und Zweck des Protokolliercodes für die Methodenein- und -ausgabe in der Postprozessor-Phase besteht darin, dass er nicht mit Ihren Codedateien in Konflikt gerät und Sie beim Umgestalten Ihres Codes nicht darüber nachdenken müssen. das ist seine Macht.
Alles, was Greg gezeigt hat, ist, dass es ein dummes, dummes Paradigma gibt.
quelle
let concat (x : string) y = x + y;; concat "Hello, " "World!";;
sieht aus wie es zwei Argumente braucht, was fehle ich?concat "Hello, "
Sie tatsächlich eine Funktion erstellen, die gerechty
ist undx
die als lokale Bindung als "Hallo" vordefiniert wurde. Wenn diese Zwischenfunktion zu sehen wäre, würde sie ungefähr so aussehenlet concat_x y = "Hello, " + y
. Und danach rufst du anconcat_x "World!"
. Die Syntax macht es weniger offensichtlich, aber dadurch können Sie neue Funktionen "backen" - zum Beispiellet printstrln = print "%s\n" ;; printstrln "woof"
. Auch wenn Sie so etwas tunlet f(x,y) = x + y
, ist das eigentlich nur ein Tupelargument .Mein Gott, dieser Typ ist unerträglich aggressiv. Ich wünschte, ich würde nur den Code in Ihrer Frage lesen, anstatt diesen Vortrag zu sehen.
Ich glaube nicht, dass ich diesen Ansatz jemals verwenden würde, wenn es nur um die Verwendung von AOP geht. Greg sagt, es sei gut für einfache Situationen. Folgendes würde ich in einer einfachen Situation tun:
Ja, ich habe es geschafft, ich habe AOP komplett beseitigt! Warum? Weil Sie in einfachen Situationen kein AOP benötigen .
Vom Standpunkt der funktionalen Programmierung aus macht es mir wenig Angst, nur einen Parameter pro Funktion zuzulassen. Trotzdem ist dies wirklich kein Design, das gut mit C # zusammenarbeitet - und wenn Sie sich gegen die Gegebenheiten Ihrer Sprache richten, können Sie nichts KÜSSEN.
Ich würde diesen Ansatz nur verwenden, wenn zunächst ein Befehlsmodell erstellt werden müsste, z. B. wenn ich einen Rückgängig-Stapel benötige oder mit WPF-Befehlen arbeite .
Ansonsten würde ich nur einen Rahmen oder eine Reflexion verwenden. Postsharp funktioniert auch in Silverlight und Compact Framework - so wie er es nennt „Magie“ nicht wirklich magisch ist überhaupt .
Ich bin auch nicht damit einverstanden, Frameworks zu vermeiden, um Junioren die Dinge erklären zu können. Es tut ihnen nicht gut. Wenn Greg seine Junioren so behandelt, wie er es vorschlägt, wie dickschädlige Idioten behandelt zu werden, dann vermute ich, dass seine Senior-Entwickler auch nicht besonders gut sind, da ihnen wahrscheinlich nicht die Gelegenheit gegeben wurde, während ihrer Zeit etwas zu lernen Junior Jahre.
quelle
Ich habe ein unabhängiges College-Studium über AOP absolviert. Ich habe tatsächlich eine Arbeit über einen Ansatz zum Modellieren von AOP mit einem Eclipse-Plug-In geschrieben. Das ist eigentlich etwas irrelevant, nehme ich an. Die wichtigsten Punkte sind: 1) Ich war jung und unerfahren und 2) Ich habe mit AspectJ gearbeitet. Ich kann Ihnen sagen, dass die "Magie" der meisten AOP-Frameworks nicht so kompliziert ist. Ich habe ungefähr zur gleichen Zeit an einem Projekt gearbeitet, bei dem versucht wurde, den Einzelparameter-Ansatz mithilfe einer Hash-Tabelle durchzuführen. IMO, der Single-Parameter-Ansatz ist wirklich ein Framework und ist invasiv. Selbst in diesem Beitrag habe ich mehr Zeit darauf verwendet, den Single-Parameter-Ansatz zu verstehen, als den deklarativen Ansatz zu überprüfen. Ich werde eine Einschränkung hinzufügen, dass ich den Film nicht gesehen habe, sodass die "Magie" dieses Ansatzes in der Verwendung von Teilanwendungen liegen kann.
Ich glaube, Greg hat Ihre Frage beantwortet. Sie sollten zu diesem Ansatz wechseln, wenn Sie glauben, in einer Situation zu sein, in der Sie Ihren Junior-Entwicklern übermäßig viel Zeit damit verbringen, AOP-Frameworks zu erklären. IMO, wenn Sie in diesem Boot sind, stellen Sie wahrscheinlich die falschen Junior-Entwickler ein. Ich glaube nicht, dass AOP einen deklarativen Ansatz erfordert, aber für mich ist es aus gestalterischer Sicht viel klarer und nicht-invasiver.
quelle
IConsume<T>
Beispiel zu kompliziert für das, was erreicht wurde.Sofern mir nichts fehlt, ist der Code, den Sie gezeigt haben, das Entwurfsmuster der Verantwortungskette, das sich hervorragend eignet, wenn Sie eine Reihe von Aktionen für ein Objekt ausführen müssen (z. B. Befehle, die eine Reihe von Befehlshandlern durchlaufen) Laufzeit.
AOP mit PostSharp ist gut, wenn Sie beim Kompilieren wissen, welches Verhalten Sie hinzufügen möchten. Das Code-Weben von PostSharp bedeutet so gut wie, dass der Zeitaufwand für die Ausführung Null ist, und hält den Code in der Tat sehr sauber (insbesondere, wenn Sie anfangen, Dinge wie Multicast-Aspekte zu verwenden). Ich denke nicht, dass die grundlegende Verwendung von PostSharp besonders schwierig zu erklären ist. Der Nachteil von PostSharp ist, dass sich die Kompilierzeiten erheblich verlängern.
Ich verwende beide Techniken im Produktionscode und obwohl es einige Überlappungen gibt, in denen sie angewendet werden können, denke ich, dass sie größtenteils wirklich auf verschiedene Szenarien abzielten.
quelle
In Bezug auf seine Alternative - dort gewesen, das getan. Nichts ist vergleichbar mit der Lesbarkeit eines einzeiligen Attributs.
Geben Sie neuen Leuten einen kurzen Vortrag und erklären Sie ihnen, wie die Dinge in AOP funktionieren.
quelle
Was Greg beschreibt, ist absolut vernünftig. Und auch darin steckt Schönheit. Das Konzept ist in einem anderen Paradigma anwendbar als die reine Objektorientierung. Es ist eher ein prozeduraler Ansatz oder ein Flow-orientierter Design-Ansatz. Wenn Sie also mit Legacy-Code arbeiten, ist die Anwendung dieses Konzepts sehr schwierig, da möglicherweise umfangreiche Umgestaltungen erforderlich sind.
Ich werde versuchen, ein anderes Beispiel zu geben. Vielleicht nicht perfekt, aber ich hoffe, es macht den Punkt klarer.
Wir haben also einen Produktservice, der ein Repository verwendet (in diesem Fall verwenden wir einen Stub). Der Service erhält eine Liste der Produkte.
Natürlich können Sie auch eine Schnittstelle an den Dienst übergeben.
Als nächstes möchten wir eine Liste der Produkte in einer Ansicht anzeigen. Dafür brauchen wir eine Schnittstelle
und einen Befehl, der die Liste der Produkte enthält
und die Aussicht
Jetzt brauchen wir Code, der all dies ausführt. Dies machen wir in einer Klasse namens Application. Die Run () -Methode ist die integrierende Methode, die keine oder nur sehr wenig Geschäftslogik enthält. Die Abhängigkeiten werden als Methoden in den Konstruktor eingefügt.
Zuletzt erstellen wir die Anwendung in der Hauptmethode.
Das Coole ist nun, dass wir Aspekte wie Protokollierung oder Ausnahmebehandlung hinzufügen können, ohne den vorhandenen Code zu berühren und ohne ein Framework oder Anmerkungen. Für die Ausnahmebehandlung fügen wir zB einfach eine neue Klasse hinzu:
Und dann stecken wir es während der Komposition am Einstiegspunkt der Anwendung zusammen. Wir müssen nicht einmal den Code in der Application-Klasse berühren. Wir ersetzen nur eine Zeile:
Um es wieder aufzunehmen: Wenn wir ein flussorientiertes Design haben, können wir Aspekte hinzufügen, indem wir die Funktionalität innerhalb einer neuen Klasse hinzufügen. Dann müssen wir eine Zeile in der Kompositionsmethode ändern und das wars.
Ich denke, eine Antwort auf Ihre Frage ist, dass Sie nicht einfach von einem Ansatz zum anderen wechseln können, sondern sich entscheiden müssen, welchen architektonischen Ansatz Sie in Ihrem Projekt verfolgen.
edit: Eigentlich habe ich gerade gemerkt, dass das Teilanwendungsmuster, das für den Produktservice verwendet wird, die Dinge etwas komplizierter macht. Wir müssen eine weitere Klasse um die Product-Service-Methode wickeln, um auch hier Aspekte hinzufügen zu können. Es könnte so etwas sein:
Die Komposition muss dann folgendermaßen geändert werden:
quelle