diff options
32 files changed, 1293 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61fdd38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,252 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml
\ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..479cafd --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/KukaPizza.dll", + "args": [], + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +}
\ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..4d9f40f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/KukaPizza.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/KukaPizza.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/KukaPizza.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +}
\ No newline at end of file diff --git a/Adapters/IPayment.cs b/Adapters/IPayment.cs new file mode 100644 index 0000000..c07b7e5 --- /dev/null +++ b/Adapters/IPayment.cs @@ -0,0 +1,9 @@ +using KukaPizza.Models; + +namespace KukaPizza.Adapters +{ +public interface IPayment + { + void Pay(double price); + } +}
\ No newline at end of file diff --git a/Adapters/MastercardPayment.cs b/Adapters/MastercardPayment.cs new file mode 100644 index 0000000..d6b7953 --- /dev/null +++ b/Adapters/MastercardPayment.cs @@ -0,0 +1,13 @@ +using System; +using KukaPizza.Models; + +namespace KukaPizza.Adapters +{ + public class MasterCardPayment + { + public void PayWithMastercard(double price) + { + Console.WriteLine($"\nPaid {price} with Mastercard card."); + } + } +}
\ No newline at end of file diff --git a/Adapters/MastercardPaymentAdapter.cs b/Adapters/MastercardPaymentAdapter.cs new file mode 100644 index 0000000..8908315 --- /dev/null +++ b/Adapters/MastercardPaymentAdapter.cs @@ -0,0 +1,13 @@ +using KukaPizza.Models; + +namespace KukaPizza.Adapters +{ + public class MasterCardPaymentAdapter : IPayment + { + private MasterCardPayment _mastercardPayment = new MasterCardPayment(); + public void Pay(double price) + { + _mastercardPayment.PayWithMastercard(price); + } + } +}
\ No newline at end of file diff --git a/Adapters/VisaPayment.cs b/Adapters/VisaPayment.cs new file mode 100644 index 0000000..8042956 --- /dev/null +++ b/Adapters/VisaPayment.cs @@ -0,0 +1,13 @@ +using System; +using KukaPizza.Models; + +namespace KukaPizza.Adapters +{ + public class VisaPayment : IPayment + { + public void Pay(double price) + { + Console.WriteLine($"\nPaid {price} with Visa card."); + } + } +}
\ No newline at end of file diff --git a/Builders/IPizzaBuilder.cs b/Builders/IPizzaBuilder.cs new file mode 100644 index 0000000..dac9009 --- /dev/null +++ b/Builders/IPizzaBuilder.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using KukaPizza.Models; + +namespace KukaPizza.Builders +{ + public interface IPizzaBuilder + { + void ChooseBase(Pizza pizza); + void AddExtraToppings(List<Topping> extraToppings); + void ChooseSize(PizzaSize size); + } +}
\ No newline at end of file diff --git a/Builders/PizzaBuilder.cs b/Builders/PizzaBuilder.cs new file mode 100644 index 0000000..e86cdaf --- /dev/null +++ b/Builders/PizzaBuilder.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using KukaPizza.Models; + +namespace KukaPizza.Builders +{ + public class PizzaBuilder : IPizzaBuilder + { + PizzaOrder _pizzaOrder = new PizzaOrder(); + public void ChooseBase(Pizza pizza) + { + _pizzaOrder.BasePizza = pizza; + } + + public void AddExtraToppings(List<Topping> extraToppings) + { + _pizzaOrder.ExtraToppings = extraToppings; + } + + public void ChooseSize(PizzaSize size) + { + _pizzaOrder.Size = size; + } + + public PizzaOrder GetResult() + { + return _pizzaOrder; + } + } +}
\ No newline at end of file diff --git a/Controllers/CheckOrdersController.cs b/Controllers/CheckOrdersController.cs new file mode 100644 index 0000000..861fb2e --- /dev/null +++ b/Controllers/CheckOrdersController.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using KukaPizza.Models; +using KukaPizza.Views; + +namespace KukaPizza.Controllers +{ + public class CheckOrdersController : Controller + { + private static Database db = Database.Instance; + + public List<PizzaOrder> Orders { get { return db.Orders; } } + public CheckOrdersController() + { + new CheckOrdersView(this); + } + } +}
\ No newline at end of file diff --git a/Controllers/Controller.cs b/Controllers/Controller.cs new file mode 100644 index 0000000..afa7295 --- /dev/null +++ b/Controllers/Controller.cs @@ -0,0 +1,10 @@ +using System; +using KukaPizza.Views; + +namespace KukaPizza.Controllers +{ + public abstract class Controller + { + + } +}
\ No newline at end of file diff --git a/Controllers/MainController.cs b/Controllers/MainController.cs new file mode 100644 index 0000000..b7167e5 --- /dev/null +++ b/Controllers/MainController.cs @@ -0,0 +1,28 @@ +using System; +using KukaPizza.Views; + +namespace KukaPizza.Controllers +{ + public class MainController : Controller + { + public MainController() + { + new MainView(this); + } + + public void OpenOrderPizza() + { + new OrderPizzaController(); + } + + public void OpenCheckOrders() + { + new CheckOrdersController(); + } + + public void Close() + { + System.Environment.Exit(0); + } + } +}
\ No newline at end of file diff --git a/Controllers/OrderPizzaController.cs b/Controllers/OrderPizzaController.cs new file mode 100644 index 0000000..acee28a --- /dev/null +++ b/Controllers/OrderPizzaController.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using KukaPizza.Adapters; +using KukaPizza.Builders; +using KukaPizza.Models; +using KukaPizza.Views; + +namespace KukaPizza.Controllers +{ + public class OrderPizzaController : Controller + { + private static Database db = Database.Instance; + + public Pizza[] Pizzas { get { return db.Pizzas; } } + + public Topping[] Toppings { get { return db.Toppings; } } + private static List<int> _selectedToppings = new List<int>(); + public List<int> SelectedToppings { get { return _selectedToppings; } } + + private PizzaOrder _order; + public PizzaOrder Order { get { return _order; } } + + private PizzaBuilder pb = new PizzaBuilder(); + + public OrderPizzaController() + { + new OrderPizzaStep1View(this); + } + + public void ChooseBasePizza(int number) + { + try + { + pb.ChooseBase(db.Pizzas[number]); + } + catch { } + } + + public void SelectTopping(int number) + { + _selectedToppings.Add(number); + } + + public void AddExtraToppings() + { + List<Topping> extraToppings = new List<Topping>(); + foreach (var s in _selectedToppings) + { + extraToppings.Add(db.Toppings[s]); + } + + pb.AddExtraToppings(extraToppings); + } + + public void ChooseSize(int number) + { + PizzaSize size; + + switch (number) + { + case 1: + size = PizzaSize.Small; + break; + case 2: + size = PizzaSize.Medium; + break; + case 3: + size = PizzaSize.Large; + break; + default: + size = PizzaSize.Medium; + break; + } + pb.ChooseSize(size); + + _order = pb.GetResult(); + } + + public void MakePaymentWithVisa() + { + IPayment payment = new VisaPayment(); + payment.Pay(Order.GetPrice()); + } + + public void MakePaymentWithMastercard() + { + IPayment payment = new MasterCardPaymentAdapter(); + payment.Pay(Order.GetPrice()); + } + + public void ConfirmOrder() + { + _selectedToppings = new List<int>(); + db.Orders.Add(_order); + } + + public void CancelOrder() + { + } + + public void GoBackToMainView() + { + new MainController(); + } + } +}
\ No newline at end of file diff --git a/Database.cs b/Database.cs new file mode 100644 index 0000000..e717bed --- /dev/null +++ b/Database.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.Linq; + +using KukaPizza.Models; + +namespace KukaPizza +{ + public sealed class Database + { + private static readonly Database instance = new Database(); + + static Database() { } + private Database() + { + _toppings = new Topping[] { + new Topping("ham", ToppingCategory.Meat, 1.23), + new Topping("beef", ToppingCategory.Meat, 2.12), + new Topping("chicken", ToppingCategory.Meat, 2.11), + new Topping("pepperoni", ToppingCategory.Meat, 1.43), + new Topping("mushrooms", ToppingCategory.Vegetable, 1.35), + new Topping("corn", ToppingCategory.Vegetable, 1.76), + new Topping("tomatoes", ToppingCategory.Vegetable, 1.99), + new Topping("peppers", ToppingCategory.Vegetable, 1.11), + new Topping("black olives", ToppingCategory.Vegetable, 1.22), + new Topping("onion", ToppingCategory.Vegetable, 1.30), + new Topping("mozzarella", ToppingCategory.Cheese, 2.42), + new Topping("Grana Padano", ToppingCategory.Cheese, 4.23), + new Topping("tomato sauce", ToppingCategory.Sauce, 1.25), + new Topping("spicy tomato sauce", ToppingCategory.Sauce, 1.40), + new Topping("BBQ sauce", ToppingCategory.Sauce, 1.50) + }; + + _pizzas = new Pizza[] { + new Pizza("Margherita", ( + from t in _toppings + where t.Name == "mozzarella" + || t.Name == "herbal tomato sauce" + select t + ).ToList(), 20.99), + new Pizza("Classica", ( + from t in _toppings + where t.Name == "ham" + || t.Name == "mushrooms" + || t.Name == "mozzarella" + || t.Name == "tomato sauce" + select t + ).ToList(), 32.99), + new Pizza("Pepperoni", ( + from t in _toppings + where t.Name == "pepperoni" + || t.Name == "mozarella" + || t.Name == "tomato sauce" + select t + ).ToList(), 32.99), + new Pizza("Texas", ( + from t in _toppings + where t.Name == "chicken" + || t.Name == "corn" + || t.Name == "onion" + || t.Name == "mozzarella" + || t.Name == "BBQ sauce" + select t + ).ToList(), 34.99), + new Pizza("Prosciutto", ( + from t in _toppings + where t.Name == "ham" + || t.Name == "Grana Padano" + || t.Name == "tomatoes" + || t.Name == "mozzarella" + || t.Name == "tomato sauce" + select t + ).ToList(), 37.99), + new Pizza("European", ( + from t in _toppings + where t.Name == "beef" + || t.Name == "ham" + || t.Name == "mushrooms" + || t.Name == "mozzarella" + || t.Name == "tomato sauce" + select t + ).ToList(), 34.99), + new Pizza("Farmer", ( + from t in _toppings + where t.Name == "chicken" + || t.Name == "onion" + || t.Name == "peppers" + || t.Name == "mushrooms" + || t.Name == "mozzarella" + || t.Name == "tomato sauce" + select t + ).ToList(), 34.99), + new Pizza("Capricciosa", ( + from t in _toppings + where t.Name == "ham" + || t.Name == "mushrooms" + || t.Name == "tomatoes" + || t.Name == "black olives" + || t.Name == "mozzarella" + || t.Name == "tomato sauce" + select t + ).ToList(), 34.99), + }; + + _orders = new List<PizzaOrder>(); + } + + public static Database Instance { get { return instance; } } + + private Topping[] _toppings; + private Pizza[] _pizzas; + private List<PizzaOrder> _orders; + + public Topping[] Toppings { get { return _toppings; } } + public Pizza[] Pizzas { get { return _pizzas; } } + public List<PizzaOrder> Orders { get { return _orders; } } + } +}
\ No newline at end of file diff --git a/KukaPizza.csproj b/KukaPizza.csproj new file mode 100644 index 0000000..d453e9a --- /dev/null +++ b/KukaPizza.csproj @@ -0,0 +1,8 @@ +<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
+ </PropertyGroup>
+
+</Project>
diff --git a/Models/Pizza.cs b/Models/Pizza.cs new file mode 100644 index 0000000..6cc053c --- /dev/null +++ b/Models/Pizza.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace KukaPizza.Models +{ + public class Pizza + { + private string _name; + private List<Topping> _toppings; + private double _price; + + public Pizza(string name, List<Topping> toppings, double price) + { + _name = name; + _toppings = toppings; + _price = price; + } + + public string Name { get { return _name; } } + public List<Topping> Toppings { get { return _toppings; } } + public double Price { get { return _price; } } + + public string ToppingsToString() + { + string toppings = ""; + + for (int i = 0; i < _toppings.Count - 1; i++) + { + toppings += _toppings[i].Name + ", "; + } + + toppings += _toppings[_toppings.Count - 1].Name; + + return toppings; + } + } +}
\ No newline at end of file diff --git a/Models/PizzaOrder.cs b/Models/PizzaOrder.cs new file mode 100644 index 0000000..a8a983a --- /dev/null +++ b/Models/PizzaOrder.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; + +namespace KukaPizza.Models +{ + public enum PizzaSize + { + Small, + Medium, + Large + } + public class PizzaOrder + { + public Pizza BasePizza { get; set; } + public List<Topping> ExtraToppings { get; set; } + public PizzaSize Size { get; set; } + + public string ExtraToppingsToString() + { + string toppings = ""; + + for (int i = 0; i < ExtraToppings.Count - 1; i++) + { + toppings += ExtraToppings[i].Name + ", "; + } + + toppings += ExtraToppings[ExtraToppings.Count - 1].Name; + + return toppings; + } + + public double GetPrice() + { + double totalPrice = BasePizza.Price; + + foreach (var t in ExtraToppings) + { + totalPrice += t.Price; + } + + return totalPrice; + } + } +}
\ No newline at end of file diff --git a/Models/Topping.cs b/Models/Topping.cs new file mode 100644 index 0000000..ab273e3 --- /dev/null +++ b/Models/Topping.cs @@ -0,0 +1,29 @@ +namespace KukaPizza.Models +{ + public enum ToppingCategory + { + Vegetable, + Meat, + Spice, + Cheese, + SeaFood, + Sauce + } + + public class Topping + { + private string _name; + private ToppingCategory _category; + private double _price; + public Topping(string name, ToppingCategory category, double price) + { + _name = name; + _category = category; + _price = price; + } + + public string Name { get { return _name; } } + public ToppingCategory Category { get { return _category; } } + public double Price { get { return _price; } } + } +}
\ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..8deb16c --- /dev/null +++ b/Program.cs @@ -0,0 +1,17 @@ +using System;
+using KukaPizza.Controllers;
+
+namespace KukaPizza
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ Console.Title = "Kuka Pizza";
+ Console.CursorVisible = false;
+ Console.Clear();
+
+ new MainController();
+ }
+ }
+}
diff --git a/README.md b/README.md new file mode 100644 index 0000000..b624c90 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Kuka Pizza + +A simple console program for ordering pizza that implements software design patterns used in object oriented programming. Final assignment of Software Design Patterns course at KEA - Københavns Erhvervsakademi. + +## Preview + +![Preview](meta/preview.gif) + +## 1. Introduction + +Our final task in the Software Design Patterns course was to create a program in an object-oriented programming language that would include four design patterns that we have learned during the lessons. + +I decided to develop the program in C# because it is the language I was taught before and am most familiar with it. Together with it, I used .NET Core that is a free and open-source, cross-platform framework, commonly used with C#. + +The program I have created is a simple console application that simulates a pizza ordering software. I decided to realize this idea because I believed that this kind of project would allow me to implement some design patterns in a similar way they would be in a real-life scenario. My focus during the development was on the patterns, therefore the application is not that rich in terms of functionality. + +The program’s features include ordering of pizza and displaying the list of previously ordered pizzas. When the user opens the program, he/she is greeted with a welcoming screen that allows accessing these two features. To navigate in the application, the user has to press number keys corresponding to the option on the screen, or type the full number and press the enter key. Upon selecting the first option, “Order a pizza”, the user starts a step-by-step process of ordering. The first question is what should be the base of the requested pizza. Then, the user can choose some additional toppings on the pizza. The third step is to choose what should be the size of the pizza. On the last screen, the user is shown the final order, as created in the previous steps, and asked for confirmation whether it is correct. When the user selects the “yes” option, he/she is taken back to the welcoming screen. If the user chooses the second option there, he/she can see the order on the list. + +## 2. Design patterns used + +The patterns to be used were not completely obvious before the development started. For example, I expected that for the creation of pizzas, I will use some creational patterns like Factory, Builder, or Prototype. Some of the patterns became apparent only during the work after some problems appeared. Then, I would stop to think of the pattern I could use to solve them. Surprisingly, I also managed to implement a pattern first, without realizing it. After all, the patterns I used are the following: Template Method, Singleton, Builder, and Adapter. + +One of the first issues was how to create nice navigation within the application with each screen being encapsulated. I came up with an idea to create an abstract class that would have some methods that could be used universally on all the screens, e.g. for navigation, and methods that are specific to the screen, i.e. for drawing and user input. Then, I created all the screens based on that class. Only after some time, I realized that I have in fact used the Template Method pattern. + +Another issue was that I needed something to hold the data on available pizzas and toppings. Something that could be accessible anywhere and unique. I could use a database or a file but because it was supposed to be a simple program, I decided to solve it differently. The perfect solution for this kind of problem is a Singleton. It can have only one instance, so I could make sure that I am always using the same objects I created inside it, and that they are persistent and easily accessible. + +The creation of a pizza was a problem that I decided to solve with a Builder pattern. I chose it because the making of a pizza order is a more complex task that consists of multiple steps that require user input. That is exactly the purpose of the Builder pattern. The Factory pattern might seem similar but in comparison to it but it requires the entire object to be built in a single method call, with all the parameters passed in on a single line, which I could not do because I wanted to create the pizza in smaller steps. + +The last issue did not occur naturally but was created by me to show a problem that could happen while working on a real program. After the pizza order is made, the user is asked to confirm it and pay. He/she has two possibilities: to pay with Visa, or with MasterCard. Let’s say the Visa was implemented first and the code was based on the methods provided by the Visa library. Then, I wanted to add MasterCard support but it turned out its library is not compatible with the payment logic inside the application. To solve this problem, I used the Adapter pattern. + +### 2.1. Template Method + +![Template Method](meta/TemplateMethod.png) + +I use the Template Method pattern to create all the views within the application and easily navigate between them. I created an abstract class **View** that has two methods that have no body but need to be implemented in any class that inherits **View**: **Draw()** and **Interact()**. The first one is used to draw the user interface and information in the console. The second one is used for getting user input. Because on all of the views, the printed information, as well as the possible options, may vary, they need to be implemented in the concrete classes such as **OrderPizzaStep1View**. However, all of the views should have some methods that will always work in the same way. These methods are: **Init(Controller)** and **NavigateTo(Type)**. Both of them are implemented in the abstract **View** class and are used in the concrete classes. **Init(Controller)** initializes the view, i.e. draws it and starts the listener for user input. As the name suggests, **NavigateTo(Type)** allows for navigation between the views. + +So, the Template Method helped me avoid code duplication because I could have some universal methods, and at the same time, allowed me to have varying behavior for the specific concrete classes. + +### 2.2. Singleton + +![Singleton](meta/Singleton.png) + +I used the Singleton to create a fake database that would allow some data like **Toppings**, **Pizzas**, and **Orders** to be accessible throughout the whole application. Just like in case of a real database, there can be only one instance of **Database** class. Because its constructor is private, it can only be used inside this class. And it is created as a variable **instance** that is then exposed to the outside with **Instance** property. This is a thread-safe implementation with lazy loading, meaning it will only be created when it is accessed for the first time. + +### 2.3. Builder + +![Builder](meta/Builder.png) + +The Builder pattern is a creational pattern that is used for creating complex objects in steps. It separates the construction of a complex object from its representation. In my application, the user has to follow a process consisting of multiple steps to order a pizza. Each of these steps has a corresponding action performed by **PizzaBuilder**. **PizzaBuilder** is an implementation of **IPizzaBuilder** interface. It is the only one, but there could be more. **OrderPizzaController** has an instance of **PizzaBuilder** and when the user chooses options in the application, his/her options are being first sent to the controller which then calls the building methods from the builder. When all the steps are finished, the builder allows to get the final result with **GetResult()** method which returns the **PizzaOrder**. + +The Builder pattern isolates code for construction and representation. It allows creating different objects based on some parameters and gives more control over the construction process. + +### 2.4. Adapter + +![Adapter](meta/Adapter.png) + +I used the Adapter pattern to solve the problem of incompatible interface of **MastercardPayment** with the already existing **IPayment** logic. I wrapped **MastercardPayment** in **MastercardPaymentAdapter** that implements the **IPayment** interface. The adapter simply creates an instance of **MastercardPayment** and calls its **PayWithMastercard(double)** method. This way, I could easily adjust the incompatible class with the rest of the code without making many changes to it. + +## 3. Conclusion + +During the development of the application, I stumbled upon many issues that I managed to solve with different patterns that match the specific situation. I used Template Method for creation of encapsulated views, Singleton for storing data, Builder for making orders, and Adapter for using incompatible payment library. + +The usage of design patterns not only solves the problems when designing applications in object-oriented languages but also help the code to be of high quality, reusable, and properly organized. diff --git a/Views/CheckOrdersView.cs b/Views/CheckOrdersView.cs new file mode 100644 index 0000000..bedf38f --- /dev/null +++ b/Views/CheckOrdersView.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using KukaPizza.Controllers; +using KukaPizza.Models; + +namespace KukaPizza.Views +{ + public class CheckOrdersView : View + { + private CheckOrdersController _controller; + private List<PizzaOrder> orders; + public CheckOrdersView(CheckOrdersController controller) + { + _controller = controller; + orders = controller.Orders; + Init(_controller); + } + + protected override void Draw() + { + Console.Clear(); + Console.Write( +@"================================================================================ + Your orders +================================================================================ +"); + foreach (var o in orders) + { + Console.WriteLine($" Base pizza: {o.BasePizza.Name}"); + Console.WriteLine($" Extra toppings: {o.ExtraToppingsToString()}"); + Console.WriteLine($" Size: {o.Size}"); + Console.Write("--------------------------------------------------------------------------------"); + } + + Console.WriteLine("\n 0. Go back"); + } + + protected override void Interact() + { + int choice = 0; + + do + { + choice = Console.ReadKey(true).KeyChar; + + Draw(); + } while (choice != '0'); + } + } +}
\ No newline at end of file diff --git a/Views/MainView.cs b/Views/MainView.cs new file mode 100644 index 0000000..42f7c91 --- /dev/null +++ b/Views/MainView.cs @@ -0,0 +1,68 @@ +using System; +using KukaPizza.Controllers; + +namespace KukaPizza.Views +{ + public class MainView : View + { + private MainController _controller; + public MainView(MainController controller) + { + _controller = controller; + Init(_controller); + + } + protected override void Draw() + { + Console.Write(@" + + + + __ __ __ ____ +/\ \/\ \ /\ \ /\ _`\ __ +\ \ \/'/' __ __\ \ \/'\ __ \ \ \L\ \/\_\ ____ ____ __ + \ \ , < /\ \/\ \\ \ , < /'__`\ \ \ ,__/\/\ \/\_ ,`\ /\_ ,`\ /'__`\ + \ \ \\`\\ \ \_\ \\ \ \\`\ /\ \L\.\_ \ \ \/ \ \ \/_/ /_\/_/ /_/\ \L\.\_ + \ \_\ \_\ \____/ \ \_\ \_\ \__/.\_\ \ \_\ \ \_\/\____\ /\____\ \__/.\_\ + \/_/\/_/\/___/ \/_/\/_/\/__/\/_/ \/_/ \/_/\/____/ \/____/\/__/\/_/ + + + + + 1. Order a pizza + 2. Check orders + + 0. Exit + + + +================================================================================ + Copyright © 2019 Marcin Zelent & Paulius Klezys +================================================================================"); + } + + protected override void Interact() + { + int choice = 0; + + do + { + choice = Console.ReadKey(true).KeyChar; + + switch (choice) + { + case '1': + _controller.OpenOrderPizza(); + break; + case '2': + _controller.OpenCheckOrders(); + break; + } + + Draw(); + } while (choice != '0'); + + if (choice == '0') _controller.Close(); + } + } +}
\ No newline at end of file diff --git a/Views/OrderPizzaStep1View.cs b/Views/OrderPizzaStep1View.cs new file mode 100644 index 0000000..d596fee --- /dev/null +++ b/Views/OrderPizzaStep1View.cs @@ -0,0 +1,61 @@ +using System; +using KukaPizza.Controllers; +using KukaPizza.Models; + +namespace KukaPizza.Views +{ + public class OrderPizzaStep1View : View + { + private static OrderPizzaController _controller; + private Pizza[] pizzas; + + public OrderPizzaStep1View(OrderPizzaController controller) + { + _controller = controller; + pizzas = controller.Pizzas; + Init(_controller); + } + + protected override void Draw() + { + Console.Clear(); + Console.Write( +@"================================================================================ + Step 1. Choose base pizza +================================================================================ + +"); + + for (int i = 0; i < pizzas.Length; i++) + { + Console.WriteLine($" {i + 1}. {pizzas[i].Name} ({pizzas[i].ToppingsToString()}) - {pizzas[i].Price}"); + } + + Console.WriteLine("\n 0. Go back"); + Console.Write($"\nChoose number [0-{pizzas.Length}]: "); + } + + protected override void Interact() + { + string choice = ""; + + do + { + Console.CursorVisible = true; + choice = Console.ReadLine(); + Console.CursorVisible = false; + + if (choice == "") choice = "-1"; + + int choiceInt = Int32.Parse(choice); + if (choiceInt > 0 && choiceInt < pizzas.Length + 1) + { + _controller.ChooseBasePizza(choiceInt - 1); + NavigateTo(typeof(OrderPizzaStep2View)); + } + + Draw(); + } while (choice != "0"); + } + } +}
\ No newline at end of file diff --git a/Views/OrderPizzaStep2View.cs b/Views/OrderPizzaStep2View.cs new file mode 100644 index 0000000..b8abcc7 --- /dev/null +++ b/Views/OrderPizzaStep2View.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using KukaPizza.Controllers; +using KukaPizza.Models; + +namespace KukaPizza.Views +{ + public class OrderPizzaStep2View : View + { + private static OrderPizzaController _controller; + private Topping[] toppings; + private List<int> selectedToppings; + public OrderPizzaStep2View(OrderPizzaController controller) + { + _controller = controller; + toppings = _controller.Toppings; + selectedToppings = _controller.SelectedToppings; + Init(_controller); + } + + + protected override void Draw() + { + Console.Clear(); + Console.Write( +@"================================================================================ + Step 2. Add extra toppings +================================================================================ + +"); + + for (int i = 0; i < toppings.Length; i++) + { + Console.WriteLine($" [{(selectedToppings.Contains(i + 1) ? 'x' : ' ')}] {i + 1}. {toppings[i].Name} - {toppings[i].Price}"); + } + + Console.WriteLine($"\n {toppings.Length + 1}. Done\n 0. Go back"); + Console.Write($"\nChoose number [0-{toppings.Length + 1}]: "); + } + + protected override void Interact() + { + string choice = ""; + + do + { + Console.CursorVisible = true; + choice = Console.ReadLine(); + Console.CursorVisible = false; + + if (choice == "") choice = "-1"; + + int choiceInt = Int32.Parse(choice); + + if (choiceInt != 0 && choiceInt != toppings.Length + 1) + _controller.SelectTopping(choiceInt); + else if (choiceInt == toppings.Length + 1) + { + _controller.AddExtraToppings(); + NavigateTo(typeof(OrderPizzaStep3View)); + } + + Draw(); + } while (choice != "0"); + } + } +}
\ No newline at end of file diff --git a/Views/OrderPizzaStep3View.cs b/Views/OrderPizzaStep3View.cs new file mode 100644 index 0000000..6fbfebc --- /dev/null +++ b/Views/OrderPizzaStep3View.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using KukaPizza.Controllers; +using KukaPizza.Models; + +namespace KukaPizza.Views +{ + public class OrderPizzaStep3View : View + { + private OrderPizzaController _controller; + + public OrderPizzaStep3View(OrderPizzaController controller) + { + _controller = controller; + Init(_controller); + } + + protected override void Draw() + { + Console.Clear(); + Console.Write( +@"================================================================================ + Step 3. Choose pizza size +================================================================================ + + 1. Small + 2. Medium + 3. Large + +"); + Console.WriteLine($" 0. Go back"); + Console.Write($"\nChoose number [0-3]: "); + } + + protected override void Interact() + { + string choice = ""; + + do + { + Console.CursorVisible = true; + choice = Console.ReadLine(); + Console.CursorVisible = false; + + if (choice == "") choice = "-1"; + + int choiceInt = Int32.Parse(choice); + if (choiceInt > 0 && choiceInt < 4) + { + _controller.ChooseSize(choiceInt); + NavigateTo(typeof(OrderPizzaStep4View)); + } + + Draw(); + } while (choice != "0"); + } + } +}
\ No newline at end of file diff --git a/Views/OrderPizzaStep4View.cs b/Views/OrderPizzaStep4View.cs new file mode 100644 index 0000000..b027e7e --- /dev/null +++ b/Views/OrderPizzaStep4View.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using KukaPizza.Controllers; +using KukaPizza.Models; + +namespace KukaPizza.Views +{ + public class OrderPizzaStep4View : View + { + private OrderPizzaController _controller; + private PizzaOrder order; + + public OrderPizzaStep4View(OrderPizzaController controller) + { + _controller = controller; + order = _controller.Order; + Init(_controller); + } + protected override void Draw() + { + Console.Clear(); + Console.Write( +@"================================================================================ + Step 4. Confirm order +================================================================================ + +"); + Console.WriteLine($" Base pizza: {order.BasePizza.Name}"); + Console.WriteLine($" Extra toppings: {order.ExtraToppingsToString()}"); + Console.WriteLine($" Size: {order.Size}"); + Console.WriteLine($" Price: {order.GetPrice()}"); + + Console.Write( +@" + Do you want to place this order? + 1. Yes, pay with Visa card + 2. Yes, pay with Mastercard card + 3. No, go back +"); + Console.Write($"\nChoose number [1-3]: "); + } + + protected override void Interact() + { + string choice = ""; + + do + { + Console.CursorVisible = true; + choice = Console.ReadLine(); + Console.CursorVisible = false; + + if (choice == "1") + { + _controller.MakePaymentWithVisa(); + } + else if (choice == "2") + { + _controller.MakePaymentWithMastercard(); + } + else if (choice == "3") + { + _controller.CancelOrder(); + Console.WriteLine("\nOrder canceled."); + } + + if (choice == "1" || choice == "2") + { + _controller.ConfirmOrder(); + Console.WriteLine("\nOrder successfully placed."); + } + + Console.WriteLine("Press any key to return to the main screen..."); + Console.ReadKey(); + _controller.GoBackToMainView(); + + + Draw(); + } while (choice != "3"); + } + } +}
\ No newline at end of file diff --git a/Views/View.cs b/Views/View.cs new file mode 100644 index 0000000..5db14c9 --- /dev/null +++ b/Views/View.cs @@ -0,0 +1,24 @@ +using System; +using KukaPizza.Controllers; + +namespace KukaPizza.Views +{ + public abstract class View + { + private Controller _controller; + public void Init(Controller controller) + { + _controller = controller; + Draw(); + Interact(); + } + + protected abstract void Draw(); + protected abstract void Interact(); + + public void NavigateTo(Type view) + { + Activator.CreateInstance(view, _controller); + } + } +}
\ No newline at end of file diff --git a/meta/Adapter.png b/meta/Adapter.png Binary files differnew file mode 100644 index 0000000..eaccefe --- /dev/null +++ b/meta/Adapter.png diff --git a/meta/Builder.png b/meta/Builder.png Binary files differnew file mode 100644 index 0000000..705ce8d --- /dev/null +++ b/meta/Builder.png diff --git a/meta/Singleton.png b/meta/Singleton.png Binary files differnew file mode 100644 index 0000000..1705bf0 --- /dev/null +++ b/meta/Singleton.png diff --git a/meta/TemplateMethod.png b/meta/TemplateMethod.png Binary files differnew file mode 100644 index 0000000..a09389d --- /dev/null +++ b/meta/TemplateMethod.png diff --git a/meta/preview.gif b/meta/preview.gif Binary files differnew file mode 100644 index 0000000..67b5d28 --- /dev/null +++ b/meta/preview.gif |