patterncsharpMinor
Coffee making program
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
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:
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
we now use this to derive concrete ingredients from it:
we also use this to create another abstractions which is a
and of course some concrete coffee types:
But we need more. Ingredients need to be combined in to something even more concrete - a recipe.
A recipe can be for various things, we are interested only in drinks, so let's create it:
This will allow us to create a menu:
Lastly we need customers...
If a customer wants to buy something we can create a new one and assign a drink to him:
You have a textual representation of the recipes. You can achieve this by for example creating a new class specialized only in this:
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?
You can of course add meals or flatware and create even more complex menus and services.
Your current model violates two principles:
- OCP: you cannot easily add new coffee types without touching multiple functions.
- SRP: the
PrepareCoffeemethod 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,Espressoall areIngredientand can be used as such.
- ISP: does not apply
- DIP: checked - High Level Classes
Recipe--> Abstraction LayerIngredient--> Low Level ClassesMilk,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.