Das von Martin Fowler definierte und in Patterns der Enterprise-Anwendungsarchitektur veröffentlichte Money-Pattern ist eine großartige Möglichkeit, Wert-Einheiten-Paare darzustellen. Es wird Money Pattern genannt, weil es in einem finanziellen Kontext entstanden ist, und wir werden seine Verwendung hauptsächlich in diesem Zusammenhang mit PHP veranschaulichen.
Ich habe keine Ahnung, wie PayPal implementiert wird, aber ich denke, es ist eine gute Idee, die Funktionalität als Beispiel zu nehmen. Lassen Sie mich Ihnen zeigen, was ich meine, mein PayPal-Konto hat zwei Währungen: US-Dollar und Euro. Es hält die zwei Werte getrennt, aber ich kann Geld in jeder Währung erhalten, ich kann meinen Gesamtbetrag in einer der beiden Währungen sehen und ich kann in einer der beiden Währungen ablesen. Stellen Sie sich für dieses Beispiel vor, wir extrahieren in einer der Währungen und führen eine automatische Umrechnung durch, wenn das Gleichgewicht dieser bestimmten Währung geringer ist als das, was wir transferieren möchten, es aber immer noch genug Geld in der anderen Währung gibt. Außerdem beschränken wir uns auf nur zwei Währungen.
Wenn ich ein Kontoobjekt erstellen und verwenden würde, möchte ich es mit einer Kontonummer initialisieren.
Funktion testItCanCrateANewAccount () $ this-> assertInstanceOf ("Konto", neues Konto (123));
Dies wird offensichtlich fehlschlagen, da wir noch keine Kontoklasse haben.
Klassenkonto
Nun, das in ein neues schreiben "Account.php"
Datei und forderte es im Test, machte es bestanden. Dies geschieht jedoch nur, um uns mit der Idee vertraut zu machen. Als nächstes denke ich daran, das Konto zu bekommen Ich würde
.
function testItCanCrateANewAccountWithId () $ this-> assertEquals (123, (neues Konto (123)) -> getId ());
Ich habe den vorherigen Test tatsächlich in diesen geändert. Es gibt keinen Grund, den ersten zu behalten. Es hat sein Leben gelebt, was bedeutet, dass ich gezwungen war, über das nachzudenken Konto
Klasse und erstellen Sie es tatsächlich. Wir können jetzt weitermachen.
Klassenkonto private $ id; Funktion __construct ($ id) $ this-> id = $ id; public function getId () return $ this-> id;
Der Test ist bestanden und Konto
fängt an, wie eine echte Klasse auszusehen.
Basierend auf unserer PayPal-Analogie möchten wir möglicherweise eine Haupt- und eine Sekundärwährung für unser Konto definieren.
privates $ Konto; geschützte Funktion setUp () $ this-> account = new Account (123); […] Funktion testItCanHavePrimaryAndSecondaryCurrencies () $ this-> account-> setPrimaryCurrency ("EUR"); $ this-> account-> setSecondaryCurrency ('USD'); $ this-> assertEquals (array ('primary' => 'EUR', 'second' => 'USD'), $ this-> account-> getCurrencies ());
Nun wird der obige Test uns zwingen, den folgenden Code zu schreiben.
Klassenkonto private $ id; private $ primaryCurrency; private $ secondaryCurrency; […] Funktion setPrimaryCurrency ($ Currency) $ this-> primaryCurrency = $ currency; Funktion setSecondaryCurrency ($ currency) $ this-> secondaryCurrency = $ currency; function getCurrencies () return array ('primary' => $ this-> primaryCurrency, 'secondary' => $ this-> secondaryCurrency);
Zur Zeit behalten wir die Währung als einfache Zeichenfolge. Das kann sich in Zukunft ändern, aber wir sind noch nicht da.
Es gibt endlose Gründe, warum man Geld nicht als einfachen Wert darstellt. Fließkomma-Berechnungen? Jemand? Was ist mit Währungsfraktionalen? Sollten wir 10, 100 oder 1000 Cent in einer exotischen Währung haben? Nun, dies ist ein weiteres Problem, das wir vermeiden müssen. Wie sieht es mit der Zuteilung unteilbarer Cent aus??
Es gibt einfach zu viele und exotische Probleme, wenn Sie mit Geld arbeiten, um sie in Code aufzuschreiben, so dass wir direkt auf die Lösung, das Geldmuster, eingehen. Dies ist ein ziemlich einfaches Muster mit großen Vorteilen und vielen Anwendungsfällen, die weit außerhalb des Finanzbereichs liegen. Wann immer Sie ein Wert-Einheiten-Paar repräsentieren müssen, sollten Sie wahrscheinlich dieses Muster verwenden.
Das Geldmuster ist im Grunde eine Klasse, die einen Betrag und eine Währung enthält. Dann werden alle mathematischen Operationen bezüglich des Wertes in Bezug auf die Währung definiert. "zuteilen ()"
ist eine spezielle Funktion, um einen bestimmten Geldbetrag zwischen zwei oder mehreren Empfängern zu verteilen.
Also als Benutzer von Geld
Ich möchte das in einem Test machen können:
Die Klasse MoneyTest erweitert PHPUnit_Framework_TestCase function testWeCanCreateAMoneyObject () $ money = new Money (100, Currency :: USD ());
Das geht aber noch nicht. Wir brauchen beides Geld
und Währung
. Noch mehr brauchen wir Währung
Vor Geld
. Dies wird eine einfache Klasse sein, daher werde ich den Test jetzt überspringen. Ich bin mir ziemlich sicher, dass die IDE den Großteil des Codes für mich generieren kann.
Klasse Currency private $ centFactor; private $ stringRepresentation; private Funktion __construct ($ centFactor, $ stringRepresentation) $ this-> centFactor = $ centFactor; $ this-> stringRepresentation = $ stringRepresentation; public function getCentFactor () return $ this-> centFactor; function getStringRepresentation () return $ this-> stringRepresentation; statische Funktion USD () return new self (100, 'USD'); statische Funktion EUR () return new self (100, 'EUR');
Das reicht für unser Beispiel. Wir haben zwei statische Funktionen für USD- und EUR-Währungen. In einer realen Anwendung haben wir wahrscheinlich einen allgemeinen Konstruktor mit einem Parameter und laden alle Währungen aus einer Datenbanktabelle oder besser aus einer Textdatei.
Binden Sie als Nächstes die beiden neuen Dateien in den Test ein:
required_once '… /Currency.php'; required_once '… /Money.php'; Die Klasse MoneyTest erweitert PHPUnit_Framework_TestCase function testWeCanCreateAMoneyObject () $ money = new Money (100, Currency :: USD ());
Dieser Test schlägt immer noch fehl, kann es aber zumindest finden Währung
jetzt. Wir fahren mit einem Minimum fort Geld
Implementierung. Ein bisschen mehr als das, was dieser Test unbedingt erfordert, da es sich zumeist um automatisch generierten Code handelt.
Klasse Geld privater $ Betrag; private $ Währung; Funktion __construct ($ Betrag, Währung $ Währung) $ dieser-> Betrag = $ Betrag; $ this-> Währung = $ Währung;
Bitte beachten Sie, wir erzwingen den Typ Währung
für den zweiten Parameter in unserem Konstruktor. Dies ist ein guter Weg, um zu vermeiden, dass unsere Kunden Junk als Währung einsenden.
Das erste, was mir in den Sinn kam, nachdem ich das minimale Objekt in Betrieb hatte, war, dass ich irgendwie Geldobjekte vergleichen muss. Dann fiel mir ein, dass PHP ziemlich geschickt ist, wenn es um den Vergleich von Objekten geht. Deshalb habe ich diesen Test geschrieben.
Funktion testItCanTellTwoMoneyObjectAreEqual () $ m1 = neues Geld (100, Währung: USD ()); $ m2 = neues Geld (100, Währung: USD ()); $ this-> assertEquals ($ m1, $ m2); $ this-> assertTrue ($ m1 == $ m2);
Nun, das geht tatsächlich vorüber. Das "assertEquals"
Funktion kann die beiden Objekte und sogar die eingebaute Gleichheitsbedingung von PHP vergleichen "=="
sagt mir, was ich erwarte. nett.
Aber was ist, wenn wir daran interessiert sind, dass einer größer ist als der andere? Zu meiner noch größeren Überraschung verläuft auch der folgende Test problemlos.
Funktion testOneMoneyIsBiggerThanTheOther () $ m1 = neues Geld (200, Währung: USD ()); $ m2 = neues Geld (100, Währung: USD ()); $ this-> assertGreaterThan ($ m2, $ m1); $ this-> assertTrue ($ m1> $ m2);
Was führt uns zu…
Funktion testOneMoneyIsLessThanTheOther () $ m1 = neues Geld (100, Währung: USD ()); $ m2 = neues Geld (200, Währung: USD ()); $ this-> assertLessThan ($ m2, $ m1); $ this-> assertTrue ($ m1 < $m2);
… Ein Test, der sofort bestanden wird.
Da so viel PHP-Magie tatsächlich mit Vergleichen arbeitet, konnte ich nicht widerstehen, diesen auszuprobieren.
Funktion testTwoMoneyObjectsCanBeAdded () $ m1 = neues Geld (100, Währung: USD ()); $ m2 = neues Geld (200, Währung: USD ()); $ summe = neues Geld (300, Währung :: USD ()); $ this-> assertEquals ($ sum, $ m1 + $ m2);
Was scheitert und sagt:
Objekt der Klasse Money konnte nicht in int konvertiert werden
Hmm. Das hört sich ziemlich offensichtlich an. An diesem Punkt müssen wir eine Entscheidung treffen. Es ist möglich, diese Übung mit noch mehr PHP-Magie fortzusetzen, aber dieser Ansatz wird dieses Tutorial irgendwann in ein PHP-Cheatsheet anstelle eines Entwurfsmusters umwandeln. Lassen Sie uns die Entscheidung treffen, die eigentlichen Methoden zu implementieren, um Geldobjekte hinzuzufügen, zu subtrahieren und zu multiplizieren.
Funktion testTwoMoneyObjectsCanBeAdded () $ m1 = neues Geld (100, Währung: USD ()); $ m2 = neues Geld (200, Währung: USD ()); $ summe = neues Geld (300, Währung :: USD ()); $ this-> assertEquals ($ sum, $ m1-> add ($ m2));
Dieser Test schlägt ebenfalls fehl, aber bei einem Fehler, der uns sagt, gibt es keine "hinzufügen"
Methode auf Geld
.
öffentliche Funktion getAmount () return $ this-> betrag; Funktion add ($ other) neues Geld zurückgeben ($ this-> Betrag + $ other-> getAmount (), $ this-> currency);
Um zwei zusammenzufassen Geld
Objekte benötigen wir einen Weg, um die Menge des Objekts abzurufen, das wir als Argument übergeben. Ich ziehe es vor, einen Getter zu schreiben, aber es wäre auch eine akzeptable Lösung, die Klassenvariable auf public zu setzen. Aber was ist, wenn wir Dollars zu Euros hinzufügen möchten?
/ ** * @expectedException Ausnahme * @expectedExceptionMessage Beide Moneys müssen dieselbe Währung haben * / function testItThrowsExceptionIfWeTryToAddTwoMoneysWithDifferentCurrency () $ m1 = new Money (100, Currency :: USD ()); $ m2 = neues Geld (100, Währung: EUR ()); $ m1-> add ($ m2);
Es gibt verschiedene Möglichkeiten, mit Operationen umzugehen Geld
Objekte mit unterschiedlichen Währungen. Wir werden eine Ausnahme werfen und erwarten es im Test. Alternativ könnten wir einen Währungsumrechnungsmechanismus in unserer Anwendung implementieren, bezeichnen und beides konvertieren Geld
Objekte in eine Standardwährung und vergleichen Sie sie. Wenn wir einen komplexeren Währungsumrechnungsalgorithmus hätten, könnten wir immer von einem zum anderen umwandeln und in dieser umgerechneten Währung vergleichen. Die Sache ist, wenn Konversionen stattfinden, müssen Konvertierungsgebühren in Betracht gezogen werden und die Dinge werden ziemlich kompliziert. Lasst uns einfach diese Ausnahme werfen und weitermachen.
öffentliche Funktion getCurrency () return $ this-> currency; function add (Money $ other) $ this-> saveSameCurrencyWith ($ other); neues Geld zurückgeben ($ dieses-> Betrag + $ anderes-> getAmount (), $ this-> Währung); private Funktion sureSameCurrencyWith (Money $ other) if ($ this-> currency! = $ other-> getCurrency ()) wirft neue Ausnahme ("Beide Moneys müssen dieselbe Währung haben");
Das ist besser. Wir prüfen, ob die Währungen unterschiedlich sind, und geben eine Ausnahme aus. Ich habe es bereits als separate private Methode geschrieben, weil ich weiß, dass wir es auch bei den anderen mathematischen Operationen brauchen werden.
Subtraktion und Multiplikation sind der Addition sehr ähnlich, hier also der Code und Sie können die Tests im angefügten Quellcode finden.
Funktion subtrahieren (Money $ other) $ this-> saveSameCurrencyWith ($ other); if ($ other> $ this) wirft eine neue Ausnahme ("Abgezogenes Geld ist mehr als das, was wir haben"); neues Geld zurückgeben ($ dieser-> Betrag - $ andere-> getAmount (), $ this-> Währung); Funktion multiplyBy ($ multiplier, $ roundMethod = PHP_ROUND_HALF_UP) $ product = round ($ this-> Betrag * $ multiplier, 0, $ roundMethod); neues Geld zurückgeben ($ Produkt, $ this-> Währung);
Bei der Subtraktion müssen wir sicherstellen, dass wir genug Geld haben, und bei der Multiplikation müssen wir Maßnahmen ergreifen, um die Dinge auf- oder abzurunden, damit eine Division (Multiplikation mit Zahlen unter eins) keine "halben Cents" erzeugt. Wir behalten unseren Betrag in Cent, dem niedrigst möglichen Faktor der Währung. Wir können es nicht mehr teilen.
Wir haben eine fast vollständige Geld
und Währung
. Es ist Zeit, diese Objekte vorzustellen Konto
. Wir werden mit anfangen Währung
, und ändern Sie unsere Tests entsprechend.
Funktion testItCanHavePrimaryAndSecondaryCurrencies () $ this-> account-> setPrimaryCurrency (Currency :: EUR ()); $ this-> account-> setSecondaryCurrency (Currency :: USD ()); $ this-> assertEquals (array ('primary' => Currency :: EUR (), 'second' => Currency :: USD ()), $ this-> account-> getCurrencies ());
Aufgrund der dynamischen Typisierung von PHP verläuft dieser Test problemlos. Ich möchte jedoch die Methoden in zwingen Konto
benutzen Währung
Objekte und akzeptieren nichts anderes. Dies ist nicht obligatorisch, aber ich finde solche Typenhinweise äußerst nützlich, wenn jemand anderes unseren Code verstehen muss.
Funktion setPrimaryCurrency (Währung $ Currency) $ this-> primaryCurrency = $ currency; Funktion setSecondaryCurrency (Währung $ currency) $ this-> secondaryCurrency = $ currency;
Nun ist es für jeden offensichtlich, dass er diesen Code zum ersten Mal liest Konto
arbeitet mit Währung
.
Die zwei grundlegenden Aktionen, die ein Konto bieten muss, sind: Einzahlung - dh das Hinzufügen von Geld zu einem Konto - und das Abheben - dh das Entfernen von Geld von einem Konto. Die Einzahlung hat eine Quelle und die Auszahlung hat ein anderes Ziel als unser Girokonto. Wir werden nicht näher auf die Umsetzung dieser Transaktionen eingehen, wir werden uns nur darauf konzentrieren, die Auswirkungen auf unser Konto umzusetzen. Wir können uns also einen solchen Test zur Ablagerung vorstellen.
Funktion testAccountCanDepositMoney () $ this-> account-> setPrimaryCurrency (Currency :: EUR ()); $ money = neues Geld (100, Währung: EUR ()); // Das ist 1 EURO $ this-> Konto-> Einzahlung ($ money); $ this-> assertEquals ($ money, $ this-> account-> getPrimaryBalance ());
Dies wird uns zwingen, ziemlich viel Implementierungscode zu schreiben.
Klassenkonto private $ id; private $ primaryCurrency; private $ secondaryCurrency; private $ secondaryBalance; private $ primaryBalance; function getSecondaryBalance () return $ this-> secondaryBalance; function getPrimaryBalance () return $ this-> primaryBalance; function __construct ($ id) $ this-> id = $ id; […] Funktion Einzahlung (Money $ money) $ this-> primaryCurrency == $ money-> getCurrency ()? $ this-> primaryBalance = $ money: $ this-> secondaryBalance = $ money;
OK OK. Ich weiß, ich habe mehr geschrieben, als für die Produktion absolut notwendig war. Aber ich will dich nicht mit kleinen Schritten zu Tode langweilen und ich bin mir auch ziemlich sicher, was den Code angeht Sekundärbilanz
wird korrekt funktionieren. Es wurde fast vollständig von der IDE generiert. Ich werde sogar das Testen überspringen. Während dieser Code unseren Test bestanden hat, müssen wir uns fragen, was passiert, wenn wir nachfolgende Einzahlungen tätigen? Wir möchten, dass unser Geld dem vorherigen Kontostand hinzugefügt wird.
Funktion testSubsequentDepositsAddUpTheMoney () $ this-> account-> setPrimaryCurrency (Currency :: EUR ()); $ money = neues Geld (100, Währung: EUR ()); // Das ist 1 EURO $ this-> Konto-> Einzahlung ($ money); // ein Euro auf dem Konto $ this-> account-> Deposit ($ money); // Zwei Euro auf dem Konto $ this-> assertEquals ($ money-> multiplyBy (2), $ this-> account-> getPrimaryBalance ());
Nun, das scheitert. Also müssen wir unseren Produktionscode aktualisieren.
Funktion Einzahlung (Money $ money) if ($ this-> primaryCurrency == $ money-> getCurrency ()) $ this-> primaryBalance = $ this-> primaryBalance? : neues Geld (0, $ this-> primaryCurrency); $ this-> primaryBalance = $ this-> primaryBalance-> add ($ money); else $ this-> secondaryBalance = $ this-> secondaryBalance? : neues Geld (0, $ dieser-> Sekundärwährung); $ this-> secondaryBalance = $ this-> secondaryBalance-> add ($ money);
Das ist viel besser. Wir sind wahrscheinlich mit dem fertig Anzahlung
Methode und wir können mit fortfahren abheben
.
Funktion testAccountCanWithdrawMoneyOfSameCurrency () $ this-> account-> setPrimaryCurrency (Currency :: EUR ()); $ money = neues Geld (100, Währung: EUR ()); // Das ist 1 EURO $ this-> Konto-> Einzahlung ($ money); $ this-> account- >draw (neues Geld (70, Währung :: EUR ())); $ this-> assertEquals (neues Geld (30, Währung :: EUR ()), $ this-> account-> getPrimaryBalance ());
Dies ist nur ein einfacher Test. Die Lösung ist auch einfach.
Funktion Abheben (Money $ money) $ this-> primaryCurrency == $ money-> getCurrency ()? $ this-> primaryBalance = $ this-> primaryBalance-> subtrahieren ($ money): $ this-> secondaryBalance = $ this-> secondaryBalance-> subtract ($ money);
Nun, das funktioniert, aber was ist, wenn wir eine verwenden wollen? Währung
das ist nicht in unserem konto? Dafür sollten wir eine Expetion werfen.
/ ** * @expectedException Ausnahme * @expectedExceptionMessage Dieses Konto hat keine Währung USD * / Funktion testThrowsExceptionForInexistentCurrencyOnWithdraw () $ this-> account-> setPrimaryCurrency (Currency :: EUR ()); $ money = neues Geld (100, Währung: EUR ()); // Das ist 1 EURO $ this-> Konto-> Einzahlung ($ money); $ dieses-> Konto-> Abheben (neues Geld (70, Währung :: USD ()));
Das wird uns auch zwingen, unsere Währungen zu überprüfen.
Funktion Abheben (Money $ money) $ this-> validateCurrencyFor ($ money); $ this-> primaryCurrency == $ money-> getCurrency ()? $ this-> primaryBalance = $ this-> primaryBalance-> subtrahieren ($ money): $ this-> secondaryBalance = $ this-> secondaryBalance-> subtract ($ money); private Funktion validateCurrencyFor (Money $ money) if (! in_array ($ money-> getCurrency (), $ this-> getCurrencies ())) wirft neue Ausnahme (sprintf ('Dieses Konto hat keine Währung% s', $ money -> getCurrency () -> getStringRepresentation ()));
Was aber, wenn wir uns mehr zurückziehen wollen als das, was wir haben? Dieser Fall wurde bereits angesprochen, als wir die Subtraktion weiter implementierten Geld
. Hier ist der Test, der es beweist.
/ ** * @expectedException Ausnahme * @expectedExceptionMessage Abgezogenes Geld ist mehr als das, was wir haben * / function testItThrowsExceptionIfWeTryToSubtractMoreMoneyThanWeHave () $ this-> account-> setPrimaryCurrency (Currency :: EUR ()); $ money = neues Geld (100, Währung: EUR ()); // Das ist 1 EURO $ this-> Konto-> Einzahlung ($ money); $ this-> account- >draw (neues Geld (150, Währung :: EUR ()));
Wenn wir mit mehreren Währungen arbeiten, ist der Austausch zwischen ihnen eine der schwierigsten Aufgaben. Das Schöne an diesem Entwurfsmuster ist, dass wir dieses Problem etwas vereinfachen können, indem wir es in seiner eigenen Klasse isolieren und einkapseln. Während die Logik in einem Austausch
Klasse kann sehr anspruchsvoll sein, ihre Verwendung wird viel einfacher. Stellen Sie sich für dieses Tutorial vor, dass wir einige grundlegende Informationen haben Austausch
nur logik. 1 EUR = 1,5 USD.
class exchange function convert (Geld $ Geld, Währung $ inWährung) if ($ toCurrency == Währung :: EUR () && $ money-> getCurrency () == Währung :: USD ()) gibt neues Geld ($ Geld) zurück -> multiplyBy (0,67) -> getAmount (), $ toCurrency); if ($ toCurrency == Currency :: USD () && $ money-> getCurrency () == Currency :: EUR ()) gibt neues Geld zurück ($ money-> multiplyBy (1.5) -> getAmount (), $ toCurrency) ; $ Geld zurückgeben;
Wenn wir von EUR in USD umrechnen, multiplizieren wir den Wert mit 1,5, wenn wir von USD in EUR konvertieren, dividieren Sie den Wert durch 1,5. Andernfalls gehen wir davon aus, dass wir zwei Währungen desselben Typs konvertieren, also nichts tun und das Geld einfach zurückgeben . In der Realität wäre dies natürlich eine viel kompliziertere Klasse.
Jetzt mit einem Austausch
Klasse, Konto
kann verschiedene Entscheidungen treffen, wenn wir uns zurückziehen wollen Geld
in einer Währung, aber wir haben nicht genug in dieser Währung. Hier ist ein Test, der es besser veranschaulicht.
Funktion testItConvertsMoneyFromTheOtherCurrencyWhenWeDoNotHaveEnoughInTheCurrentOne () $ this-> account-> setPrimaryCurrency (Currency :: USD ()); $ money = neues Geld (100, Währung: USD ()); // Das ist 1 USD $ this-> Konto-> Einzahlung ($ money); $ this-> account-> setSecondaryCurrency (Currency :: EUR ()); $ money = neues Geld (100, Währung: EUR ()); // Das ist 1 EURO = 1,5 USD $ this-> Konto-> Einzahlung ($ money); $ this-> account- >draw (neues Geld (200, Währung :: USD ())); // Das ist 2 USD $ this-> assertEquals (neues Geld (0, Währung :: USD ()), $ this-> account-> getPrimaryBalance ()); $ this-> assertEquals (neues Geld (34, Währung :: EUR ()), $ this-> account-> getSecondaryBalance ());
Wir setzen die Hauptwährung unseres Kontos auf USD und zahlen einen Dollar ein. Dann setzen wir die Sekundärwährung auf EUR und zahlen einen Euro ein. Dann ziehen wir zwei Dollar ab. Schlussendlich erwarten wir, dass wir bei null Dollar und 0,34 Euro bleiben werden. Natürlich löst dieser Test eine Ausnahme aus, daher müssen wir eine Lösung für dieses Dilemma implementieren.
Funktion Abheben (Money $ money) $ this-> validateCurrencyFor ($ money); if ($ this-> primaryCurrency == $ money-> getCurrency ()) if ($ this-> primaryBalance> = $ money) $ this-> primaryBalance = $ this-> primaryBalance-> subtrahieren ($ money); else $ ourMoney = $ this-> primaryBalance-> add ($ this-> secondaryToPrimary ()); $ residualMoney = $ ourMoney-> subtrahieren ($ money); $ this-> primaryBalance = neues Geld (0, $ this-> primaryCurrency); $ this-> secondaryBalance = (neuer Exchange ()) -> convert ($ verbleibenderMoney, $ this-> secondaryCurrency); else $ this-> secondaryBalance = $ this-> secondaryBalance-> subtract ($ money); private function secondaryToPrimary () return (neuer Exchange ()) -> convert ($ this-> secondaryBalance, $ this-> primaryCurrency);
Wow, viele Änderungen mussten vorgenommen werden, um diese automatische Konvertierung zu unterstützen. Wenn wir aus unserer Primärwährung herausziehen und nicht genug Geld haben, rechnen wir unseren Saldo der Sekundärwährung in Primärwährung um und versuchen die Subtraktion erneut. Wenn wir immer noch nicht genug Geld haben, das $ unserGeld
Objekt wird die entsprechende Ausnahme auslösen. Andernfalls setzen wir unser primäres Guthaben auf Null, und wir werden das verbleibende Geld zurück in die Sekundärwährung umtauschen und unser sekundäres Guthaben auf diesen Wert setzen.
Es liegt an der Logik unseres Kontos, eine ähnliche automatische Umrechnung für Sekundärwährung zu implementieren. Wir werden eine solche symmetrische Logik nicht implementieren. Wenn Ihnen die Idee gefällt, betrachten Sie sie als Übung für Sie. Denken Sie auch an eine allgemeinere private Methode, die in beiden Fällen die Magie der automatischen Konvertierung auslösen würde.
Diese komplexe Änderung unserer Logik zwingt uns auch dazu, einen weiteren unserer Tests zu aktualisieren. Wann immer wir eine automatische Konvertierung durchführen wollen, müssen wir eine Balance haben, auch wenn sie nur Null ist.
/ ** * @expectedException Ausnahme * @expectedExceptionMessage Abgezogenes Geld ist mehr als das, was wir haben * / function testItThrowsExceptionIfWeTryToSubtractMoreMoneyThanWeHave () $ this-> account-> setPrimaryCurrency (Currency :: EUR ()); $ money = neues Geld (100, Währung: EUR ()); // Das ist 1 EURO $ this-> Konto-> Einzahlung ($ money); $ this-> account-> setSecondaryCurrency (Currency :: USD ()); $ money = new Money (0, Currency :: USD ()); $ this-> account-> Deposit ($ money); $ this-> account- >draw (neues Geld (150, Währung :: EUR ()));
Die letzte Methode, die wir implementieren müssen Geld
ist zuteilen
. Dies ist die Logik, die entscheidet, was zu tun ist, wenn das Geld zwischen verschiedenen Konten aufgeteilt wird, die nicht exakt gemacht werden können. Wenn wir beispielsweise 0,10 Cent haben und diese zwischen 30 und 70 Prozent zwischen zwei Konten aufteilen möchten, ist das einfach. Ein Konto erhält drei Cent und die anderen sieben. Wenn wir jedoch die gleiche Zuteilung von fünf Cent im Verhältnis 30: 70 vornehmen wollen, haben wir ein Problem. Die genaue Aufteilung würde 1,5 Cent auf einem Konto und 3,5 auf dem anderen Konto betragen. Wir können jedoch keine Cents teilen, also müssen wir unseren eigenen Algorithmus implementieren, um das Geld zuzuteilen.
Es gibt mehrere Lösungen für dieses Problem. Ein gängiger Algorithmus besteht darin, jedem Konto einen Cent nacheinander hinzuzufügen. Wenn ein Konto mehr Cents als seinen genauen mathematischen Wert hat, sollte es von der Zuweisungsliste gestrichen werden und kein Geld mehr erhalten. Hier ist eine grafische Darstellung.
Und ein Test, um unseren Punkt zu beweisen, ist unten.
Funktion testItCanAllocateMoneyBetween2Accounts () $ a1 = $ this-> anAccount (); $ a2 = $ this-> anAccount (); $ money = new Money (5, Currency :: USD ()); $ money-> zuteilen ($ a1, $ a2, 30, 70); $ this-> assertEquals (neues Geld (2, Währung :: USD ()), $ a1-> getPrimaryBalance ()); $ this-> assertEquals (neues Geld (3, Währung :: USD ()), $ a2-> getPrimaryBalance ()); private Funktion anAccount () $ account = neues Konto (1); $ account-> setPrimaryCurrency (Currency :: USD ()); $ Konto-> Einzahlung (neues Geld (0, Währung: USD ())); $ konto zurückgeben;
Wir erstellen einfach eine Geld
Objekt mit fünf Cent und zwei Konten. Wir nennen zuteilen
und erwarten Sie zwei bis drei Werte in den beiden Konten. Wir haben auch eine Hilfsmethode erstellt, um Konten schnell zu erstellen. Der Test schlägt wie erwartet fehl, aber wir können den Test problemlos bestehen.
Funktion zuordnen (Konto $ a1, Konto $ a2, $ a1Percent, $ a2Percent) $ exactA1Balance = $ this-> Betrag * $ a1Percent / 100; $ exactA2Balance = $ this-> Betrag * $ a2Percent / 100; $ oneCent = neues Geld (1, $ this-> Währung); while ($ this-> Betrag> 0) if ($ a1-> getPrimaryBalance () -> getAmount ()) < $exactA1Balance) $a1->Einzahlung ($ OneCent); $ dieser-> Betrag--; if ($ dieser-> Betrag <= 0) break; if ($a2->getPrimaryBalance () -> getAmount () < $exactA2Balance) $a2->Einzahlung ($ OneCent); $ dieser-> Betrag--;
Nun, nicht der einfachste Code, aber er funktioniert einwandfrei, wie das Bestehen unseres Tests beweist. Das einzige, was wir noch tun können, ist, die kleine Vervielfältigung innerhalb der während
Schleife.
Funktion zuordnen (Konto $ a1, Konto $ a2, $ a1Percent, $ a2Percent) $ exactA1Balance = $ this-> Betrag * $ a1Percent / 100; $ exactA2Balance = $ this-> Betrag * $ a2Percent / 100; while ($ this-> Betrag> 0) $ this-> allocateTo ($ a1, $ exactA1Balance); if ($ dieser-> Betrag) <= 0) break; $this->AllocateTo ($ a2, $ exactA2Balance); private Funktion allocateTo ($ account, $ exactBalance) if ($ account-> getPrimaryBalance () -> getAmount () < $exactBalance) $account->Einzahlung (neues Geld (1, $ diese-> Währung)); $ dieser-> Betrag--;
Was ich an diesem kleinen Muster erstaunlich finde, ist die große Anzahl von Fällen, in denen wir es anwenden können.
Wir sind mit unserem Geldmuster fertig. Wir haben gesehen, dass es sich um ein einfaches Muster handelt, das die Besonderheiten des Geldkonzeptes enthält. Wir haben auch gesehen, dass diese Einkapselung die Rechenlast von Account verringert. Das Konto kann sich darauf konzentrieren, das Konzept aus Sicht der Bank auf einer höheren Ebene darzustellen. Konto kann Methoden wie Verbindung mit Kontoinhabern, IDs, Transaktionen und Geld implementieren. Es wird ein Orchestrator sein, kein Taschenrechner. Das Geld wird sich um die Berechnungen kümmern.
Was ich an diesem kleinen Muster erstaunlich finde, ist die große Anzahl von Fällen, in denen wir es anwenden können. Grundsätzlich können Sie jedes Mal, wenn Sie ein Wert-Einheiten-Paar haben, es verwenden. Stellen Sie sich vor, Sie haben eine Wetteranwendung und möchten eine Temperaturdarstellung implementieren. Das wäre das Äquivalent unseres Geldobjekts. Sie können Fahrenheit oder Celsius als Währungen verwenden.
Ein anderer Anwendungsfall liegt vor, wenn Sie eine Kartenanwendung haben und Entfernungen zwischen Punkten darstellen möchten. Sie können dieses Muster einfach verwenden, um zwischen metrischen und imperialen Messungen zu wechseln. Wenn Sie mit einfachen Einheiten arbeiten, können Sie das Exchange-Objekt löschen und die einfache Konvertierungslogik in Ihrem "Money" -Objekt implementieren.
Ich hoffe, Ihnen hat dieses Tutorial gefallen, und ich bin gespannt, wie Sie dieses Konzept auf unterschiedliche Weise nutzen können. Danke fürs Lesen.