Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
A C# mintamegfeleltetési funkciói szintaxist biztosítanak az algoritmusok kifejezéséhez. Ezekkel a technikákkal implementálhatja a viselkedést az osztályokban. Az objektumorientált osztálytervezést adatorientált implementációval kombinálva tömör kódot biztosíthat valós objektumok modellezése közben.
Ebben az oktatóanyagban a következőket sajátíthatja el:
- Az objektumorientált osztályokat adatmintákkal fejezheti ki.
- Ezeket a mintákat a C#mintaegyező funkcióival valósíthatja meg.
- A implementáció ellenőrzéséhez használja a fordítódiagnosztikát.
Előfeltételek
- A legújabb .NET SDK
- Visual Studio Code szerkesztő
- A C# fejlesztőkészlet
Csatornazár szimulációjának létrehozása
Ebben az oktatóanyagban egy C#-osztályt hoz létre, amely egy zsilipetszimulál. Röviden, a csatornazár egy olyan eszköz, amely emeli és csökkenti a hajókat, miközben két vízszakasz között haladnak különböző szinteken. A zár két kapuval és valamilyen mechanizmussal rendelkezik a vízszint módosításához.
Normál működése során a hajó belép az egyik kapun, miközben a zsilip vízszintje megegyezik azzal a vízszinttel, amely azon az oldalon van, amelyen a hajó belép. Miután a hajó a zsilipbe került, a vízszint megváltozik, hogy megfeleljen annak a vízszintnek, ahol a hajó elhagyja a zsilipet. Amint a vízszint megegyezik ezen az oldalon, megnyílik a kapu a kilépési oldalon. A biztonsági intézkedések biztosítják, hogy a kezelő ne hozzon létre veszélyes helyzetet a csatornában. A vízszint csak akkor módosítható, ha mindkét kapu zárva van. Legfeljebb egy kapu lehet nyitva. A kapu megnyitásához a zár vízszintjének meg kell egyeznie a nyitott kapun kívüli vízszinttel.
Ennek a viselkedésnek a modellezéséhez létrehozhat egy C#-osztályt. Egy CanalLock osztály támogatja a parancsokat bármelyik kapu megnyitásához vagy bezárásához. Más parancsokkal is rendelkezne a víz felemeléséhez vagy csökkentéséhez. Az osztálynak támogatnia kell a tulajdonságokat a kapuk és a vízszint aktuális állapotának olvasásához. A módszerek megvalósítják a biztonsági intézkedéseket.
Osztály definiálása
Létrehozhat egy konzolalkalmazást a CanalLock osztály teszteléséhez. Hozzon létre egy új konzolprojektet a .NET 5-höz a Visual Studio vagy a .NET CLI használatával. Ezután adjon hozzá egy új osztályt, és nevezze el CanalLock. Ezután tervezzen meg egy nyilvános API-t, de hagyja a metódusokat nem implementálva:
public enum WaterLevel
{
Low,
High
}
public class CanalLock
{
// Query canal lock state:
public WaterLevel CanalLockWaterLevel { get; private set; } = WaterLevel.Low;
public bool HighWaterGateOpen { get; private set; } = false;
public bool LowWaterGateOpen { get; private set; } = false;
// Change the upper gate.
public void SetHighGate(bool open)
{
throw new NotImplementedException();
}
// Change the lower gate.
public void SetLowGate(bool open)
{
throw new NotImplementedException();
}
// Change water level.
public void SetWaterLevel(WaterLevel newLevel)
{
throw new NotImplementedException();
}
public override string ToString() =>
$"The lower gate is {(LowWaterGateOpen ? "Open" : "Closed")}. " +
$"The upper gate is {(HighWaterGateOpen ? "Open" : "Closed")}. " +
$"The water level is {CanalLockWaterLevel}.";
}
Az előző kód inicializálja az objektumot, így mindkét kapu zárva van, és a vízszint alacsony. Ezután írja be a következő tesztkódot a Main metódusba, hogy útmutatást nyújthasson az osztály első implementációjának létrehozásakor:
// Create a new canal lock:
var canalGate = new CanalLock();
// State should be doors closed, water level low:
Console.WriteLine(canalGate);
canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");
Console.WriteLine("Boat enters lock from lower gate");
canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate: {canalGate}");
canalGate.SetWaterLevel(WaterLevel.High);
Console.WriteLine($"Raise the water level: {canalGate}");
canalGate.SetHighGate(open: true);
Console.WriteLine($"Open the higher gate: {canalGate}");
Console.WriteLine("Boat exits lock at upper gate");
Console.WriteLine("Boat enters lock from upper gate");
canalGate.SetHighGate(open: false);
Console.WriteLine($"Close the higher gate: {canalGate}");
canalGate.SetWaterLevel(WaterLevel.Low);
Console.WriteLine($"Lower the water level: {canalGate}");
canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");
Console.WriteLine("Boat exits lock at upper gate");
canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate: {canalGate}");
Ezután adja hozzá az egyes metódusok első implementációját a CanalLock osztályhoz. Az alábbi kód az osztály módszereit implementálja a biztonsági szabályok betartása nélkül. Biztonsági teszteket később adhat hozzá:
// Change the upper gate.
public void SetHighGate(bool open)
{
HighWaterGateOpen = open;
}
// Change the lower gate.
public void SetLowGate(bool open)
{
LowWaterGateOpen = open;
}
// Change water level.
public void SetWaterLevel(WaterLevel newLevel)
{
CanalLockWaterLevel = newLevel;
}
Az eddig írt tesztek sikeresen átmennek. Megvalósítottad az alapokat. Most írjon egy tesztet az első hibafeltételhez. Az előző tesztek végén mindkét kapu zárva van, és a vízszint alacsonyra van állítva. Adjon hozzá egy tesztet a felső kapu megnyitásához:
Console.WriteLine("=============================================");
Console.WriteLine(" Test invalid commands");
// Open "wrong" gate (2 tests)
try
{
canalGate = new CanalLock();
canalGate.SetHighGate(open: true);
}
catch (InvalidOperationException)
{
Console.WriteLine("Invalid operation: Can't open the high gate. Water is low.");
}
Console.WriteLine($"Try to open upper gate: {canalGate}");
Ez a teszt meghiúsul, mert megnyílik a kapu. Első implementációként a következő kóddal kijavíthatja:
// Change the upper gate.
public void SetHighGate(bool open)
{
if (open && (CanalLockWaterLevel == WaterLevel.High))
HighWaterGateOpen = true;
else if (open && (CanalLockWaterLevel == WaterLevel.Low))
throw new InvalidOperationException("Cannot open high gate when the water is low");
}
A tesztek sikeresek. Ha azonban további teszteket ad hozzá, további if záradékokat ad hozzá, és teszteli a különböző tulajdonságokat. Ezek a metódusok hamarosan túl bonyolultak lesznek, amikor további feltételes feltételeket ad hozzá.
A parancsok implementálása mintákkal
Jobb módszer, ha mintákat használunk annak megállapítására, hogy az objektum érvényes állapotban van-e egy parancs végrehajtásához. Kifejezheti, ha egy parancs három változó függvényeként engedélyezett: a kapu állapota, a víz szintje és az új beállítás:
| Új beállítás | Kapu állapota | Vízszint | Eredmény |
|---|---|---|---|
| Zárt | Zárt | Magas | Zárt |
| Zárt | Zárt | Alacsony | Zárt |
| Zárt | Nyitott | Magas | Zárt |
|
|
|
|
|
| Nyitott | Zárt | Magas | Nyitott |
| Nyitott | Zárt | Alacsony | Lezárva (hiba miatt) |
| Nyitott | Nyitott | Magas | Nyitott |
|
|
|
|
|
A táblázat negyedik és utolsó sora áthúzott szöveggel rendelkezik, mert érvénytelenek. A most hozzáadott kódnak meg kell győződnie arról, hogy a magas víz kapuja soha nem nyílik meg, ha a víz alacsony. Ezek az állapotok egyetlen kapcsolókifejezésként kódolhatók (ne feledje, hogy false a "Zárt" értéket jelöli):
HighWaterGateOpen = (open, HighWaterGateOpen, CanalLockWaterLevel) switch
{
(false, false, WaterLevel.High) => false,
(false, false, WaterLevel.Low) => false,
(false, true, WaterLevel.High) => false,
(false, true, WaterLevel.Low) => false, // should never happen
(true, false, WaterLevel.High) => true,
(true, false, WaterLevel.Low) => throw new InvalidOperationException("Cannot open high gate when the water is low"),
(true, true, WaterLevel.High) => true,
(true, true, WaterLevel.Low) => false, // should never happen
};
Próbálja ki ezt a verziót. A tesztek sikeresek, és érvényesítik a kódot. A teljes táblázat a bemenetek és eredmények lehetséges kombinációit mutatja. Ez azt jelenti, hogy Ön és más fejlesztők gyorsan áttekinthetik a táblázatot, és láthatják, hogy az összes lehetséges bemenetet lefedte. Még egyszerűbb, a fordító is segíthet. Az előző kód hozzáadása után láthatja, hogy a fordító figyelmeztetést hoz létre: CS8524 azt jelzi, hogy a kapcsolókifejezés nem fedi le az összes lehetséges bemenetet. Ennek a figyelmeztetésnek az az oka, hogy az egyik bemenet egy enum típusú. A fordító az "összes lehetséges bemenetet" az alapul szolgáló típus összes bemeneteként értelmezi, általában egy int. Ez a switch kifejezés csak a enumdeklarált értékeket ellenőrzi. A figyelmeztetés eltávolításához hozzáadhat egy általános elvetési mintát a kifejezés utolsó karjához. Ez a feltétel kivételt eredményez, mert érvénytelen bemenetet jelez:
_ => throw new InvalidOperationException("Invalid internal state"),
Az előző kapcsolókarnak utolsónak kell lennie a switch kifejezésben, mert minden bemenetnek megfelel. Próbálja meg korábban elhelyezni a sorrendben. Ez egy CS8510 fordítóhibát okoz a mintában lévő nem elérhető kód miatt. A kapcsolókifejezések természetes struktúrája lehetővé teszi a fordító számára, hogy hibákat és figyelmeztetéseket generáljon a lehetséges hibákra. A fordító "biztonsági háló" megkönnyíti a megfelelő kód létrehozását kevesebb iterációban, valamint a kapcsolókarok helyettesítő karakterekkel való kombinálásának szabadságát. A fordító hibákat jelez, ha a kombináció nem elérhető ágakhoz vezet, amelyeket nem várt, és figyelmeztetést ad, ha eltávolít egy szükséges ágat.
Az első változás az összes kar egyesítése, ahol a parancs a kapu bezárása; ez mindig engedélyezett. Adja hozzá a következő kódot a kapcsolókifejezés első karjaként:
(false, _, _) => false,
Miután hozzáadta az előző kapcsolókart, négy fordítóhibát fog kapni, egyet mindegyik karon, ahol a parancs falsetalálható. Ezeket a karokat már az újonnan hozzáadott kar fedi. Ezt a négy sort biztonságosan eltávolíthatja. Az Ön szándéka az volt, hogy ez az új kapcsolókar lecserélje ezeket a feltételeket.
Ezután egyszerűsítheti a négy kart, ahol a parancs a kapu megnyitása. Mindkét esetben, ha a vízszint magas, a kaput meg lehet nyitni. (Az egyikben már nyitva van.) Az egyik eset, amikor a vízszint alacsony, kivételt jelent, a másiknak pedig nem szabad megtörténnie. Ha a vízzár már érvénytelen állapotban van, biztonságosan ki lehet dobni ugyanezt a kivételt. Az alábbi egyszerűsítéseket végezheti el ezekre a fegyverekre vonatkozóan:
(true, _, WaterLevel.High) => true,
(true, false, WaterLevel.Low) => throw new InvalidOperationException("Cannot open high gate when the water is low"),
_ => throw new InvalidOperationException("Invalid internal state"),
Futtassa le újra a teszteket, és sikeresek lesznek. A SetHighGate metódus végleges verziója:
// Change the upper gate.
public void SetHighGate(bool open)
{
HighWaterGateOpen = (open, HighWaterGateOpen, CanalLockWaterLevel) switch
{
(false, _, _) => false,
(true, _, WaterLevel.High) => true,
(true, false, WaterLevel.Low) => throw new InvalidOperationException("Cannot open high gate when the water is low"),
_ => throw new InvalidOperationException("Invalid internal state"),
};
}
Készítsd el a mintákat magad
Most, hogy megismerte a technikát, töltse ki a SetLowGate és SetWaterLevel metódusokat. Első lépésként adja hozzá a következő kódot az érvénytelen műveletek teszteléséhez ezeken a metódusokon:
Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetWaterLevel(WaterLevel.High);
canalGate.SetLowGate(open: true);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't open the lower gate. Water is high.");
}
Console.WriteLine($"Try to open lower gate: {canalGate}");
// change water level with gate open (2 tests)
Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetLowGate(open: true);
canalGate.SetWaterLevel(WaterLevel.High);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't raise water when the lower gate is open.");
}
Console.WriteLine($"Try to raise water with lower gate open: {canalGate}");
Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetWaterLevel(WaterLevel.High);
canalGate.SetHighGate(open: true);
canalGate.SetWaterLevel(WaterLevel.Low);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't lower water when the high gate is open.");
}
Console.WriteLine($"Try to lower water with high gate open: {canalGate}");
Futtassa újra az alkalmazást. Láthatja, hogy az új tesztek sikertelenek, és a csatornazár érvénytelen állapotba kerül. Próbálja meg saját maga implementálni a többi metódust. Az alsó kapu beállítására szolgáló módszernek hasonlónak kell lennie a felső kapu beállításához. A vízszintet megváltoztató módszer különböző ellenőrzéseket végez, de hasonló szerkezetet kell követnie. Hasznos lehet, ha ugyanazt a folyamatot használja a vízszintet állító módszerhez. Kezdje mind a négy bemenettel: Mindkét kapu állapota, a vízszint aktuális állapota és a kért új vízszint. A kapcsolókifejezésnek a következővel kell kezdődnie:
CanalLockWaterLevel = (newLevel, CanalLockWaterLevel, LowWaterGateOpen, HighWaterGateOpen) switch
{
// elided
};
Összesen 16 kapcsolókart kell kitöltenie. Ezután tesztelje és egyszerűsítse le.
Csináltál ehhez hasonló módszereket?
// Change the lower gate.
public void SetLowGate(bool open)
{
LowWaterGateOpen = (open, LowWaterGateOpen, CanalLockWaterLevel) switch
{
(false, _, _) => false,
(true, _, WaterLevel.Low) => true,
(true, false, WaterLevel.High) => throw new InvalidOperationException("Cannot open low gate when the water is high"),
_ => throw new InvalidOperationException("Invalid internal state"),
};
}
// Change water level.
public void SetWaterLevel(WaterLevel newLevel)
{
CanalLockWaterLevel = (newLevel, CanalLockWaterLevel, LowWaterGateOpen, HighWaterGateOpen) switch
{
(WaterLevel.Low, WaterLevel.Low, true, false) => WaterLevel.Low,
(WaterLevel.High, WaterLevel.High, false, true) => WaterLevel.High,
(WaterLevel.Low, _, false, false) => WaterLevel.Low,
(WaterLevel.High, _, false, false) => WaterLevel.High,
(WaterLevel.Low, WaterLevel.High, false, true) => throw new InvalidOperationException("Cannot lower water when the high gate is open"),
(WaterLevel.High, WaterLevel.Low, true, false) => throw new InvalidOperationException("Cannot raise water when the low gate is open"),
_ => throw new InvalidOperationException("Invalid internal state"),
};
}
A teszteknek át kell mennie, és a csatornazárnak biztonságosan kell működnie.
Összefoglalás
Ebben az oktatóanyagban megtanulta, hogyan használhatja a mintaegyezést egy objektum belső állapotának ellenőrzésére, mielőtt bármilyen módosítást alkalmaz az adott állapotra. A tulajdonságok kombinációit ellenőrizheti. Miután táblákat készített bármelyik áttűnéshez, tesztelheti a kódot, majd egyszerűsítheti az olvashatóságot és a karbantarthatóságot. Ezek a kezdeti újrabontások további újrabontásokat javasolhatnak, amelyek ellenőrzik a belső állapotot, vagy más API-módosításokat kezelnek. Ez az oktatóanyag az osztályokat és objektumokat egy adatorientáltabb, mintaalapú megközelítéssel kombinálta az osztályok implementálásához.