Как создать идеальное ООП-приложение [закрыто]

98

Недавно пробовал для компании "х". Они прислали мне несколько вопросов и сказали, что нужно решить только один.

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

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

Когда я покупаю товары, я получаю квитанцию, в которой указаны названия всех товаров и их цена (включая налоги), в конце указана общая стоимость товаров и общая сумма уплаченных налогов с продаж.
Правила округления для налога с продаж заключаются в том, что для ставки налога n% полочная цена p содержит (np / 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
Может быть, они ожидали, что вы будете различать типы продуктов, используя иерархию наследования, а не перечисление? (Хотя я думаю, что этот подход был бы довольно запутанным для данного сценария.)
Дуглас
Я предполагаю, что они отклонили ваше решение именно потому, что вы не определили никаких интерфейсов.
Крис Гесслер
28
Как правило, если кто-то просит вас на собеседовании продемонстрировать навыки ООП, вам следует избегать использования оператора switch - вместо этого используйте иерархию наследования.
Джо
4
Должен быть опубликован в обзоре кода.
Дерек,
Я также разместил там сообщение, но не смог найти там хорошего решения. Но все могут увидеть мое новое решение, которое я создал после помощи других codeproject.com/Questions/332077/… здесь вы также можете найти мой новый код.
Sunder

Ответы:

246

Во-первых, небеса, не делайте финансовых расчетов вдвое . Делайте финансовые расчеты в десятичном формате ; вот для чего это нужно. Используйте double для решения физических , а не финансовых проблем.

Главный недостаток вашей программы в том, что политика размещена не в том месте . Кто отвечает за расчет налогов? Вы поручили продукту рассчитывать налоги, но когда вы покупаете яблоко, книгу или стиральную машину, вещь , которую вы собираетесь купить , не отвечает за указание вам, сколько налогов вы собираетесь платить. Это. Правительственная политика обязана сообщить вам об этом. Ваш дизайн в значительной степени нарушает основной принцип объектно-ориентированного проектирования, согласно которому объекты должны нести ответственность за свои собственные проблемы , а не за чьи-либо еще. Задача стиральной машины - это стирать вашу одежду, а не взимать правильную импортную пошлину. Если изменится налоговое законодательство, вы не захотите его менятьобъект стиральной машины , вы хотите изменить объект политики .

Итак, как подойти к подобного рода проблемам в будущем?

Я бы начал с выделения каждого важного существительного в описании проблемы:

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

Итак, каковы отношения между всеми этими существительными?

  • Базовый налог с продаж - это разновидность налога с продаж.
  • Импортная пошлина - это своего рода налог с продаж.
  • Ставка налога с продаж является десятичной.
  • Книги - это своего рода Предмет
  • Еда - это своего рода предмет
  • Медицинские товары - это своего рода товар
  • Предметы могут быть импортированными товарами
  • Элемент имеет имя, которое является строкой
  • У предмета есть полочная цена, которая является десятичной. (Примечание: действительно ли у предмета есть цена? Две одинаковые стиральные машины могут быть проданы по разным ценам в разных магазинах или в одном магазине в разное время. Лучше всего сказать, что Ценовая политика связывает предмет с его цена.)
  • Политика освобождения от налога с продаж описывает условия, при которых налог с продаж не применяется к товару.
  • В квитанции есть список предметов, их цены и налоги.
  • Квитанция имеет общую
  • Квитанция содержит общую сумму налога

... и так далее. Как только у вас будут определены все отношения между всеми существительными, вы можете приступить к проектированию иерархии классов. Есть абстрактный базовый класс Item. Книга наследует от нее. Есть абстрактный класс SalesTax; BasicSalesTax наследуется от него. И так далее.

Эрик Липперт
источник
12
вам нужно больше, чем только что было предоставлено? Похоже, вам нужно больше узнать о том, как реализовано наследование и что такое полиморфизм.
Induster
27
@sunder: этого ответа более чем достаточно. Теперь вы обязаны развивать свои навыки, возможно, используя это в качестве первого примера. Обратите внимание, что ваш пример - это определение примера из реальной жизни. Вы провалили собеседование в реальной жизни, потому что для этого реального кода требовался реальный дизайн, которого вы не предоставили.
Грег Д.
9
@Narayan: doubleидеально подходит для ситуаций, когда более чем достаточно быть в пределах 0,00000001% от правильного ответа. Если вы хотите выяснить, с какой скоростью падает кирпич через полсекунды, вычислите дважды. Когда вы выполняете финансовую арифметику в двойном размере, вы получаете такие ответы, как например, цена после уплаты налогов составляет 43,79999999999999 долларов, и это выглядит глупо, хотя и очень близко к правильному ответу.
Эрик Липперт,
31
+1 Вы выделили замечательное упражнение, которое состоит в том, чтобы изучить каждое существительное в поставленной задаче, а затем перечислить их отношения друг с другом. Отличная идея.
Крис Тонкинсон
3
@ Жордао: В десятичном виде добавление 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поскольку окончательный расчет налога может включать в себя знание всех продуктов в корзине.

xxbbcc
источник
Единственная проблема, с которой я столкнулся с этим ответом, заключается в том, что в нем описывается, как создать лучшую систему оплаты продуктов (что действительно), но на самом деле нет подробного описания методологий ООП. Это может быть реализовано на любом языке. Без демонстрации каких-либо интерфейсов, наследования, полиморфизма и т.д. он все равно не пройдет тест.
Тайм-аут
Что касается последнего пункта: IMO лучшее место для расчета налогов - это отдельный класс TaxCalculator из-за принципа единой ответственности.
Радек
спасибо за ответ, но насколько это практично. Каждая компания работает в таких обширных и чистых моделях OOPS.
Sunder
@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 сезон ----- $ 1,73
                                ПРОМЕЖУТОЧНЫЙ ИТОГ ----- 184,47 долл. США
                                ИТОГО ----- $ 10,68
                                    ИТОГО ----- 195,15 $
---------------- СПАСИБО ЗА ВЫБОР НАС ----------------

Вам обязательно стоит взглянуть на эти книги :-)

В качестве предостережения: мое решение все еще очень неполное, я просто сосредоточился на сценарии счастливого пути, чтобы иметь хорошую основу для дальнейшего развития.

Жордау
источник
Я просмотрел ваше решение и нашел его довольно интересным. Хотя я считаю, что класс Order не должен нести ответственность за печать Reciept. Точно так же класс TaxMethod не должен отвечать за расчет налога. Кроме того, TaxMethodPractice не должен содержать список TaxMethod. Вместо этого этот список должен содержать класс SalesPolicy. Классу SalesEngine следует передать SalesPolicy, Order и TaxCalculator. SalesEngine применит SalesPolicy к элементам в Заказе и рассчитает налог с помощью TaxCalculator
CKing
@bot: интересные наблюдения .... Прямо сейчас Orderраспечатывает квитанцию, но Receiptзнает о собственном форматировании. Кроме того, TaxMethodPractice - это своего рода налоговая политика, она содержит все налоги, применимые к определенному сценарию. TaxMethods - это налоговые калькуляторы. Я чувствую, что вам не хватает только класса привязки более высокого уровня , такого как предлагаемый вами SalesEngine. Это интересная идея.
Жордау,
Я просто чувствую, что у каждого класса должна быть одна четко определенная ответственность, а классы, которые представляют объекты реального мира, должны вести себя так, как это происходит в реальном мире. В этом отношении TaxMethod можно разделить на два класса. TaxCriteria и TaxCalculator. Точно так же в Заказе не должно быть распечатки квитанции. ReceiptGenerator следует передать Receipt для генерации квитанции.
CKing 06
@bot: Полностью согласен! Хороший дизайн - ТВЕРДЫЙ ! TaxMethod - это налоговый калькулятор, а TaxEligibilityCheck - налоговый критерий. Это отдельные сущности. Что касается квитанции, да, разделение генерирующей части еще больше улучшит конструкцию.
Жордау,
1
Эта идея исходит из шаблона спецификации , посмотрите!
Жордау,
12

За исключением того факта, что вы используете класс под названием product, вы не продемонстрировали, что знаете, что такое наследование, вы не создали множественное наследование классов от Product, никакого полиморфизма. Проблему можно было решить, используя несколько концепций ООП (даже чтобы показать, что вы их знаете). Это задача собеседования, поэтому вы хотите показать, как много вы знаете.

Однако сейчас я бы не впал в депрессию. Тот факт, что вы не продемонстрировали их здесь, не означает, что вы их еще не знаете или не можете их изучить.

Вам просто нужно немного больше опыта в ООП или интервью.

Удачи!

Андрей Г
источник
на самом деле это был мой первый дизайн, я создал еще один, но не могу показать вам, как превышается лимит символов.
Sunder
Вы можете продемонстрировать это на каком-нибудь примере.
sunder
@sunder: Вы можете просто обновить вопрос своим новым дизайном.
Бьярке Фройнд-Хансен
10

Люди, которые начали изучать программирование с помощью ООП, не испытывают больших проблем, чтобы понять, что это означает, потому что это так же, как и в реальной жизни . Если у вас есть навыки программирования не только в объектно-ориентированном программировании, это может быть труднее понять.

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

Во-вторых, попытайтесь получить разные базовые сущности. Вы поймете, что некоторые могут иметь общие свойства или способности , вы должны поместить это в абстрактные объекты. . Вам следует начать рисовать красивую схему своей программы.

Затем вы должны указать функции (методы, функции, подпрограммы, называйте это как хотите): например, объект продукта не должен иметь возможность вычислять налог с продаж . Двигатель продаж должен.

Не испытывайте проблем со всеми громкими словами ( интерфейсы , свойства , полиморфизм , наследие и т. Д.) И шаблонами проектирования с первого раза, даже не пытайтесь создать красивый код или что-то еще ... Просто подумайте о простых объектах и взаимодействия между ними, как в реальной жизни .

После этого попробуйте прочитать серьезную и краткую литературу об этом. Я думаю, что Википедия и Викиучебники - действительно хороший способ начать, а потом просто читать о GoF, шаблонах проектирования и UML .

smonff
источник
3
+1 за «Прежде всего выключите экран». Я считаю, что силу мышления слишком часто принимают за мощность вычислений.
kontur
1
+1 за простейший подход - карандаш и бумагу. Часто люди путаются, сидя перед IDE :)
Нирадж Гулия
Некоторые ученые говорят, что наш мозг невнимателен при просмотре экрана. Когда я изучаю проектирование архитектуры программного обеспечения, наш учитель заставляет нас работать на бумаге. Он не возражает против мощного программного обеспечения UML. Важно сначала понять вещи.
smonff 01
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 .

Например, принцип Open Closed гласит, что если вы хотите добавить новую функциональность, вам не нужно добавлять код в существующий класс, а скорее добавлять новый класс.

Для вашего примера приложения это будет означать, что добавление нового налога с продаж потребует добавления нового класса. То же самое и с разными продуктами, которые являются исключениями из правил.

Очевидно, что правило округления относится к отдельному классу - принцип единой ответственности гласит, что каждый класс несет единственную ответственность.

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

Простой алгоритм написания идеально разработанной программы:

  1. Напишите код, который решает проблему
  2. Проверить, соответствует ли код принципам SOLID
  3. Если есть нарушения правил, чем goto 1.
Devdimi
источник
2

Идеальная реализация ООП полностью спорна. Из того, что я вижу в вашем вопросе, вы можете модулировать код в зависимости от роли, которую они выполняют, для вычисления окончательной цены, такой как Product, Tax, ProductDB и так далее.

  1. Productможет быть абстрактным классом, а производные типы, такие как Books, Food, могут быть унаследованы от него. Применимость налога может определяться производными типами. Продукт будет определять, применим ли налог, на основе производного класса.

  2. TaxCriteria может быть перечислением, и это может быть указано во время покупки (импорт, применимость налога с продаж).

  3. Taxкласс будет рассчитывать налог на основе TaxCriteria.

  4. Наличие ShoppingCartItemas, предложенного XXBBCC, может инкапсулировать экземпляры продукта и налога, и это отличный способ разделить детали продукта по количеству, общей цене с налогом и т. Д.

Удачи.

Картик
источник
1

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

Кроме того, я не видел никаких классов, связанных с покупкой и созданием квитанции, как было задано в вопросе.

Питер Цетински
источник
1

Вот отличный пример объектно-ориентированного шаблона для продуктов, налогов и т. Д. Обратите внимание на использование интерфейсов, которое очень важно в объектно-ориентированном дизайне.

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);
    }
LucidCoder
источник
Добро пожаловать в stackoverflow. Убедитесь, что вы объяснили свой ответ в ответ на вопрос. OP не просто ищет решение, а почему решение лучше / хуже.
Simon.SA