[JAVA] Was ist es? ~ 3 Arten von "Nein" ~

Was ist es?

Ich denke, es ist notwendig, beim Schreiben von Code in der Praxis viel "nichts" zu berücksichtigen, beispielsweise, dass der Wert in einer bestimmten Situation nicht vorhanden ist oder das Ergebnis nicht erhalten wird, obwohl auf die Datenbank verwiesen wird. Schließlich gibt es viele Techniken, um Dinge auszudrücken, die nicht existieren, wie z. B. "null", "optional" und "nullObject pattern".

Ich möchte zusammenfassen, was ich über den Umgang damit denke.

Müde vom Hören?

Klar, ich liebe "Optional" und "Entweder", aber dieses Mal spreche ich nicht über Techniken.

Selbst wenn ich in einem Wort "Nein" sage, habe ich in letzter Zeit das Gefühl, dass es einige Typen gibt. Dieses Mal möchte ich die Arten von "nichts" zusammenfassen.

Was sind die Vorteile?

Es ist nur eine "Klassifizierung, die ich an dieser Stelle in Betracht gezogen habe", aber sie macht den Verantwortungsbereich für jedes Paket klar. Wir glauben auch, dass es einfacher sein wird, einem Design näher zu kommen, das ordnungsgemäße Unit-Tests ermöglicht, und die Logik zu organisieren und zu warten.

Einige Annahmen des Designs

Natürlich gibt es viele verschiedene Designs, daher kann ich nichts sagen, aber hier nehmen wir den folgenden Umriss an. Sollte es als "Yurufuwa DDD Style Layer Design" verstanden werden? "... ??

Name Überblick Bemerkungen
api Es ist der Ausgangspunkt der Verarbeitung und nutzt den Service
Dies kann eine URL oder ein Befehlszeilentool sein
Wird diesmal nicht angezeigt
service Behandeln Sie die Domäne und realisieren Sie die Verarbeitung, die das System ausführen soll
domain Beschreiben der Geschäftslogik
repository Schnittstelle zur Interaktion mit Datenbanken und externen Systemen Als Schnittstelle in Domain platzieren
mapper Repository implementieren Diesmal nicht implementiert

Lass es uns versuchen

Wie üblich bereiten wir ein passendes Thema vor. Diesmal scheint es, dass ein bestimmter Telekommunikationsanbieter Festnetz- und Mobilfunkverträge verwaltet.

Umriss des Themas

Wir werden ein System realisieren, das Verträge mit bestimmten Mitgliedern verwaltet.

Mitglieder können bis zu einer Mobilfunkleitung und einer Festnetzleitung haben. (Feste Linie erscheint diesmal nicht)

Die mobile Leitung verfügt über bis zu 1 Sprachoption. Die Sprachoption umfasst bis zu einen Anrufbeantworter.

Basierend auf der obigen Datenstruktur sind die folgenden Anforderungen erfüllt.

Anfrage

Erfüllen Sie die folgenden vier Anforderungen. Geben Sie für die gesamte Verarbeitung die Mitglieds-ID ein.

  1. Prüfen Sie, ob Sie eine Mobilfunkleitung beantragen können
  1. Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können
  1. Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind
  1. Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren

Offensichtlich "nicht" ist besetzt ...

Ein Beispiel für eine einfache Antwort

domain Zunächst werde ich ehrlich gesagt "Mobilfunk-> Sprachoption-> Anrufbeantworteroption" mit "Optional" ausdrücken. (Dieses Mal muss die Technik des "Nicht" nicht "Optional" sein, aber da es die einfachste ist, werde ich alles unten mit "Optional" beschreiben. Die Essenz ist sogar mit "Null" fast dieselbe.)

MobileLine.java


@AllArgsConstructor
public class MobileLine {
    @Getter
    private final MobileLineId id;
    @Getter
    private final MonthlyFee fee = new MonthlyFee(1500);
    @Getter
    private final Optional<VoiceOption> voiceOption;
}

VoiceOption.java


@AllArgsConstructor
public class VoiceOption {
    @Getter
    private final VoiceOptionId id;
    @Getter
    private final MonthlyFee fee = new MonthlyFee(700);
    @Getter
    private final Optional<AnswerPhoneOption> answerPhoneOption;
}

AnswerPhoneOption.java


@AllArgsConstructor
public class AnswerPhoneOption {
    @Getter
    private final AnswerPhoneOptionId id;
    @Getter
    private final MonthlyFee fee = new MonthlyFee(200);
}

Es ist einfach. Es hat einen Preis, den Sie möglicherweise benötigen, und bis zu einem Unterelement. (MonthlyFee und XxxId sind nur primitive Wrapper)

repository Als nächstes kommt "Repository". Sie müssen sich jedoch nur auf die Stammklasse beziehen.

MobileLineRepository.java


public interface MobileLineRepository {
    Optional<MobileLine> findMobileLine(UserId userId);
}

Mitglieder haben möglicherweise keine Mobilfunkleitung, daher ist dies "optional". Bild der externen Schlüsselreferenz.

service Schließlich gibt es "Service", aber hier verbinden wir uns einfach.

MobileLineService.java


public class MobileLineService {
    private MobileLineRepository repository;

    // 1.Prüfen Sie, ob Sie eine Mobilfunkleitung beantragen können
    public boolean checkMobileLineApplicable(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        return !mobileLineOptional.isPresent();
    }

    // 2.Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können
    public boolean checkAnswerPhoneCancellable(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        MobileLine mobileLine = mobileLineOptional
                .orElseThrow(() -> new RuntimeException("Mobilfunkleitung nicht gefunden"));

        if (mobileLine.getVoiceOption().isPresent()) {
            return mobileLine.getVoiceOption().get().getAnswerPhoneOption().isPresent();
        } else {
            return false;
        }
    }

    // 3.Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind
    public AnswerPhoneOptionCancellation getAnswerPhoneOptionIdForCancellation(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        MobileLine mobileLine = mobileLineOptional
                .orElseThrow(() -> new RuntimeException("Mobilfunkleitung nicht gefunden"));

        VoiceOption voiceOption = mobileLine.getVoiceOption()
                .orElseThrow(() -> new RuntimeException("Sprachoption nicht gefunden"));

        AnswerPhoneOption answerPhoneOption = voiceOption.getAnswerPhoneOption()
                .orElseThrow(() -> new RuntimeException("Anrufbeantworteroption nicht gefunden"));

        return new AnswerPhoneOptionCancellation(
                mobileLine.getId(),
                voiceOption.getId(),
                answerPhoneOption.getId()
        );
    }

    // 4.Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren
    public MonthlyFee totalMonthlyFee(UserId userId) {
        Optional<MobileLine> mobileLineOptional = repository.findMobileLine(userId);

        MobileLine mobileLine = mobileLineOptional
                .orElseThrow(() -> new RuntimeException("Mobilfunkleitung nicht gefunden"));

        if (mobileLine.getVoiceOption().isPresent()) {
            if (mobileLine.getVoiceOption().get().getAnswerPhoneOption().isPresent()) {
                return MonthlyFee.sum(
                        mobileLine.getFee(),
                        mobileLine.getVoiceOption().get().getFee(),
                        mobileLine.getVoiceOption().get().getAnswerPhoneOption().get().getFee()
                );
            } else {
                return MonthlyFee.sum(
                        mobileLine.getFee(),
                        mobileLine.getVoiceOption().get().getFee()
                );
            }
        } else {
            return mobileLine.getFee();
        }
    }
}

Sie sind fertig. (Sie können den Code etwas mehr in "MobileLine" verschieben, aber ich habe ihn in den Dienst geschrieben, um das Verständnis des gesamten Bilds des Prozesses zu erleichtern.)

Ich glaube nicht, dass es viele Leute gibt, die das sehen und "wunderbaren Code" fühlen! Einige Leute sind jedoch möglicherweise nicht in der Lage herauszufinden, was falsch ist und was zu tun ist.

Von hier aus möchte ich diesen Code verbessern, während ich das Thema interpretiere und den Code erkläre.

3 Arten von "Nein"

Es ist ein kleiner Sprung, aber ich schreibe seit 1-2 Jahren Code und habe mich in letzter Zeit stark gefühlt.

Es gibt drei Arten von "Nein"! Das ist. Lassen Sie uns über jedes mit dem obigen Thema und Code nachdenken.

Ein "Nein" ist normal

Das erste ist das.

In diesem Beispiel die Mobilfunkleitung in "1. Überprüfen Sie, ob Sie eine Mobilfunkleitung beantragen können" und Die Sprachoption und die Anrufbeantworteroption "4. Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren".

Wenn diese nicht vorhanden sind, werden sie als ** ein Muster des normalen Systems ** behandelt. Grundsätzlich ist dieser Fall ** keine Ausnahme **.

B "Nein" ist möglich, aber ein Fehler

Dieser Fall entspricht ** einem Fehlermuster **, wenn es dem obigen Satz entspricht. Wenn Sie jedoch einen Fehler in einem Wort sagen, ist er nicht von dem später beschriebenen dritten Typ zu unterscheiden, sodass wir ihn als ** Geschäftsfehler ** im Sinne eines ** Fehlers bezeichnen, der in den Service-Spezifikationen erwartet wird **.

Möglicherweise liegt ein Servicefehler vor. Kurz gesagt, ich finde es gut, wenn es bei Ihren Verwandten keine Unstimmigkeiten gibt. Ich nenne es einen Geschäftsfehler, weil es ein Fehler ist, der von der Geschäftslogik erkannt werden sollte.

Dieser Fehler schreibt Logik zum Erkennen und Schützen. Ich kann nicht sicher sagen, weil es vom Design abhängen kann, aber es gibt in diesem Fall keine Ausnahmen. ** **.

In diesem Beispiel entspricht die Anrufbeantworteroption "2. Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können". Wenn es einen Anrufbeantworter gibt, ist dieser "normal", und wenn dies nicht der Fall ist, ist dies "einer der vielen anderen Gründe, warum Sie nicht stornieren können".

Unterschied zwischen A und B.

Der Unterschied ist der Unterschied in der nachfolgenden Verarbeitung.

Da A ein normales System mit oder ohne ist, gibt es keinen Unterschied im Durchfluss. Wenn andererseits B vorhanden ist, machen Sie XX, wenn nicht, machen Sie stattdessen XX, oder wenn nicht, fahren Sie mit ~ ~ fort und so weiter.

B ist ein Bild der Verzweigung mit "Ja / Nein" im Flussdiagramm.

C "Nein" ist unmöglich

Der letzte ist ** ein Fehler **, der in den Service-Spezifikationen nicht angenommen (definiert) wird. Ich nenne es ** Systemfehler **, weil dies hauptsächlich aus Systemgründen geschieht.

Beispielsweise schlägt die DB-Verbindung fehl, der externe Systemaufruf läuft ab oder der Wert, der in der DB hätte beschädigt werden sollen, kann nicht referenziert werden. Es kommt zu Datenbeschädigung / -verlust aufgrund doppelter Übermittlung oder fehlerhafter Operation.

Kurz gesagt, es handelt sich um einen Fehler, der nicht auftritt, wenn der Dienst normal funktioniert und wie erwartet verwendet wird.

In diesem Fall wäre ** mit Ausnahmen ** angemessen. Ich fange es nicht eins nach dem anderen.

In diesem Beispiel die mobile Leitung von "2. Überprüfen Sie, ob Sie die Anrufbeantworteroption stornieren können". Optionen für mobile Leitungen und Sprache sowie Anrufbeantworteroptionen in "3. Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind", Die Mobilfunkleitung in "4. Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren" ist gleichwertig.

In 2. gibt es beispielsweise eine Mobilfunkleitung (thematisch), sodass es unerwartet ist, dass es keine solche Leitung gibt. Ich weiß nicht was passiert ist. (Eine bestimmte Voraussetzung ist beispielsweise ein Prozess, der von einem Bildschirm aus aufgerufen wird und nur an eine Person mit einer Mobilfunkleitung übertragen werden kann. Das ist nicht ungewöhnlich, oder?)

Unterschied zwischen B und C.

Im Gegensatz zu B erscheint C nicht im Flussdiagramm mit "Ja / Nein".

Zum Beispiel ist "Ja / Nein-Statusinkonsistenz aufgetreten" oder "Ja / Nein-Verbindung zur Datenbank fehlgeschlagen" im Flussdiagramm der Dienstspezifikation nicht angegeben, oder?

Im Gegensatz zu B ist C häufig nicht in der Lage, eine nachfolgende Verarbeitung durchzuführen. Es ist nicht sinnvoll, nach Sprachoptionen zu suchen, wenn Sie Ihre Mobilfunkleitung nicht durchsuchen können, oder? In vielen Fällen wird in diesem Fall nichts passieren.

In anderen Fällen wird B reproduziert, C jedoch nicht.

B sagt zum Beispiel: "Wenn Sie nicht über die Anrufbeantworteroption verfügen, können Sie überprüfen, wie oft Sie stornieren können ** und Sie erhalten das gleiche Ergebnis **". C hat möglicherweise etwas wie "Ich wurde angegriffen und konnte aufgrund hoher Auslastung keine Verbindung zur Datenbank herstellen, aber ich habe mich damit befasst ** und war erfolgreich **, als ich mich erneut bewarb."

Was soll ich dann tun

Selbst wenn ich einfach "Vorhandensein oder Nichtvorhandensein einer Sprachoption" sage, habe ich festgestellt, dass die Handhabung zu diesem Zeitpunkt anders ist.

Dieses Mal möchte ich den Sprung wagen und die Politik verfolgen, "jedes Mal eine andere Klasse zu bilden".

Organisieren Sie 4 Anforderungen

Ich werde versuchen, die notwendigen Informationen als "Nein" zu organisieren.

1. Prüfen Sie, ob Sie eine Mobilfunkleitung beantragen können

Es werden keine Berechnungen durchgeführt, es reicht also zu wissen, ob es wirklich existiert.

2. Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können

Auch hier gibt es keine spezielle Berechnung, daher scheint es keinen erforderlichen Wert zu geben.

3. Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind

Unter der Annahme, dass alles hier ist, benötigen wir die "ID" aller Elemente.

4. Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren

Hier ist es normal, auch wenn es nicht unter der Mobilfunkleitung liegt, und in einigen Fällen ist der Preis des Elements erforderlich.

Vergleiche mit der ersten Klasse

Wenn ich es so betrachte, ist die erste von mir erstellte "MobileLine" tatsächlich wie eine Summe von vier Anforderungen geformt.

MobileLine.java


@AllArgsConstructor
public class MobileLine {
    @Getter
    private final MobileLineId id;
    @Getter
    private final MonthlyFee fee = new MonthlyFee(1500);
    @Getter
    private final Optional<VoiceOption> voiceOption;
}

Es stellt sich jedoch heraus, dass "ID" und monatliche Gebühren weniger wahrscheinlich sind und dass "VoiceOption" nicht immer "optional" ist.

Wenn Sie alle Elemente haben und sie unter Berücksichtigung der Möglichkeit, dass sie nicht vorhanden sind, auf "Optional" setzen, können Sie natürlich alle Anforderungen erfüllen. In Wirklichkeit hat dies jedoch nicht viel Verdienst und es nimmt nur an Schärfe zu. (Ich werde das am Ende ansprechen)

Daher werde ich von hier aus "eine Klasse vorstellen, die sich bis zur letzten Minute auf die minimalen Elemente konzentriert".

Erneuerte Klasse!

Die Anzahl der Klassen wird sehr groß sein, aber ich habe beschlossen, den Sprung zu wagen, also werde ich eine große Anzahl von Klassen erstellen.

1. Prüfen Sie, ob Sie eine Mobilfunkleitung beantragen können

Keine Klasse erforderlich. Das Vorhandensein oder Fehlen von "Boolean" ist ausreichend.

2. Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können

MobileLineForAnswerPhoneCancellableCheck.java


@AllArgsConstructor
public class MobileLineForAnswerPhoneCancellableCheck {
    @Getter
    private final Optional<VoiceOptionForAnswerPhoneCancellableCheck> voiceOption;
}

VoiceOptionForAnswerPhoneCancellableCheck.java


@AllArgsConstructor
public class VoiceOptionForAnswerPhoneCancellableCheck {
    @Getter
    private final Optional<AnswerPhoneOptionForAnswerPhoneCancellableCheck> answerPhoneOption;
}

AnswerPhoneOptionForAnswerPhoneCancellableCheck.java


@AllArgsConstructor
public class AnswerPhoneOptionForAnswerPhoneCancellableCheck {
}

Mit den aktuellen Spezifikationen ist es möglich zu beurteilen, ob eine Anwendung aufgrund der Anwesenheit oder Abwesenheit erfolgen soll oder nicht, sodass überhaupt kein Wert vorliegt. (Es fühlt sich ein wenig nutzlos an, aber in Wirklichkeit benötigen Sie verschiedene Werte wie verschiedene Status und Jahre der Vertragsfortführung, und ich hoffe, dass Sie Ihr Gehirn mit solchen Wahnvorstellungen ergänzen können.)

3. Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind

MobileLineForAnswerPhoneCancellation.java


@AllArgsConstructor
public class MobileLineForAnswerPhoneCancellation {
    @Getter
    private final MobileLineId id;
    @Getter
    private final VoiceOptionForAnswerPhoneCancellation voiceOption;
}

VoiceOptionForAnswerPhoneCancellation.java


@AllArgsConstructor
public class VoiceOptionForAnswerPhoneCancellation {
    @Getter
    private final VoiceOptionId id;
    @Getter
    private final AnswerPhoneOptionForAnswerPhoneCancellation answerPhoneOption;
}

AnswerPhoneOptionForAnswerPhoneCancellation.java


@AllArgsConstructor
public class AnswerPhoneOptionForAnswerPhoneCancellation {
    @Getter
    private final AnswerPhoneOptionId id;
}

Dies hat eine andere Atmosphäre als zuvor. Ich habe eine ID, die nur für diese Anforderung benötigt wird, und alle optionalen IDs fehlen.

4. Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren

MobileLineForTotalMonthlyFee.java


@AllArgsConstructor
public class MobileLineForTotalMonthlyFee {
    @Getter
    private final MonthlyFee fee = new MonthlyFee(1500);
    @Getter
    private final Optional<VoiceOptionForTotalMonthlyFee> voiceOption;
}

VoiceOptionForTotalMonthlyFee.java


@AllArgsConstructor
public class VoiceOptionForTotalMonthlyFee {
    @Getter
    private final MonthlyFee fee = new MonthlyFee(700);
    @Getter
    private final Optional<AnswerPhoneOptionForTotalMonthlyFee> answerPhoneOption;
}

AnswerPhoneOptionForTotalMonthlyFee.java


@AllArgsConstructor
public class AnswerPhoneOptionForTotalMonthlyFee {
    @Getter
    private final MonthlyFee fee = new MonthlyFee(200);
}

Es gibt auch eine monatliche Gebühr, die nur für diese Anforderung benötigt wird, und ein untergeordnetes Element in "Optional".

Repository

Da sich die zurückgegebene Klasse geändert hat, ändert sich auch die Methode im Repository.

MobileLineRepository.java


public interface MobileLineRepository {
    boolean isMobileLineApplicable(UserId userId); //Keine Notwendigkeit, die Klasse zurückzubekommen

    MobileLineForAnswerPhoneCancellableCheck findForCancellable(UserId userId);

    MobileLineForAnswerPhoneCancellation findForCancel(UserId userId);

    MobileLineForTotalMonthlyFee findForTotalMonthlyFee(UserId userId);
}

Das "Optional" ist weg!

Wo ist das "Optional" von hier verschwunden, zum Beispiel "MobileLineForAnswerPhoneCancellation.java"? Anstatt irgendwohin zu ziehen, ist es wirklich weg.

Wenn in der Implementierungsklasse von "Repository" eine Statusinkonsistenz auftritt, ist dies eine Ausnahme. Der Teil, der ".orElseThrow ()" in der Serviceschicht ausgeführt hat, wird auf Inkonsistenz überprüft, bevor er in "Optional" eingeschlossen wird. Wenn dies auftritt, ist dies eine Ausnahme.

Sie müssen ohnehin in der Lage sein, "get ()" später zu erhalten. Machen wir also eine Ausnahme, wenn Sie feststellen, dass dies nicht möglich ist. Auf diese Weise müssen Sie es nicht als "Optional" in "Domain" behalten und später überprüfen.

Bedienung

Es ist einfach, wenn Sie das alles haben. Ich denke sogar, dass der einfachste in erster Linie "Service" ist.

Alles was Sie tun müssen, ist die "Domain" zu verwenden, die Sie bisher erstellt haben.

MobileLineService.java


public class MobileLineService {
    private MobileLineRepository repository;

    // 1.Prüfen Sie, ob Sie eine Mobilfunkleitung beantragen können
    public boolean checkMobileLineApplicable(UserId userId) {
        return repository.isMobileLineApplicable(userId);
    }

    // 2.Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können
    public boolean checkAnswerPhoneCancellable(UserId userId) {
        MobileLineForAnswerPhoneCancellableCheck mobileLine = repository.findForCancellable(userId);

        return mobileLine.getVoiceOption().map(it -> it.getAnswerPhoneOption().isPresent()).orElse(false); //Ein wenig Einfallsreichtum mit Karte oder sonst
    }

    // 3.Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind
    public AnswerPhoneOptionCancellation cancelAnswerPhoneOption(UserId userId) {
        MobileLineForAnswerPhoneCancellation mobileLine = repository.findForCancel(userId);

        return new AnswerPhoneOptionCancellation(
                mobileLine.getId(),                                    //IsPresent muss nicht überprüft werden
                mobileLine.getVoiceOption().getId(),                   //Es wird bestätigt, dass ein Wert vorhanden ist, da das Repository keine Ausnahme ausgelöst hat
                mobileLine.getVoiceOption().getAnswerPhoneOption().getId()
        );
    }

    // 4.Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren
    public MonthlyFee totalMonthlyFee(UserId userId) {
        MobileLineForTotalMonthlyFee mobileLine = repository.findForTotalMonthlyFee(userId);

        return MonthlyFee.sum(
                mobileLine.getFee(),                                                          //Ein wenig Einfallsreichtum mit Karte oder sonst
                mobileLine.getVoiceOption().map(it -> it.getFee()).orElse(MonthlyFee.zero()), //Es kann etwas einfacher sein, wenn Sie es gehorsam als 0 Yen hinzufügen
                mobileLine.getVoiceOption().flatMap(it -> it.getAnswerPhoneOption()).map(it -> it.getFee()).orElse(MonthlyFee.zero())
        );
    }
}

Der große Unterschied zum ersten "MobileLineService" besteht darin, dass nicht nach Systemfehlern gesucht wird. "Es sollte keine Elemente geben" glaubt an die Klasse, die vom "Repository" zurückgegeben wird.

Sie können sich also nur auf die Berechnungen konzentrieren, die für Ihre Anforderungen durchgeführt werden müssen.

Eigentlich nur ein bisschen mehr ...

Jetzt, wo ich so weit gekommen bin, möchte ich noch etwas reparieren.

Sie können sich also nur auf die Berechnungen konzentrieren, die für Ihre Anforderungen durchgeführt werden müssen.

Dies ist die Geschäftslogik. Sie können es sich als "die notwendigen Berechnungen zur Erfüllung der Service-Spezifikationen" vorstellen.

Sie möchten die Berechnung testen, oder? Du solltest wollen. Es ist unmöglich, es nicht zu tun, oder? Ja ich möchte.

Hier oder so!

return mobileLine.getVoiceOption().map(it -> it.getAnswerPhoneOption().isPresent()).orElse(false);

Hier oder so!

return MonthlyFee.sum(
        mobileLine.getFee(),
        mobileLine.getVoiceOption().map(it -> it.getFee()).orElse(MonthlyFee.zero()),
        mobileLine.getVoiceOption().flatMap(it -> it.getAnswerPhoneOption()).map(it -> it.getFee()).orElse(MonthlyFee.zero())
);

Sind Sie sicher, dass Sie die Bereitstellung durchführen können, ohne den Betrieb zu überprüfen? Ist es absolut in Ordnung?

Wenn Sie alle Muster im "Service" -Test bestehen möchten, müssen Sie viele Rückgabewerte für "Repository" vorbereiten.

Es können verspottete oder registrierte Dummy-Daten in der Datenbank sein, aber das können Sie nicht.

Daher sollten wir die folgende Beschreibung in der Tabelle der Beschreibung der ersten Schicht beachten.

domain Beschreiben Sie die Geschäftslogik

Auch erneuerte Klasse! !!

Ich werde es nur einmal posten. Dies ist die letzte Verbesserung.

2. Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können

MobileLineForAnswerPhoneCancellableCheck.java


@AllArgsConstructor
public class MobileLineForAnswerPhoneCancellableCheck {
    private final Optional<VoiceOptionForAnswerPhoneCancellableCheck> voiceOption;

    public boolean isAnswerPhoneCancellable() {
        return voiceOption.map(it -> it.isAnswerPhoneCancellable()).orElse(false);
    }
}

VoiceOptionForAnswerPhoneCancellableCheck.java


@AllArgsConstructor
public class VoiceOptionForAnswerPhoneCancellableCheck {
    private final Optional<AnswerPhoneOptionForAnswerPhoneCancellableCheck> answerPhoneOption;

    public boolean isAnswerPhoneCancellable() {
        return answerPhoneOption.isPresent();
    }
}

AnswerPhoneOptionForAnswerPhoneCancellableCheck.java


public class AnswerPhoneOptionForAnswerPhoneCancellableCheck {
}

Fragen Sie die Stammklasse "Können Sie sich bewerben?"

3. Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind

MobileLineForAnswerPhoneCancellation.java


@AllArgsConstructor
public class MobileLineForAnswerPhoneCancellation {
    private final MobileLineId mobileLineId;
    private final VoiceOptionId voiceOptionId;
    private final AnswerPhoneOptionId answerPhoneOptionId;

    public AnswerPhoneOptionCancellation cancel() {
        return new AnswerPhoneOptionCancellation(
                mobileLineId,
                voiceOptionId,
                answerPhoneOptionId
        );
    }
}

Ich wagte den Sprung und beschloss, die Niststruktur zu stoppen und sie zu einer flachen Klasse zu machen. Ich brauche sie sowieso alle.

Ich sagte nur "eine Sammlung von Materialien für Informationen, die für die Stornierung notwendig sind". Wenn Sie der Stammklasse "Erstellen Sie die für die Stornierung erforderlichen Informationen" mitteilen, werden diese unter Verwendung der Materialien zusammengestellt. Sie können nicht sehen, was das Material speziell auf der Außenseite ist. Es ist gekapselt.

4. Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren

MobileLineForTotalMonthlyFee.java


@AllArgsConstructor
public class MobileLineForTotalMonthlyFee {
    private final MonthlyFee fee = new MonthlyFee(1500);
    private final Optional<VoiceOptionForTotalMonthlyFee> voiceOption;

    public MonthlyFee getTotalMonthlyFee() {
        return MonthlyFee.sum(
                fee,
                voiceOption.map(it -> it.voiceOptionAndAnswerPhoneOptionFee()).orElse(MonthlyFee.zero())
        );
    }
}

VoiceOptionForTotalMonthlyFee.java


@AllArgsConstructor
public class VoiceOptionForTotalMonthlyFee {
    private final MonthlyFee fee = new MonthlyFee(700);
    private final Optional<AnswerPhoneOptionForTotalMonthlyFee> answerPhoneOption;

    public MonthlyFee voiceOptionAndAnswerPhoneOptionFee() {
        return MonthlyFee.sum(
                fee,
                answerPhoneOption.map(it -> it.answerPhoneOptionFee()).orElse(MonthlyFee.zero())
        );
    }
}

AnswerPhoneOptionForTotalMonthlyFee.java


@AllArgsConstructor
public class AnswerPhoneOptionForTotalMonthlyFee {
    private final MonthlyFee fee = new MonthlyFee(200);

    public MonthlyFee answerPhoneOptionFee() {
        return fee;
    }
}

Ich war hier ein wenig verwirrt, aber bereite eine Methode vor, die die Summe der Preise unter sich zurückgibt, und die Oberschicht wird sich selbst hinzufügen. Nun, die Details im Inneren sind eine triviale Angelegenheit, und auch hier müssen Sie der Stammklasse nur "totalisieren" sagen.

Repository

Ich denke, es ist besser, das Repository nach den Anforderungen der zurückzugebenden Klasse zu unterteilen. Es geschah, als ich mit der Paketorganisation fortfuhr. (In letzter Zeit bin ich immer noch nicht sehr zuversichtlich.)

Daher ist das Repository auch in so viele Klassen unterteilt wie die Anzahl der erstellten Klassen.

Ich werde den Code weglassen.

Bedienung

MobileLineService.java


public class MobileLineService {
    private MobileLineRepository mobileLineRepository;
    private MobileLineForAnswerPhoneCancellableCheckRepository forAnswerPhoneCancellableCheckRepository;
    private MobileLineForAnswerPhoneCancellationRepository forAnswerPhoneCancellationRepository;
    private MobileLineForTotalMonthlyFeeRepository totalMonthlyFeeRepository;

    // 1.Prüfen Sie, ob Sie eine Mobilfunkleitung beantragen können
    public boolean checkMobileLineApplicable(UserId userId) {
        return mobileLineRepository
                .isMobileLineApplicable(userId);
    }

    // 2.Überprüfen Sie, ob Sie die Anrufbeantworteroption abbrechen können
    public boolean checkAnswerPhoneCancellable(UserId userId) {
        return forAnswerPhoneCancellableCheckRepository.find(userId)
                .isAnswerPhoneCancellable();
    }

    // 3.Holen Sie sich die Elemente, die zum Abbrechen der Anrufbeantworteroption erforderlich sind
    public AnswerPhoneOptionCancellation cancelAnswerPhoneOption(UserId userId) {
        return forAnswerPhoneCancellationRepository.find(userId)
                .cancel();
    }

    // 4.Beziehen Sie sich auf den Gesamtwert aller vertraglich vereinbarten monatlichen Nutzungsgebühren
    public MonthlyFee totalMonthlyFee(UserId userId) {
        return totalMonthlyFeeRepository.find(userId)
                .getTotalMonthlyFee();
    }
}

Sie können jetzt einfach die Stammklasse aus dem Repository anfordern und die Stammklasse auffordern, die Berechnung durchzuführen! !!

Das ist der einzige Service.

Die Berechnung und Verarbeitung wurden getrennt.

Der in die Domänenklasse verschobene Berechnungstest kann manuell durchgeführt werden, ohne dass Dummy-Daten in die Datenbank eingegeben werden müssen. Sie können direkt unter der Stammklasse "neu" und so viele testen, wie Sie möchten.

Überlegung zur Verbesserung

Verantwortlichkeiten

Ich habe den Code dreimal gepostet.

Wenn Sie es so betrachten, können Sie sehen, dass die Verantwortlichkeiten allmählich vom Dienst getrennt und in die Domäne verschoben wurden. Oder besser gesagt, der erste Dienst war schließlich zu schwer.

Robustheit

Betrachten wir es aus einer etwas anderen Perspektive

In der Regel sollte "Optional" nicht "get ()" sein. (Ich werde die Details weglassen, aber im Grunde ist es besser, nach dem Zweitplatzierten nach get () nach einem Verbesserungsplan zu suchen.)

Als wir uns verbesserten, verschwanden "get ()" und "orElseThrow ()", so dass es keine Ausnahmen um "Optional" gab.

Durch das Befehlen der Root-Klasse ist @ Getter verschwunden. Dies bedeutet, dass die Kapselung erfolgreich war.

Die zuletzt erstellte Klasse ist nach Anforderungen getrennt. Selbst wenn nur für eine bestimmte Anforderung ein zusätzlicher Wert erforderlich ist, wirkt sich die Änderung nur auf diese Klasse aus. Die Serviceklasse sagt der Stammklasse nur "~~", ohne die darin enthaltenen Elemente zu kennen.

Selbst wenn das Antragsdatum zum Stornierungsurteil hinzugefügt wird, muss der Service nicht repariert werden, und eine erneute Bewertung ist nur im Falle einer Preisberechnung nicht erforderlich.

3 Arten von "Nein"

normal

Es gibt keine Ausnahmen

Nicht als Zweig im Flussdiagramm der Dienstspezifikation angegeben 1.png

Geschäftsfehler

Es gibt keine Ausnahmen

Wird im Ablaufdiagramm der Dienstspezifikation als Zweig angegeben 2.png

Die nachfolgende Verarbeitung nach dem Auftreten wird ebenfalls normal gemäß den Spezifikationen verarbeitet

Da es in der Dienstspezifikation geschrieben ist, wird es von "Domäne" erkannt, die Geschäftslogik schreibt

Wenn der Datenstatus gleich ist, tritt immer der gleiche Geschäftsfehler auf

Systemfehler

Verwenden Sie Ausnahmen

Nicht als Zweig im Flussdiagramm der Dienstspezifikation angegeben 3.png

In vielen Fällen kann die nachfolgende Verarbeitung nach ihrem Auftreten nicht fortgesetzt werden.

Da es nicht in der Dienstspezifikation geschrieben ist und Ausnahmen enthält, schützen Sie es mit "Mapper", anstatt es mit "Domain" zu erkennen, die Geschäftslogik schreibt.

Selbst wenn der Datenstatus derselbe ist, kann er aufgrund von Lade- oder Kommunikationsunterbrechungen auftreten oder nicht. (Wenn die Daten inkonsistent sind, werden sie immer auf die gleiche Weise angezeigt.)

Summensatzklasse gegen Spezialklasse

Erstens wirkt sich das Ändern einer Anforderung bei speziellen Klassen nicht auf eine andere aus.

Die Summensatzklasse setzt alle Werte, die nur von bestimmten Anforderungen gehalten werden dürfen, auf "Optional". (Oder "Liste")

Dies ist beispielsweise "Das Stornierungsantragsdatum ist" leer ", außer beim Stornieren. Wenn Sie jedoch die Stornierung stornieren, sollte das Stornierungsantragsdatum einen Wert enthalten." Es wird ein "Optional" geben, das eine große Menge an Vorkenntnissen erfordert, wie "Es ist immer eine leere Liste unmittelbar nach der Bewerbung, aber beim Abbrechen sollte es eine Liste sein, die 1-3 Elemente enthält." (Oder "Liste") Das ist super scharf. (wahre Geschichte)

In Anbetracht der Anwendung, Änderung, Stornierung, Stornierung, optionalen Elemente usw. wird die Summensatzklasse mit "Optional" abgedeckt. orElseThrow (" Es sollte einen Fall von ~ ~ geben ") Können Sie sich nicht leicht vorstellen, abgedeckt zu werden?

Ausnahme

Ich denke nicht, dass Ausnahmen in "Domain" gemacht werden sollten.

Da es sich um eine Berechnung im Zusammenhang mit "Domain" handelt, kommt eine Zustandsinkonsistenz nicht in Frage. Dies liegt daran, dass ich denke, dass der Wert den Fehler ausdrücken sollte, da der Fehler erwartet wird, solange er als Geschäftslogik berechnet wird.

Lassen Sie uns alle Ausnahmen auf "Mapper" verschieben. (Leicht gewalttätig)

Was ist mit "Service"? "Service" kann für "Fang" geeignet sein. Beispiel: "Wenn ein Systemfehler vorliegt, wird ein Alarm ausgegeben."

Was bleibt im Service

Diesmal gab es nur eine Routenklasse, aber zum Beispiel Wie "Ich habe einen Vertrag für eine Anrufbeantworteroption" & "Es gibt keinen unbezahlten Betrag für eine Anrufbeantworteroption" & "Zwei Jahre sind seit dem Vertrag vergangen" Andere Repositorys und Stammklassen werden ebenfalls angezeigt, wenn es sich um eine zusammengesetzte Bedingung handelt.

Ich denke, es liegt in der Verantwortung der Service-Schicht, diese und große Domänen zu verwalten, für die alle diese Domänen erforderlich sind. jetzt.

Ist es so etwas wie eine Klasse, die die meisten spezifischen Berechnungen und Systemfehler, die das Ende berücksichtigen sollte, nicht kennt, sondern nur die "Verarbeitungsreihenfolge" und die "ganzen Zeichen" kennt?

Prüfung

Im ersten Beispiel mussten Dummy-Daten der Datenbank eingegeben werden, um die wesentlichen Muster der Berechnungslogik abzudecken.

Im letzten Beispiel wurde die Berechnung in "Domäne" unterteilt, sodass Sie "Neu" und mit einem beliebigen Wert testen können.

Umfassend

Ich schrieb am Anfang:

Es ist nur eine "Klassifizierung, die ich an dieser Stelle in Betracht gezogen habe", aber sie macht den Verantwortungsbereich für jedes Paket klar. Wir glauben auch, dass es sich natürlich einem Design annähert, das ordnungsgemäße Unit-Tests ermöglicht und die Organisation und Wartung der Logik erleichtert.

Eigentlich habe ich zu diesem Zeitpunkt nicht viel nachgedacht, also dachte ich, ich würde es löschen, wenn ich keine gute Verbindung herstellen könnte, aber ich war erleichtert, dass es unerwartet verbunden war.

Systemfehler, Verarbeitung und Berechnungen befinden sich jetzt in jeder Schicht, können in Einheiten getestet werden und Änderungen der Anforderungen wirken sich nicht mehr auf andere Anforderungen aus!

das ist alles

das ist alles.

Könnten Sie vorstellen, dass es eine Menge Verdienste gibt, wenn Sie nur "nichts" in Betracht ziehen?

Optional ist praktisch und vor allem liebe ich den Fehlertyp Soyu, aber ich mache nichts unangemessen Optional Ich denke, dass es im Vergleich zu den Anforderungen natürlich einem guten Design näher kommt.

Recommended Posts

Was ist es? ~ 3 Arten von "Nein" ~
'% 02d' Was ist der% von% 2?
Was für ein StringUtil ist gut?
Was für eine Methode ist define_method?
Was ist ein Test? ・ Über die Wichtigkeit eines Tests
Wie ist die Datenstruktur von ActionText?
Was ist Cubby?
Was ist java
Was ist Maven?
Was ist Jackson?
Was ist Selbst
Was ist Jenkins?
Was ist ArgumentMatcher?
Was ist IM-Jonglieren?
Was ist params
Was ist SLF4J?
Was ist Fassade? ??
Was ist Java <>?
Was ist Gradle?
Was ist POJO?
Was ist java
Was ist centOS?
Was ist RubyGem?
Was ist before_action?
Was ist Docker?
Was ist Byte?
Was ist Tomcat?
Was ist JSP? ~ Lassen Sie uns die Grundlagen von JSP kennen !! ~
Es ist keine umschließende Instanz vom Typ Hoge zugänglich.
Rufen Sie den ersten Wochentag im aktuellen Gebietsschema ab (welcher Tag ist heute?)
Was ist Maven Assembly?
Überprüfen Sie, ob has_many vorhanden ist und zu optional gehört: Was ist wahr?
Was ist Docker-Compose?
Was ist ein Konstruktor?
Was ist vue cli
Was ist eine Schnittstelle?
Was ist Rubys Selbst?
Was ist harte Codierung?
Was ist ein Stream?
Was ist Rubys attr_accessor?
Was ist die Erlaubnis verweigert?
Was ist Instanzsteuerung?
Was ist ein Initialisierer?
Was ist Spring Tools 4?
Was ist ein Operator?
Was ist Objektorientierung?
Was ist ein MVC-Modell?
Was ist eine Anmerkung?
Was ist Java-Technologie?
Was ist Java API-Java?
Was ist @ (Instanzvariable)?
Was ist Gradles Artefakt?
Was ist JPA-Prüfung?
[Java] Was ist flatMap?