Как создать идеальное приложение для ООП

Недавно я пытался создать компанию x. Они прислали мне несколько вопросов и сказали мне решить только одну.

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

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

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

"Мне сказали, что они заинтересованы в Design Aspect вашего решения и хотели бы оценить мои навыки объектно-ориентированного программирования.

Это то, что они сказали своими словами

  • Для решения мы хотели бы, чтобы вы использовали Java, Ruby или С#.
  • Нам интересен ПРОЕКТНЫЙ АСПЕКТ вашего решения и хотел бы оценить ваши навыки объектно-ориентированного программирования.
  • Вы можете использовать внешние библиотеки или инструменты для создания или тестирования. В частности, вы можете использовать библиотеки модульного тестирования или инструменты сборки для выбранного языка (например, 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
     }
 }

вы можете вводить uncommnet и запускать для разных входов.

Я предоставил решение, но я был отклонен.

"Они сказали, что они не могут рассмотреть меня за наши текущие открытые позиции, потому что решение кода не является удовлетворительным".

Пожалуйста, расскажите мне, что здесь отсутствует. Является ли это решение не хорошим решением OOAD.
Как я могу улучшить свои навыки OOAD.
Мои старшие также говорят, что отличное приложение OOAD также не будет работать практически.

Спасибо

Ответ 1

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

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

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

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

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

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

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

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

Ответ 2

Если компания сообщает что-то о таких библиотеках, как NUnit, JUnit или Test:: Unit, более чем вероятно, что TDD действительно импортует. В вашем примере кода нет тестов вообще.

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

  • Единичные тесты (например, NUnit)
  • Издевательская (например, RhinoMocks)
  • Сохранение (например, NHibernate)
  • Контейнеры IoC (например, NSpring)
  • шаблоны проектирования
  • Принцип SOLID

Я хотел бы рекомендовать www.dimecasts.net как впечатляющий источник бесплатных качественных скринкастов, который охватывает все вышеупомянутые темы.

Ответ 3

Это очень субъективно, но вот несколько моментов, которые я сделал бы о вашем коде:

  • По-моему, вы смешали Product и ShoppingCartItem. Product должно иметь название продукта, налоговый статус и т.д., но не количество. Количество не является собственностью продукта - для каждого клиента компании, который покупает этот конкретный продукт, будет отличаться.

  • ShoppingCartItem должен иметь Product и количество. Таким образом, клиент может свободно покупать более или менее одного продукта. С вашей текущей настройкой это невозможно.

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

Ответ 4

Прежде всего, это очень хороший вопрос для интервью. Это хороший показатель многих навыков.

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

  • Моделирование доменов → как вы создаете хорошую модель решения? Какие объекты вы создаете? Как они будут решать требования? Поиск существительных - хорошее начало, но как вы решаете, хорош ли ваш выбор? Какие другие объекты вам нужны? Какие знания домена необходимы для его решения?
  • Разделение проблем, ослабление связи, высокая степень сцепления → Как вы выделяете части дизайна, которые имеют разные проблемы или изменения, и как вы их связываете? Как вы держите свой дизайн гибким и текущим?
  • Тестирование модулей, рефакторинг, TDD → Каков ваш процесс для решения? Вы пишете тесты, используете макетные объекты, рефакторинг, итерацию?
  • Чистый код, языковые идиомы → Используете ли вы возможности своего языка программирования? Вы пишете понятный код? Имеют ли смысл ваши абстракции? Насколько поддерживаемым является код?
  • Инструменты. Используете ли вы источник управления? Построить инструменты? Иды?

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

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

Но я могу показать вам, как Я (частично) решил эту проблему, как пример (на Java). Посмотрите в Program class, чтобы увидеть, как все это объединяется, чтобы распечатать эту квитанцию:

------------------ THIS IS YOUR ORDER ------------------
(001)                Domain Driven Design -----   $69.99
(001)    Growing Object Oriented Software -----   $49.99
(001)                 House M.D. Season 1 -----   $29.99
(001)                 House M.D. Season 7 -----   $34.50
(IMD)    Growing Object Oriented Software -----    $2.50
(BST)                 House M.D. Season 1 -----    $3.00
(BST)                 House M.D. Season 7 -----    $3.45
(IMD)                 House M.D. Season 7 -----    $1.73
                                SUB-TOTAL -----  $184.47
                                TAX TOTAL -----   $10.68
                                    TOTAL -----  $195.15
---------------- THANKS FOR CHOOSING US ----------------

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

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

Ответ 5

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

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

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

Удачи!

Ответ 6

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

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

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

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

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

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

Ответ 7

Во-первых, не смешивайте класс Product классом Receipt (ShoppingCart), quantity должно быть частью ReceipItem (ShoppingCartItem), а также Tax & Cost. TotalTax & TotalCost должны быть частью ShoppingCart.

Мой класс Product, имеет только Name & Price и некоторые свойства только для IsImported такие как 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.

Ответ 8

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

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

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

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

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

Удачи.

Ответ 9

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

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

Ответ 11

Очень хорошей отправной точкой в ​​отношении правил проектирования являются принципы SOLID.

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

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

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

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

Простым алгоритмом для написания совершенной программы будет:

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

Ответ 12

Атака на проблему стоимости с налогом с использованием шаблона посетителя.

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