Як створити ідеальну програму OOP [закрито]


98

Нещодавно я намагався створити компанію "х". Вони надіслали мені кілька запитань і сказали вирішити лише одне.

Проблема така -

Основний податок з продажу застосовується за ставкою 10% на всі товари, крім книг, продуктів харчування та медичних виробів, які не звільняються.
Ввізне мито - це додатковий податок з продажу, що застосовується до всіх імпортних товарів за ставкою 5%, без виключень.

Купуючи товари, я отримую квитанцію, в якій перераховано найменування всіх предметів та їх ціну (включаючи податок), закінчуючи загальною вартістю предметів та загальною сумою сплаченого податку з продажу.
Правила округлення податку з продажу полягають у тому, що для податкової ставки у розмірі n% ціна на полиці p містить (н.п. / 100 округлення до найближчого 0,05) суми податку з продажу.

"Вони сказали мені, що вони зацікавлені в аспекті дизайну вашого рішення і хочуть оцінити мої навички об'єктно-орієнтованого програмування ".

Про це вони сказали власними словами

  • Для вирішення ми хотіли б використовувати Java, Ruby або C #.
  • Нас зацікавив АСПЕКТ ПРОЕКТУВАННЯ Вашого рішення і хотіли б оцінити Ваші навички об'єктно-орієнтованого програмування .
  • Ви можете використовувати зовнішні бібліотеки чи інструменти для побудови чи тестування. Зокрема, ви можете використовувати одиничні бібліотеки тестування або створювати інструменти, доступні для вибраної мови (наприклад, JUnit, Ant, NUnit, NAnt, Test :: Unit, Rake тощо).
  • За бажанням ви можете також включити коротке пояснення вашого дизайну та припущення разом із кодом.
  • Будь ласка, зауважте, що ми НЕ очікуємо веб-додатку або всебічного інтерфейсу користувача. Швидше за все, ми очікуємо простий додаток на основі консолі та зацікавлений у вашому вихідному коді.

Тому я надав нижче код - ви можете просто скопіювати код вставити і запустити в 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
     }
 }

ви можете скасувати введення та запустити різні входи.

Я надав рішення, але мене відхилили.

"Вони сказали, що вони не можуть вважати мене за наші нині відкриті позиції, оскільки рішення коду є незадовільним".

Підкажіть, будь ласка, мене, чого тут не вистачає. Це рішення не є хорошим рішенням OOAD.
Як я можу вдосконалити свої навички OOAD.
Мої старші також говорять, що ідеальний додаток OOAD також не працюватиме практично.

Дякую


2
Можливо, вони очікували, що ви будете відрізняти типи продуктів, використовуючи ієрархію спадкування, а не перерахування? (Хоча я думаю, що цей підхід був би досить суворим для даного сценарію.)
Дуглас,

Я гадаю, що вони відхилили ваше рішення msotly, оскільки ви не визначили жодних інтерфейсів.
Кріс Гесслер

28
Як правило, якщо хтось попросить вас в ситуації інтерв'ю продемонструвати навички OOP, вам слід намагатися уникати використання оператора перемикання - замість цього використовувати ієрархію спадкування.
Джо

4
Слід розмістити в огляді коду.
Дерек

Я також розміщував там, але не зміг отримати хороше рішення там. Але кожен може побачити моє нове рішення, яке я створив після допомоги інших codeproject.com/Questions/332077/… тут також можна знайти мій новий код.
Сандер

Відповіді:


246

По-перше, добрі небеса не роблять фінансових розрахунків подвійно . Робіть фінансові розрахунки у десятковій формі ; саме це і є. Використовуйте подвійний для вирішення проблем фізики , а не фінансових проблем.

Основний недолік дизайну у вашій програмі полягає в тому політика знаходиться в неправильному місці . Хто відповідає за обчислення податків? Ви поклали на товар відповідальність за обчислення податків, але купуючи яблуко чи книгу чи пральну машину, річ, яку ви збираєтеся придбати , не несе відповідальності за те, яка сума податку ви збираєтесь платити це. Урядова політика несе відповідальність за те, що вам це скаже. Ваш дизайн масово порушує основний принцип проекту OO, що об’єкти повинні відповідати за власні проблеми , а не за чужі. Турбота пральної машини - прання вашого одягу, а не стягнення правильного ввізного мита. Якщо податкове законодавство зміниться, ви не хочете його змінюватиоб'єкт пральної машини , ви хочете змінити об'єкт політики .

Отже, як підійти до подібних проблем у майбутньому?

Я б почав з виділення кожного важливого іменника в описі проблеми:

Основний податок з продажу застосовується за ставкою 10% на всі товари , крім книг , продуктів харчування та медичних виробів, які не звільняються. Ввізне мито - це додатковий податок з продажу, що застосовується до всіх імпортних товарів за ставкою 5%, без виключень . Купуючи товари, я отримую квитанцію, в якій перераховано найменування всіх предметів та їх ціну (включаючи податок ), закінчуючи загальною вартістюпредметів та загальну суму сплаченого податку з продажу . Правила округлення податку з продажу полягають у тому, що для податкової ставки у розмірі n% ціна на полиці p містить (н.п. / 100 округлення до найближчого 0,05) суми податку з продажу .

Тепер, які стосунки між усіма цими іменниками?

  • Основний податок з продажу - це вид податку з продажу
  • Ввізне мито - це вид податку з продажу
  • Податок з продажу має ставку, яка є десятковою
  • Книги - це свого роду предмет
  • Їжа є своєрідним предметом
  • Медичні вироби є своєрідним предметом
  • Товари можуть бути імпортованими товарами
  • Елемент має ім'я, яке є рядком
  • Товар має ціну на полиці, яка є десятковою. (Примітка: чи дійсно такий товар має ціну? Дві однакові пральні машини можуть бути продані за різними цінами в різних магазинах або в одному і тому ж магазині в різний час. Кращим дизайном може бути те, що політика цін стосується товару до його ціна.)
  • Політика звільнення від податку з продажу описує умови, при яких податок на продаж не застосовується до предмета.
  • У квитанції є список предметів, їх ціни та податки.
  • У квитанції є загальна сума
  • Квитанція має загальний податок

... і так далі. Після того, як у вас з’ясуються всі зв’язки між усіма іменниками, ви можете приступити до проектування ієрархії класів. Існує абстрактний базовий клас Item. Книга успадковує від неї. Існує абстрактний клас SalesTax; BasicSalesTax успадковує від нього. І так далі.


12
вам потрібно більше, ніж тільки що було надано? Схоже, вам потрібно дізнатися більше про те, як реалізується успадкування та що таке поліморфізм.
Індустр

27
@sunder: Ця відповідь більш ніж достатня. Тепер ви несете відповідальність за розвиток своїх навичок, можливо, використовуючи це як перший приклад. Зауважте, що ваш приклад - це визначення прикладу реального життя. Ви провалили інтерв'ю в реальному житті, оскільки цей код у реальному житті потребував реального дизайну, який ви не надавали.
Грег Д

9
@Narayan: doubleідеально підходить для ситуацій, коли знаходження в межах 0,00000001% правильної відповіді більш ніж достатньо. Якщо ви хочете розібратися, як швидко падає цегла через півсекунди, робіть математику в парних. Коли ви робите фінансову арифметику в парних пар, ви отримуєте відповіді, як ціна після сплати податку - 43,79999999999999 доларів, і це виглядає нерозумно, хоча це надзвичайно близько до правильної відповіді.
Ерік Ліпперт

31
+1 Ви виділили чудову вправу, яка полягає у вивченні кожного іменника у заявленій проблемі та перерахуванні їх взаємин між собою. Чудова ідея.
Кріс Тонкінсон

3
@ Jordão: У десятковому періоді додавання 0,10 десяти разів дає 1,00. Але додавання 1,0 / 333,0 триста тридцять три рази не обов'язково дає один або в десяткових, або в подвійних. У десятковій мірі точно представлені дроби, що мають в знаменнику потужність десять; у парних це дроби з потужностями двох. Все інше представлено приблизно.
Ерік Ліпперт

38

Якщо компанія розповідає щось про такі бібліотеки, як NUnit, JUnit або Test :: Unit, більш ніж ймовірно, що TDD дійсно імпортує їх. У вашому зразку коду взагалі немає тестів.

Я б спробував продемонструвати практичні знання з:

  • Одиничні випробування (наприклад, NUnit)
  • Глузування (наприклад, RhinoMocks)
  • Наполегливість (наприклад, NHibernate)
  • Контейнери IoC (наприклад, NSpring)
  • шаблони дизайну
  • Принцип твердості

Я хотів би рекомендувати www.dimecasts.net як вражаюче джерело безкоштовних, якісних екранізацій, що висвітлює всі вищезазначені теми.


19

Це дуже суб'єктивно, але ось декілька моментів, які я хотів би зазначити щодо вашого коду:

  • На мій погляд, ви змішали Productі ShoppingCartItem. Productповинна мати назву товару, податковий статус тощо, але не кількість. Кількість не є властивістю товару - воно буде різним для кожного клієнта компанії, який купує саме той товар.

  • ShoppingCartItemповинна мати а Productі кількість. Таким чином замовник може вільно купувати більше або менше одного і того ж товару. З поточною установкою це неможливо.

  • Розрахунок остаточного податку також не повинен бути частиною Product- він повинен бути частиною чогось подібного, ShoppingCartоскільки остаточний розрахунок податку може включати знання всіх продуктів у кошику.


Єдина проблема, з якою я маю цю відповідь, - це опис того, як створити кращу систему оплати продуктів (що є дійсною), але насправді не розроблено методологій OOP. Це може бути реалізовано будь-якою мовою. Не показуючи якихось інтерфейсів, успадкування, поліморфізму тощо, він все-таки провалить тест.
Час очікування

Що стосується останнього пункту: найкращим місцем для обчислення податку IMO є окремий клас TaxCalculator через єдиний принцип відповідальності.
Радек

дякую за відповідь, але наскільки це практично. чи працює кожна компанія в таких обширних і чистих моделях OOPS.
Сандер

@shyamsunder У моїй відповіді немає нічого дуже чистого. Він не використовує інтерфейси / успадкування, які є важливими аспектами OOD, але він показує найважливіший принцип - на мій погляд, - і це покладати обов'язки там, де їм належить. Як вказували інші відповіді, головна проблема вашого дизайну полягає в тому, що ви змішали обов'язки між різними акторами і це призведе до проблем при додаванні функцій. Більшість великих програмних засобів можуть зростати, лише якщо вони дотримуються цих принципів.
xxbbcc

Хороша відповідь, але я також згоден, що обчислення податку має бути окремим об'єктом.

14

Перш за все, це дуже вдале запитання для інтерв'ю. Це гарна оцінка багатьох навичок.

Вам потрібно зрозуміти багато речей, щоб дати хорошу відповідь ( ідеальної відповіді немає ), як високого, так і низького рівня. Ось пара:

  • Моделювання домену -> як створити хорошу модель рішення? Які об’єкти ви створюєте? Як вони вирішать вимоги? Пошук іменників - це хороший початок, але як ви вирішите, чи хороший ваш вибір сутностей? Які ще суб'єкти вам потрібні? Які доменні знання вам потрібні, щоб їх вирішити?
  • Розділення проблем, слабке з'єднання, висока згуртованість -> Як ви відокремлюєте частини конструкції, які мають різні проблеми або темпи змін, і як ви їх співвідносите? Як ви підтримуєте свій дизайн гнучким та сучасним?
  • Тестування одиниць, рефакторинг, TDD -> Який у вас процес розробки рішення? Ви пишете тести, використовуєте макетні об’єкти, рефактор, ітерацію?
  • Чистий код, Мовні ідіоми -> Чи використовуєте ви функції програми програмування, щоб допомогти вам? Ви пишете зрозумілий код? Чи мають сенс ваші рівні абстракції? Наскільки ретельним є код?
  • Інструменти : чи використовуєте ви керування джерелами? Збір інструментів? ІДЕ?

Звідти ви можете вести багато цікавих дискусій, включаючи принципи проектування (як принципи SOLID), шаблони дизайну, структури аналізу, доменне моделювання, вибір технологій, майбутні шляхи еволюції (наприклад, що робити, якщо я додаю базу даних, або багатий шар інтерфейсу, що потрібно змінити?), компроміси, нефункціональні вимоги (продуктивність, ремонтопридатність, безпека, ...), тестування приймання тощо ...

Я не буду коментувати, як ви повинні змінити своє рішення, просто щоб ви більше приділяли увагу цим поняттям.

Але я можу показати вам, як я (частково) вирішив цю проблему , лише на прикладі (на Java). Подивіться в Programкласі, щоб побачити, як все разом надрукувати цю квитанцію:

------------------ ЦЕ ВАШЕ ЗАМОВЛЕННЯ ------------------
(001) Дизайн, керований доменом ----- 69,99 дол
(001) Зростаюче об'єктно-орієнтоване програмне забезпечення ----- 49,99 дол
(001) Будинок медичного сезону 1 сезон ----- 29,99 дол
(001) Будинок медичного сезону 7 сезон ----- 34,50 дол
(IMD) Зростаюче об’єктно-орієнтоване програмне забезпечення ----- 2,50 дол
(BST) Будинок МД Сезон 1 ----- 3,00 дол
(BST) Будинок медичного сезону 7 сезон ----- 3,45 дол
(IMD) Будинок МД 7 сезон 7–1,73 дол
                                SUB-TOTAL ----- 184,47 дол
                                ПОДАТКОВИЙ ВСЕ ----– 10,68 дол
                                    ВСЬОГО ----- 195,15 дол
---------------- ДЯКУЄТЬСЯ, ЩО ВИБУТЬ НАС ----------------

Ви обов'язково подивіться на ці книги :-)

Як застереження: моє рішення все ще дуже незавершене, я просто зосередився на сценарії щасливого шляху, щоб мати гарну основу для розвитку.


Я переглянув ваше рішення і знайшов це досить цікавим. Хоча я вважаю, що клас Order не повинен нести відповідальність за друк Рецепту. Аналогічно, клас TaxMethod не повинен відповідати за обчислення податку. Крім того, TaxMethodPractice не повинен містити перелік TaxMethod. Натомість клас, який називається SalesPolicy, повинен містити цей список. Клас під назвою SalesEngine повинен передаватися SalesPolicy, ордеру та податковому калькулятору. SalesEngine застосує SalesPolicy до товарів у Порядку та обчислить податок за допомогою TaxCalculator
CKing

@bot: цікаві спостереження .... Прямо зараз Orderдрукує квитанцію, але Receiptзнає про власне форматування. Також TaxMethodPractice є своєрідною податковою політикою, вона містить усі податки, що застосовуються до певного сценарію. TaxMethods є калькуляторами податків. Я відчуваю, що вам не вистачає лише класу прив'язки вищого рівня , як, наприклад, ваш запропонований SalesEngine. Це цікава ідея.
Йордао

Я просто відчуваю, що кожен клас повинен мати єдину чітко визначену відповідальність, і класи, які представляють об'єкти реального світу, повинні поводитись так, як це відповідає реальному світу. З цього питання TaxMethod можна розділити на два класи. Податкові критерії та податковий розрахунок. Так само Наказ не повинен друкувати квитанцію. Генератору прийому повинен бути переданий розписку про генерування квитанції.
CKing

@bot: Я повністю згоден! Гарні конструкції - ТВЕРДІ ! TaxMethod - це калькулятор податку, а TaxEligibilityCheck - податковий критерій. Вони є окремими утвореннями. Що стосується отримання, то так, розділення генераторної частини ще більше покращить дизайн.
Йордао

1
Ця ідея виходить із структури специфікацій , погляньте!
Йордао

12

За винятком того, що ви використовуєте клас під назвою продукт, ви не продемонстрували, що знаєте, що таке спадщина, ви не створили багатократне класифікаційне успадкування від Product, немає поліморфізму. Проблему можна було вирішити за допомогою декількох концепцій OOP (навіть просто щоб показати, що ви їх знаєте). Це проблема інтерв'ю, тому ви хочете показати, наскільки ви знаєте.

Однак я б не перетворився на депресію. Те, що ви не демонстрували їх тут, не означає, що ви їх вже не знаєте або не в змозі їх вивчити.

Вам просто потрібно трохи більше досвіду роботи або з ООП, або з інтерв'ю.

Удачі!


насправді це був мій перший дизайн, я створив ще один, але не можу показати вам, як обмеження кількості символів перевищує.
Сандер

чи можете ви продемонструвати це за допомогою будь-якого прикладу.
Сандер

@sunder: Ви можете просто оновити питання своїм новим дизайном.
Bjarke Freund-Hansen

10

У людей, які почали навчатися програмуванню з ООП, не виникає великих проблем, щоб зрозуміти, що це означає, адже це так само, як і в реальному житті . Якщо у вас є навички з іншими програмуваннями сімейними, ніж OO, це може бути складніше зрозуміти.

Перш за все, вимкніть екран або вийдіть з улюбленого IDE. Візьміть папір і олівець і складіть список сутностей , відносин , людей , машин , процесів , матеріалів тощо. Все, що може виникнути у вашій кінцевій програмі.

По-друге, спробуйте отримати різні основні сутності. Ви зрозумієте, що деякі можуть ділитися властивостями чи здібностями , вам доведеться помістити це в абстрактні об’єкти . Ви повинні почати малювати хорошу схему своєї програми.

Далі слід поставити функції (методи, функції, підпрограми, називати це так, як вам потрібно): наприклад, товарний об'єкт не повинен мати змогу нарахувати податок з продажу . Продажі двигуна об'єкт повинен.

Не відчувайте проблем з усіма великими словами ( інтерфейси , властивості , поліморфізм , спадщина тощо) та шаблонами дизайну в перший раз, навіть не намагайтеся зробити гарний код чи будь-що інше ... Просто подумайте про прості об'єкти та взаємозв'язки між нею як у реальному житті .

Після цього спробуйте прочитати про це серйозну стислу літературу. Я думаю, що Wikipedia та Wikibooks - це дійсно хороший спосіб почати, а потім просто прочитати матеріали про GoF, шаблони дизайну та UML .


3
+1 для "Перш за все, вимкніть екран". Я думаю, що сила мислення занадто часто помиляється з силою обчислень.
kontur

1
+1 за найпростіший підхід використання олівця та паперу. Багато разів люди плутаються, сидячи перед ІДЕ :)
Неєрай Гулія

Деякі вчені сказали, що наш мозок неуважний під час перегляду екрана. Коли я вивчаю дизайн архітектури програмного забезпечення, наш викладач змушує нас працювати на папері. Він не проти потужних програмних засобів UML. Важливо - спершу зрозуміти речі.
smonff

4

Спочатку не змішуйте Productклас з класом Receipt ( ShoppingCart), він quantityповинен бути частиною ReceipItem( ShoppingCartItem), а також Tax& Cost. TotalTax& TotalCostПовинно бути частиноюShoppingCart .

Мій Productклас, має лише Name& Price& властивості для читання лише на кшталт 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); } }
}

Частина вашого податкового розрахунку поєднується з Product. Продукт не визначає податкову політику, це податкові класи. Виходячи з опису проблеми, існує два види податку з продажу: Basicта Dutyподатки. Ви можете використовувати його Template Method Design Patternдля досягнення:

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

І нарешті клас із застосування податків:

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

    }
}

Ви можете спробувати їх на MyFiddle .


2

Дуже хорошим початковим пунктом щодо правил дизайну є принципи SOLID .

Наприклад, принцип відкритого закриття зазначає, що якщо ви хочете додати нову функціональність, вам не потрібно додавати код до існуючого класу, а скоріше додати новий клас.

Для вашої вибіркової заявки це означатиме, що для додавання нового податку з продажу потрібно буде додати новий клас. Те саме стосується різних продуктів, які є винятками з правила.

Правило округлення, очевидно, належить до окремого класу - принцип єдиної відповідальності зазначає, що кожен клас несе єдину відповідальність.

Я думаю, що спроба написати код сама принесла б набагато більше користі, ніж просто написати хороше рішення та вставити його сюди.

Простим алгоритмом написання ідеально розробленої програми був би:

  1. Напишіть якийсь код, який вирішує проблему
  2. Перевірте, чи відповідає код принципам SOLID
  3. Якщо є порушення правил, ніж goto 1.

2

Досконала реалізація ООП є абсолютно дискусійною. З того, що я бачу у вашому запитанні, ви можете модулювати код на основі ролі, яку вони виконують для обчислення кінцевої ціни, наприклад, Product, Tax, ProductDB тощо.

  1. Productможе бути абстрактним класом і похідні типи, такі як Книги, Їжа, можуть бути успадковані від нього. Застосування податків можна визначити за похідними типами. Продукт підкаже, чи застосовується податок на основі похідного класу.

  2. TaxCriteria може бути перерахунком, і це можна вказати під час покупки (імпорт, застосування податку на продаж).

  3. Taxклас буде обчислювати податок на основі TaxCriteria.

  4. Якщо ShoppingCartItemзапропоновано XXBBCC, можна інкапсулювати примірники товарів і податків, і це прекрасний спосіб розділити деталі продукту за кількістю, загальною ціною з податком тощо.

Удачі.


1

З точки зору OOA / D, одна з головних проблем, яку я бачу, полягає в тому, що більшість атрибутів вашого класу мають надмірне ім’я класу в імені атрибута. напр. ціна товару , тип продукту . У цьому випадку, де б ви не використовували цей клас, у вас буде надмірно багатослівний і дещо заплутаний код, наприклад product.productName. Видаліть зайві префікси / суфікси імені класу зі своїх атрибутів.

Крім того, я не бачив жодних класів, пов’язаних із придбанням та створенням квитанції, про що було задано у запитанні.


1

Ось чудовий приклад схеми OO для продуктів, податків тощо ... Зауважте використання інтерфейсів, що важливо в дизайні ОО.

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


3
Я вважаю за краще зробити продукт класним (абстрактним) над тим, щоб зробити його інтерфейсом. Я також не став би кожен продукт окремим класом. Я мав би створити один клас на категорію.
CodesInChaos

@CodeInChaos - Більшу частину часу вам потрібно обоє, але якщо ви намагаєтеся влаштувати роботу на посаду архітектора, я вирішив би реалізувати інтерфейси над абстрактним класом.
Кріс Гесслер

1
Інтерфейси в цьому прикладі взагалі не мають сенсу. Вони призводять лише до дублювання коду в кожному класі, що реалізує їх. Кожен клас реалізує це однаково.
Пьотр Перек

0

Виникла вартість з проблемою оподаткування за допомогою шаблону відвідувачів.

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

Ласкаво просимо в stackoverflow. Будь ласка, переконайтесь, що ви пояснили свою відповідь у відповідь на питання. ОП - це не просто пошук рішення, а чому рішення краще / гірше.
Simon.SA
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.