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.
Implementációt akkor határozhat meg, ha egy felület tagját deklarálja. Ez a funkció új képességeket biztosít, amelyekben megadhat alapértelmezett implementációkat a felületeken deklarált funkciókhoz. Az osztályok kiválaszthatják, hogy mikor bírálják felül a funkciókat, mikor érdemes használni az alapértelmezett funkciókat, és mikor ne deklarálják a különálló funkciók támogatását.
Ebben az oktatóanyagban az alábbiakkal fog megismerkedni:
- Olyan felületeket hozhat létre, amelyek különálló funkciókat leíró implementációkat tartalmaznak.
- Az alapértelmezett implementációkat használó osztályok létrehozása.
- Hozzon létre olyan osztályokat, amelyek felülbírálják az alapértelmezett implementációk egy részét vagy mindegyikét.
Előfeltételek
Be kell állítania a gépet a .NET futtatására, beleértve a C# fordítót is. A C#-fordító a Visual Studio 2022-ben vagy a .NET SDK-val érhető el.
A bővítmények korlátozásai
A felület részeként megjelenő viselkedés implementálásának egyik módja az alapértelmezett viselkedést biztosító bővítménytagok definiálása. Az interfészek minimális taghalmazt deklarálnak, miközben nagyobb felületet biztosítanak minden olyan osztály számára, amely ezt az interfészt megvalósítja. A Enumerable kiterjesztési tagok például a LINQ-lekérdezések forrásává tehetnek bármilyen sorozatot az implementáció révén.
A bővítménytagok feloldása fordításkor történik a változó deklarált típusának használatával. Az interfészt megvalósító osztályok jobb implementációt biztosíthatnak bármely bővítménytag számára. A változó deklarációknak meg kell egyezniük a implementálási típussal, hogy a fordító kiválaszthassa az implementációt. Ha a fordítási idő típusa megegyezik a felülettel, a metódushívások feloldódnak a bővítménytagnak. A bővítménytagokkal kapcsolatos másik probléma, hogy ezek a tagok bárhol elérhetők, ahol a bővítménytagot tartalmazó osztály elérhető. Az osztályok nem deklarálhatják, hogy kell-e, vagy sem biztosítaniuk a bővítménytagokban deklarált funkciókat.
Az alapértelmezett implementációkat interfészmeteként deklarálhatja. Ezután minden osztály automatikusan az alapértelmezett implementációt használja. Minden olyan osztály, amely jobb implementációt biztosíthat, felülbírálhatja az interfészmetódus definícióját egy jobb algoritmussal. Ez a technika egy bizonyos értelemben hasonlít a bővítménytagok használatára.
Ebből a cikkből megtudhatja, hogyan teszik lehetővé az alapértelmezett felületi implementációk az új forgatókönyveket.
Az alkalmazás megtervezése
Fontolja meg egy otthoni automatizálási alkalmazást. Valószínűleg sok különböző típusú fény és jelző van, amelyeket az egész házban lehet használni. Minden fénynek támogatnia kell az API-kat a be- és kikapcsoláshoz, valamint az aktuális állapot jelentéséhez. Egyes fények és jelzők más funkciókat is támogathatnak, például:
- Kapcsolja be a világítást, majd kapcsolja ki egy időzítő után.
- Villogjon a fény egy ideig.
Ezen kiterjesztett képességek némelyike a minimális készletet támogató eszközökön emulálható. Ez azt jelzi, hogy alapértelmezett implementációt biztosít. Azoknál az eszközöknél, amelyek több beépített képességgel rendelkeznek, az eszközszoftver a natív képességeket használja. Más fények esetén dönthetnek úgy, hogy implementálják az interfészt, és az alapértelmezett implementációt használják.
Az alapértelmezett felülettagok jobb megoldást nyújtanak erre a forgatókönyvre, mint a bővítménytagok. Az osztályszerzők szabályozhatják, hogy milyen felületeket szeretnének implementálni. Az általuk választott felületek metódusként érhetők el. Emellett mivel az alapértelmezett illesztőmetódusok alapértelmezés szerint virtuálisak, a metódusküldés mindig az osztály implementálását választja.
Hozzuk létre a kódot, hogy bemutassuk ezeket a különbségeket.
Felületek létrehozása
Először hozza létre az összes fény viselkedését meghatározó felületet:
public interface ILight
{
void SwitchOn();
void SwitchOff();
bool IsOn();
}
Egy alapszintű lámpatest implementálhatja ezt az interfészt az alábbi kódban látható módon:
public class OverheadLight : ILight
{
private bool isOn;
public bool IsOn() => isOn;
public void SwitchOff() => isOn = false;
public void SwitchOn() => isOn = true;
public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}
Ebben az oktatóanyagban a kód nem IoT-eszközöket vezet, hanem e tevékenységeket a konzolra írt üzenetekkel emulálja. A kódot a ház automatizálása nélkül is felfedezheti.
Ezután határozzuk meg az interfészt egy olyan fényhez, amely automatikusan kikapcsolhat egy időtúllépés után:
public interface ITimerLight : ILight
{
Task TurnOnFor(int duration);
}
Alapszintű implementációt is hozzáadhat a többletterheléshez, de jobb megoldás, ha ezt az interfészdefiníciót úgy módosítja, hogy alapértelmezett implementációt virtual biztosítson:
public interface ITimerLight : ILight
{
public async Task TurnOnFor(int duration)
{
Console.WriteLine("Using the default interface method for the ITimerLight.TurnOnFor.");
SwitchOn();
await Task.Delay(duration);
SwitchOff();
Console.WriteLine("Completed ITimerLight.TurnOnFor sequence.");
}
}
Az OverheadLight osztály a felület támogatásának deklarálásával implementálhatja az időzítőfüggvényt:
public class OverheadLight : ITimerLight { }
Egy másik fénytípus egy kifinomultabb protokollt is támogathat. A következő kódban látható módon biztosíthat saját implementációt TurnOnFor:
public class HalogenLight : ITimerLight
{
private enum HalogenLightState
{
Off,
On,
TimerModeOn
}
private HalogenLightState state;
public void SwitchOn() => state = HalogenLightState.On;
public void SwitchOff() => state = HalogenLightState.Off;
public bool IsOn() => state != HalogenLightState.Off;
public async Task TurnOnFor(int duration)
{
Console.WriteLine("Halogen light starting timer function.");
state = HalogenLightState.TimerModeOn;
await Task.Delay(duration);
state = HalogenLightState.Off;
Console.WriteLine("Halogen light finished custom timer function");
}
public override string ToString() => $"The light is {state}";
}
A felülírt virtuális osztálymetódusokkal ellentétben a TurnOnFor osztály HalogenLight deklarációja nem használja a override kulcsszót.
Képességek keverése és egyeztetése
Az alapértelmezett felületi metódusok előnyei egyre egyértelműbbek lesznek, amikor fejlettebb képességeket vezet be. A felületek használatával kombinálhatja és egyeztetheti a képességeket. Emellett lehetővé teszi, hogy minden osztályszerző válasszon az alapértelmezett és az egyéni implementáció között. Adjunk hozzá egy alapértelmezett implementációval rendelkező felületet a villogó fényhez:
public interface IBlinkingLight : ILight
{
public async Task Blink(int duration, int repeatCount)
{
Console.WriteLine("Using the default interface method for IBlinkingLight.Blink.");
for (int count = 0; count < repeatCount; count++)
{
SwitchOn();
await Task.Delay(duration);
SwitchOff();
await Task.Delay(duration);
}
Console.WriteLine("Done with the default interface method for IBlinkingLight.Blink.");
}
}
Az alapértelmezett implementációval bármilyen fény villoghat. A mennyezeti lámpa az alapértelmezett implementációval időzítő és villogási funkciókkal bővíthető.
public class OverheadLight : ILight, ITimerLight, IBlinkingLight
{
private bool isOn;
public bool IsOn() => isOn;
public void SwitchOff() => isOn = false;
public void SwitchOn() => isOn = true;
public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}
Egy új fénytípus, amely LEDLight támogatja az időzítőfüggvényt és a villogó függvényt is. Ez a világos stílus implementálja a felületeket és ITimerLight a IBlinkingLight felületeket is, és felülbírálja a metódustBlink:
public class LEDLight : IBlinkingLight, ITimerLight, ILight
{
private bool isOn;
public void SwitchOn() => isOn = true;
public void SwitchOff() => isOn = false;
public bool IsOn() => isOn;
public async Task Blink(int duration, int repeatCount)
{
Console.WriteLine("LED Light starting the Blink function.");
await Task.Delay(duration * repeatCount);
Console.WriteLine("LED Light has finished the Blink function.");
}
public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}
A ExtraFancyLight villogó és az időzítő függvényt is támogathatja közvetlenül:
public class ExtraFancyLight : IBlinkingLight, ITimerLight, ILight
{
private bool isOn;
public void SwitchOn() => isOn = true;
public void SwitchOff() => isOn = false;
public bool IsOn() => isOn;
public async Task Blink(int duration, int repeatCount)
{
Console.WriteLine("Extra Fancy Light starting the Blink function.");
await Task.Delay(duration * repeatCount);
Console.WriteLine("Extra Fancy Light has finished the Blink function.");
}
public async Task TurnOnFor(int duration)
{
Console.WriteLine("Extra Fancy light starting timer function.");
await Task.Delay(duration);
Console.WriteLine("Extra Fancy light finished custom timer function");
}
public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}
A HalogenLight korábban létrehozott nem támogatja a villogást. Ezért ne adja hozzá a IBlinkingLight a saját támogatott felületek listájához.
A fénytípusok észlelése mintaegyeztetés használatával
Most írjunk egy tesztkódot. A C#mintaegyeztetési funkciójával meghatározhatja a fény képességeit, ha megvizsgálja, hogy mely interfészeket támogatja. Az alábbi módszer az egyes fények támogatott képességeit gyakorolja:
private static async Task TestLightCapabilities(ILight light)
{
// Perform basic tests:
light.SwitchOn();
Console.WriteLine($"\tAfter switching on, the light is {(light.IsOn() ? "on" : "off")}");
light.SwitchOff();
Console.WriteLine($"\tAfter switching off, the light is {(light.IsOn() ? "on" : "off")}");
if (light is ITimerLight timer)
{
Console.WriteLine("\tTesting timer function");
await timer.TurnOnFor(1000);
Console.WriteLine("\tTimer function completed");
}
else
{
Console.WriteLine("\tTimer function not supported.");
}
if (light is IBlinkingLight blinker)
{
Console.WriteLine("\tTesting blinking function");
await blinker.Blink(500, 5);
Console.WriteLine("\tBlink function completed");
}
else
{
Console.WriteLine("\tBlink function not supported.");
}
}
Az Ön Main metódusában az alábbi kód sorrendben hozza létre az egyes fénytípusokat, és teszteli azt a fényt.
static async Task Main(string[] args)
{
Console.WriteLine("Testing the overhead light");
var overhead = new OverheadLight();
await TestLightCapabilities(overhead);
Console.WriteLine();
Console.WriteLine("Testing the halogen light");
var halogen = new HalogenLight();
await TestLightCapabilities(halogen);
Console.WriteLine();
Console.WriteLine("Testing the LED light");
var led = new LEDLight();
await TestLightCapabilities(led);
Console.WriteLine();
Console.WriteLine("Testing the fancy light");
var fancy = new ExtraFancyLight();
await TestLightCapabilities(fancy);
Console.WriteLine();
}
Hogyan határozza meg a fordító a legjobb implementációt?
Ez a forgatókönyv egy implementációk nélküli alapfelületet mutat be. Egy metódus hozzáadása az ILight interfészhez új összetettségeket vezet be. Az alapértelmezett interfészmetelyeket szabályozó nyelvi szabályok minimálisra csökkentik a több származtatott interfészt implementáló konkrét osztályokra gyakorolt hatást. Bővítsük az eredeti felületet egy új módszerrel, hogy bemutassuk, ez hogyan változtatja meg a használatát. Minden jelzőfény enumerált értékként jelentheti az energiaállapotát:
public enum PowerStatus
{
NoPower,
ACPower,
FullBattery,
MidBattery,
LowBattery
}
Az alapértelmezett implementáció nem feltételezi a következő teljesítményt:
public interface ILight
{
void SwitchOn();
void SwitchOff();
bool IsOn();
public PowerStatus Power() => PowerStatus.NoPower;
}
Ezek a módosítások gond nélkül lefordulnak, annak ellenére, hogy a ExtraFancyLight deklarálja a ILight interfész és a két származtatott interfész, ITimerLight és IBlinkingLight, támogatását. A felületen csak egy "legközelebbi" implementáció van deklarálva ILight . Minden felülbírálást deklaráló osztály a "legközelebbi" implementációvá válik. Az előző osztályokban olyan példákat láthattál, amelyek felülírják más származtatott interfészek tagjait.
Ne bírálja felül ugyanazt a metódust több származtatott felületen. Ezzel kétértelmű metódushívást hoz létre, amikor egy osztály mindkét származtatott felületet implementálja. A fordító nem tud egyetlen jobb módszert választani, ezért hibát okoz. Például, ha mind a IBlinkingLight, mind a ITimerLight felülbírált egy Power()-t, akkor a OverheadLight-nak egy pontosabb felülbírálást kell adnia. Ellenkező esetben a fordító nem tud választani a két származtatott interfész implementációi között. Ez a helyzet az alábbi ábrán látható:
Az előző ábra a kétértelműséget szemlélteti.
OverheadLight nem biztosít implementációt a(z) ILight.Power() számára. Mindkettőt IBlinkingLight , és ITimerLight adjon meg konkrétabb felülbírálásokat. Egy ILight.Power() példányon történő OverheadLight hívás nem egyértelmű. A kétértelműség feloldásához új felülbírálást OverheadLight kell hozzáadnia.
Ezt a helyzetet általában elkerülheti, ha a felületdefiníciók kicsik maradnak, és egyetlen funkcióra összpontosítanak. Ebben a forgatókönyvben a fény minden képessége a saját felülete; csak osztályok örökölnek több felületet.
Ez a minta egy olyan forgatókönyvet mutat be, amelyben meghatározhatók az osztályokba keverhető különálló funkciók. Az osztály által támogatott felületek deklarálásával deklarálhatja a támogatott funkciókat. A virtuális alapértelmezett felületi metódusok használata lehetővé teszi az osztályok számára, hogy bármilyen vagy az összes felületi metódushoz más implementációt használjanak vagy definiáljanak. Ez a nyelvi képesség új módszereket kínál az ön által létrehozott valós rendszerek modellezésére. Az alapértelmezett felületi metódusok egyértelműbb módot biztosítanak a kapcsolódó osztályok kifejezésére, amelyek különböző funkciókkal keveredhetnek és egyezhetnek az adott képességek virtuális implementációinak használatával.