1
\$\begingroup\$

I saw this post and tried to do my attempt. This is the requirement of task:

Design a coffee machine which makes different beverages based on set ingredients. The initialization of the recipes for each drink should be hard-coded, although it should be relatively easy to add new drinks. The machine should display the ingredient stock (+cost) and menu upon startup, and after every piece of valid user input. Drink cost is determined by the combination of ingredients. For example, Coffee is 3 units of coffee (75 cents per), 1 unit of sugar (25 cents per), 1 unit of cream (25 cents per). Ingredients and Menu items should be printed in alphabetical order. If the drink is out of stock, it should print accordingly. If the drink is in stock, it should print "Dispensing: ". To select a drink, the user should input a relevant number. If they submit "r" or "R" the ingredients should restock, and "q" or "Q" should quit. Blank lines should be ignored, and invalid input should print an invalid input message.

My code looks like this:

CoffeeMachine

public class CoffeeMachine
{
    Dictionary<string, ConsoleKey> _productsConsoleKey;


    public CoffeeMachine()
    {
        Initialize();
    }

    private void Initialize()
    {
        _productsConsoleKey = new Dictionary<string, ConsoleKey>()
        {
            { Products.Mocha, ConsoleKey.D1 },
            { Products.Soy, ConsoleKey.D2 },
            { Products.Whip, ConsoleKey.D3 },
            { Products.DarkRoast, ConsoleKey.D1 },
            { Products.Espresso, ConsoleKey.D2 },
            { Products.HouseBlend, ConsoleKey.D3 },
        };
    }

    public void Start()
    {
        Beverage beverage;
        var beverageFactory = new BeverageFactory();
        var stock = new Stock();
        var menu = new BeverageMenu(stock, beverageFactory);
        while (stock.Count != 0)
        {
            beverage = SelectCoffee(beverageFactory, stock, menu);
            beverage = SelectCondiment(beverage, beverageFactory, stock, menu);
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("\n******* Thank you for beverage *******");
            Console.WriteLine($"Your beverage is {beverage.Description}. It costs {beverage.Cost()}\n\n\n");
            Console.WriteLine("\n******* Thank you for beverage *******");
            Console.ForegroundColor = ConsoleColor.Gray;
            beverage = null;
        }
        Console.WriteLine("Sorry, the stock is empty");
    }


    private Beverage SelectCondiment(Beverage beverage, BeverageFactory beverageFactory, Stock stock, BeverageMenu menu)
    {
        var emptyProduct = string.Empty;
        while (true)
        {
            menu.Show();

            Console.WriteLine();
            Console.WriteLine("Please, select a condiment:");
            Console.WriteLine($"1. {Products.Mocha} {menu.Items[Products.Mocha]}");
            Console.WriteLine($"2. {Products.Soy} {menu.Items[Products.Soy]}");
            Console.WriteLine($"3. {Products.Whip} {menu.Items[Products.Whip]}");
            Console.WriteLine($"4. No condiment");
            var input = Console.ReadKey();
            beverage = CreateCondiment(input.Key, ref emptyProduct, beverage, beverageFactory, stock);
            if (!string.IsNullOrEmpty(emptyProduct))
                Console.WriteLine($"\nPlease, select another condiment as {emptyProduct} is out of stock\n");
            else
            {
                beverage = RestockBeverages(ref emptyProduct, beverage, beverageFactory, stock);
                break;
            }

        }
        Console.WriteLine($" \n Your beverage is {beverage.Description}  \n ");
        return beverage;
    }


    private Beverage RestockBeverages(ref string emptyProduct, Beverage beverage, BeverageFactory beverageFactory, Stock stock)
    {
        var restock = true;
        while (restock)
        {
            Console.WriteLine($"\nYour condiment is {beverage.Title}");
            Console.WriteLine($"\nPress r to restock {beverage.Title}.\nPress q to quit\n");
            var input = Console.ReadKey();
            switch (input.Key)
            {
                case ConsoleKey.R:
                    beverage = CreateCondiment(_productsConsoleKey[beverage.Title], ref emptyProduct, beverage/**/, beverageFactory, stock);
                    break;
                case ConsoleKey.Q:
                    restock = false;
                    break;
            }
        }
        return beverage;
    }


    private Beverage CreateCondiment(ConsoleKey key, ref string emptyProduct, Beverage beverage, BeverageFactory beverageFactory, Stock stock)
    {
        switch (key)
        {
            case ConsoleKey.D1:
            case ConsoleKey.NumPad1:
                beverage = stock.Items[Products.Mocha] > 0 ? beverageFactory.Mocha(beverage) : null;
                emptyProduct = beverage == null ? Products.Mocha : string.Empty;
                break;
            case ConsoleKey.D2:
            case ConsoleKey.NumPad2:
                beverage = stock.Items[Products.Soy] > 0 ? beverageFactory.Soy(beverage) : null;
                emptyProduct = beverage == null ? Products.Soy : string.Empty;
                break;
            case ConsoleKey.D3:
            case ConsoleKey.NumPad3:
                beverage = stock.Items[Products.Whip] > 0 ? beverageFactory.Whip(beverage) : null;
                emptyProduct = beverage == null ? Products.Whip : string.Empty;
                break;
        }
        return beverage;
    }


    private Beverage SelectCoffee(BeverageFactory beverageFactory, Stock stock, BeverageMenu menu)
    {
        var emptyProduct = string.Empty;
        Beverage beverage = beverageFactory.DarkRoast();

        while (true)
        {
            menu.Show();

            Console.WriteLine("Please, select a coffee:");
            Console.WriteLine($"1. {Products.DarkRoast} {menu.Items[Products.DarkRoast]}");
            Console.WriteLine($"2. {Products.Espresso} {menu.Items[Products.Espresso]}");
            Console.WriteLine($"3. {Products.HouseBlend} {menu.Items[Products.HouseBlend]}");
            var input = Console.ReadKey();


            switch (input.Key)
            {
                case ConsoleKey.D1:
                case ConsoleKey.NumPad1:
                    beverage = stock.Items[Products.DarkRoast] > 0 ? beverageFactory.DarkRoast() : null;
                    emptyProduct = beverage == null ? Products.DarkRoast : string.Empty;
                    break;
                case ConsoleKey.D2:
                case ConsoleKey.NumPad2:
                    beverage = stock.Items[Products.Espresso] > 0 ? beverageFactory.Espresso() : null;
                    emptyProduct = beverage == null ? Products.Espresso : string.Empty;
                    break;
                case ConsoleKey.D3:
                case ConsoleKey.NumPad3:
                    beverage = stock.Items[Products.HouseBlend] > 0 ? beverageFactory.HouseBlend() : null;
                    emptyProduct = beverage == null ? Products.HouseBlend : string.Empty;
                    break;
            }
            if (!string.IsNullOrEmpty(emptyProduct))
                Console.WriteLine($"\nPlease, select another coffee as {emptyProduct} is out of stock\n");
            else
                break;
        }

        Console.WriteLine($" \n Your beverage is {beverage.Description}  \n ");
        return beverage;
    }
}

BeverageMenu

public class BeverageMenu
{
    public SortedDictionary<string, string> Items { get; private set; }

    private readonly Stock _stock;
    private readonly BeverageFactory _beverageFactory;


    public BeverageMenu(Stock stock, BeverageFactory beverageFactory)
    {
        _stock = stock;
        _beverageFactory = beverageFactory;
        Items = new SortedDictionary<string, string>();
    }

    public void Show()
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("******* Beverage Menu *******");
        Console.ForegroundColor = ConsoleColor.Gray;

        if (_stock.Count == 0)
        {
            Console.WriteLine("Sorry, there is no coffee");
            return;
        }
        if (Items.Count == 0)
        {
            var houseBlend = _beverageFactory.HouseBlend();
            Items.Add(houseBlend.Title, $", Price: {houseBlend.Price}" + HasStock(houseBlend.Title));

            var darkRoast = _beverageFactory.DarkRoast();
            Items.Add(darkRoast.Title, $", Price: {darkRoast.Price}" + HasStock(darkRoast.Title));

            var espresso = _beverageFactory.Espresso();
            Items.Add(espresso.Title, $", Price: {espresso.Price}" + HasStock(espresso.Title));

            var mocha = _beverageFactory.Mocha(espresso);
            Items.Add(mocha.Title, $", Price: {mocha.Price}" + HasStock(mocha.Title));

            var whip = _beverageFactory.Whip(espresso);
            Items.Add(whip.Title, $", Price: {whip.Price}" + HasStock(whip.Title));

            var soy = _beverageFactory.Soy(espresso);
            Items.Add(soy.Title, $", Price: {soy.Price}" + HasStock(soy.Title));
        }

        foreach (var item in Items)
            Console.WriteLine(item.Key + item.Value);
    }


    /// <summary>
    /// Whether the stock has product
    /// </summary>
    /// <param name="productTitle">Title of product</param>
    /// <returns></returns>
    private string HasStock(string productTitle)
    {
        return _stock.Items[productTitle] > 0 ? string.Empty : ". Out of the stock";
    }
}

Products

public static class Products
{
    public static string DarkRoast => "Dark Roast";


    public static string Espresso => "Espresso";


    public static string HouseBlend => "House Blend Coffee";


    public static string Mocha => "Mocha";


    public static string Soy => "Soy";


    public static string Whip => "Whip";
}

Stock:

public class Stock
{
    public Dictionary<string, int> Items { get; private set; }

    /// <summary>
    /// Overall Count of items in Stock
    /// </summary>
    public int Count { get; private set; }

    
    private readonly int INITIAL_COUNT = 10;
    

    public Stock()
    {
        Initialize();
    }


    private void Initialize()
    {
        Items = new Dictionary<string, int>
        {
            { Products.DarkRoast, 0 },
            { Products.Espresso, INITIAL_COUNT },
            { Products.HouseBlend, INITIAL_COUNT },
            { Products.Mocha, INITIAL_COUNT },
            { Products.Soy, INITIAL_COUNT },
            { Products.Whip, INITIAL_COUNT }
        };
        Count = Items.Count * INITIAL_COUNT;
    }


    public bool GetItem(string productName) 
    {
        if (!Items.ContainsKey(productName))
            return false;

        if (Items[productName] < 1)
            return false;

        --Items[productName];
        return true;
    }
}

Beverage

public abstract class Beverage
{
    public abstract string Description { get; }


    public abstract double Cost();


    public abstract double Price  { get; }


    public abstract string Title { get; }

}

CondimentDecorator:

public abstract class CondimentDecorator : Beverage
{
    
}

DarkRoast:

public class DarkRoast : Beverage
{
    public override string Title => Products.DarkRoast;

    public override string Description => Title;

    public override double Price => .99;

    

    public override double Cost()
    {
        return Price;
    }
}

Espresso

public class Espresso : Beverage
{
    public override string Title => Products.Espresso;


    public override string Description => Title;

    public override double Price => 1.99;

    

    public override double Cost()
    {
        return Price;
    }
}

HouseBlend

public class HouseBlend : Beverage
{
    public override string Title => Products.HouseBlend;


    public override string Description => Title;

    public override double Price => .89;

    

    public override double Cost()
    {
        return Price;
    }
}

Mocha:

public class Mocha : CondimentDecorator
{
    private readonly Beverage _beverage;


    public override string Title => Products.Mocha;


    public override string Description => _beverage.Description + $", {Title}";

    public override double Price => .20;

    

    public Mocha(Beverage beverage)
    {
        _beverage = beverage;
    }


    public override double Cost()
    {
        return Price + _beverage.Cost();
    }
}

Soy

public class Soy : CondimentDecorator
{
    private readonly Beverage _beverage;


    public override string Title => Products.Soy;


    public override string Description => _beverage.Description + $", {Title}";

    public override double Price => .15;

    

    public Soy(Beverage beverage)
    {
        _beverage = beverage;
    }


    public override double Cost()
    {
        return Price + _beverage.Cost();
    }
}

Whip

public class Whip : CondimentDecorator
{
    private readonly Beverage _beverage;

    public override double Price => .10;


    public override string Title => Products.Whip;

    public override string Description => _beverage.Description + $",  {Title}";

    

    public Whip(Beverage beverage)
    {
        _beverage = beverage;
    }


    public override double Cost()
    {
        return Price + _beverage.Cost();
    }
}

Code can be run though the following code:

static void Main(string[] args)
{
        var coffeeMachine = new CoffeeMachine();
        coffeeMachine.Start();
}

I am really looking forward to your comments.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Rainer's answer is correct, you misunderstood the beverage/ingredient separation. It's hard to review this further without rewriting the core logic, which defeats the purpose of a review. \$\endgroup\$
    – Flater
    May 17 at 8:17
  • \$\begingroup\$ Anything related to cost, price, money, or currency should use Decimal instead of double. \$\endgroup\$
    – Rick Davin
    May 17 at 12:23
5
\$\begingroup\$

I think you misunderstood the task.

In the original task, there was a clear distiction between drinks and ingredients. There was a list of ingredients, each with a name, a price and a stock. There was a separate list of drinks, each consisting of a name and a list of ingredients. Customers could only order drinks and the software then had to find the drink in the list, check the list of ingredients and verify that all ingredients are in stock, then calculate the final price from the individual prices of the ingredients. Your software does none of that.

Another big problem is that you use classes and inheritance for things that should be instances or lists of instances, all of the same class. For example, there should be a "Drink" class and then a list that contains several instances of the "Drink" class, the first with the "Name" property set to "Coffee", the second with "Caffe Mocha", the third with "Cappuccino" and so on.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.