Wie viel Logik in Gettern

46

Meine Kollegen sagen mir, dass es bei Gettern und Setzern so wenig Logik wie möglich geben sollte.

Ich bin jedoch davon überzeugt, dass viele Dinge in Gettern und Setzern verborgen sein können, um Benutzer / Programmierer vor Implementierungsdetails zu schützen.

Ein Beispiel von dem, was ich tue:

public List<Stuff> getStuff()
{
   if (stuff == null || cacheInvalid())
   {
       stuff = getStuffFromDatabase();
   }
   return stuff;
}

Ein Beispiel, wie mir die Arbeit sagt, Dinge zu tun (sie zitieren 'Clean Code' von Onkel Bob):

public List<Stuff> getStuff()
{
    return stuff;
}

public void loadStuff()
{
    stuff = getStuffFromDatabase();
}

Wie viel Logik ist in einem Setter / Getter angemessen? Was nützt ein leerer Getter und Setter außer einer Verletzung des Datenversteckens?

Maarten van Leunen
quelle
6
Das sieht für mich eher nach tryGetStuff () aus ...
Bill Michell
16
Dies ist kein Getter. Dieser Begriff wird für den Lesezugriff auf eine Eigenschaft verwendet und nicht für eine Methode, die Sie versehentlich mit einem 'get' im Namen versehen.
Boris Yankov
6
Ich weiß nicht, ob dieses zweite Beispiel ein gutes Beispiel für dieses saubere Codebuch ist, das Sie erwähnen, oder ob jemand das falsche Ende des Strichs dafür gefunden hat, aber eine Sache, die nicht so spröde ist, ist sauberer Code.
Jon Hanna
@BorisYankov Nun ... die zweite Methode ist. public List<Stuff> getStuff() { return stuff; }
R. Schmitz
Abhängig vom genauen Anwendungsfall teile ich mein Caching gerne in eine separate Klasse auf. Erstellen Sie eine StuffGetterSchnittstelle, implementieren Sie ein, StuffComputerdas die Berechnungen ausführt, und verpacken Sie es in ein Objekt von StuffCacher, das entweder für den Zugriff auf den Cache oder für die Weiterleitung von Aufrufen an das zu verpackende Objekt verantwortlich ist StuffComputer.
Alexander

Antworten:

71

Die Art und Weise, wie die Arbeit Ihnen sagt, Dinge zu tun, ist lahm.

Als Faustregel gilt: Wenn das Abrufen des Materials rechenintensiv ist (oder wenn die meisten Chancen bestehen, dass es im Cache gefunden wird), ist Ihr Stil von getStuff () in Ordnung. Wenn bekannt ist, dass das Abrufen des Zeugs rechenintensiv und so teuer ist, dass Werbung für dessen Kosten an der Schnittstelle erforderlich ist, würde ich es nicht getStuff (), berechneStuff () oder ähnliches nennen, um dies anzuzeigen es wird etwas zu tun geben.

In beiden Fällen ist die Art und Weise, in der Ihnen die Arbeit vorschreibt, lahm, da getStuff () explodiert, wenn loadStuff () nicht im Voraus aufgerufen wurde. Daher möchten sie im Wesentlichen, dass Sie Ihre Schnittstelle komplizieren, indem Sie die Komplexität der Reihenfolge einführen dazu. Order-of-Operations handelt so ziemlich von der schlimmsten Komplexität, die ich mir vorstellen kann.

Mike Nakis
quelle
23
+1 für die Angabe der Komplexität der Reihenfolge der Vorgänge. Um das Problem zu umgehen, muss ich bei der Arbeit möglicherweise immer loadStuff () im Konstruktor aufrufen, aber das wäre auch schlecht, da es bedeutet, dass es immer geladen werden muss. Im ersten Beispiel werden Daten nur dann träge geladen, wenn sie benötigt werden. Das ist so gut wie es nur geht.
Laurent
6
Normalerweise befolge ich die Regel: "Wenn es wirklich billig ist, benutze einen Property Getter. Wenn es teuer ist, benutze eine Funktion." Das tut mir in der Regel gut, und es kommt mir auch gut vor, wenn ich es so nenne, wie Sie es angedeutet haben.
Denis Troller
3
Wenn es scheitern kann - ist es kein Getter. Was ist in diesem Fall, wenn die DB-Verbindung unterbrochen ist?
Martin Beckett
6
+1, ich bin ein bisschen geschockt darüber, wie viele falsche Antworten gepostet wurden. Getter / Setter dienen zum Ausblenden von Implementierungsdetails, andernfalls sollte die Variable nur veröffentlicht werden.
Izkata
2
Vergessen Sie nicht, dass das Aufrufen der loadStuff()Funktion vor der getStuff()Funktion auch bedeutet, dass die Klasse nicht ordnungsgemäß abstrahiert, was unter der Haube vor sich geht.
rjzii
23

Logik in Gettern ist vollkommen in Ordnung.

Das Abrufen von Daten aus einer Datenbank ist jedoch weit mehr als "Logik". Es handelt sich um eine Reihe sehr teurer Operationen, bei denen viele Dinge schief gehen können, und zwar auf nicht deterministische Weise. Ich würde zögern, das implizit in einem Getter zu tun.

Andererseits unterstützen die meisten ORMs das verzögerte Laden von Sammlungen, was im Grunde genau das ist, was Sie tun.

Michael Borgwardt
quelle
18

Ich denke, dass es laut 'Clean Code' so weit wie möglich aufgeteilt werden sollte, in so etwas wie:

public List<Stuff> getStuff() {
   if (hasStuff()) {
       return stuff;
   }
   loadStuff();
   return stuff;
}

private boolean hasStuff() {
    if (stuff == null) {
       return false;
    }
    if (cacheInvalid()) {
       return false;        
    }
    return true;
} 

private void loadStuff() {
    stuff = getStuffFromDatabase();
}

Dies ist natürlich völliger Unsinn, da die schöne Form, die Sie geschrieben haben, mit einem Bruchteil Code, den jeder auf einen Blick versteht, das Richtige tut:

public List<Stuff> getStuff() {
   if (stuff == null || cacheInvalid()) {
       stuff = getStuffFromDatabase();
   }
   return stuff;
}

Es sollte nicht die Kopfschmerzen des Anrufers sein, wie das Zeug unter die Haube kommt, und insbesondere sollte es nicht die Kopfschmerzen des Anrufers sein, daran zu denken, Dinge in einer willkürlichen "richtigen Reihenfolge" anzurufen.

Joonas Pulakka
quelle
8
-1. Die wirklichen Kopfschmerzen werden auftreten, wenn der Anrufer feststeckt und herausfindet, warum ein einfacher Getter-Aufruf zu einem langsamen Datenbankzugriff geführt hat.
Domenic
14
@Domenic: Der Datenbankzugriff muss auf jeden Fall erfolgen. Sie sparen niemandem Leistung, wenn Sie dies nicht tun. Wenn Sie das brauchen List<Stuff>, gibt es nur einen Weg, es zu bekommen.
DeadMG
4
@lukas: Danke, ich habe mich nicht an alle Tricks erinnert, die in 'Clean' Code verwendet wurden, um triviale Code-Teile noch eine Zeile länger zu machen ;-) Jetzt behoben.
Joonas Pulakka
2
Sie verleumden Robert Martin. Er würde niemals eine einfache boolesche Disjunktion in eine Funktion mit neun Zeilen erweitern. Ihre Funktion hasStuffist das Gegenteil von sauberem Code.
Kevin Cline
2
Ich las den Anfang dieser Antwort und wollte sie umgehen, weil ich dachte, "da geht noch ein Buchanbeter", und dann fiel mir der Teil "Natürlich, das ist völliger Unsinn" auf. Gut gesagt! C -: =
Mike Nakis
8

Sie sagen mir, dass es bei Gettern und Setzern so wenig Logik wie möglich geben sollte.

Es muss so viel Logik geben, wie nötig ist, um die Bedürfnisse der Klasse zu erfüllen. Ich persönlich bevorzuge so wenig wie möglich, aber wenn Sie Code verwalten, müssen Sie normalerweise die ursprüngliche Schnittstelle mit den vorhandenen Gettern / Setzern belassen, aber viel Logik in diese einfügen, um neuere Geschäftslogik zu korrigieren (zum Beispiel ein "Kunde") "Getter in einer Post-911-Umgebung müssen die" know your customer "- und OFAC- Vorschriften erfüllen, kombiniert mit einer Firmenrichtlinie , die das Erscheinen von Kunden aus bestimmten Ländern (wie Kuba oder Iran) verbietet."

In Ihrem Beispiel bevorzuge ich Ihr Beispiel und mag das Beispiel "Onkel Bob" nicht, da Benutzer / Betreuer in der Version "Onkel Bob" daran denken müssen, loadStuff()vor dem Anruf anzurufen getStuff()- dies ist ein Rezept für eine Katastrophe, wenn einer Ihrer Betreuer vergisst (oder schlimmer, wusste es nie). Die meisten Orte, an denen ich in den letzten zehn Jahren gearbeitet habe, verwenden immer noch Code, der mehr als ein Jahrzehnt alt ist. Daher ist eine einfache Wartung ein entscheidender Faktor.

Tangurena
quelle
6

Sie haben Recht, Ihre Kollegen liegen falsch.

Vergessen Sie die Faustregeln aller, was eine get-Methode tun oder nicht tun sollte. Eine Klasse sollte eine Abstraktion von etwas darstellen. Ihre Klasse ist lesbar stuff. In Java ist es üblich, 'get'-Methoden zum Lesen von Eigenschaften zu verwenden. Es wurden Milliarden von Framework-Zeilen geschrieben, die das Lesen stuffdurch Aufrufen erwarten getStuff. Wenn Sie Ihre Funktion fetchStuffoder etwas anderes benennen getStuff, ist Ihre Klasse mit all diesen Frameworks nicht kompatibel.

Sie können sie auf Hibernate verweisen, wo 'getStuff ()' einige sehr komplizierte Dinge ausführen kann und bei einem Fehler eine RuntimeException auslöst.

Kevin Cline
quelle
Der Ruhezustand ist ein ORM, daher drückt das Paket selbst die Absicht aus. Diese Absicht ist nicht so leicht zu verstehen, wenn das Paket selbst kein ORM ist.
FMJaguar
@FMJaguar: Es ist leicht zu verstehen. Ruhezustand abstrahiert Datenbankoperationen, um ein Netzwerk von Objekten darzustellen. Das OP abstrahiert eine Datenbankoperation, um ein Objekt mit einer benannten Eigenschaft darzustellen stuff. Beide verbergen Details, um das Schreiben des aufrufenden Codes zu vereinfachen.
Kevin Cline
Wenn diese Klasse eine ORM-Klasse ist, wird die Absicht bereits in anderen Zusammenhängen ausgedrückt: Die Frage bleibt: "Woher kennt ein anderer Programmierer die Nebenwirkungen des Aufrufs des Getter?". Wenn das Programm 1k-Klassen und 10k-Getter enthält, kann eine Richtlinie, die Datenbankaufrufe in einer dieser Klassen zulässt, Probleme verursachen
FMJaguar,
4

Klingt so, als würde es sich um eine Debatte zwischen Purismus und Anwendung handeln, die davon abhängt, wie Sie die Funktionsnamen steuern möchten. Vom angewandten Standpunkt aus würde ich viel lieber sehen:

List<String> names = clientRoster.getNames();
List<String> emails = clientRoster.getEmails();

Im Gegensatz zu:

myObject.load();
List<String> names = clientRoster.getNames();
List<String> emails = clientRoster.getEmails();

Oder noch schlimmer:

myObject.loadNames();
List<String> names = clientRoster.getNames();
myOjbect.loadEmails();
List<String> emails = clientRoster.getEmails();

Dies führt dazu, dass anderer Code redundanter und schwerer zu lesen ist, da Sie alle ähnlichen Aufrufe durchlaufen müssen. Darüber hinaus unterbricht der Aufruf von Loader-Funktionen oder Ähnlichem den gesamten Zweck der Verwendung von OOP, da Sie nicht mehr von den Implementierungsdetails des Objekts, mit dem Sie arbeiten, abstrahiert werden. Wenn Sie ein clientRosterObjekt haben, sollten Sie sich nicht darum kümmern müssen, wie es getNamesfunktioniert, wie wenn Sie a anrufen loadNamesmüssten. Sie sollten nur wissen, dass getNamesSie a List<String>mit den Namen der Kunden erhalten.

Es hört sich also so an, als würde sich das Problem mehr um die Semantik und den besten Namen für die Funktion zum Abrufen der Daten handeln. Wenn die Firma (und andere) ein Problem mit dem Präfix getund hat set, wie wäre es dann, wenn Sie retrieveNamesstattdessen die Funktion aufrufen ? Hier steht, was gerade passiert, aber es bedeutet nicht, dass die Operation augenblicklich erfolgt, wie es von einer getMethode zu erwarten ist .

Halten Sie die Logik in einer Accessor-Methode auf ein Minimum, da sie im Allgemeinen als augenblicklich angesehen werden und nur eine nominelle Interaktion mit der Variablen auftritt. Dies gilt jedoch auch im Allgemeinen nur für einfache Typen, komplexe Datentypen (z. B. List), deren ordnungsgemäße Kapselung in einer Eigenschaft für mich schwieriger ist und die im Allgemeinen andere Methoden für die Interaktion mit ihnen verwenden als ein strenger Mutator und Accessor.

rjzii
quelle
2

Das Aufrufen eines Getters sollte dasselbe Verhalten aufweisen wie das Lesen eines Feldes:

  • Es sollte billig sein, den Wert abzurufen
  • Wenn Sie einen Wert mit dem Setter setzen und ihn dann mit dem Getter lesen, sollte der Wert der gleiche sein
  • Das Erhalten des Wertes sollte keine Nebenwirkungen haben
  • Es sollte keine Ausnahme auslösen
Sjoerd
quelle
2
Dem stimme ich nicht ganz zu. Ich stimme zu, dass es keine Nebenwirkungen haben sollte, aber ich denke, es ist vollkommen in Ordnung, es so zu implementieren, dass es sich von einem Feld unterscheidet. Mit Blick auf die .Net-BCL wird InvalidOperationException häufig beim Betrachten von Gettern verwendet. Siehe auch die Antwort von MikeNakis zur Reihenfolge der Operationen.
Max
Stimmen Sie allen Punkten außer dem letzten zu. Es ist sicherlich möglich, dass zum Abrufen eines Werts eine Berechnung oder eine andere Operation erforderlich ist, die von anderen Werten oder Ressourcen abhängt, die möglicherweise nicht festgelegt wurden. In diesen Fällen würde ich erwarten, dass der Getter eine Art Ausnahme auslöst.
TMN
1
@TMN: Im besten Fall sollte die Klasse so organisiert sein, dass Getter keine Operationen ausführen müssen, die Ausnahmen auslösen können. Das Minimieren der Stellen, die Ausnahmen auslösen können, führt zu weniger unerwarteten Überraschungen.
hugomg
8
Ich werde mit dem zweiten Punkt mit einem bestimmten Beispiel anderer Meinung zu sein: foo.setAngle(361); bar = foo.getAngle(). barkönnte sein 361, aber es könnte auch legitim sein, 1wenn Winkel an einen Bereich gebunden sind.
zzzzBov
1
-1. (1) ist in diesem Beispiel günstig - nach dem faulen Laden. (2) zur Zeit gibt es keine „Setter“ in dem Beispiel, aber wenn jemand eine nach fügt hinzu, und es nur Sätze stuff, die Getter werden den gleichen Wert zurück. (3) Das langsame Laden, wie im Beispiel gezeigt, erzeugt keine "sichtbaren" Nebenwirkungen. (4) ist umstritten, vielleicht ein gültiger Punkt, da die Einführung des "Lazy Loading" später den früheren API-Vertrag ändern kann - aber man muss sich diesen Vertrag ansehen, um eine Entscheidung zu treffen.
Doc Brown
2

Ein Getter, der andere Eigenschaften und Methoden aufruft, um seinen eigenen Wert zu berechnen, impliziert auch eine Abhängigkeit. Wenn Ihre Eigenschaft beispielsweise in der Lage sein muss, sich selbst zu berechnen, und dafür ein anderes Mitglied festgelegt werden muss, müssen Sie sich über versehentliche Nullverweise Gedanken machen, wenn auf Ihre Eigenschaft im Initialisierungscode zugegriffen wird, in dem nicht unbedingt alle Mitglieder festgelegt sind.

Das bedeutet nicht, dass Sie niemals auf ein anderes Mitglied zugreifen, das nicht das Eigenschafts-Backing-Feld innerhalb des Getters ist. Es bedeutet nur, dass Sie darauf achten, was Sie über den erforderlichen Status des Objekts implizieren und ob dieser dem von Ihnen erwarteten Kontext entspricht Diese Eigenschaft in zugegriffen werden.

In den beiden konkreten Beispielen, die Sie gegeben haben, ist der Grund, warum ich eines vor dem anderen wählen würde, völlig anders. Ihr Getter wird beim ersten Zugriff initialisiert, z . B. Lazy Initialization . Es wird angenommen, dass das zweite Beispiel zu einem früheren Zeitpunkt initialisiert wird, z . B. Explizite Initialisierung .

Wann genau die Initialisierung erfolgt, kann wichtig sein oder auch nicht.

Beispielsweise kann es sehr sehr langsam sein und muss während eines Ladeschritts ausgeführt werden, bei dem der Benutzer eine Verzögerung erwartet, anstatt dass die Leistung unerwartet nachlässt, wenn der Benutzer zum ersten Mal den Zugriff auslöst (dh Benutzer klickt mit der rechten Maustaste, Kontextmenü wird angezeigt, Benutzer) hat schon wieder rechts geklickt).

Manchmal gibt es auch einen offensichtlichen Punkt bei der Ausführung, an dem alles auftritt, was den zwischengespeicherten Eigenschaftswert beeinflussen / verschmutzen kann. Möglicherweise überprüfen Sie sogar, ob sich keine der Abhängigkeiten ändert, und lösen später Ausnahmen aus. In dieser Situation ist es sinnvoll, den Wert an diesem Punkt auch zwischenzuspeichern, auch wenn die Berechnung nicht besonders teuer ist, nur um zu vermeiden, dass die Codeausführung komplexer und mental schwerer nachzuvollziehen ist.

Trotzdem ist Lazy Initialization in vielen anderen Situationen sehr sinnvoll. Wie es bei der Programmierung oft vorkommt, ist es schwierig, sich auf eine Regel zu beschränken, und es kommt auf den konkreten Code an.

James
quelle
0

Mach es einfach so, wie @MikeNakis gesagt hat ... Wenn du nur das Zeug bekommst, ist es in Ordnung ... Wenn du etwas anderes machst, erstelle eine neue Funktion, die den Job macht und mache sie öffentlich.

Wenn Ihre Immobilie / Funktion nur das tut, was der Name sagt, ist nicht mehr viel Platz für Komplikationen. Kohäsion ist der Schlüssel IMO

Ivan Crojach Karačić
quelle
1
Seien Sie vorsichtig, Sie können zu viel von Ihrem inneren Zustand bloßstellen. Sie möchten nicht mit vielen leeren loadFoo()oder preloadDummyReferences()oder createDefaultValuesForUninitializedFields()Methoden fertig werden, nur weil die anfängliche Implementierung Ihrer Klasse diese benötigt.
TMN
Klar ... Ich habe nur gesagt, wenn Sie tun, was der Name besagt, dass es nicht viele Probleme geben sollte ... aber was Sie sagen, ist absolut wahr ...
Ivan Crojach Karačić
0

Persönlich würde ich die Anforderung von Stuff über einen Parameter im Konstruktor offenlegen und jeder Klasse erlauben, Dinge zu instanziieren, um herauszufinden, woher sie kommen sollten. Wenn stuff null ist, sollte es null zurückgeben. Ich bevorzuge es, keine cleveren Lösungen wie das Original des OP zu versuchen, weil es eine einfache Möglichkeit ist, Fehler tief in Ihrer Implementierung zu verbergen, bei denen es nicht offensichtlich ist, was möglicherweise schief geht, wenn etwas kaputt geht.

EricBoersma
quelle
0

Es gibt hier wichtigere Themen als nur "Angemessenheit" und Sie sollten Ihre Entscheidung auf diese stützen . Vor allem ist die große Entscheidung hier, ob Sie den Leuten erlauben möchten, den Cache zu umgehen oder nicht.

  1. Überlegen Sie zunächst, ob es eine Möglichkeit gibt, den Code so zu reorganisieren, dass alle erforderlichen Ladeaufrufe und die Cache-Verwaltung im Konstruktor / Initialisierer ausgeführt werden. Wenn dies möglich ist, können Sie eine Klasse erstellen, deren Invariante es Ihnen ermöglicht, den einfachen Getter aus Teil 2 mit der Sicherheit des komplexen Getters aus Teil 1 zu bearbeiten. (Ein Win-Win-Szenario)

  2. Wenn Sie eine solche Klasse nicht erstellen können, entscheiden Sie, ob Sie einen Kompromiss eingehen und ob Sie dem Konsumenten erlauben möchten, den Code für die Cache-Überprüfung zu überspringen oder nicht.

    1. Wenn es wichtig ist, dass der Konsument die Cache-Überprüfung nie überspringt und Sie die Leistungsnachteile nicht stören, koppeln Sie die Überprüfung im Getter und machen es dem Konsumenten unmöglich, das Falsche zu tun.

    2. Wenn es in Ordnung ist, die Cache-Überprüfung zu überspringen, oder es sehr wichtig ist, dass Sie eine garantierte O (1) -Leistung im Getter haben, verwenden Sie separate Aufrufe.

Wie Sie vielleicht schon bemerkt haben, bin ich kein großer Fan der "Clean-Code" -Philosophie "Alles in winzige Funktionen aufteilen". Wenn Sie eine Reihe von orthogonalen Funktionen haben, die in beliebiger Reihenfolge aufgerufen werden können, erhalten Sie mit geringem Aufwand mehr Ausdruckskraft. Wenn Ihre Funktionen jedoch Auftragsabhängigkeiten aufweisen (oder nur in einer bestimmten Reihenfolge wirklich nützlich sind), erhöht das Aufteilen der Funktionen nur die Anzahl der Möglichkeiten, mit denen Sie Falsches tun können, und bietet nur geringen Nutzen.

hugomg
quelle
-1, Konstruktoren sollten konstruieren, nicht initialisieren. Wenn Sie die Datenbanklogik in einen Konstruktor einfügen, kann diese Klasse nicht mehr getestet werden, und wenn Sie mehr als eine Handvoll davon haben, wird die Startzeit Ihrer Anwendung immens. Und das ist nur für den Anfang.
Domenic
@Domenic: Dies ist ein semantisches und sprachabhängiges Problem. Der Punkt, an dem ein Objekt für die Verwendung geeignet ist und die entsprechenden Invarianten bereitstellt, nachdem es vollständig erstellt wurde.
23.
0

Meiner Meinung nach sollte Getters nicht viel Logik beinhalten. Sie sollten keine Nebenwirkungen haben und Sie sollten niemals eine Ausnahme von ihnen bekommen. Es sei denn natürlich, Sie wissen, was Sie tun. Die meisten meiner Getter haben keine Logik und gehen einfach auf ein Feld. Die bemerkenswerte Ausnahme war jedoch eine öffentliche API, deren Verwendung so einfach wie möglich sein sollte. Ich hatte also einen Getter, der scheitern würde, wenn kein anderer Getter aufgerufen worden wäre. Die Lösung? Eine Codezeile wie var throwaway=MyGetter;im Getter, die davon abhing. Ich bin nicht stolz darauf, aber ich sehe immer noch keinen saubereren Weg, es zu tun

Earlz
quelle
0

Dies sieht aus wie ein Lesevorgang aus dem Cache mit verzögertem Laden. Wie andere angemerkt haben, kann das Prüfen und Laden auf andere Weise erfolgen. Das Laden muss möglicherweise synchronisiert werden, damit nicht zwanzig Threads gleichzeitig geladen werden.

Es kann angebracht sein, einen Namen getCachedStuff()für den Getter zu verwenden, da er keine konsistente Ausführungszeit hat.

Abhängig von der Funktionsweise der cacheInvalid()Routine ist die Überprüfung auf Null möglicherweise nicht erforderlich. Ich würde nicht erwarten, dass der Cache gültig ist, wenn er stuffnicht aus der Datenbank ausgefüllt wurde.

BillThor
quelle
0

Die Hauptlogik, die ich bei Gettern erwarten würde, die eine Liste zurückgeben, ist Logik, um sicherzustellen, dass die Liste nicht modifizierbar ist. So wie es aussieht, können beide Beispiele die Kapselung unterbrechen.

so etwas wie:

public List<Stuff> getStuff()
{
    return Collections.unmodifiableList(stuff);
}

Was das Cachen im Getter angeht, denke ich, dass dies in Ordnung wäre, aber ich könnte versucht sein, die Cache-Logik zu verschieben, wenn das Erstellen des Caches eine erhebliche Zeit in Anspruch nimmt. dh es kommt darauf an.

jk.
quelle
0

Abhängig vom genauen Anwendungsfall teile ich mein Caching gerne in eine separate Klasse auf. Erstellen Sie eine StuffGetterSchnittstelle, implementieren Sie ein, StuffComputerdas die Berechnungen ausführt, und verpacken Sie es in ein Objekt von StuffCacher, das entweder für den Zugriff auf den Cache oder für die Weiterleitung von Aufrufen an das zu verpackende Objekt verantwortlich ist StuffComputer.

interface StuffGetter {
     public List<Stuff> getStuff();
}

class StuffComputer implements StuffGetter {
     public List<Stuff> getStuff() {
         getStuffFromDatabase()
     }
}

class StuffCacher implements StuffGetter {
     private stuffComputer; // DI this
     private Cache<List<Stuff>> cache = new Cache<>();

     public List<Stuff> getStuff() {
         if cache.hasStuff() {
             return cache.getStuff();
         }

         List<Stuffs> stuffs = stuffComputer.getStuff();
         cache.store(stuffs);
         return stuffs;
     }
}

Mit diesem Design können Sie Caching auf einfache Weise hinzufügen, Caching entfernen, die zugrunde liegende Ableitungslogik ändern (z. B. Zugriff auf eine Datenbank im Vergleich zur Rückgabe von Scheindaten) usw. Es ist etwas wortreich, aber für ausreichend fortgeschrittene Projekte lohnt es sich.

Alexander
quelle
-1

IMHO ist es sehr einfach, wenn Sie ein Design von Vertrag verwenden. Entscheiden Sie, was Ihr Getter bereitstellen soll, und codieren Sie entsprechend (einfacher Code oder eine komplexe Logik, die möglicherweise beteiligt oder an eine andere Stelle delegiert ist).

Kemoda
quelle
+1: Ich stimme dir zu! Wenn das Objekt nur Daten enthalten soll, sollten die Getter nur den aktuellen Inhalt des Objekts zurückgeben. In diesem Fall liegt es in der Verantwortung eines anderen Objekts, die Daten zu laden. Wenn der Vertrag besagt, dass das Objekt der Proxy eines Datenbankdatensatzes ist, sollte der Getter die Daten sofort abrufen. Noch komplizierter kann es werden, wenn die Daten geladen, aber nicht aktuell sind: Soll das Objekt über Änderungen in der Datenbank informiert werden? Ich denke, es gibt keine eindeutige Antwort auf diese Frage.
Giorgio