Cara membuat aplikasi OOP yang sempurna [tutup]

98

Baru-baru ini saya mencoba untuk perusahaan 'x'. Mereka mengirimi saya beberapa pertanyaan dan mengatakan kepada saya untuk menyelesaikan hanya satu.

Masalahnya seperti ini -

Pajak penjualan dasar berlaku dengan tarif 10% untuk semua barang, kecuali buku, makanan, dan produk medis yang dikecualikan.
Bea masuk adalah pajak penjualan tambahan yang berlaku atas semua barang impor dengan tarif 5%, tanpa pengecualian.

Ketika saya membeli barang, saya menerima tanda terima yang mencantumkan nama semua barang dan harganya (termasuk pajak), diakhiri dengan biaya total barang, dan jumlah total pajak penjualan yang dibayarkan.
Aturan pembulatan untuk pajak penjualan adalah bahwa untuk tarif pajak n%, harga rak p berisi (np / 100 dibulatkan ke dekat 0,05) jumlah pajak penjualan.

"Mereka mengatakan kepada saya, mereka tertarik dengan Aspek Desain solusi Anda dan ingin mengevaluasi Keterampilan Pemrograman Berorientasi Objek saya ."

Inilah yang mereka katakan dengan kata-kata mereka sendiri

  • Untuk solusinya, kami ingin Anda menggunakan Java, Ruby atau C #.
  • Kami tertarik dengan ASPEK DESAIN solusi Anda dan ingin mengevaluasi Keterampilan Pemrograman Berorientasi Objek Anda .
  • Anda dapat menggunakan pustaka atau alat eksternal untuk membangun atau menguji tujuan. Secara khusus, Anda dapat menggunakan pustaka pengujian unit atau alat build yang tersedia untuk bahasa pilihan Anda (misalnya, JUnit, Ant, NUnit, NAnt, Test :: Unit, Rake, dll.)
  • Secara opsional, Anda juga dapat menyertakan penjelasan singkat tentang desain dan asumsi Anda bersama dengan kode Anda.
  • Mohon dicatat bahwa kami TIDAK mengharapkan aplikasi berbasis web atau UI yang komprehensif. Sebaliknya, kami mengharapkan aplikasi berbasis konsol yang sederhana dan tertarik dengan kode sumber Anda.

Jadi saya berikan kode di bawah ini - Anda cukup menyalin kode tempel dan menjalankannya di 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
     }
 }

Anda dapat membatalkan input dan menjalankan input yang berbeda.

Saya memberikan solusinya tetapi saya ditolak.

"Mereka berkata, mereka tidak dapat mempertimbangkan saya untuk posisi terbuka kami saat ini karena solusi kode tidak memuaskan."

Tolong bimbing saya apa yang hilang di sini. Apakah solusi ini bukan solusi OOAD yang baik.
Bagaimana cara meningkatkan keterampilan OOAD saya.
Senior saya juga bilang aplikasi OOAD yang sempurna juga tidak akan bekerja secara praktis.

Terima kasih

memisahkan
sumber
2
Mungkin mereka mengharapkan Anda untuk membedakan di antara jenis produk menggunakan hierarki warisan, bukan enumerasi? (Meskipun saya pikir pendekatan itu akan agak berbelit-belit untuk skenario yang diberikan.)
Douglas
Dugaan saya adalah bahwa mereka menolak solusi Anda karena Anda tidak mendefinisikan antarmuka apa pun.
Chris Gessler
28
Sebagai aturan praktis, jika seseorang meminta Anda dalam situasi wawancara untuk mendemonstrasikan keterampilan OOP, Anda harus mencoba menghindari penggunaan pernyataan switch - alih-alih gunakan hierarki warisan.
Joe
4
Harus diposting dalam tinjauan kode.
Derek
Saya telah memposting di sana juga tetapi tidak bisa mendapatkan solusi yang baik di sana. Tetapi semua orang dapat melihat solusi baru saya yang saya buat setelah bantuan dari orang lain codeproject.com/Questions/332077/… di sini Anda juga dapat menemukan kode baru saya.
mulai

Jawaban:

246

Pertama, surga tidak melakukan perhitungan keuangan dua kali lipat . Lakukan perhitungan keuangan dalam desimal ; untuk itulah. Gunakan ganda untuk memecahkan masalah fisika , bukan masalah keuangan .

Kelemahan desain utama dalam program Anda adalah kebijakan berada di tempat yang salah . Siapa yang bertugas menghitung pajak? Anda telah menempatkan produk yang bertanggung jawab untuk menghitung pajak, tetapi ketika Anda membeli apel atau buku atau mesin cuci, barang yang akan Anda beli tidak bertanggung jawab untuk memberi tahu Anda berapa banyak pajak yang akan Anda bayarkan. Itu. Kebijakan pemerintah bertanggung jawab untuk memberi tahu Anda hal itu. Desain Anda secara besar-besaran melanggar prinsip desain OO dasar bahwa objek harus bertanggung jawab atas masalah mereka sendiri , dan bukan orang lain. Masalah mesin cuci adalah mencuci pakaian Anda, bukan membebankan bea masuk yang benar. Jika undang-undang perpajakan berubah, Anda tidak ingin mengubahnyaobjek mesin cuci , Anda ingin mengubah objek kebijakan .

Jadi, bagaimana cara mengatasi masalah semacam ini di masa depan?

Saya akan mulai dengan menyoroti setiap kata benda penting dalam deskripsi masalah:

Pajak penjualan dasar berlaku dengan tarif 10% untuk semua barang , kecuali buku , makanan , dan produk medis yang dikecualikan. Bea masuk adalah pajak penjualan tambahan yang berlaku atas semua barang impor dengan tarif 5%, tanpa pengecualian . Ketika saya membeli barang, saya menerima tanda terima yang mencantumkan nama semua barang dan harganya (termasuk pajak ), diakhiri dengan total biayaitem, dan jumlah total pajak penjualan yang dibayarkan. Aturan pembulatan untuk pajak penjualan adalah bahwa untuk tarif pajak n%, harga rak p berisi (np / 100 dibulatkan ke dekat 0,05) jumlah pajak penjualan .

Sekarang, apa hubungan antara semua kata benda itu?

  • Pajak Penjualan Dasar adalah sejenis Pajak Penjualan
  • Bea Masuk adalah salah satu jenis Pajak Penjualan
  • Pajak Penjualan memiliki Tarif yang merupakan Desimal
  • Buku adalah sejenis Item
  • Makanan adalah sejenis Barang
  • Produk Medis adalah sejenis Barang
  • Barang bisa jadi Barang Impor
  • Item memiliki Nama yang merupakan String
  • Item memiliki Harga Rak yang merupakan Desimal. (Catatan: apakah suatu barang benar-benar memiliki harga? Dua mesin cuci yang identik mungkin dijual dengan harga berbeda di toko yang berbeda, atau di toko yang sama pada waktu yang berbeda. Desain yang lebih baik mungkin mengatakan bahwa Kebijakan Penetapan Harga terkait dengan Barang tersebut Harganya.)
  • Kebijakan Pembebasan Pajak Penjualan menjelaskan kondisi di mana Pajak Penjualan tidak dapat diterapkan pada suatu Barang.
  • Tanda Terima memiliki daftar Barang, harga dan pajaknya.
  • Tanda Terima memiliki total
  • Tanda terima memiliki pajak total

... dan seterusnya. Setelah Anda memiliki semua hubungan antara semua kata benda yang berhasil, Anda dapat mulai merancang hierarki kelas. Ada Item kelas dasar abstrak. Buku mewarisi darinya. Ada kelas abstrak SalesTax; BasicSalesTax mewarisi darinya. Dan seterusnya.

Eric Lippert
sumber
12
Anda membutuhkan lebih dari apa yang baru saja disediakan? Sepertinya Anda perlu mempelajari lebih lanjut tentang bagaimana pewarisan diterapkan, dan apa itu polimorfisme.
Induster
27
@ Under: Jawaban ini lebih dari cukup. Sekarang menjadi tanggung jawab Anda untuk mengembangkan keterampilan Anda, mungkin menggunakan ini sebagai contoh pertama. Perhatikan bahwa contoh Anda adalah definisi dari contoh kehidupan nyata. Anda gagal dalam wawancara kehidupan nyata karena kode kehidupan nyata ini membutuhkan desain kehidupan nyata yang tidak Anda berikan.
Greg D
9
@Narayan: doubleideal untuk situasi di mana berada dalam 0,00000001% dari jawaban yang benar sudah lebih dari cukup. Jika Anda ingin mengetahui seberapa cepat batu bata jatuh setelah setengah detik, lakukan penghitungan ganda. Ketika Anda melakukan aritema keuangan dalam dua kali lipat, Anda akan mendapatkan jawaban seperti harga setelah pajak adalah 43.79999999999999 dolar dan itu terlihat konyol meskipun sangat dekat dengan jawaban yang benar.
Eric Lippert
31
+1 Anda telah menyoroti latihan yang luar biasa, yaitu memeriksa setiap kata benda dalam masalah yang dinyatakan, dan kemudian menyebutkan hubungannya satu sama lain. Ide yang hebat.
Chris Tonkinson
3
@ Jordão: Dalam desimal, menambahkan 0.10 sepuluh kali akan menghasilkan 1.00. Tetapi menambahkan 1.0 / 333.0 tiga ratus tiga puluh tiga kali tidak selalu memberikan satu desimal atau dua kali lipat. Dalam desimal, pecahan yang memiliki pangkat sepuluh di penyebutnya sama persis; di ganda, itu adalah pecahan dengan pangkat dua. Yang lainnya direpresentasikan kira-kira.
Eric Lippert
38

Jika perusahaan memberi tahu sesuatu tentang pustaka seperti NUnit, JUnit atau Test :: Unit lebih dari mungkin bahwa TDD benar-benar penting bagi mereka. Dalam contoh kode Anda tidak ada tes sama sekali.

Saya akan mencoba mendemonstrasikan pengetahuan praktis tentang:

  • Tes unit (mis. NUnit)
  • Mocking (mis. RhinoMocks)
  • Kegigihan (mis. NHibernate)
  • IoC Containers (mis. NSpring)
  • pola desain
  • Prinsip SOLID

Saya ingin merekomendasikan www.dimecasts.net sebagai sumber screencast gratis dan berkualitas baik yang mencakup semua topik yang disebutkan di atas.

Radek
sumber
19

Ini sangat subjektif, tetapi berikut adalah beberapa poin yang akan saya buat tentang kode Anda:

  • Menurut pendapat saya, Anda bercampur Productdan ShoppingCartItem. Productharus memiliki nama produk, status pajak, dll. tetapi bukan kuantitas. Kuantitas bukanlah properti suatu produk - akan berbeda untuk setiap pelanggan perusahaan yang membeli produk tersebut.

  • ShoppingCartItemharus memiliki Productdan kuantitas. Dengan begitu pelanggan dapat dengan bebas membeli lebih banyak atau lebih sedikit produk yang sama. Dengan penyiapan Anda saat ini, itu tidak mungkin.

  • Menghitung pajak final juga tidak boleh menjadi bagian dari Product- harus menjadi bagian dari sesuatu seperti ShoppingCartkarena penghitungan pajak final mungkin melibatkan mengetahui semua produk di keranjang.

xxbbcc
sumber
Satu-satunya masalah yang saya miliki dengan jawaban ini adalah menjelaskan bagaimana membangun sistem pembayaran produk yang lebih baik (yang valid) tetapi tidak terlalu menguraikan metodologi OOP. Ini dapat diterapkan dalam bahasa apa pun. Tanpa menunjukkan semacam antarmuka, pewarisan, polimorfisme, dll. Dia masih akan gagal dalam ujian.
Batas waktu
Mengacu pada poin terakhir: Tempat terbaik IMO untuk penghitungan pajak adalah kelas Kalkulator Pajak yang terpisah karena prinsip tanggung jawab tunggal.
Radek
terima kasih atas jawabannya tetapi betapa praktisnya itu. apakah setiap perusahaan bekerja dalam model OOPS yang begitu luas dan murni.
bawah
@shyamsunder Jawaban saya benar-benar murni. Itu tidak menggunakan antarmuka / warisan yang merupakan aspek penting dari OOD, tetapi itu menunjukkan prinsip yang paling penting - menurut pendapat saya - dan itu menempatkan tanggung jawab di tempatnya. Seperti yang ditunjukkan oleh jawaban lain, masalah utama dengan desain Anda adalah bahwa Anda mencampurkan tanggung jawab di antara berbagai aktor dan itu akan menyebabkan masalah saat menambahkan fitur. Sebagian besar perangkat lunak besar hanya dapat berkembang jika mengikuti prinsip-prinsip ini.
xxbbcc
Jawaban yang bagus, tetapi saya juga setuju bahwa perhitungan pajak harus menjadi objek tersendiri.
14

Pertama-tama, ini adalah pertanyaan wawancara yang sangat bagus. Ini adalah ukuran yang bagus untuk banyak keterampilan.

Ada banyak hal yang perlu Anda pahami untuk memberikan jawaban yang baik (tidak ada jawaban yang sempurna), baik tingkat tinggi maupun tingkat rendah. Ini beberapa:

  • Pemodelan Domain -> bagaimana Anda membuat model solusi yang baik? Objek apa yang Anda buat? Bagaimana mereka akan menyelesaikan persyaratan? Mencari kata benda adalah awal yang baik, tetapi bagaimana Anda memutuskan apakah pilihan entitas Anda bagus? Apa yang lainnya entitas yang Anda perlu? Apa pengetahuan domain yang Anda butuhkan untuk mengatasinya?
  • Pemisahan masalah, kopling longgar, kohesi tinggi -> Bagaimana Anda memisahkan bagian-bagian desain yang memiliki perhatian atau tingkat perubahan berbeda dan bagaimana Anda menghubungkannya? Bagaimana Anda menjaga agar desain Anda tetap fleksibel dan terkini?
  • Pengujian unit, refactoring, TDD -> Apa proses Anda untuk mendapatkan solusi? Apakah Anda menulis tes, menggunakan objek tiruan, refactor, iterate?
  • Kode bersih, Idiom bahasa -> Apakah Anda menggunakan fitur bahasa pemrograman untuk membantu Anda? Apakah Anda menulis kode yang dapat dimengerti? Apakah level abstraksi Anda masuk akal? Seberapa dapat dipelihara kode tersebut?
  • Alat : Apakah Anda menggunakan kontrol sumber? Bangun alat? IDE?

Dari sana, Anda dapat melakukan banyak diskusi menarik, yang melibatkan prinsip desain (seperti prinsip SOLID), pola desain, pola analisis, pemodelan domain, pilihan teknologi, jalur evolusi masa depan (misalnya bagaimana jika saya menambahkan database, atau lapisan UI yang kaya, apa yang perlu diubah?), kompromi, persyaratan non-fungsional (kinerja, pemeliharaan, keamanan, ...), pengujian penerimaan, dll ...

Saya tidak akan berkomentar tentang bagaimana Anda harus mengubah solusi Anda, hanya saja Anda harus lebih fokus pada konsep ini.

Tapi, saya dapat menunjukkan kepada Anda bagaimana saya (sebagian) memecahkan masalah ini , hanya sebagai contoh (di Java). Lihat di Programkelas untuk melihat bagaimana semuanya bersatu untuk mencetak tanda terima ini:

------------------ INI ADALAH PESANAN ANDA ------------------
(001) Desain Didorong Domain ----- $ 69,99
(001) Tumbuh Perangkat Lunak Berorientasi Objek ----- $ 49,99
(001) House MD Musim 1 ----- $ 29,99
(001) House MD Musim 7 ----- $ 34,50
(IMD) Menumbuhkan Perangkat Lunak Berorientasi Objek ----- $ 2,50
(BST) House MD Musim 1 ----- $ 3,00
(BST) House MD Musim 7 ----- $ 3,45
(IMD) House MD Musim 7 ----- $ 1,73
                                SUB-TOTAL ----- $ 184.47
                                TOTAL PAJAK ----- $ 10,68
                                    TOTAL ----- $ 195.15
---------------- TERIMA KASIH TELAH MEMILIH KAMI ----------------

Anda pasti harus melihat buku-buku itu :-)

Sekadar peringatan: solusi saya masih sangat belum lengkap, saya hanya fokus pada skenario jalan bahagia agar memiliki fondasi yang baik untuk membangun.

Jordão
sumber
Saya memeriksa solusi Anda dan menganggapnya cukup menarik. Meskipun saya merasa bahwa kelas Order seharusnya tidak bertanggung jawab untuk mencetak Reciept. Demikian pula, kelas TaxMethod seharusnya tidak bertanggung jawab untuk menghitung pajak. Selain itu, TaxMethodPractice tidak boleh berisi daftar TaxMethod. Sebaliknya, kelas yang disebut SalesPolicy harus berisi daftar ini. Kelas bernama SalesEngine harus diteruskan SalesPolicy, Order dan TaxCalculator. SalesEngine akan menerapkan SalesPolicy pada item dalam Pesanan dan menghitung Pajak menggunakan TaxCalculator
CKing
@bot: pengamatan menarik .... Sekarang, Ordermencetak tanda terima, tapi Receipttahu tentang formatnya sendiri. Selain itu, TaxMethodPractice adalah sejenis kebijakan pajak, yang menahan semua pajak yang berlaku untuk skenario tertentu. TaxMethods adalah kalkulator pajak. Saya merasa Anda hanya melewatkan beberapa kelas pengikatan tingkat yang lebih tinggi , seperti SalesEngine yang Anda usulkan. Itu ide yang menarik.
Jordão
Saya hanya merasa bahwa setiap kelas harus memiliki satu tanggung jawab yang terdefinisi dengan baik dan kelas yang mewakili objek dunia nyata harus berperilaku dengan cara yang sejalan dengan dunia nyata. Untuk itu, TaxMethod dapat dibagi menjadi dua kelas. TaxCriteria dan TaxCalculator. Demikian pula, Pesanan tidak boleh mencetak tanda terima. ReceiptGenerator harus diberikan Tanda Terima untuk menghasilkan tanda terima.
CKing
@ bot: Saya sangat setuju! Desain yang bagus SOLID ! TaxMethod adalah kalkulator pajak, dan TaxEligibilityCheck adalah kriteria pajak. Mereka adalah entitas yang terpisah. Untuk tanda terima, ya, membelah bagian pembangkit akan semakin meningkatkan desainnya.
Jordão
1
Ide itu berasal dari pola spesifikasinya , lihatlah!
Jordão
12

Kecuali fakta bahwa Anda menggunakan kelas yang disebut produk, Anda belum menunjukkan bahwa Anda tahu apa itu warisan, Anda belum membuat beberapa kelas mewarisi dari Produk, tidak ada polimorfisme. Masalahnya bisa diselesaikan dengan menggunakan beberapa konsep OOP (bahkan hanya untuk menunjukkan bahwa Anda mengetahuinya). Ini adalah masalah wawancara jadi Anda ingin menunjukkan seberapa banyak Anda tahu.

Namun saya tidak akan berubah menjadi depresi sekarang. Fakta bahwa Anda tidak mendemonstrasikannya di sini tidak berarti Anda belum mengenal mereka atau tidak dapat mempelajarinya.

Anda hanya perlu sedikit lebih banyak pengalaman dengan OOP atau wawancara.

Semoga berhasil!

Andrei G
sumber
sebenarnya ini adalah desain pertama saya, saya membuat yang lain tetapi tidak dapat menunjukkan kepada Anda karena batas karakter terlampaui.
mulai
dapatkah Anda mendemonstrasikannya dengan bantuan contoh apa pun.
bawah
@sunder: Anda cukup memperbarui pertanyaan dengan desain baru Anda.
Bjarke Freund-Hansen
10

Orang yang telah mulai belajar pemrograman dengan OOP tidak memiliki masalah besar untuk memahami apa artinya, karena ini seperti dalam kehidupan nyata . Jika Anda memiliki keterampilan dengan famili pemrograman lain selain OO, mungkin akan lebih sulit untuk dipahami.

Pertama-tama, matikan layar Anda, atau keluar dari IDE favorit Anda. Ambil kertas dan pensil dan buat daftar entitas , relasi , orang , mesin , proses , barang , dll. Segala sesuatu yang dapat ditemukan ke dalam program akhir Anda.

Kedua, cobalah untuk mendapatkan entitas dasar yang berbeda . Anda akan memahami bahwa beberapa dapat berbagi properti atau kemampuan , Anda harus meletakkannya di objek abstrak . Anda harus mulai menggambar skema yang bagus dari program Anda.

Selanjutnya Anda harus meletakkan fonctionnalities (metode, fungsi, subrutin, panggil sesuai keinginan): misalnya, objek produk seharusnya tidak dapat menghitung pajak penjualan . Sebuah mesin penjualan objek harus.

Jangan merasa kesulitan dengan semua kata-kata besar ( antarmuka , properti , polimorfisme , warisan , dll.) Dan pola desain untuk pertama kalinya, jangan coba-coba membuat kode yang indah atau apa pun ... Pikirkan saja objek sederhana dan interaksi di antara itu seperti dalam kehidupan nyata .

Setelah itu, cobalah membaca beberapa literatur ringkas yang serius tentang ini. Saya pikir Wikipedia dan Wikibooks adalah cara yang sangat bagus untuk memulai dan kemudian membaca hal-hal tentang GoF dan Design Patterns dan UML .

smonff
sumber
3
+1 untuk "Pertama-tama, matikan layar Anda". Menurut saya kekuatan berpikir terlalu sering disalahartikan dengan kekuatan komputasi.
kontur
1
1 untuk mengambil pendekatan paling sederhana dengan menggunakan pensil dan kertas. Banyak kali orang bingung saat duduk di depan IDE :)
Neeraj Gulia
Beberapa ilmuwan mengatakan bahwa otak kita lalai saat menonton layar. Saat saya belajar desain arsitektur perangkat lunak, guru kami menyuruh kami mengerjakan kertas. Dia tidak keberatan dengan perangkat lunak UML yang kuat. Yang penting mengerti dulu.
smonff
4

Pertama, jangan gabungkan Productkelas dengan kelas Receipt ( ShoppingCart), yang quantityharus menjadi bagian dari ReceipItem( ShoppingCartItem), serta Tax& Cost. The TotalTax& TotalCostharus menjadi bagian dari ShoppingCart.

ProductKelas saya , hanya memiliki Name& Price& beberapa properti hanya-baca seperti 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); } }
}

Bagian penghitungan pajak Anda digabungkan dengan Product. Produk tidak menentukan kebijakan pajak, melainkan kelas Pajak. Berdasarkan uraian masalahnya, ada dua jenis Pajak Penjualan: Basicdan DutyPajak. Anda dapat menggunakan Template Method Design Patternuntuk mencapainya:

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; } }
}

Dan akhirnya kelas untuk menerapkan pajak:

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));
        }

    }
}

Anda dapat mencobanya di MyFiddle .

Daniel B
sumber
2

Titik awal yang sangat baik tentang aturan desain adalah prinsip SOLID .

Misalnya prinsip Terbuka Tertutup menyatakan bahwa jika Anda ingin menambahkan fungsionalitas baru, Anda tidak perlu menambahkan kode ke kelas yang ada, melainkan menambahkan kelas baru.

Untuk aplikasi sampel Anda, ini berarti menambahkan pajak penjualan baru akan memerlukan penambahan kelas baru. Hal yang sama berlaku untuk produk berbeda yang merupakan pengecualian dari aturan tersebut.

Aturan pembulatan jelas berlaku di kelas yang terpisah - prinsip Tanggung Jawab Tunggal menyatakan bahwa setiap kelas memiliki tanggung jawab tunggal.

Saya pikir mencoba menulis kode sendiri akan membawa manfaat yang jauh lebih banyak daripada hanya menulis solusi yang baik dan menempelkannya di sini.

Algoritme sederhana untuk menulis program yang dirancang sempurna adalah:

  1. Tuliskan beberapa kode yang menyelesaikan masalah
  2. Periksa apakah kode tersebut sesuai dengan prinsip SOLID
  3. Jika ada pelanggaran aturan dari goto 1.
devdimi
sumber
2

Penerapan OOP yang sempurna masih bisa diperdebatkan. Dari apa yang saya lihat di pertanyaan Anda, Anda dapat memodularisasi kode berdasarkan peran yang mereka lakukan untuk menghitung harga akhir seperti Produk, Pajak, ProductDB, dan sebagainya.

  1. Productbisa menjadi kelas abstrak dan jenis turunan seperti Buku, Makanan dapat diwariskan darinya. Penerapan pajak dapat ditentukan oleh jenis turunannya. Produk akan memberi tahu apakah pajak itu berlaku atau tidak berdasarkan kelas turunannya.

  2. TaxCriteria dapat menjadi enum dan ini dapat ditentukan selama pembelian (diimpor, penerapan Pajak Penjualan).

  3. Taxkelas akan menghitung pajak berdasarkan TaxCriteria.

  4. Memiliki ShoppingCartItemseperti yang disarankan oleh XXBBCC dapat merangkum contoh Produk dan Pajak dan ini adalah cara yang bagus untuk memisahkan detail produk dengan kuantitas, harga total dengan pajak, dll.

Semoga berhasil.

Karthik
sumber
1

Dari perspektif OOA / D yang ketat, satu masalah utama yang saya lihat adalah bahwa sebagian besar atribut kelas Anda memiliki nama kelas yang berlebihan dalam nama atribut. misalnya Harga Produk , Jenis Produk . Dalam kasus ini, di mana pun Anda menggunakan kelas ini, Anda akan memiliki kode yang terlalu bertele-tele dan agak membingungkan, misalnya product.productName. Hapus awalan / sufiks nama kelas yang berlebihan dari atribut Anda.

Selain itu, saya tidak melihat kelas yang peduli dengan pembelian dan pembuatan tanda terima seperti yang ditanyakan dalam pertanyaan.

Peter Cetinski
sumber
1

Berikut adalah contoh yang bagus dari pola OO untuk Produk, Pajak, dll .. Perhatikan penggunaan Antarmuka, yang penting dalam desain OO.

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

Chris Gessler
sumber
3
Saya lebih suka membuat produk menjadi kelas (abstrak) daripada menjadikannya antarmuka. Saya juga tidak akan menjadikan setiap produk sebagai kelas yang terpisah. Paling banyak saya akan membuat satu kelas per kategori.
CodesInChaos
@CodeInChaos - Sebagian besar waktu Anda membutuhkan keduanya, tetapi jika Anda mencoba untuk mendapatkan pekerjaan sebagai arsitek, saya akan memilih untuk mengimplementasikan Antarmuka melalui kelas Abstrak.
Chris Gessler
1
Antarmuka dalam contoh ini tidak memiliki arti sama sekali. Mereka hanya menyebabkan duplikasi kode di setiap kelas yang menerapkannya. Setiap kelas menerapkannya dengan cara yang sama.
Piotr Perak
0

Menghadapi Masalah Biaya dengan Pajak menggunakan pola Pengunjung.

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);
    }
LucidCoder
sumber
Selamat datang di stackoverflow. Harap pastikan Anda menjelaskan jawaban Anda dalam menanggapi pertanyaan tersebut. OP tidak hanya mencari solusi tetapi mengapa solusi itu lebih baik / lebih buruk.
Simon.SA