Cách tạo ứng dụng OOP hoàn hảo [đã đóng]


98

Gần đây tôi đã cố gắng cho một công ty 'x'. Họ gửi cho tôi một số câu hỏi và bảo tôi chỉ giải một câu.

Vấn đề là như thế này -

Thuế bán hàng cơ bản được áp dụng với thuế suất 10% đối với tất cả hàng hóa, ngoại trừ sách, thực phẩm và các sản phẩm y tế được miễn thuế.
Thuế nhập khẩu là loại thuế bán hàng bổ sung áp dụng đối với tất cả hàng hóa nhập khẩu với thuế suất 5%, không được miễn trừ.

Khi tôi mua các mặt hàng, tôi nhận được một biên lai ghi tên của tất cả các mặt hàng và giá của chúng (bao gồm cả thuế), trong đó có tổng chi phí của các mặt hàng và tổng số thuế bán hàng đã trả.
Các quy tắc làm tròn đối với thuế bán hàng là đối với thuế suất n%, giá bán của p chứa (np / 100 làm tròn đến 0,05 gần nhất) thuế bán hàng.

“Họ nói với tôi rằng họ quan tâm đến Khía cạnh thiết kế của giải pháp của bạn và muốn đánh giá Kỹ năng lập trình hướng đối tượng của tôi .”

Đây là những gì họ đã nói bằng lời của họ

  • Đối với giải pháp, chúng tôi muốn bạn sử dụng Java, Ruby hoặc C #.
  • Chúng tôi quan tâm đến HOÀN THÀNH THIẾT KẾ của giải pháp của bạn và muốn đánh giá Kỹ năng lập trình hướng đối tượng của bạn .
  • Bạn có thể sử dụng các thư viện hoặc công cụ bên ngoài cho mục đích xây dựng hoặc thử nghiệm. Cụ thể, bạn có thể sử dụng các thư viện kiểm thử đơn vị hoặc xây dựng các công cụ có sẵn cho ngôn ngữ bạn đã chọn (ví dụ: JUnit, Ant, NUnit, NAnt, Test :: Unit, Rake, v.v.)
  • Theo tùy chọn, bạn cũng có thể bao gồm giải thích ngắn gọn về thiết kế và các giả định cùng với mã của bạn.
  • Vui lòng lưu ý rằng chúng tôi KHÔNG mong đợi một ứng dụng dựa trên web hoặc một giao diện người dùng toàn diện. Thay vào đó, chúng tôi đang mong đợi một ứng dụng đơn giản, dựa trên bảng điều khiển và quan tâm đến mã nguồn của bạn.

Vì vậy, tôi đã cung cấp mã bên dưới - bạn chỉ có thể sao chép mã dán và chạy trong VS.

class Program
 {
     static void Main(string[] args)
     {
         try
         {
             double totalBill = 0, salesTax = 0;
             List<Product> productList = getProductList();
             foreach (Product prod in productList)
             {
                 double tax = prod.ComputeSalesTax();
                 salesTax += tax;
                 totalBill += tax + (prod.Quantity * prod.ProductPrice);
                 Console.WriteLine(string.Format("Item = {0} : Quantity = {1} : Price = {2} : Tax = {3}", prod.ProductName, prod.Quantity, prod.ProductPrice + tax, tax));
             }
             Console.WriteLine("Total Tax : " + salesTax);
             Console.WriteLine("Total Bill : " + totalBill);                
        }
         catch (Exception ex)
         {
             Console.WriteLine(ex.Message);
         }
         Console.ReadLine();
     }

    private static List<Product> getProductList()
     {
         List<Product> lstProducts = new List<Product>();
         //input 1
         lstProducts.Add(new Product("Book", 12.49, 1, ProductType.ExemptedProduct, false));
         lstProducts.Add(new Product("Music CD", 14.99, 1, ProductType.TaxPaidProduct, false));
         lstProducts.Add(new Product("Chocolate Bar", .85, 1, ProductType.ExemptedProduct, false));

        //input 2
         //lstProducts.Add(new Product("Imported Chocolate", 10, 1, ProductType.ExemptedProduct,true));
         //lstProducts.Add(new Product("Imported Perfume", 47.50, 1, ProductType.TaxPaidProduct,true));

        //input 3
         //lstProducts.Add(new Product("Imported Perfume", 27.99, 1, ProductType.TaxPaidProduct,true));
         //lstProducts.Add(new Product("Perfume", 18.99, 1, ProductType.TaxPaidProduct,false));
         //lstProducts.Add(new Product("Headache Pills", 9.75, 1, ProductType.ExemptedProduct,false));
         //lstProducts.Add(new Product("Imported Chocolate", 11.25, 1, ProductType.ExemptedProduct,true));
         return lstProducts;
     }
 }

public enum ProductType
 {
     ExemptedProduct=1,
     TaxPaidProduct=2,
     //ImportedProduct=3
 }

class Product
 {
     private ProductType _typeOfProduct = ProductType.TaxPaidProduct;
     private string _productName = string.Empty;
     private double _productPrice;
     private int _quantity;
     private bool _isImportedProduct = false;

    public string ProductName { get { return _productName; } }
     public double ProductPrice { get { return _productPrice; } }
     public int Quantity { get { return _quantity; } }

    public Product(string productName, double productPrice,int quantity, ProductType type, bool isImportedProduct)
     {
         _productName = productName;
         _productPrice = productPrice;
         _quantity = quantity;
         _typeOfProduct = type;
         _isImportedProduct = isImportedProduct;
     }

    public double ComputeSalesTax()
     {
         double tax = 0;
         if(_isImportedProduct) //charge 5% tax directly
             tax+=_productPrice*.05;
         switch (_typeOfProduct)
         {
             case ProductType.ExemptedProduct: break;
             case ProductType.TaxPaidProduct:
                 tax += _productPrice * .10;
                 break;
         }
         return Math.Round(tax, 2);
         //round result before returning
     }
 }

bạn có thể bỏ đầu vào mạng và chạy cho các đầu vào khác nhau.

Tôi đã cung cấp giải pháp nhưng tôi đã bị từ chối.

"Họ nói rằng họ không thể xem xét tôi cho các vị trí mở hiện tại của chúng tôi vì giải pháp mã không thỏa đáng."

Xin vui lòng hướng dẫn tôi những gì còn thiếu ở đây. Giải pháp này không phải là một giải pháp OOAD tốt.
Tôi có thể cải thiện kỹ năng OOAD của mình bằng cách nào.
Các tiền bối của tôi cũng nói rằng ứng dụng OOAD hoàn hảo cũng sẽ không hoạt động thực tế.

Cảm ơn


2
Có thể họ mong đợi bạn phân biệt giữa các loại sản phẩm bằng cách sử dụng hệ thống phân cấp kế thừa, thay vì liệt kê? (Mặc dù tôi nghĩ rằng cách tiếp cận đó khá phức tạp đối với kịch bản đã cho.)
Douglas

Tôi đoán là họ đã từ chối giải pháp của bạn vì bạn không xác định bất kỳ giao diện nào.
Chris Gessler

28
Theo nguyên tắc chung, nếu ai đó yêu cầu bạn trong một tình huống phỏng vấn để thể hiện kỹ năng OOP, bạn nên cố gắng tránh sử dụng câu lệnh switch - thay vào đó hãy sử dụng hệ thống phân cấp kế thừa.
Joe

4
Nên được đăng trong bài đánh giá mã.
Derek

Tôi cũng đã đăng ở đó nhưng không thể nhận được giải pháp tốt ở đó. Nhưng mọi người có thể thấy giải pháp mới của tôi mà tôi đã tạo sau khi được người khác trợ giúp codeproject.com/Questions/332077/… tại đây bạn cũng có thể tìm thấy mã mới của tôi.
gãy

Câu trả lời:


246

Trước hết, trời tốt không tính toán tài chính gấp đôi . Làm các phép tính tài chính trong số thập phân ; Đó là những gì nó cho. Sử dụng gấp đôi để giải quyết các vấn đề vật lý , không phải vấn đề tài chính .

Lỗ hổng thiết kế chính trong chương trình của bạn là chính sách đặt sai chỗ . Ai chịu trách nhiệm tính thuế? Bạn đã đặt sản phẩm chịu trách nhiệm tính thuế, nhưng khi bạn mua một quả táo, một cuốn sách hay một chiếc máy giặt, thứ bạn định mua không có trách nhiệm cho bạn biết bạn sẽ phải trả bao nhiêu thuế nó. Chính sách của chính phủ có trách nhiệm cho bạn biết điều đó. Thiết kế của bạn vi phạm hàng loạt nguyên tắc thiết kế OO cơ bản mà các đối tượng phải chịu trách nhiệm về các mối quan tâm của chính chúng chứ không phải của bất kỳ ai khác. Lo ngại của người giặt máy là giặt quần áo của bạn, không tính thuế nhập khẩu đúng không. Nếu luật thuế thay đổi, bạn không muốn thay đổiđối tượng máy giặt , bạn muốn thay đổi đối tượng chính sách .

Vậy, làm thế nào để tiếp cận những loại vấn đề này trong tương lai?

Tôi sẽ bắt đầu bằng cách đánh dấu mọi danh từ quan trọng trong mô tả vấn đề:

Thuế bán hàng cơ bản được áp dụng với thuế suất 10% đối với tất cả hàng hóa , ngoại trừ sách , thực phẩmcác sản phẩm y tế được miễn thuế. Thuế nhập khẩu là loại thuế bán hàng bổ sung áp dụng đối với tất cả hàng hóa nhập khẩu với thuế suất 5%, không được miễn trừ . Khi tôi mua các mặt hàng, tôi nhận được một biên lai liệt kê tên của tất cả các mặt hànggiá của chúng (bao gồm cả thuế ), kết thúc với tổng chi phícủa các mặt hàng và tổng số tiền thuế bán hàng đã trả. Các quy tắc làm tròn đối với thuế bán hàng là đối với thuế suất n%, giá bán của p chứa (np / 100 làm tròn đến 0,05 gần nhất) thuế bán hàng .

Bây giờ, mối quan hệ giữa tất cả những danh từ đó là gì?

  • Thuế bán hàng cơ bản là một loại Thuế bán hàng
  • Thuế nhập khẩu là một loại Thuế bán hàng
  • Thuế bán hàng có Tỷ lệ là số thập phân
  • Sách là một loại vật phẩm
  • Thực phẩm là một loại vật phẩm
  • Sản phẩm y tế là một loại vật phẩm
  • Các mặt hàng có thể là Hàng nhập khẩu
  • Một mục có Tên là một chuỗi
  • Một mặt hàng có Giá kệ là số thập phân. (Lưu ý: một mặt hàng có thực sự có giá không? Hai máy giặt giống nhau có thể được bán với giá khác nhau tại các cửa hàng khác nhau hoặc tại cùng một cửa hàng vào những thời điểm khác nhau. Một thiết kế tốt hơn có thể nói rằng Chính sách giá liên quan đến một Mặt hàng giá của nó.)
  • Chính sách Miễn thuế Bán hàng mô tả các điều kiện mà theo đó Thuế Bán hàng không thể áp dụng cho một Mặt hàng.
  • Biên lai có danh sách các mặt hàng, giá cả và thuế của chúng.
  • Biên lai có tổng số
  • Biên lai có tổng số thuế

... và như thế. Khi bạn đã xử lý được tất cả các mối quan hệ giữa tất cả các danh từ, thì bạn có thể bắt đầu thiết kế một hệ thống phân cấp lớp. Có một Item lớp cơ sở trừu tượng. Sách kế thừa từ nó. Có một lớp trừu tượng SalesTax; BasicSalesTax kế thừa từ nó. Và như thế.


12
bạn cần nhiều hơn những gì vừa được cung cấp? Có vẻ như bạn cần tìm hiểu thêm về cách thực hiện kế thừa và tính đa hình là gì.
Induster

27
@sunder: Câu trả lời này là quá đủ. Bây giờ bạn có trách nhiệm phát triển các kỹ năng của mình, có thể sử dụng điều này làm ví dụ đầu tiên. Lưu ý rằng ví dụ của bạn là định nghĩa của một ví dụ thực tế. Bạn đã thất bại trong một cuộc phỏng vấn ngoài đời thực bởi vì mã đời thực này cần một thiết kế ngoài đời thực mà bạn không cung cấp.
Greg D

9
@Narayan: doublelý tưởng cho các trường hợp trong đó 0,00000001 % câu trả lời đúng là quá đủ. Nếu bạn muốn tính xem một viên gạch rơi xuống nhanh bao nhiêu sau nửa giây, hãy làm phép toán nhân đôi. Khi bạn tính toán tài chính gấp đôi, bạn sẽ nhận được các câu trả lời như giá sau thuế là 43,79999999999999 đô la và điều đó trông thật ngớ ngẩn mặc dù nó cực kỳ gần với câu trả lời đúng.
Eric Lippert

31
+1 Bạn đã đánh dấu một bài tập đáng chú ý, đó là kiểm tra từng danh từ trong bài toán đã nêu, sau đó liệt kê các mối quan hệ của chúng với nhau. Ý tưởng tuyệt vời.
Chris Tonkinson

3
@ Jordão: Trong số thập phân, thêm 0,10 lần mười lần sẽ cho 1,00. Nhưng thêm 1,0 / 333,0 ba trăm ba mươi ba lần không nhất thiết phải cho một ở dạng thập phân hoặc nhân đôi. Trong hệ thập phân, các phân số có lũy thừa mười ở mẫu số được biểu diễn chính xác; trong nhân đôi, nó là phân số với lũy thừa của hai. Bất cứ điều gì khác được đại diện gần đúng.
Eric Lippert

38

Nếu công ty cho biết điều gì đó về các thư viện như NUnit, JUnit hoặc Test :: Unit thì có nhiều khả năng là TDD thực sự nhập vào họ. Trong mẫu mã của bạn không có thử nghiệm nào cả.

Tôi sẽ cố gắng chứng minh kiến ​​thức thực tế về:

  • Bài kiểm tra đơn vị (ví dụ: NUnit)
  • Chế giễu (ví dụ: RhinoMocks)
  • Tính bền bỉ (ví dụ: NHibernate)
  • IoC Container (ví dụ: NSpring)
  • thiết kế mẫu
  • Nguyên lý SOLID

Tôi muốn giới thiệu www.dimecasts.net như một nguồn ấn tượng về các video màn hình miễn phí, chất lượng tốt bao gồm tất cả các chủ đề được đề cập ở trên.


19

Điều này rất chủ quan nhưng đây là một vài điểm mà tôi muốn thực hiện về mã của bạn:

  • Theo tôi bạn đã trộn ProductShoppingCartItem. Productnên có tên sản phẩm, tình trạng thuế, v.v. nhưng không có số lượng. Số lượng không phải là thuộc tính của một sản phẩm - nó sẽ khác nhau đối với mỗi khách hàng của công ty mua sản phẩm cụ thể đó.

  • ShoppingCartItemnên có a Productvà số lượng. Bằng cách đó, khách hàng có thể tự do mua nhiều hơn hoặc ít hơn cùng một sản phẩm. Với thiết lập hiện tại của bạn, điều đó là không thể.

  • Tính toán thuế cuối cùng cũng không nên là một phần của Product- nó phải là một phần của cái gì đó giống như ShoppingCartviệc tính thuế cuối cùng có thể liên quan đến việc biết tất cả các sản phẩm trong giỏ hàng.


Vấn đề duy nhất tôi gặp phải với câu trả lời này là nó mô tả cách xây dựng hệ thống thanh toán sản phẩm tốt hơn (hợp lệ) nhưng không thực sự giải thích rõ về các phương pháp OOP. Điều này có thể được thực hiện bằng bất kỳ ngôn ngữ nào. Nếu không hiển thị một số loại giao diện, kế thừa, đa hình, v.v. thì anh ta vẫn sẽ trượt bài kiểm tra.
Hết giờ

Đề cập đến điểm cuối cùng: Nơi tốt nhất của IMO để tính thuế là loại Máy tính thuế riêng biệt do nguyên tắc tương ứng duy nhất.
Radek

cảm ơn vì đã trả lời nhưng nó như thế nào. mọi công ty có hoạt động trong các mô hình OOPS rộng rãi và thuần túy như vậy không.
gãy

@shyamsunder Không có gì thực sự thuần túy về câu trả lời của tôi. Nó không sử dụng giao diện / kế thừa vốn là những khía cạnh quan trọng của OOD, nhưng nó thể hiện nguyên tắc quan trọng nhất - theo ý kiến ​​của tôi - và đó là đặt trách nhiệm vào vị trí của chúng. Như các câu trả lời khác đã chỉ ra, vấn đề chính với thiết kế của bạn là bạn trộn lẫn trách nhiệm giữa các tác nhân khác nhau và điều đó sẽ dẫn đến các vấn đề khi thêm các tính năng. Hầu hết các phần mềm lớn chỉ có thể phát triển nếu chúng tuân theo các nguyên tắc này.
xxbbcc

Câu trả lời hay nhưng tôi cũng đồng ý rằng việc tính thuế nên là một đối tượng riêng biệt.

14

Trước hết, đây là một câu hỏi phỏng vấn rất hay. Đó là một thước đo tốt về nhiều kỹ năng.

Có nhiều điều bạn cần hiểu để đưa ra câu trả lời tốt ( không có câu trả lời hoàn hảo), cả ở cấp độ cao và cấp độ thấp. Đây là một vài:

  • Mô hình hóa miền -> làm thế nào để bạn tạo ra một mô hình giải pháp tốt? Bạn tạo ra những đối tượng nào? Họ sẽ giải quyết các yêu cầu như thế nào? Tìm kiếm danh từ là một khởi đầu tốt, nhưng làm thế nào để bạn quyết định xem lựa chọn thực thể của bạn có tốt không? Bạn cần những thực thể nào khác? Bạn cần kiến thức miền nào để giải quyết nó?
  • Tách các mối quan tâm, khớp nối lỏng lẻo, tính liên kết cao -> Làm thế nào để bạn tách các phần của thiết kế có mối quan tâm hoặc tỷ lệ thay đổi khác nhau và bạn liên hệ chúng như thế nào? Làm thế nào để bạn giữ cho thiết kế của mình linh hoạt và hiện tại?
  • Kiểm thử đơn vị, tái cấu trúc, TDD -> Quy trình của bạn để đưa ra giải pháp là gì? Bạn có viết các bài kiểm tra, sử dụng các đối tượng giả lập, cấu trúc lại, lặp lại không?
  • Mã sạch, thành ngữ ngôn ngữ -> Bạn có sử dụng các tính năng của ngôn ngữ lập trình của bạn để giúp bạn không? Bạn có viết mã dễ hiểu không? Mức độ trừu tượng của bạn có hợp lý không? Mã có thể bảo trì như thế nào?
  • Công cụ : Bạn có sử dụng kiểm soát nguồn không? Xây dựng công cụ? IDE?

Từ đó, bạn có thể có nhiều cuộc thảo luận thú vị, liên quan đến các nguyên tắc thiết kế (như nguyên tắc SOLID), các mẫu thiết kế, mẫu phân tích, mô hình miền, lựa chọn công nghệ, các lộ trình phát triển trong tương lai (ví dụ: điều gì sẽ xảy ra nếu tôi thêm cơ sở dữ liệu hoặc lớp giao diện người dùng phong phú, những gì cần thay đổi?), đánh đổi, yêu cầu phi chức năng (hiệu suất, khả năng bảo trì, bảo mật, ...), kiểm tra chấp nhận, v.v.

Tôi sẽ không bình luận về cách bạn nên thay đổi giải pháp của mình, chỉ là bạn nên tập trung nhiều hơn vào những khái niệm này.

Nhưng, tôi có thể chỉ cho bạn cách tôi (một phần) đã giải quyết vấn đề này , chỉ như một ví dụ (trong Java). Hãy xem trong Programlớp để biết cách tất cả kết hợp lại với nhau để in biên lai này:

------------------ ĐÂY LÀ ĐƠN HÀNG CỦA BẠN ------------------
(001) Thiết kế theo hướng miền ----- $ 69,99
(001) Phần mềm hướng đối tượng đang phát triển ----- $ 49,99
(001) House MD Phần 1 ----- $ 29,99
(001) House MD Phần 7 ----- $ 34,50
(IMD) Phần mềm hướng đối tượng đang phát triển ----- $ 2,50
(BST) House MD Season 1 ----- $ 3,00
(BST) House MD Season 7 ----- $ 3,45
(IMD) House MD Season 7 ----- $ 1,73
                                SUB-TOTAL ----- $ 184,47
                                TỔNG THUẾ ----- $ 10,68
                                    TỔNG ----- $ 195,15
---------------- CẢM ƠN ĐÃ CHỌN CHÚNG TÔI ----------------

Bạn chắc chắn nên xem qua những cuốn sách đó :-)

Cũng cần lưu ý: giải pháp của tôi vẫn chưa hoàn thiện, tôi chỉ tập trung vào kịch bản con đường hạnh phúc để có một nền tảng tốt để xây dựng.


Tôi đã xem qua giải pháp của bạn và thấy nó khá thú vị. Mặc dù tôi cảm thấy rằng lớp Order không nên chịu trách nhiệm in Reciept. Tương tự, lớp TaxMethod sẽ không chịu trách nhiệm tính thuế. Ngoài ra, TaxMethodPractice không nên chứa danh sách TaxMethod. Thay vào đó, một lớp được gọi là SalesPolicy sẽ chứa danh sách này. Một lớp được gọi là SalesEngine phải được thông qua SalesPolicy, Order và TaxCalculator. SalesEngine sẽ áp dụng Chính sách bán hàng trên các mặt hàng trong Đơn đặt hàng và tính Thuế bằng Công cụ tính thuế
CKing

@bot: quan sát thú vị .... Ngay bây giờ, Orderin biên lai, nhưng Receiptbiết về định dạng của chính nó. Ngoài ra, TaxMethodPractice là một loại chính sách thuế, nó chứa tất cả các loại thuế áp dụng cho một trường hợp nhất định. TaxMethods là công cụ tính thuế. Tôi cảm thấy rằng bạn chỉ thiếu một số lớp ràng buộc cấp cao hơn , như SalesEngine được đề xuất của bạn. Đó là một ý tưởng thú vị.
Jordão

Tôi chỉ cảm thấy rằng mọi lớp phải có một trách nhiệm được xác định rõ ràng và các lớp đại diện cho các đối tượng trong thế giới thực phải hoạt động theo kiểu phù hợp với thế giới thực. Đối với vấn đề đó, một TaxMethod có thể được chia thành hai lớp. Một tiêu chí thuế và một máy tính thuế. Tương tự một Đơn hàng không được in biên lai. Một ReceiptGenerator phải được chuyển cho một Biên nhận để tạo biên lai.
CKing

@bot: Tôi hoàn toàn đồng ý! Thiết kế tốt là RẮN ! TaxMethod một công cụ tính thuế và một TaxEli đủ điều kiệnCheck một tiêu chí thuế. Chúng là những thực thể riêng biệt. Đối với phần tiếp nhận, vâng, việc tách phần tạo ra sẽ cải thiện thiết kế hơn nữa.
Jordão

1
Ý tưởng đó xuất phát từ mẫu đặc tả , hãy xem!
Jordão

12

Ngoại trừ thực tế là bạn đang sử dụng một lớp được gọi là sản phẩm, bạn chưa chứng minh rằng bạn biết kế thừa là gì, bạn chưa tạo nhiều kế thừa có lớp từ Sản phẩm, không có tính đa hình. Vấn đề có thể được giải quyết bằng cách sử dụng nhiều khái niệm OOP (thậm chí chỉ để chứng tỏ rằng bạn biết chúng). Đây là một vấn đề phỏng vấn nên bạn muốn thể hiện mình biết bao nhiêu.

Tuy nhiên, tôi sẽ không chuyển sang trầm cảm ngay bây giờ. Việc bạn không chứng minh chúng ở đây không có nghĩa là bạn chưa biết chúng hoặc không thể học chúng.

Bạn chỉ cần thêm một chút kinh nghiệm với OOP hoặc phỏng vấn.

Chúc may mắn!


thực sự đây là thiết kế đầu tiên của tôi, tôi đã tạo một thiết kế khác nhưng không thể hiển thị cho bạn vì giới hạn ký tự đang vượt quá.
sunder

bạn có thể chứng minh nó với sự giúp đỡ của bất kỳ ví dụ nào.
sunder

@sunder: Bạn chỉ cần cập nhật câu hỏi với thiết kế mới của mình.
Bjarke Freund-Hansen

10

Những người bắt đầu học lập trình với OOP không gặp khó khăn lớn để hiểu ý nghĩa của nó, bởi vì nó cũng giống như trong cuộc sống thực . Nếu bạn có kỹ năng lập trình quen thuộc khác với OO, nó có thể khó hiểu hơn.

Trước hết, hãy tắt màn hình hoặc thoát IDE yêu thích của bạn. Lấy một tờ giấybút chì và lập danh sách các thực thể , quan hệ , con người , máy móc , quy trình , nội dung , v.v. mọi thứ có thể gặp phải trong chương trình cuối cùng của bạn.

Thứ hai, cố gắng lấy các thực thể cơ bản khác nhau . Bạn sẽ hiểu rằng một số có thể chia sẻ thuộc tính hoặc khả năng , bạn phải đặt nó trong các đối tượng trừu tượng . Bạn nên bắt đầu vẽ một lược đồ đẹp cho chương trình của mình.

Tiếp theo, bạn phải đặt fonctionnalities (phương thức, hàm, chương trình con, gọi nó như bạn muốn): ví dụ, một đối tượng sản phẩm sẽ không thể tính thuế bán hàng . Một đối tượng công cụ bán hàng nên.

Đừng cảm thấy rắc rối với tất cả các từ lớn ( giao diện , thuộc tính , đa hình , di sản , v.v.) và các mẫu thiết kế trong lần đầu tiên, thậm chí đừng cố tạo mã đẹp hoặc bất cứ điều gì ... Chỉ cần nghĩ đến các đối tượng đơn giản và giữanhư trong cuộc sống thực .

Sau đó, hãy cố gắng đọc một số văn bản ngắn gọn nghiêm túc về điều này. Tôi nghĩ WikipediaWikibooks là một cách thực sự tốt để bắt đầu và sau đó chỉ cần đọc những thứ về GoF và Mẫu thiết kế UML .


3
+1 cho "Trước hết, hãy tắt màn hình của bạn". Tôi nghĩ rằng sức mạnh của suy nghĩ thường bị nhầm lẫn với sức mạnh của máy tính.
kontur 29/02/12

1
+1 để thực hiện cách tiếp cận đơn giản nhất là sử dụng bút chì và giấy. Nhiều lần người dân bị lẫn lộn khi ngồi ở phía trước của IDE :)
Neeraj Gulia

Một số nhà khoa học cho rằng não của chúng ta không chú ý khi xem màn hình. Khi tôi học thiết kế kiến ​​trúc phần mềm, giáo viên của chúng tôi bắt chúng tôi làm việc trên giấy. Anh ấy không bận tâm đến các phần mềm UML mạnh mẽ. Điều quan trọng là phải hiểu những điều trước.
smonff

4

Đầu tiên, đừng trộn Productlớp với lớp Receipt ( ShoppingCart), lớp quantityphải là một phần của ReceipItem( ShoppingCartItem), cũng như Tax& Cost. Các TotalTax& TotalCostnên là một phần của ShoppingCart.

ProductLớp của tôi , chỉ có Name& Price& một số thuộc tính chỉ đọc như IsImported:

class Product
{
    static readonly IDictionary<ProductType, string[]> productType_Identifiers = 
        new Dictionary<ProductType, string[]>
        {
            {ProductType.Food, new[]{ "chocolate", "chocolates" }},
            {ProductType.Medical, new[]{ "pills" }},
            {ProductType.Book, new[]{ "book" }}
        };

    public decimal ShelfPrice { get; set; }

    public string Name { get; set; }

    public bool IsImported { get { return Name.Contains("imported "); } }

    public bool IsOf(ProductType productType)
    {
        return productType_Identifiers.ContainsKey(productType) &&
            productType_Identifiers[productType].Any(x => Name.Contains(x));
    }
}

class ShoppringCart
{
    public IList<ShoppringCartItem> CartItems { get; set; }

    public decimal TotalTax { get { return CartItems.Sum(x => x.Tax); } }

    public decimal TotalCost { get { return CartItems.Sum(x => x.Cost); } }
}

class ShoppringCartItem
{
    public Product Product { get; set; }

    public int Quantity { get; set; }

    public decimal Tax { get; set; }

    public decimal Cost { get { return Quantity * (Tax + Product.ShelfPrice); } }
}

Phần tính thuế của bạn được kết hợp với Product. Một Sản phẩm không xác định các chính sách thuế mà nó là các lớp Thuế. Dựa trên mô tả của vấn đề, có hai loại Thuế bán hàng: BasicDutythuế. Bạn có thể sử dụng Template Method Design Patternđể đạt được nó:

abstract class SalesTax
{
    abstract public bool IsApplicable(Product item);
    abstract public decimal Rate { get; }

    public decimal Calculate(Product item)
    {
        if (IsApplicable(item))
        {
            //sales tax are that for a tax rate of n%, a shelf price of p contains (np/100)
            var tax = (item.ShelfPrice * Rate) / 100;

            //The rounding rules: rounded up to the nearest 0.05
            tax = Math.Ceiling(tax / 0.05m) * 0.05m;

            return tax;
        }

        return 0;
    }
}

class BasicSalesTax : SalesTax
{
    private ProductType[] _taxExcemptions = new[] 
    { 
        ProductType.Food, ProductType.Medical, ProductType.Book 
    };

    public override bool IsApplicable(Product item)
    {
        return !(_taxExcemptions.Any(x => item.IsOf(x)));
    }

    public override decimal Rate { get { return 10.00M; } }
}

class ImportedDutySalesTax : SalesTax
{
    public override bool IsApplicable(Product item)
    {
        return item.IsImported;
    }

    public override decimal Rate { get { return 5.00M; } }
}

Và cuối cùng là lớp áp dụng thuế:

class TaxCalculator
{
    private SalesTax[] _Taxes = new SalesTax[] { new BasicSalesTax(), new ImportedDutySalesTax() };

    public void Calculate(ShoppringCart shoppringCart)
    {
        foreach (var cartItem in shoppringCart.CartItems)
        {
            cartItem.Tax = _Taxes.Sum(x => x.Calculate(cartItem.Product));
        }

    }
}

Bạn có thể dùng thử chúng tại MyFiddle .


2

Một điểm khởi đầu rất tốt về các quy tắc thiết kế là SOLID nguyên tắc .

Ví dụ, nguyên tắc Mở Đóng tuyên bố rằng nếu bạn muốn thêm chức năng mới, bạn không phải thêm mã vào lớp hiện có mà phải thêm lớp mới.

Đối với ứng dụng mẫu của bạn, điều này có nghĩa là việc thêm thuế bán hàng mới sẽ yêu cầu thêm lớp mới. Điều tương tự cũng xảy ra đối với các sản phẩm khác nhau ngoại lệ đối với quy tắc.

Quy tắc làm tròn rõ ràng là đi trong lớp riêng biệt - nguyên tắc Trách nhiệm duy nhất nói rằng mọi lớp đều có một trách nhiệm duy nhất.

Tôi nghĩ rằng cố gắng tự viết mã sẽ mang lại nhiều lợi ích hơn là chỉ viết một giải pháp tốt và dán nó vào đây.

Một thuật toán đơn giản để viết chương trình được thiết kế hoàn hảo sẽ là:

  1. Viết một số mã giải quyết vấn đề
  2. Kiểm tra xem mã có tuân thủ các nguyên tắc SOLID không
  3. Nếu có vi phạm quy tắc hơn goto 1.

2

Một triển khai OOP hoàn hảo là hoàn toàn có thể tranh cãi. Từ những gì tôi thấy trong câu hỏi của bạn, bạn có thể mô-đun hóa mã dựa trên vai trò mà chúng thực hiện để tính giá cuối cùng như Sản phẩm, Thuế, Sản phẩmDB, v.v.

  1. Productcó thể là một lớp trừu tượng và các kiểu dẫn xuất như Sách, Thực phẩm có thể được kế thừa từ nó. Khả năng áp dụng thuế có thể được quyết định bởi các loại có nguồn gốc. Sản phẩm sẽ cho biết liệu thuế có được áp dụng hay không dựa trên lớp dẫn xuất.

  2. TaxCriteria có thể là một enum và điều này có thể được chỉ định trong quá trình mua hàng (nhập khẩu, khả năng áp dụng Thuế bán hàng).

  3. Taxlớp sẽ tính thuế dựa trên TaxCriteria.

  4. Có một ShoppingCartItemnhư được đề xuất bởi XXBBCC có thể bao gồm các trường hợp Sản phẩm và Thuế và đó là một cách tuyệt vời để tách riêng chi tiết sản phẩm với số lượng, tổng giá với thuế, v.v.

Chúc may mắn.


1

Từ góc độ OOA / D nghiêm ngặt, một vấn đề chính mà tôi thấy là hầu hết các thuộc tính lớp của bạn có tên thừa của lớp trong tên thuộc tính. ví dụ: Giá sản phẩm , loại Sản phẩm . Trong trường hợp này, ở mọi nơi bạn sử dụng lớp này, bạn sẽ có mã quá dài và hơi khó hiểu, ví dụ: product.productName. Xóa tiền tố / hậu tố tên lớp thừa khỏi các thuộc tính của bạn.

Ngoài ra, tôi không thấy bất kỳ lớp học nào liên quan đến việc mua và tạo biên lai như đã được hỏi trong câu hỏi.


1

Đây là một ví dụ tuyệt vời về mẫu OO cho Sản phẩm, Thuế, v.v. Lưu ý việc sử dụng Giao diện, điều cần thiết trong thiết kế OO.

http://www.dreamincode.net/forums/topic/185426-design-patterns-strategy/


3
Tôi muốn tạo sản phẩm một lớp (trừu tượng) hơn là tạo giao diện. Tôi cũng sẽ không biến mỗi sản phẩm thành một lớp riêng biệt. Nhiều nhất tôi sẽ tạo một lớp cho mỗi danh mục.
CodesInChaos

@CodeInChaos - Hầu hết thời gian bạn cần cả hai nhưng nếu bạn đang cố gắng đạt được công việc như một kiến ​​trúc sư, tôi sẽ chọn triển khai Giao diện trên một lớp Tóm tắt.
Chris Gessler

1
Các giao diện trong ví dụ này không có ý nghĩa gì cả. Chúng chỉ dẫn đến sự trùng lặp mã trong mỗi lớp thực hiện chúng. Mỗi lớp thực hiện nó theo cùng một cách.
Piotr Perak

0

Đã tấn công vấn đề Chi phí với Thuế bằng mẫu Khách truy cập.

public class Tests
    {
        [SetUp]
        public void Setup()
        {
        }

        [Test]
        public void Input1Test()
        {
            var items = new List<IItem> {
                new Book("Book", 12.49M, 1, false),
                new Other("Music CD", 14.99M, 1, false),
                new Food("Chocolate Bar", 0.85M, 1, false)};

            var visitor = new ItemCostWithTaxVisitor();

            Assert.AreEqual(12.49, items[0].Accept(visitor));
            Assert.AreEqual(16.49, items[1].Accept(visitor));
            Assert.AreEqual(0.85, items[2].Accept(visitor));
        }

        [Test]
        public void Input2Test()
        {
            var items = new List<IItem> {
                new Food("Bottle of Chocolates", 10.00M, 1, true),
                new Other("Bottle of Perfume", 47.50M, 1, true)};

            var visitor = new ItemCostWithTaxVisitor();

            Assert.AreEqual(10.50, items[0].Accept(visitor));
            Assert.AreEqual(54.65, items[1].Accept(visitor));
        }

        [Test]
        public void Input3Test()
        {
            var items = new List<IItem> {
                new Other("Bottle of Perfume", 27.99M, 1, true),
                new Other("Bottle of Perfume", 18.99M, 1, false),
                new Medicine("Packet of headache pills", 9.75M, 1, false),
                new Food("Box of Chocolate", 11.25M, 1, true)};

            var visitor = new ItemCostWithTaxVisitor();

            Assert.AreEqual(32.19, items[0].Accept(visitor));
            Assert.AreEqual(20.89, items[1].Accept(visitor));
            Assert.AreEqual(9.75, items[2].Accept(visitor));
            Assert.AreEqual(11.80, items[3].Accept(visitor));
        }
    }

    public abstract class IItem : IItemVisitable
    { 
        public IItem(string name,
            decimal price,
            int quantity,
            bool isImported)
            {
                Name = name;
                Price = price;
                Quantity = quantity;
                IsImported = isImported;
            }

        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Quantity { get; set; }
        public bool IsImported { get; set; }

        public abstract decimal Accept(IItemVisitor visitor);
    }

    public class Other : IItem, IItemVisitable
    {
        public Other(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this), 2);
    }

    public class Book : IItem, IItemVisitable
    {
        public Book(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this),2);
    }

    public class Food : IItem, IItemVisitable
    {
        public Food(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this), 2);
    }

    public class Medicine : IItem, IItemVisitable
    {
        public Medicine(string name, decimal price, int quantity, bool isImported) : base(name, price, quantity, isImported)
        {
        }

        public override decimal Accept(IItemVisitor visitor) => Math.Round(visitor.Visit(this), 2);
    }

    public interface IItemVisitable
    {
        decimal Accept(IItemVisitor visitor);
    }

    public class ItemCostWithTaxVisitor : IItemVisitor
    {
        public decimal Visit(Food item) => CalculateCostWithTax(item);

        public decimal Visit(Book item) => CalculateCostWithTax(item);

        public decimal Visit(Medicine item) => CalculateCostWithTax(item);

        public decimal CalculateCostWithTax(IItem item) => item.IsImported ?
            Math.Round(item.Price * item.Quantity * .05M * 20.0M, MidpointRounding.AwayFromZero) / 20.0M + (item.Price * item.Quantity)
            : item.Price * item.Quantity;

        public decimal Visit(Other item) => item.IsImported ?
            Math.Round(item.Price * item.Quantity * .15M * 20.0M, MidpointRounding.AwayFromZero) / 20.0M + (item.Price * item.Quantity)
            : Math.Round(item.Price * item.Quantity * .10M * 20.0M, MidpointRounding.AwayFromZero) / 20.0M + (item.Price * item.Quantity);
    }

    public interface IItemVisitor
    {
        decimal Visit(Food item);
        decimal Visit(Book item);
        decimal Visit(Medicine item);
        decimal Visit(Other item);
    }

Chào mừng bạn đến với stackoverflow. Hãy đảm bảo rằng bạn giải thích câu trả lời của mình cho câu hỏi. OP không chỉ tìm kiếm một giải pháp mà tại sao một giải pháp tốt hơn / tệ hơn.
Simon.SA
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.