Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. PHP
Code

Money Pattern Phương pháp thích hợp để đại biểu cho các cặp đơn vị - giá trị

by
Difficulty:IntermediateLength:LongLanguages:

Vietnamese (Tiếng Việt) translation by Thai An (you can also view the original English article)

Money Pattern, do Martin Fowler định nghĩa và được xu tất bản trong mô hình kiến trúc ứng dụng doanh nghiệp, là một cách tuyệt vời để thể hiện các cặp đơn vị -giá trị. Nó được gọi là Money Pattern (mô hình tiền tệ) vì nó nổi bật lên trong bối cảnh tài chính và chúng tôi sẽ minh họa việc sử dụng mô hình trong bằng PHP.


Một tài khoản kiểu như PayPal

Tôi không hiểu PayPal được triển khai như thế nào, nhưng tôi nghĩ nên lấy chức năng của nó làm ví dụ. Hãy để tôi cho bạn biết ý tôi là gì, tài khoản PayPal của tôi có hai loại tiền: US đô và Euro. PayPal tách biệt hai giá trị này, nhưng tôi có thể nhận bằng bất kỳ loại tiền nào, tôi có thể thấy tổng số tiền của mình ở bất kỳ loại tiền nào trong hai loại tiền và tôi có thể trích xuất một trong hai loại. Cho mục đích của ví dụ này, hãy tưởng tượng rằng chúng tôi trích xuất bằng bất kỳ loại tiền tệ nào và chuyển đổi tự động được thực hiện nếu số dư của loại tiền cụ thể đó thấp hơn số tiền chúng tôi muốn chuyển nhưng vẫn có đủ tiền bằng loại tiền khác. Ngoài ra, chúng tôi sẽ giới hạn ví dụ chỉ có hai loại tiền tệ.


Lấy một tài khoản

Nếu tôi đã tạo và sử dụng một đối tượng Account, tôi muốn khởi tạo nó bằng số tài khoản.

Điều này rõ ràng sẽ thất bại vì chúng tôi chưa có class Account

Chà, viết nó trong một file "Account.php" mới và yêu cầu nó trong test, đã giúp nó vượt qua. Tuy nhiên, tất cả điều này được thực hiện chỉ để khiến chúng ta thoải mái với ý tưởng. Tiếp theo, tôi đang nghĩ đến việc lấy id của tài khoản.

Thực sự tôi đã thay đổi test trước đó thành test này. Không cần phải giữ test đầu tiên. Cứ để mặc nó, có nghĩa là nó thúc ép tôi phải suy nghĩ về class Account và thực sự phải tạo ra nó. Giờ chúng ta có thể tiếp tục.

Test đã vượt qua và Account bắt đầu trông giống như một class thực sự.


Tiền tệ

Dựa trên sự tương đồng PayPal của chúng tôi, chúng tôi có thể muốn xác định loại tiền chính và tiền phụ cho tài khoản của mình.

Bây giờ test trên sẽ buộc chúng ta viết đoạn code sau.

Hiện tại, chúng tôi đang biến tiền tệ trở thành một string đơn giản. Có thể trong tương lai điều này sẽ thay đổi, nhưng hiện giờ thì chưa.


Đưa tiền cho tôi

Có vô số lý do tại sao không biểu đạt tiền tệ hư một giá trị đơn giản. Các phép tính cho float? Bất kỳ ai? Còn phân số tiền tệ thì sao? Chúng ta có nên có 10, 100 hoặc 1000 xu bằng những ngoại tệ? Chà, đây là một vấn đề khác chúng ta sẽ phải tránh. Còn việc phân bổ những xu không thể chia được?

Có quá nhiều vấn đề về ngoại tệ khi làm việc với tiền khi chuyển thành code, vì vậy chúng ta sẽ đi thẳng vào giải pháp, Money Pattern. Đây là một pattern (mô hình) khá đơn giản, với những lợi thế lớn và nhiều trường hợp sử dụng, vượt xa lĩnh vực tài chính. Bất cứ khi nào bạn phải biểu đạt cho một cặp đơn vị giá trị, có lẽ bạn nên sử dụng pattern này.

UML

Money Pattern về cơ bản là một class gói gọn một số tiền và kiểu tiền tệ. Sau đó, nó định nghĩa tất cả các phép toán dựa trên giá trị liên quan đến tiền tệ. "allocate()" là một hàm đặc biệt để phân phối một lượng tiền cụ thể giữa hai hoặc nhiều người nhận.

Vì vậy, với tư cách là người sử dụng của Money, tôi muốn có thể thực hiện việc này trong một test:

Nhưng điều đó sẽ không hiệu quả. Chúng tôi cần cả Money và Currency. Thậm chí nhiều hơn, chúng ta cần Currency trước cả Money. Đây sẽ là một class đơn giản, vì vậy tôi sẽ bỏ qua việc test nó ngay bây giờ. Tôi khá chắc chắn rằng IDE có thể tạo ra gần như toàn bộ code cho tôi.

Đó là đủ cho ví dụ của chúng tôi. Có hai hàm static cho tiền USD và EUR. Trong một ứng dụng thực tế, có lẽ chúng ta sẽ có một constructor chung có tham số và tải tất cả loại tiền từ bảng cơ sở dữ liệu hoặc, thậm chí tốt hơn, từ file text.

Tiếp theo, bao gồm hai file mới trong test:

Test này vẫn thất bại, nhưng ít nhất hiện giờ có thể tìm thấy Currency. Chúng tôi tiếp tục với triển khai Money tối thiểu. Nhiều hơn một chút so với test yêu cầu, lần nữa là code tự động sinh ra.

Xin lưu ý, chúng tôi ép buộc kiểu Currency cho tham số thứ hai trong hàm constructor của chúng tôi. Đây là cách hay để tránh khách hàng của chúng tôi gửi rác dưới dạng tiền tệ.


So sánh tiền

Điều đầu tiên xuất hiện trong đầu tôi sau khi đối tượng tối thiểu khởi chạy là tôi sẽ phải so sánh các đối tượng money bằng cách nào đó. Sau đó, tôi nhớ rằng PHP khá thông minh khi so sánh các đối tượng, vì vậy tôi đã tạo ra test này.

Vâng, test đó thực sự đã vượt qua. Hàm "assertEquals" có thể so sánh hai đối tượng và thậm chí cả điều kiện đẳng thức tích hợp từ PHP "==" đang cho tôi biết điều tôi mong đợi. Tốt.

Nhưng, nếu chúng ta quan tâm đến một đối tượng lớn hơn cái kia thì sao? Trước sự ngạc nhiên của tôi, test sau đây cũng thành công mà không gặp vấn đề gì.

Điều này dẫn chúng ta đến ...

... một test và nó vượt qua ngay lập tức.


Cộng, trừ, nhân

Thấy rất nhiều phép màu của PHP thực sự hiệu quả khi so sánh, tôi không thể cưỡng lại việc thử cái này.

Test đã thất bại và cho tôi biết rằng:

Hừm. Điều này khá rõ. Tại thời điểm này, chúng tôi phải đưa ra quyết định. Có thể tiếp tục bài tập này với PHP, nhưng cách tiếp cận này ở một số thời điểm chuyển bài hướng dẫn thành PHP cheatsheet thay vì một design patter (mô hình thiết kế). Vì vậy, hãy đưa ra quyết định để thực hiện các phương thức thực tế để thêm, trừ và nhân các đối tượng tiền.

Test này cũng thất bại, nhưng với lỗi cho chúng tôi biết không có phương thức "add" trong Money.

Để cộng hai đối tượng Money, chúng ta cần một cách để lấy số lượng đối tượng chúng ta truyền vào làm đối số. Tôi thích viết một getter, nhưng đặt biến class thành public cũng sẽ là một giải pháp chấp nhận được. Nhưng nếu chúng ta muốn cộng Đô vào Euro thì sao?

Có một số cách để xử lý các phép tính trên các đối tượng Money với các loại tiền tệ khác nhau. Chúng tôi sẽ đề ra một ngoại lệ và chờ kết quả test từ nó. Ngoài ra, chúng tôi có thể triển khai cơ chế chuyển đổi tiền tệ trong ứng dụng của mình, gọi nó, chuyển đổi cả hai đối tượng Money thành một số loại tiền tệ mặc định và so sánh chúng. Hoặc, nếu chúng ta có một thuật toán chuyển đổi tiền tệ phức tạp hơn, chúng ta luôn có thể chuyển đổi từ loại này sang loại khác và so sánh với loại tiền được chuyển đổi đó. Vấn đề là khi chuyển đổi xảy ra, phí chuyển đổi phải được xem xét và mọi thứ sẽ trở nên khá phức tạp. Vì vậy, hãy bỏ ngoại lệ đó đi và tiếp tục.

Điều đó tốt hơn. Chúng tôi kiểm tra xem các loại tiền tệ có khác nhau không và đưa ra một ngoại lệ. Tôi đã viết nó như một phương thức private, bởi vì tôi biết chúng ta cũng sẽ cần nó trong các phép toán khác.

Phép trừ và phép nhân rất giống với phép cộng, vì vậy đây là code và bạn có thể tìm thấy các test trong code nguồn đính kèm.

Với phép trừ, chúng ta phải đảm bảo có đủ tiền và với phép nhân, chúng ta phải thực hiện các hành động để làm tròn (lên hoặc xuống) để phép chia (nhân với số nhỏ hơn một) sẽ không cho ra kết quả "nửa xu". Chúng tôi để số tiền của mình theo xu, đơn vị thấp nhất của tiền tệ. Chúng tôi không thể chia nó nhiều hơn.


Giới thiệu tiền tệ vào tài khoản của chúng tôi

Chúng tôi gần như có Money và Currency. Đã đến lúc giới thiệu những đối tượng này vào Account. Chúng tôi sẽ bắt đầu với Money và thay đổi test của chúng tôi cho phù hợp.

Do tính chất dynamic typing của PHP, test này vượt qua mà không gặp vấn đề gì. Tuy nhiên, tôi muốn thúc ép các phương thức trong Account sử dụng các đối tượng Money và không chấp nhận đối tượng nào khác. Điều này không bắt buộc, nhưng tôi thấy những kiểu hinting cực kỳ hữu dụng khi người khác cần hiểu code của chúng tôi.

Bây giờ, rõ ràng với bất kỳ ai đọc code này lần đầu tiên thì dĩ nhiên Account hiệu quả với Currency.


Giới thiệu Money với Account của chúng tôi

Hai hành động cơ bản mà bất kỳ tài khoản nào cũng phải cung cấp là: deposit (tiền gửi) - nghĩa là bổ sung tiền vào tài khoản - và withdraw (rút tiền) - nghĩa là xóa tiền khỏi tài khoản. Tiền gửi có một nguồn và rút tiền có một đích đến khác, ngoài tài khoản hiện tại của chúng tôi. Chúng tôi sẽ không đi vào chi tiết về cách triển khai các giao dịch này, chúng tôi sẽ chỉ tập trung vào việc triển khai các hiệu ứng này có trên tài khoản của chúng tôi. Vì vậy, chúng ta có thể hình dung một test thế này cho việc gửi tiền.

Điều này sẽ yêu cầu chúng ta phải tạo ra khá nhiều code triển khai.

OK. Tôi biết, tôi đã viết nhiều hơn những điều thực sự cần thiết. Nhưng tôi không muốn làm bạn phát chán với những bước quá đơn giản và tôi cũng khá chắc chắn rằng code cho juniorBalance sẽ hoạt động chính xác. Gần như hoàn toàn được tạo ra bởi IDE. Tôi thậm chí sẽ bỏ qua việc test nó. Trong khi code này làm cho test của chúng tôi vượt qua, chúng tôi phải tự hỏi điều gì xảy ra khi chúng tôi gửi tiền tiếp theo? Chúng tôi muốn tiền của chúng tôi được thêm vào số dư trước đó.

Vâng, thất bại. Vì vậy, chúng tôi phải cập nhật code của chúng tôi.

Điều này tốt hơn nhiều. Chúng tôi có thể hoàn thành với phương thức deposit và chúng tôi có thể tiếp tục withdraw.

Đây là một test đơn giản. Giải pháp cũng rất đơn giản.

Chà, nó hiệu quả, nhưng nếu chúng ta muốn sử dụng loại Currency không có trong tài khoản thì sao? Chúng ta nên gửi một Excpetion cho vấn đề đó.

Điều đó cũng sẽ yêu cầu chúng tôi kiểm tra các tiền tệ của chúng tôi.

Nhưng nếu chúng ta muốn rút nhiều hơn số chúng ta có thì sao? Trường hợp đó đã được giải quyết khi chúng tôi thực hiện phép trừ trên Money. Dưới đây là test chứng minh điều đó.


Xử lý rút tiền và chuyển đổi

Một trong những điều khó giải quyết hơn khi chúng ta làm việc với nhiều loại tiền tệ là chuyển đổi giữa chúng. Cái đẹp của pattern thiết kế này là cho phép chúng ta đơn giản hóa phần nào vấn đề này bằng cách cô lập và gói gọn nó trong class riêng của nó. Mặc dù logic trong một class Exchange có thể rất phức tạp, nhưng việc sử dụng nó trở nên dễ dàng hơn nhiều. Với mục đích của hướng dẫn này, hãy hình dung rằng chúng ta chỉ có một số logic Exchange rất cơ bản. 1 EUR = 1,5 USD.

Nếu chúng tôi chuyển đổi từ EUR sang USD, chúng tôi nhân giá trị lên 1,5, nếu chúng tôi chuyển đổi từ USD sang EUR, chúng tôi chia giá trị cho 1,5, nếu không, nghĩa là chúng tôi đang chuyển đổi hai loại tiền tệ cùng loại, vì vậy chúng tôi không làm gì và trả về số tiền. Tất nhiên, trong thực tế sẽ là một class phức tạp hơn nhiều.

Giờ đây, khi có class Exchange, Account có thể đưa ra các quyết định khác nhau khi chúng tôi muốn rút Money bằng một loại tiền tệ, nhưng chúng tôi không đưa ra một loại tiền cụ thể. Đây là một test nhằm minh họa tốt hơn.

Chúng tôi thiết lập loại tiền chính của tài khoản của chúng tôi thành USD và gửi đi 1 đô. Sau đó, chúng tôi thiết lập tiền tệ thứ cấp thành EUR và gửi 1 Euro. Sau đó, chúng tôi rút ra 2 đô. Cuối cùng, chúng tôi hy vọng sẽ vẫn còn 0 đô và 0,34 Euro. Tất nhiên test này đưa ra một exception, vì vậy chúng tôi phải triển khai một giải pháp cho vấn đề nan giải này.

Ồ, rất nhiều thay đổi đã được triển khai để hỗ trợ việc chuyển đổi tự động này. Điều đang xảy ra là nếu chúng ta trong trường hợp trích xuất từ tiền tệ chính của mình và chúng ta không có đủ tiền, chúng ta sẽ chuyển đổi số dư của tiền tệ thứ cấp thành tiền chính và thử lại phép trừ. Nếu chúng ta vẫn không có đủ tiền, đối tượng $ourMoney sẽ đưa ra exception phù hợp. Mặt khác, chúng tôi sẽ xét số dư chính của mình thành 0 và chúng tôi sẽ chuyển đổi số tiền còn lại thành tiền tệ thứ cấp và đặt số dư thứ cấp của chúng tôi thành giá trị đó.

Vẫn theo logic tài khoản của chúng tôi để khai triển chuyển đổi tự động tương tự cho tiền tệ thứ cấp. Chúng tôi sẽ không thực hiện một logic đối xứng như vậy. Nếu bạn thích ý tưởng, hãy coi nó như một bài tập cho bạn. Ngoài ra, hãy nghĩ đến một phương thức private cơ bản hơn sẽ làm thực hiện chuyển đổi tự động trong cả hai trường hợp.

Sự thay đổi phức tạp này đối với logic của chúng tôi cũng buộc phải cập nhật một trong những test khác. Bất cứ khi nào chúng tôi muốn tự động chuyển đổi, chúng tôi phải có số dư, ngay cả khi nó chỉ bằng không.


Phân bổ tiền giữa các tài khoản

Phương thức cuối cùng chúng ta cần thực hiện trên Money là allocate. Đây là logic quyết định những việc cần làm khi phân chia tiền giữa các tài khoản khác nhau mà không thể thực hiện chính xác. Ví dụ: nếu chúng tôi có 0,10 xu và chúng tôi muốn phân bổ chúng giữa hai tài khoản theo tỷ lệ phần trăm 30-70, điều đó thật dễ dàng. Một tài khoản sẽ nhận được ba xu và bảy xu còn lại. Tuy nhiên, nếu chúng tôi muốn thực hiện phân bổ tỷ lệ 30-70 tương tự cho năm xu, có vấn đề. Phân bổ chính xác sẽ là 1,5 xu trong một tài khoản và 3,5 trong tài khoản khác. Nhưng chúng tôi không thể chia xu, vì vậy chúng tôi phải thực hiện thuật toán của riêng mình để phân bổ tiền.

Có thể có một số giải pháp cho vấn đề này, một thuật toán phổ biến là thêm một xu liên tục vào mỗi tài khoản. Nếu một tài khoản có nhiều xu hơn giá trị toán học chính xác của nó, tài khoản đó sẽ bị loại khỏi danh sách phân bổ và không nhận thêm tiền. Đây là biểu diễn đồ họa.

Allocations

Và một test để chứng minh quan điểm của chúng tôi như bên dưới.

Chúng tôi chỉ tạo một đối tượng Money với năm xu và hai tài khoản. Chúng tôi gọi hàm allocate và hy vọng sẽ có 2 đến 3 giá trị trong hai tài khoản. Chúng tôi cũng đã tạo một phương thức helper để nhanh chóng tạo ra các tài khoản. Test thất bại, như mong đợi, nhưng chúng ta có thể giúp nó thành công khá dễ dàng.

Không phải là code đơn giản nhất, nhưng nó hiệu quả khi việc test thành công vượt qua của chúng tôi đã chứng minh điều đó. Điều duy nhất chúng ta vẫn có thể làm với code này là giảm sự trùng lặp nhỏ bên trong vòng lặp while.


Tổng kết

Điều tôi thấy tuyệt vời với pattern nhỏ này là một loạt các trường hợp để chúng ta có thể áp dụng.

Chúng tôi đã thực hiện với money pattern. Chúng tôi thấy rằng đó là một pattern khá đơn giản, nó gói gọn các chi tiết cụ thể của khái niệm về tiền. Chúng tôi cũng thấy rằng việc đóng gói này làm giảm bớt gánh nặng tính toán từ Account. Account có thể tập trung vào việc đại diện cho khái niệm ở cấp độ cao hơn, từ góc nhìn của ngân hàng. Account có thể thực hiện các phương thức như kết nối với chủ tài khoản, ID, giao dịch và tiền. Nó sẽ như một dàn nhạc không phải là một máy tính. Tiền sẽ đảm nhiệm việc tính toán.

Điều tôi thấy tuyệt vời với pattern nhỏ này là một loạt các trường hợp chúng ta có thể áp dụng nó. Về cơ bản, mỗi khi bạn có một cặp đơn vị - giá trị, bạn có thể dùng nó. Hãy tưởng tượng bạn có một ứng dụng về thời tiết và bạn muốn triển khai một biễu diễn nhiệt độ. Đó sẽ là tương đương với đối tượng Money của chúng tôi. Bạn có thể dùng Fahrenheit hoặc Celsius làm tiền tệ.

Một trường hợp sử dụng khác là khi bạn có một ứng dụng bản đồ và bạn muốn diễn tả khoảng cách giữa các địa điểm. Bạn có thể dễ dàng sử dụng pattern này để chuyển đổi giữa các phép đo Metric hoặc Imperial. Khi bạn làm việc với các đơn vị đơn giản, bạn có thể bỏ đối tượng Exchange và triển khai logic chuyển đổi đơn giản bên trong đối tượng "Money" của mình.

Vậy tôi hy vọng bạn thích hướng dẫn này và tôi rất muốn nghe bạn sử dụng khái niệm này theo những cách khác nhau. Cảm ơn bạn đã đọc bài viết.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.