Skip to content

Skapa menyprogram med klasser

Vi tar vårt menyprogram från kmom01 och gör det objektorienterat.

Image description
Bild: Menyn för MenuApp2

Vi börjar med att skapa vår meny-app i terminalen. Vi ställer oss i kmom02, skapar appens katalog och går ner i den. Därefter skapar vi appen. Nedan visas också kommandot för att köra appen.

I terminalen står du i katalogen kmom02/practice och gör följande
dotnet new console -n MenuApp2
cd MenuApp2
dotnet run // Ger utskriften: Hello, World!"

Vi delar upp koden från kmom01 i två klasser, en klass med hjälpmetoder och en klass som presenterar menyn och agerar på de olika menyalternativen. Vi skapar en ny katalog i MenuApp2 och kallar den src som i källkod (source code). De två nya klasserna lägger vi i src.

Helpers är en klass som endast ska innehålla metoder vilka inte är knutna till något objekt. Det finns därför inga medlemsvariabler och klasserna blir statiska.

I klassdiagrammet blir den mellersta rutan tom eftersom det inte kommer att finnas några variabler.

Metoderna som är statiska identifieras genom att de är understrukna i klassdiagrammet.

Image description
Bild: Klassdiagram Helpers

Vi skapar först klassen Helpers som ska innehålla våra hjälpmetoder. Vi använder “namespacet” eller namnrymden MenuApp2 för att organisera vår kod. Det gör vi för att undvika namnkollisioner och för att göra koden lättare att hantera och underhålla. Vi lägger våra klasser i ett källkodsbibliotek som vi kallar src. Det skapar en ordnad och läsbar kodstruktur som underlättare för utvecklare att arbeta effektivt och lätt förstå projektets struktur.

Kodstruktur i katalogen MenuApp2 med klassen Helpers i src
// stå i kmom02/MenuApp2
mkdir src
cd src
dotnet new class -n Helpers // funkar inte detta skapa filen Helpers.cs i VS Code
cd .. // till rotkatalogen för projektet MenuApp2
tree -L 2 // om tree saknas, installera med kommandot sudo apt install tree
// Ger följande utskrift:
.
├── MenuApp2.csproj // projektfilen för MenuApp2
├── Program.cs // huvudprogrammet
├── src
├── Helpers.cs // klassen Helpers
├── bin
├── obj

Det går också bra att skapa en tom fil och kalla den Helpers.cs, antingen i VS Code eller i terminalen med touch. Helpers.cs är tom och ser ut så här:

Så här ser filen src/Helper.cs med den tomma klassen Helpers ut
// kmom02/MenuApp2/src/Helpers.cs
namespace MenuApp2.src;
public class Helpers
{
}

Vi lägger till kod i Helpers genom att kopierar metoderna GetTerminalReady, GetRandomNumberFrom10To20, ReadIntFromTerminal och CheckIfAdult från MenuApp (kmom01) och klistrar in dessa. Eftersom detta är en hjälpklass med metoder som ska vara åtkomliga via klassen görs alla metoderna publika.

Uppdatera namespace med MenuApp2.src eftersom det är praxis att namespace ska återspegla filstrukturen.

Lägg till följande kod i klassen Helpers
namespace MenuApp2.src;
public class Helpers
{
// Methods
public static void GetTerminalReady(string title)
{
Console.Clear();
Console.Title = title;
}
public static int GetRandomNumberFrom10To20()
{
// Create random class
Random random = new Random();
// Get random value from 10 (10 included) up to 21 (21 not included)
return random.Next(10, 21);
}
public static int ReadIntFromTerminal(string info)
{
Console.WriteLine("\n" + info);
string input = Console.ReadLine() ?? "1";
return int.Parse(input);
}
public static string CheckIfAdult(int age)
{
if (age < 18)
{
return "Du är inte myndig";
}
else
{
return "Grattis - du är myndig";
}
}
}

Klassen Menu skulle kunna vara statisk då den inte kommer behöva variabler men vi väljer att inte göra den statisk för att få ett exempel även på detta.

Image description
Bild: Klassdiagram Menu

Då fortsätter vi med att skapa klassen Menu med följande kommandon (terminalen, stå i kmom02/MenuApp2/src):

Skapa klassen Menu och se att strukturen i MenuApp2 ser ut så här:
dotnet new class -n Menu // funkar inte detta skapa filen Menu.cs i VS Code
cd .. // till rotkatalogen för projektet MenuApp2
tree -L 2
// Ger följande utskrift:
.
├── MenuApp2.csproj // projektfilen för MenuApp2
├── Program.cs // huvudprogrammet
├── src
├── Helpers.cs // klassen Helpers
│   └── Menu.cs // klassen Menu

Metoderna från koden i kmom01 som hör till menyn, PrintMenu() och Run(), lägger vi i klassen Menu.

Lägg till följande kod i klassen Men i filen src/Menu.cs
namespace MenuApp2.src;
public class Menu
{
// Methods
private void PrintMenu()
{
Console.WriteLine("Välj mellan dessa menyalternativ:");
Console.WriteLine("1. Skriv hälsning");
Console.WriteLine("2. Skriv ut en slumptal mellan 10 och 20");
Console.WriteLine("3. Kolla myndighetsålder");
Console.WriteLine("e. Avsluta");
}
public void Run()
{
// Setup
Helpers.GetTerminalReady("MENY"); // anrop direkt på klassen med statiska metoder
string choice = "";
while (choice != "e")
{
PrintMenu();
Console.WriteLine("\nDitt val: ");
choice = Console.ReadLine() ?? "";
switch (choice)
{
case "1":
Console.WriteLine("\nHej på dig!\n");
break;
case "2":
Console.WriteLine("\nRandom value: " + Helpers.GetRandomNumberFrom10To20());
break;
case "3":
string message = Helpers.CheckIfAdult(Helpers.ReadIntFromTerminal("Vilken är din ålder?"));
Console.WriteLine("\n" + message);
break;
case "e":
Console.WriteLine("\nDu har valt att avsluta! Hej då!");
break;
default:
Console.WriteLine("Ogiltligt menyval");
break;
}
}
}
}

För att kunna använda meny behöver vi skapa ett Meny-objekt, det vill säga ett objekt av klass-typen Menu. Via objektet anropas metoden Run().

Från och med .NET 6 så ser huvudprogrammet, eller som det också kallas toppnivåinstruktioner (top-level statements), ut så här. Vi kommer att använda detta sätt:

Huvudprogrammet i Program.cs med modern C# med toplevel statements
using MenuApp2.src; // Här visar vi att huvudprogrammet använder klassen Menu
Menu myMenu = new Menu();
myMenu.Run();

Samma kod i .NET 5 och tidigare versioner ser ut så här:

Huvudprogrammet i Program.cs som det såg i .NET 5 och tidigare med
using System;
using MenuApp2.src;
namespace MenuApp2 // Note: actual namespace depends on the project name.
{
internal class Program
{
static void Main(string[] args)
{
Menu myMenu = new Menu();
myMenu.Run();
}
}
}

För att skapa en app enligt det äldre sättet behöver du skapa en katalog för appen och går ner i den. Därefter skapar du appen med flaggan —use-program-main och du får då den gamla konstruktionen med namespace och class Program. Läs mer om det här.

Så bra, nu har vi samma resultat som innan. Kör vi programmet med “dotnet run” i terminalen ser det ut så här:

Image description
Bild: Klassdiagram Menu

För att göra menyalternativ 1 lite roligare lägger vi till utskrift av en asciibild efter hälsningen. Det är samma klass som vi kommer att använda i inlämningsuppgiften på nästa kmom. Ladda ner klassen AsciiArt och lägg den i katalogen “kmom02/MenuApp2/src”. Uppdatera namespace till MenuApp2.src.

Ladda ner filen AsciiArt.cs och kopiera den till src katalogen. Stå i MenuApp2 katalogen.
task download-code -- kmom02/AsciiArt.cs
cp ../../codeExamples/AsciiArt.cs src/.
ls src
// ger följande: AsciiArt.cs Helpers.cs Menu.cs
Början av klasser AcsiiArt ser ut så här:
namespace MenuApp2.src;
public class AsciiArt
{
public static void PrintDie(int diceRoll)
{
...

I menyalternativ 1 i klassen Menu anropar vi metoden PrintDie i klassen AsciiArt och skriver ut 2 tärningar, i detta exempel har vi argumenten 1 respektive 6.

I klassen Menu anropas metoden PrintDie i klassen AsciiArt
namespace MenuApp2.src;
...
switch (choice)
{
case "1":
Console.WriteLine("\nHej på dig!\n");
AsciiArt.PrintDie(1);
AsciiArt.PrintDie(6);
break;
...

Om vi kör programmet och väljer menyalternativ 1 ser det ut så här nu:

Image description
Bild: Menyalternativ 1 för MenuApp2

Vi lägger till metoden PrintDateAndTime, som skriver ut olika datum- och tidsinformation till konsolen.

Image description
Bild: Klassdiagram Helpers med nya metoden PrintDateAndTime()

Vi lägger till en ny metod i klassen Helpers. Vi använder DateTime objektet från C# och gör följande:

  • skapa ett objekt av klassen DateTime med datum och tid just nu och spara undan det i variabeln presentTime. Skriv därefter ut datum och tid.
  • formatera och skriv ut presentTime enligt svensk och internationell standard (ISO 8601), så att datumet skrivs på formen år-månad-dag med årtalet i fyra siffror (exempelvis 2024-02-12).
  • formatera tiden i HH:mm:ss och skriv ut.
  • skapa ett objekt 2010-10-23 i variabeln newTime skriv ut vilken veckodag det var.
  • addera till objektet i newTime 2 år, 1 månad och 7 dagar och skriv ut vilken veckodag det är.
  • skapa ett objekt av klassen TimeSpan och tilldela det resultatet av att newTime subtraheras från presentTime. Skriv ut resultatet.
Lägg till metoden PrintDateAndTime i klassen Helpers
...
public static void PrintDateAndTime()
{
DateTime presentTime = DateTime.Now;
Console.WriteLine("\nJust nu: " + presentTime.ToString());
Console.WriteLine("\nEnligt ISO 8601: " + presentTime.ToString("yyyy-MM-dd"));
Console.WriteLine("\nTiden: " + presentTime.ToString("HH:mm:ss"));
DateTime newTime = new DateTime(2010, 10, 23);
Console.WriteLine("\nVeckodag 2010-10-23: " + newTime.DayOfWeek.ToString());
newTime = newTime.AddYears(2);
newTime = newTime.AddMonths(1);
newTime = newTime.AddDays(7);
Console.WriteLine("\nVeckodag 2012-11-30: " + newTime.DayOfWeek.ToString());
TimeSpan diff = presentTime - newTime;
Console.WriteLine("\nTimeSpan: " + diff.ToString());
}

Anropa metoden som alternativ 4 i menyprogrammet.

Nu vill vi gärna ha veckodagarna på svenska. Då fixar vi genom att i klassen Helpers skapa en statisk array sweWeekdays med de svenska veckodagarna som strängar. I metoden PrintDateAndTime så tar vi reda på vilket nummer det är på veckodagen och använder det som index i den statiska array.

Image description
Bild: Klassdiagram Helpers med veckodagar på svenska

Här är koden.

Klassen Helpers med veckodagarna på svenska
namespace MenuApp2.src;
public class Helpers
{
private static string[] sweWeekdays = new string[]
{
"måndag",
"tisdag",
"onsdag",
"torsdag",
"fredag",
"lördag",
"söndag"
};
...
public static void PrintDateAndTime()
{
DateTime presentTime = DateTime.Now;
Console.WriteLine("\nJust nu: " + presentTime.ToString());
Console.WriteLine("\nEnligt ISO 8601: " + presentTime.ToString("yyyy-MM-dd"));
Console.WriteLine("\nTiden: " + presentTime.ToString("HH:mm:ss"));
DateTime newTime = new DateTime(2010, 10, 23);
int dayNo = (int)newTime.DayOfWeek;
Console.WriteLine("\nVeckodag 2010-10-23: " + sweWeekdays[dayNo]);
newTime = newTime.AddYears(2);
newTime = newTime.AddMonths(1);
newTime = newTime.AddDays(7);
dayNo = (int)newTime.DayOfWeek;
Console.WriteLine("\nVeckodag 2012-11-30: " + sweWeekdays[dayNo].ToString());
TimeSpan diff = presentTime - newTime;
Console.WriteLine("\nTimeSpan: " + diff.Days.ToString() + " dagar");
}

För att skriva ut veckodagarna så gör vi en ny metod i klassen Helpers som heter PrintDays().

Skriv ut alla veckodagarna med Join i klassen Helpers
namespace MenuApp2.src;
public class Helpers
{
...
public static void PrintDateAndTime()
{
...
TimeSpan diff = presentTime - newTime;
Console.WriteLine("\nTimeSpan: " + diff.Days.ToString() + " dagar");
PrintDays();
}
private static void PrintDays()
{
Console.WriteLine(string.Join(", ", Helpers.sweWeekdays));
}
}

I metoden PrintDays() använder vi den inbyggda metoden Join för att göra en sträng av alla strängar i den statiska arrayen sweWeekDays(i koden tydliggörs att den tillhör klassen Heplers genom Helpers.sweWeekdays).

För att dela upp en sträng i delsträngar till en array av finns metoden Split.

Eftersom veckodagarna är en array med strängar så kan vi skriva ut den istället. Vi uppdaterar PrintDays med en for-loop.

I klassen Helpers loopar vi igenom arrayen sweWeekdays med foreach
namespace MenuApp2.src;
public class Helpers
{
...
public static void PrintDateAndTime()
{
...
PrintDays();
}
private static void PrintDays()
{
Console.WriteLine("\nVeckodagarna på svenska:");
foreach (string day in Helpers.sweWeekdays)
{
Console.WriteLine(day);
}
}
...

Men tänk om vi har en lista att skriva ut istället. Vi gör om veckodagarna till en lokal lista med strängar och tillför en ny metod PrintDays med en lista som inparameter. Nu använder vi foreach för att öva på den.

I klassen Helpers gör vi om arrayen sweWeekdays till en lista och skriver ut den med foreach
namespace MenuApp2.src;
public class Helpers
{
...
public static void PrintDateAndTime()
{
...
PrintDays();
List<string> dayAsList = sweWeekdays.ToList();
PrintDays(dayAsList);
}
private static void PrintDays(List<string> dayList)
{
Console.WriteLine("\nVeckodagarna på svenska:");
foreach (string day in dayList)
{
Console.WriteLine(day);
}
}
...

När vi väljer menyalternativ 4 så ser det ut så här nu:

Image description
Bild: Klassdiagram Helpers med veckodagar på svenska

Toppen, nu har vi:

  • lärt oss att göra ett program med klasser utifrån klassdiagram
  • tränat på iterationer
  • tränat på metoder
  • använt klassen DateTime för datum och tid i olika format
  • skrivit ut listor
  • använt en ASCII-bild