Ich habe eine Webanwendung. Ich glaube nicht, dass die Technologie wichtig ist. Die Struktur ist eine N-Tier-Anwendung (siehe Abbildung links). Es gibt 3 Schichten.
Benutzeroberfläche (MVC-Muster), Business Logic Layer (BLL) und Datenzugriffsschicht (DAL)
Das Problem, das ich habe, ist, dass meine BLL massiv ist, da sie die Logik und Pfade für den Aufruf der Anwendungsereignisse aufweist.
Ein typischer Fluss durch die Anwendung könnte sein:
In der Benutzeroberfläche ausgelöstes Ereignis, Übergang zu einer Methode in der BLL, Logik ausführen (möglicherweise in mehreren Teilen der BLL), schließlich zur DAL, zurück zur BLL (wo wahrscheinlich mehr Logik vorhanden ist) und dann einen bestimmten Wert an die Benutzeroberfläche zurückgeben.
Die BLL in diesem Beispiel ist sehr beschäftigt und ich überlege, wie ich das aufteilen kann. Ich habe auch die Logik und die Objekte kombiniert, die ich nicht mag.
Die Version auf der rechten Seite ist meine Anstrengung.
Die Logik ist immer noch der Ablauf der Anwendung zwischen Benutzeroberfläche und DAL, aber es gibt wahrscheinlich keine Eigenschaften ... Nur Methoden (die meisten Klassen in dieser Ebene können möglicherweise statisch sein, da sie keinen Status speichern). In der Poco-Ebene gibt es Klassen mit Eigenschaften (z. B. eine Personenklasse, in der Name, Alter, Größe usw. angegeben sind). Diese haben nichts mit dem Ablauf der Anwendung zu tun, sondern speichern nur den Status.
Der Fluss könnte sein:
Wird sogar über die Benutzeroberfläche ausgelöst und übergibt einige Daten an den UI-Layer-Controller (MVC). Dadurch werden die Rohdaten übersetzt und in das Poco-Modell konvertiert. Das POCO-Modell wird dann an die Logikebene (die die BLL war) und schließlich an die Befehlsabfrageebene übergeben, die möglicherweise unterwegs manipuliert wird. Die Befehlsabfrageebene konvertiert das POCO in ein Datenbankobjekt (das fast dasselbe ist, aber eines ist für die Persistenz vorgesehen, das andere für das Front-End). Das Element wird gespeichert und ein Datenbankobjekt wird an die Befehlsabfrageebene zurückgegeben. Es wird dann in ein POCO konvertiert, wo es zur Logikebene zurückkehrt, möglicherweise weiterverarbeitet wird und schließlich zur Benutzeroberfläche zurückkehrt
In der gemeinsam genutzten Logik und den Schnittstellen sind möglicherweise persistente Daten enthalten, z. B. MaxNumberOf_X und TotalAllowed_X sowie alle Schnittstellen.
Sowohl die gemeinsame Logik / Schnittstelle als auch DAL bilden die "Basis" der Architektur. Diese wissen nichts über die Außenwelt.
Alles andere als die gemeinsame Logik / Schnittstellen und DAL weiß über Poco.
Der Ablauf ist dem ersten Beispiel immer noch sehr ähnlich, aber es hat jede Ebene für eine Sache verantwortlicher gemacht (sei es der Zustand, der Ablauf oder irgendetwas anderes) ... aber breche ich die OOP mit diesem Ansatz?
Ein Beispiel für die Demo von Logic und Poco könnte sein:
public class LogicClass
{
private ICommandQueryObject cmdQuery;
public PocoA Method1(PocoB pocoB)
{
return cmdQuery.Save(pocoB);
}
/*This has no state objects, only ways to communicate with other
layers such as the cmdQuery. Everything else is just function
calls to allow flow via the program */
public PocoA Method2(PocoB pocoB)
{
pocoB.UpdateState("world");
return Method1(pocoB);
}
}
public struct PocoX
{
public string DataA {get;set;}
public int DataB {get;set;}
public int DataC {get;set;}
/*This simply returns something that is part of this class.
Everything is self-contained to this class. It doesn't call
trying to directly communicate with databases etc*/
public int GetValue()
{
return DataB * DataC;
}
/*This simply sets something that is part of this class.
Everything is self-contained to this class.
It doesn't call trying to directly communicate with databases etc*/
public void UpdateState(string input)
{
DataA += input;
}
}
quelle
Antworten:
Ja, Sie brechen sehr wahrscheinlich Kern OOP - Konzepte. Aber fühlen Sie sich nicht schlecht, die Leute tun dies die ganze Zeit, es bedeutet nicht, dass Ihre Architektur "falsch" ist. Ich würde sagen, es ist wahrscheinlich weniger wartbar als ein richtiges OO-Design, aber das ist eher subjektiv und nicht deine Frage. ( Hier ist ein Artikel von mir, der die n-Tier-Architektur im Allgemeinen kritisiert.)
Begründung : Das grundlegendste Konzept von OOP ist, dass Daten und Logik eine einzige Einheit (ein Objekt) bilden. Obwohl dies eine sehr vereinfachende und mechanische Aussage ist, wird sie in Ihrem Entwurf nicht wirklich befolgt (wenn ich Sie richtig verstehe). Sie trennen die meisten Daten ganz klar von den meisten der Logik. Beispielsweise werden zustandslose (statisch-ähnliche) Methoden als "Prozeduren" bezeichnet und sind im Allgemeinen OOP-entgegengesetzt.
Es gibt natürlich immer Ausnahmen, aber dieses Design verstößt in der Regel gegen diese Dinge.
Auch hier möchte ich betonen "verletzt OOP"! = "Falsch", daher ist dies nicht unbedingt ein Werturteil. Es hängt alles von Ihren Architekturbeschränkungen, Wartbarkeitsanwendungsfällen, Anforderungen usw. ab.
quelle
Eines der Kernprinzipien der funktionalen Programmierung sind reine Funktionen.
Eines der Kernprinzipien der objektorientierten Programmierung besteht darin, Funktionen mit den Daten zu kombinieren, auf die sie einwirken.
Beide Grundprinzipien fallen weg, wenn Ihre Anwendung mit der Außenwelt kommunizieren muss. In der Tat können Sie diesen Idealen nur an einem speziell dafür vorgesehenen Ort in Ihrem System treu bleiben. Nicht jede Zeile Ihres Codes muss diese Ideale erfüllen. Aber wenn keine Zeile Ihres Codes diese Ideale erfüllt, können Sie nicht wirklich behaupten, OOP oder FP zu verwenden.
Es ist also in Ordnung, nur "Objekte" mit Daten zu haben, die Sie herumschleudern, da Sie diese benötigen, um eine Grenze zu überschreiten, die Sie einfach nicht umgestalten können, um den interessierten Code zu verschieben. Weiß nur, dass das nicht OOP ist. Das ist die Realität. OOP ist, wenn Sie innerhalb dieser Grenze alle Logik, die auf diese Daten wirkt, an einem Ort sammeln.
Nicht, dass du das auch tun müsstest. OOP ist nicht alles für alle Menschen. Es ist was es ist. Behaupte einfach nicht, dass etwas OOP folgt, wenn dies nicht der Fall ist, oder du wirst Leute verwirren, die versuchen, deinen Code zu pflegen.
Ihre POCOs scheinen eine gute Geschäftslogik zu haben, sodass ich mir keine Sorgen machen müsste, ob ich anämisch bin. Was mich betrifft ist, dass sie alle sehr wandelbar scheinen. Denken Sie daran, dass Getter und Setter keine echte Kapselung bieten. Wenn Ihr POCO auf diese Grenze zusteuert, ist das in Ordnung. Verstehen Sie nur, dass dies nicht die vollen Vorteile eines echten gekapselten OOP-Objekts bietet. Einige nennen dies ein Datenübertragungsobjekt oder DTO.
Ein Trick, den ich erfolgreich angewendet habe, ist das Herstellen von OOP-Objekten, die DTOs fressen. Ich benutze das DTO als Parameterobjekt . Mein Konstruktor liest den Status davon (als defensive Kopie gelesen ) und wirft ihn beiseite. Jetzt habe ich eine vollständig gekapselte und unveränderliche Version des DTO. Alle Methoden, die mit diesen Daten zu tun haben, können hierher verschoben werden, sofern sie sich auf dieser Seite der Grenze befinden.
Ich biete keine Getter oder Setter. Ich folge tell, frag nicht . Sie rufen meine Methoden auf und sie tun, was getan werden muss. Sie erzählen Ihnen wahrscheinlich nicht einmal, was sie getan haben. Sie machen es einfach.
Nun, irgendwann wird irgendwo etwas in eine andere Grenze stoßen und das alles fällt wieder auseinander. Das ist gut. Drehen Sie einen anderen DTO auf und werfen Sie ihn über die Mauer.
Dies ist die Essenz dessen, worum es bei der Architektur von Ports und Adaptern geht. Ich habe darüber aus einer funktionalen Perspektive gelesen . Vielleicht interessiert es dich auch.
quelle
Wenn ich Ihre Erklärung richtig lese, sehen Ihre Objekte ein bisschen so aus: (knifflig ohne Kontext)
Insofern enthalten Ihre Poco-Klassen nur Daten und Ihre Logic-Klassen die Methoden, die auf diese Daten einwirken. ja, du hast die Prinzipien von "Classic OOP" gebrochen
Wiederum ist es schwierig, anhand Ihrer allgemeinen Beschreibung zu sagen, aber ich würde riskieren, dass das, was Sie geschrieben haben, als anämisches Domänenmodell kategorisiert werden könnte.
Ich denke nicht, dass dies ein besonders schlechter Ansatz ist, und wenn Sie Ihre Pocos als Strukturen betrachten, wird der OOP im genaueren Sinne nicht sofort zerstört. Insofern sind Ihre Objekte jetzt die LogicClasses. Wenn Sie Ihren Pocos unveränderlich machen, kann das Design durchaus als funktional angesehen werden.
Wenn Sie jedoch auf Shared Logic, Pocos verweisen, die zwar fast identisch, aber nicht identisch sind, und Statik, mache ich mir langsam Sorgen um die Details Ihres Designs.
quelle
Ein potenzielles Problem, das ich in Ihrem Design gesehen habe (und das sehr häufig vorkommt) - der absolut schlimmste "OO" -Code, auf den ich jemals gestoßen bin, wurde durch eine Architektur verursacht, die "Data" -Objekte von "Code" -Objekten trennte. Das ist alptraumhaftes Zeug! Das Problem ist, dass Sie überall in Ihrem Geschäftscode, wo Sie auf Ihre Datenobjekte zugreifen möchten, dazu neigen, sie direkt in der Zeile zu codieren (Sie müssen nicht, Sie könnten eine Utility-Klasse oder eine andere Funktion erstellen, um damit umzugehen, aber genau das ist es Ich habe im Laufe der Zeit immer wieder gesehen).
Der Zugriffs- / Aktualisierungscode wird im Allgemeinen nicht erfasst, sodass Sie überall doppelte Funktionen erhalten.
Andererseits sind diese Datenobjekte beispielsweise als Datenbankpersistenz nützlich. Ich habe drei Lösungen ausprobiert:
Das Kopieren von Werten in "echte" Objekte und das Wegwerfen Ihres Datenobjekts ist mühsam (kann aber eine gültige Lösung sein, wenn Sie diesen Weg einschlagen möchten).
Das Hinzufügen von Daten-Wrangling-Methoden zu den Datenobjekten kann funktionieren, aber es kann zu einem großen chaotischen Datenobjekt führen, das mehr als eine Aufgabe erfüllt. Es kann auch die Kapselung erschweren, da viele Persistenzmechanismen öffentliche Zugriffsmechanismen benötigen ... Ich habe es nicht geliebt, als ich es getan habe, aber es ist eine gültige Lösung
Die Lösung, die sich für mich am besten bewährt hat, ist das Konzept einer "Wrapper" -Klasse, die die "Data" -Klasse kapselt und alle Funktionen zum Verwirren von Daten enthält - dann mache ich die Datenklasse überhaupt nicht verfügbar (nicht einmal Setter und Getter) es sei denn, sie werden unbedingt benötigt). Dies beseitigt die Versuchung, das Objekt direkt zu manipulieren, und zwingt Sie, dem Wrapper stattdessen gemeinsame Funktionen hinzuzufügen.
Der andere Vorteil ist, dass Sie sicherstellen können, dass sich Ihre Datenklasse immer in einem gültigen Zustand befindet. Hier ist ein kurzes Pseudocode-Beispiel:
Beachten Sie, dass die Altersüberprüfung in Ihrem Code nicht auf verschiedene Bereiche verteilt ist und dass Sie auch nicht versucht sind, sie zu verwenden, da Sie nicht einmal herausfinden können, was der Geburtstag ist (es sei denn, Sie benötigen sie für etwas anderes) Welchen Fall können Sie hinzufügen).
Ich neige dazu, das Datenobjekt nicht nur zu erweitern, weil Sie diese Kapselung und die Sicherheitsgarantie verlieren - an diesem Punkt können Sie die Methoden auch einfach der Datenklasse hinzufügen.
Auf diese Weise ist in Ihrer Geschäftslogik kein Haufen Junk / Iteratoren für den Datenzugriff verteilt, sondern sie ist viel besser lesbar und weniger redundant. Ich empfehle auch, sich daran zu gewöhnen, Sammlungen immer aus dem gleichen Grund zu verpacken - indem ich Konstrukte aus Ihrer Geschäftslogik heraushalte und sicherstelle, dass sie immer in einem guten Zustand sind.
quelle
Ändern Sie niemals Ihren Code, weil Sie denken oder jemand Ihnen sagt, dass es nicht das oder nicht das ist. Ändern Sie Ihren Code, wenn es Probleme gibt und Sie einen Weg gefunden haben, diese Probleme zu vermeiden, ohne andere zu erstellen.
Abgesehen davon, dass Sie Dinge nicht mögen, möchten Sie viel Zeit investieren, um Änderungen vorzunehmen. Notieren Sie die Probleme, die Sie gerade haben. Schreiben Sie auf, wie Ihr neues Design die Probleme lösen würde. Ermitteln Sie den Wert der Verbesserung und die Kosten für die Durchführung Ihrer Änderungen. Stellen Sie dann - und das ist am wichtigsten - sicher, dass Sie die Zeit haben, diese Änderungen abzuschließen, oder Sie werden halb in diesem Zustand, halb in diesem Zustand enden, und das ist die schlimmste mögliche Situation. (Ich habe einmal an einem Projekt mit 13 verschiedenen Arten von Zeichenfolgen gearbeitet und drei identifizierbare halbherzige Anstrengungen unternommen, um auf eine Art zu standardisieren.)
quelle
Die Kategorie "OOP" ist viel größer und abstrakter als das, was Sie beschreiben. Das alles kümmert es nicht. Es geht um klare Verantwortung, Zusammenhalt, Kopplung. Auf der Ebene, die Sie anfragen, ist es also nicht sinnvoll, nach der "OOPS-Praxis" zu fragen.
Das heißt, zu Ihrem Beispiel:
Mir scheint, dass es ein Missverständnis darüber gibt, was MVC bedeutet. Sie nennen Ihre Benutzeroberfläche "MVC", getrennt von Ihrer Geschäftslogik und "Back-End" -Steuerung. Aber für mich beinhaltet MVC die gesamte Webanwendung:
Hier gibt es einige außerordentlich wichtige Grundannahmen:
Wichtig: Die Benutzeroberfläche ist Teil von MVC. Nicht umgekehrt (wie in Ihrem Diagramm). Wenn Sie das akzeptieren, dann sind fette Models eigentlich ziemlich gut - vorausgesetzt, sie enthalten tatsächlich nichts, was sie nicht sollten.
Beachten Sie, dass "Fat Models" bedeutet, dass sich die gesamte Geschäftslogik in der Kategorie "Model" befindet (Paket, Modul, unabhängig vom Namen in der Sprache Ihrer Wahl). Einzelne Klassen sollten offensichtlich gemäß den von Ihnen selbst festgelegten Codierungsrichtlinien (z. B. einige maximale Codezeilen pro Klasse oder pro Methode usw.) OOP-strukturiert sein.
Beachten Sie auch, dass die Implementierung der Datenschicht sehr wichtige Konsequenzen hat. Insbesondere, ob die Modellschicht ohne Datenschicht funktionieren kann (z. B. für Komponententests oder für billige In-Memory-DBs auf dem Entwickler-Laptop anstelle von teuren Oracle-DBs oder was auch immer Sie haben). Dies ist jedoch wirklich ein Implementierungsdetail auf der Ebene der Architektur, auf der wir uns gerade befinden. Offensichtlich möchten Sie hier noch eine Trennung haben, dh ich möchte keinen Code sehen, der eine reine Domänenlogik hat, die direkt mit dem Datenzugriff verschachtelt ist, und dies intensiv miteinander koppelt. Ein Thema für eine andere Frage.
Um auf Ihre Frage zurückzukommen: Es scheint mir, dass es eine große Überschneidung zwischen Ihrer neuen Architektur und dem von mir beschriebenen MVC-Schema gibt, sodass Sie nicht auf einem völlig falschen Weg sind, aber Sie scheinen entweder einige Dinge neu zu erfinden, oder verwenden Sie es, weil Ihre aktuelle Programmierumgebung / Bibliotheken dies vorschlagen. Schwer zu sagen für mich. Daher kann ich Ihnen keine genaue Antwort geben, ob das, was Sie beabsichtigen, besonders gut oder schlecht ist. Sie können herausfinden, ob für jedes einzelne "Ding" genau eine Klasse verantwortlich ist. ob alles sehr kohäsiv und niedrig gekoppelt ist. Das gibt Ihnen einen guten Anhaltspunkt und ist meiner Meinung nach genug für ein gutes OOP-Design (oder, wenn Sie so wollen, einen guten Maßstab dafür).
quelle