HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Coffee making program

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
makingprogramcoffee

Problem

I work mainly at developing MSSQL databases but I've been trying to teach myself C# for a bit now and I wrote some simple code that prints a list of colleagues and their coffee preference.

I just went through it with my precarious knowledge and I can't find much more to add or remove from it, I thought maybe I can post it here and learn from it.

It really is a very innocent bit of code but I think that's exactly where fundamental mistakes are displayed the best.

```
using System;
using System.Collections.Generic;
namespace CoffeeDrinkers
{
class Program
{
static void Main(string[] args)
{
string[] coffeeDrinkers = new[] {"Anthony", "Chris", "Christian"};
foreach (var person in coffeeDrinkers)
{
Console.WriteLine(person + ": " + MakeCoffee(person));
}
Console.ReadLine();
}
private static string NewCoffee(bool milk, int milkAmount, bool sugar, int sugarSpoons)
{
var coffee = new Coffee();
coffee.Milk = milk;
if (coffee.Milk) { coffee.MilkAmount = milkAmount; }
coffee.Sugar = sugar;
if (coffee.Sugar) { coffee.SugarSpoons = sugarSpoons; }
return PrepareCoffee(coffee);
}
private static string PrepareCoffee(Coffee coffee)
{
string a = "", b = "", c = "";

if (coffee.Milk)
{
a = "White";
if (coffee.MilkAmount 1)
{
c = ", " + coffee.SugarSpoons + " sugars.";
}
else
{
c = ", one sugar.";
}
}
else
{
c = " ,no sugar.";
}
return a + b + c;
}
private static string MakeCoffee(string drinker)
{
switch (drinker)
{
case "Anthony": return Ne

Solution

Issues

Your current model violates two principles:

  • OCP: you cannot easily add new coffee types without touching multiple functions.



  • SRP: the PrepareCoffee method not only creates a coffee but also contains logic for multiple recipies.



Let's fix that...

Fix

We start by creating a few abstractions that will allow us to create more concrete objects.

The first thing we need is an Ingredient:

abstract class Ingredient
{
    protected Ingredient(string name, int amount)
    {
        Name = name;
        Amount = Amount;
    }
    public string Name { get; }
    public int Amount { get; }
}


we now use this to derive concrete ingredients from it:

class Milk : Ingredient
{
    public Milk(int amount) : base("Milk", amount) { }
}

class Sugar : Ingredient
{
    public Sugar(int amount) : base("Sugar", amount) { }
}


we also use this to create another abstractions which is a Coffee:

abstract class Coffee : Ingredient
{
    public Coffee(string name, int amount) : base(name, amount) { }
}


and of course some concrete coffee types:

class Espresso : Coffee
{
    public Espresso(int amount) : base("Espresso", amount) { }
}

class Americano : Coffee
{
    public Americano(int amount) : base("Americano", amount) { }
}


But we need more. Ingredients need to be combined in to something even more concrete - a recipe.

class Recipe : IEnumerable
{
    private readonly IEnumerable _ingredients;
    public Recipe(int id, string name, params Ingredient[] ingredients)
    {
        Id = id;
        Name = name;
        _ingredients = ingredients;
    }
    public string Name { get; }
    public int Id { get; }
    public IEnumerator GetEnumerator() => _ingredients.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}


A recipe can be for various things, we are interested only in drinks, so let's create it:

private class Drink
{
    public Drink(int id, string name, Recipe recipe)
    {
        Id = id;
        Name = name;
        Recipe = recipe;
    }
    public string Name { get; }
    public int Id { get; }
    public Recipe Recipe { get; set; }
}


This will allow us to create a menu:

var drinks = new Drink[]
{
    new Drink(1, "Espresso1", new Recipe(new Espresso(3), new Milk(2), new Sugar(1))),
    new Drink(2, "Black with Sugar", new Recipe(new Americano(3), new Sugar(2))),
}


Lastly we need customers...

class Customer
{
    public Customer(string name, Drink drink)
    {
        Name = name;
        Drink = drink;

    public string Name { get; }
    public Drink Drink { get; }
}


If a customer wants to buy something we can create a new one and assign a drink to him:

var customers = new Customer[]
{
    new Customer("Anthony", drinks.Single(x => x.Id == 1)),
    new Customer("Chris", drinks.Single(x => x.Id == 2)),
}


You have a textual representation of the recipes. You can achieve this by for example creating a new class specialized only in this:

class RecipeRenderer
{
     public string Render(Recipe recipe)
     {
         // create the string...
     }
}


With all these modules you can now add ingredients, drinks etc. without modifying anything. You just create new types and add a new item to the menu.

So how SOLID is this now?

  • SRP: checked - each module does only one thing



  • OCP: checked - can be extended without modifying any components



  • LSP: checked - Derived types must be completely substitutable for their base types. - Coffee, Milk, Espresso all are Ingredient and can be used as such.



  • ISP: does not apply



  • DIP: checked - High Level Classes Recipe --> Abstraction Layer Ingredient --> Low Level Classes Milk, Sugar



You can of course add meals or flatware and create even more complex menus and services.

Code Snippets

abstract class Ingredient
{
    protected Ingredient(string name, int amount)
    {
        Name = name;
        Amount = Amount;
    }
    public string Name { get; }
    public int Amount { get; }
}
class Milk : Ingredient
{
    public Milk(int amount) : base("Milk", amount) { }
}

class Sugar : Ingredient
{
    public Sugar(int amount) : base("Sugar", amount) { }
}
abstract class Coffee : Ingredient
{
    public Coffee(string name, int amount) : base(name, amount) { }
}
class Espresso : Coffee
{
    public Espresso(int amount) : base("Espresso", amount) { }
}

class Americano : Coffee
{
    public Americano(int amount) : base("Americano", amount) { }
}
class Recipe : IEnumerable<Ingredient>
{
    private readonly IEnumerable<Ingredient> _ingredients;
    public Recipe(int id, string name, params Ingredient[] ingredients)
    {
        Id = id;
        Name = name;
        _ingredients = ingredients;
    }
    public string Name { get; }
    public int Id { get; }
    public IEnumerator<Ingredient> GetEnumerator() => _ingredients.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

Context

StackExchange Code Review Q#145946, answer score: 8

Revisions (0)

No revisions yet.