GNU/Linux >> Znalost Linux >  >> Linux

Jak na to:Objektově orientované programování – konstruktory a dědičnost

Dříve

Už jsme téměř na konci naší cesty tímto bonusovým kolem našeho Úvodu do objektově orientovaného programování! V našem minulém článku jsme začali náš příklad Petting Zoo přehledem tříd, objektů a metod. Pokud jste nestihli začátek této série, můžete se s námi vrátit zde. Jinak se pojďme ponořit zpět!


.

Konstruktéři

Pokud jste cvičení provedli se dvěma Dog objektů, to byla trochu nuda, ne? Koneckonců, nemáme nic, co by od sebe psy oddělovalo, a bez toho, abychom se podívali do zdrojového kódu, nemáme možnost zjistit, který pes jaký štěkot produkoval.

V předchozím článku jsem zmínil, že když vytváříte objekty, voláte speciální metodu zvanou konstruktor . Konstruktor vypadá jako název třídy napsaný jako metoda. Například pro Dog třídy, konstruktor by se jmenoval Dog() .

Zvláštní věc na konstruktorech je, že jsou cestou k jakémukoli novému objektu, takže jsou skvělým místem pro volání kódu, který inicializuje objekt s výchozími hodnotami. Dále, návratová hodnota z metody konstruktoru je vždy objektem samotné třídy, a proto můžeme návratovou hodnotu konstruktoru přiřadit proměnné typu třídy, kterou vytváříme.

Doposud jsme však konstruktor vůbec nevytvořili, tak jak to, že můžeme stále volat tuto metodu?

V mnoha jazycích, včetně C#, vám tento jazyk poskytuje bezplatný a prázdný konstruktor, aniž byste museli cokoli dělat. To znamená, že chcete konstruktor; jinak by nebylo možné třídu k ničemu použít, takže jazyky pouze předpokládají, že jste ji napsali.

Tento neviditelný a volný konstruktor se nazývá výchozí konstruktor , a v našem příkladu to bude vypadat takto:

public Dog(){ }

Všimněte si, že tato syntaxe je velmi podobná Speak() Metoda, kterou jsme vytvořili dříve, kromě toho, že explicitně nevracíme hodnotu, ani nedeklarujeme návratový typ metody. Jak jsem již zmínil dříve, konstruktor vždy vrací instanci třídy, do které patří.

V tomto případě je to třída Dog , a to je důvod, proč když píšeme Dog myDog = new Dog() , můžeme nový objekt přiřadit proměnné s názvem myDog který je typu Dog .

Přidejme tedy výchozí konstruktor do našeho Dog třída. Můžete buď zkopírovat řádek výše, nebo ve Visual Studiu můžete použít zkratku:zadejte ctor a stiskněte Tab dvakrát. Měl by vám vygenerovat výchozí konstruktor, jak je znázorněno na obrázku 9:

Obrázek 9:Přidání konstruktoru s ‘ctor’

Výchozí konstruktor nám ve skutečnosti nedává nic nového, protože nyní explicitně dělá to, co bylo implicitně provedeno dříve. Je to však metoda, takže nyní můžeme přidat obsah do závorek, který se spustí, kdykoli zavoláme tento konstruktor. A protože konstruktor běží jako úplně první věc v konstrukci objektu, je to ideální místo pro přidání inicializačního kódu.

Mohli bychom například nastavit Name vlastnost našich objektů k něčemu přidáním kódu, jako je tento:

public Dog()
{
    this.Name = "Snoopy";
}

Tento příklad nastaví Name vlastnost všech nových objektů na „Snoopy“.

To samozřejmě není příliš užitečné, protože ne všichni psi se nazývají „Snoopy“, takže místo toho změňme signaturu metody konstruktoru tak, aby přijímal parametr.

Závorky metod nejsou jen proto, aby vypadaly pěkně; slouží k tomu, aby obsahovaly parametry, které můžeme použít k předání hodnot metodě. Tato funkce platí pro všechny metody, nejen pro konstruktory, ale nejprve to udělejme pro konstruktor.

Změňte výchozí podpis konstruktoru na toto:

public Dog(string dogName)

Toto přidání nám umožňuje odeslat string parametr do konstruktoru, a když to uděláme, můžeme se na tento parametr odkazovat jménem dogName .

Potom do bloku metody přidejte následující řádek:

this.Name = dogName;

Tento řádek nastavuje vlastnost tohoto objektu Name na parametr, který jsme odeslali do konstruktoru.

Všimněte si, že když změníte podpis konstruktoru, získáte v souboru Program.cs případ červených vlnovek, jak je znázorněno na obrázku 10.

Obrázek 10:Případ Red Squigglies od našeho nového konstruktéra

Když přidáme vlastní explicitní konstruktory, C# a .NET nám implicitně nevytvoří výchozí konstruktor. V našem souboru Program.cs stále vytváříme Dog objektů pomocí výchozího konstruktoru bez parametrů, který nyní již neexistuje.

Abychom tento problém vyřešili, musíme přidat parametr do našeho volání konstruktoru v Program.cs. Můžeme například aktualizovat naši konstrukční linii objektu jako takovou:

Dog myDog =nový pes(“Snoopy”);

Pokud tak učiníte, odstraníte červené vlnovky a umožníte znovu spustit kód. Pokud opustíte nebo nastavíte bod přerušení po posledním řádku kódu, můžete se podívat na panel Locals a ověřit, že Name vašeho objektu vlastnost byla skutečně nastavena, jak ukazuje obrázek 11.

Obrázek 11:Zobrazení správného nastavení vlastnosti Name

Mám to? Dobrý! Nyní máme možnost pojmenovat našeho psa, ale ve skutečnosti to není užitečný program, pokud jej lidé musí ladit, aby viděli, co děláme. Pojďme tedy kód trochu zamíchat, abychom se ujistili, že zobrazujeme jméno psa, který štěká.

Aktualizujte svého Dog objektů a změňte Speak() metoda jako taková:

public void Speak() { Console.WriteLine(this.Name + " says: Woof"); }

Změna, kterou jsme nyní provedli, říká WriteLine metoda zřetězení názvu tohoto objektu s doslovným řetězcem „říká:Woof“, což by nám mělo poskytnout výstup, který lépe zobrazuje naše úsilí. Zkuste spustit program nyní a měli byste vidět něco, co připomíná obrázek 12.

Obrázek 12:Snoopy říká:Woof

Pěkná práce! Nyní můžete vytvořit mnoho psů s různými jmény a všichni budou štěkat na váš příkaz.

Samozřejmě, že domácí zoo pouze se psy je poněkud nudné. Chci říct, miluji psy, ale možná bychom mohli přidat nějaká další zvířata, abychom zážitek trochu okořenili?
.

Dědičnost

Začněme přidáním nové Cat třídy do našeho souboru Animals.cs. Přidejte následující kód vedle třídy Dog, ale stále do závorek oboru názvů PettingZoo:

class Cat
{
    public Cat(string catName)
    {
        this.Name = catName;
    }
    string Name;
    public void Speak() { Console.WriteLine(this.Name + " says: Meow!"); }
}

Poté vytvořte Cat objekt v Program.cs a nechte ho mluvit:

Cat myCat = new Cat("Garfield");
myCat.Speak();

Spuštěním tohoto programu nyní získáte výstup podobný obrázku 13.

Obrázek 13:Garfield říká:Mňau

Náš program je předvídatelný a funguje, ale všimli jste si, jak podobné si tyto dvě třídy skutečně jsou? Podívejte se, kolik kódu jsme duplikovali ve dvou třídách? Neměla nás objektová orientace zachránit před opakovaným psaním kódu?

Odpověď je ano. Můžeme – a měli bychom – tento kód zjednodušit, abychom snížili množství duplicit. To, co se chystáme probrat, bude demonstrovat funkci OO zvanou dědičnost .

Dědičnost v objektové orientaci umožňuje vytvořit hierarchii tříd a nechat každou třídu zdědit vlastnosti a metody nadřazené třídy. Například pro naši zoo s domácími zvířaty můžeme vytvořit nadřazené Animal třídy a mají Dog a Cat zdědit z této třídy. Poté můžeme přesunout části našeho kódu do Animal třídy tak, že oba Dog a Cat třídy zdědí tento kód.

Pokud však přesuneme duplicitní části našeho kódu do tohoto Animal třída a poté Dog a Cat Každá třída by zdědila stejné vlastnosti a metody, čímž by děti vytvořily klony rodičovských tříd. Tato organizace není příliš užitečná, protože chceme mít odlišné druhy zvířat. Bylo by strašně nudné, kdyby kočky, psi a všechna ostatní zvířata byla stejná. Ve skutečnosti by pro účely našeho programu nemělo vůbec smysl nazývat je jinak.

Pokud by dědičnost znamenala, že bychom mohli vytvářet pouze dětské třídy, které by byly identické s jejich rodičovskými třídami, nemělo by velký smysl věnovat se tomuto úsilí. Takže zatímco dědíme všechny části nadřazené třídy, můžeme také přepsat určité části nadřazené třídy v podřízených třídách, aby byly podřízené třídy odlišné.

Pojďme si to projít a podívat se, jak to funguje, ano?
.

Vytvoření rodičovských a podřízených tříd

Uděláme pár kroků zpět a znovu vytvoříme Dog a Cat třídy lepším způsobem. Pro začátek potřebujeme nadřazenou třídu, která bude definovat sdílené vlastnosti a metody podřízených tříd.

V souboru Animals.cs odstraňte Dog a Cat třídy a přidejte následující definici třídy:

class Animal
{
    public string Name;
    public string Sound;
    public void Speak() { Console.WriteLine(this.Name + " says " + this.Sound); }
}

Tato třída nepřekvapivě vypadá jako předchozí Dog a Cat třídy, s uvedenými výjimkami, že jsme všechny vlastnosti a metody zveřejnili a že jsme zavedli novou vlastnost s názvem Sound typu string . Nakonec jsme aktualizovali Speak metoda k použití Zvuk proměnná.

V tomto okamžiku váš Program.cs nebude fungovat a pokud kliknete na tuto kartu, měli byste vidět několik chybových zpráv, což je přirozené, protože jsme odstranili Cat a Dog třídy. Pojďme je znovu vytvořit a udělejme to pomocí dědičnosti. Ve vašem souboru Animals.cs, hned za Animal class, přidejte následující kód:

class Dog : Animal { }
class Cat : Animal { }

Tyto nové třídy jsou mnohem kratší než dříve a nereplikují žádný kód. Novou syntaxí je zde dvojtečka následovaná názvem třídy Animal , což říká C#, že chceme oba Dog a Cat zdědit od Animal třída. Ve skutečnosti Dog a Cat stát se podřízenými třídami Animal jak je znázorněno na diagramu na obrázku 14.

Obrázek 14:Diagram dědičnosti živočišných tříd

Poznámka:Používal jsem termín Vlastnictví zatím, což je mírně nepřesné, protože správný výraz je pole jak můžete vidět na diagramu na obrázku 14. Pole je však mimo rozsah programování méně jasné, takže jsem místo toho použil Property. Technicky je vlastnost obalem kolem pole v C#.

.
Stále však máme problém s naším souborem Program.cs kvůli vlastnímu konstruktoru, který jsme vytvořili. Pokud se pokusíte spustit nebo odladit náš program, měli byste vidět chybovou zprávu, která říká, že „PettingZoo.Cat“ neobsahuje konstruktor, který bere 1 argument“ a podobný pro „PettingZoo.Dog“.

Abychom tuto chybu napravili, musíme znovu přidat konstruktory. Tentokrát však použijeme dědičnost ke zdědění konstruktoru a ukážeme, jak můžete rozšířit konstruktor, abyste změnili funkčnost nadřazené třídy.

Nejprve musíme vytvořit základní konstruktor pro Animal , který se bude podobat předchozím konstruktorům, které jsme vytvořili dříve. Přidejte do svého Animal následující kód třída:

public Animal(string animalName)
{
    this.Name = animalName;
}

Stejně jako dříve náš konstruktér nastaví jméno zvířete podle toho, co do něj předáme. To však nestačí, protože do obou Dog potřebujeme přidat konstruktor a Cat třídy. Použijeme to k definování zvuku, který každé zvíře vydává.

class Dog : Animal
{
    public Dog(string dogName) : base(dogName)
    {
        this.Sound = "Woof";
    }
}
class Cat : Animal
{
    public Cat(string catName) : base(catName)
    {
        this.Sound = "Meow";
    }
}

Pro obě třídy přidáme konstruktor, který přijímá Name parametr. Také jsme tomuto objektu nastavili Sound vlastnost zvuku, který chceme, aby zvíře vydávalo.

Na rozdíl od dřívějška však nyní voláme konstruktor rodičovské třídy a předáváme Name parametr z nadřazeného konstruktoru. Toto volání přichází přes : base() a přidáním přijatého dogName nebo catName parametry uvnitř závorek.

Poznámka:Toto : base() syntaxe je pro konstruktéry jedinečná. Další způsoby změny nebo rozšíření funkčnosti případu uvidíme později.

.
S těmito aktualizacemi našeho kódu nyní můžeme znovu spustit náš program a vidět výsledek podobný obrázku 15.

Obrázek 15:Zvířata mluví pomocí dědičnosti

Všimněte si, že nemáme ani Sound ani Name vlastnost definovaná buď v Dog nebo Cat třídy. Také nemáme, máme Speak() metoda. Místo toho sídlí v nadřazeném Animal třídy a Dog a Cat třídy dědí tyto vlastnosti a metody.

Podobně nyní můžeme vytvořit další třídy pro jakýkoli typ zvířat a budeme muset pouze replikovat vzor Dog a Cat třídy. Můžeme například vytvořit tyto třídy:

class Parrot : Animal
{
    public Parrot(string parrotName)
        : base(parrotName)
    {
        this.Sound = "I want a cracker!";
    }
}
class Pig : Animal
{
    public Pig(string pigName)
        : base(pigName)
    {
        this.Sound = "Oink";
    }
}

Potom můžeme vytvořit instanci těchto tříd v Program.cs:

Parrot myParrot = new Parrot("Polly");
myParrot.Speak();

Pig myPig = new Pig("Bacon");
myPig.Speak();

Nyní budeme mít při spuštění programu skutečnou zoo, jak ukazuje obrázek 16.

Obrázek 16:Zoo čtyř zvířat mluví

Můžete také vytvořit další podřízené třídy existujících podřízených tříd. Můžete například oddělit Dog třídy do Beagle a Pointer nebo mít Parrot třída zdědí od rodiče Bird třída, která naopak dědí z Animal . Vytvoření tohoto druhu hierarchie však přesahuje rámec tohoto článku, takže pojďme dál a nakonec se podívejme na to, jak můžeme přepsat, změnit nebo rozšířit funkčnost nadřazených tříd v podřízených třídách.
.

Úprava dědičnosti

Metodu nadřazené třídy můžeme změnit pomocí techniky zvané overriding . Syntaxe pro přepsání se může mezi jazyky lišit, ale princip změny rodičovské metody v podřízené třídě zůstává stejný.

V C# přidáme klíčové slovo override následovaný podpisem metody, kterou chcete přepsat. V nadřazené třídě také musíme deklarovat, že umožňujeme dětem přepsat metody přidáním virtual klíčového slova k podpisu metody v nadřazené třídě. (Jiné jazyky nemusí toto dodatečné označení vyžadovat.)

V našem programu musíme aktualizovat Speak() metoda v Animal třída.

public virtual void Speak() { Console.WriteLine(this.Name + " says " + this.Sound); }

Nyní můžeme vložit naše přepsání Speak() metoda v Dog class na začátku našeho bloku kódu pro tuto třídu.

class Dog : Animal
{
    public override void Speak()
    {
        base.Speak();
        Console.WriteLine("...and then runs around, chasing his tail");
    }
[remaining code omitted]

Tento kód nám říká, že chceme přepsat to, co se stane v rodičovské metodě Speak() . Stále budeme provádět rodičovskou metodu pomocí base.Speak() zavolejte, než vypíšeme další řádek hned poté.

Proč byste to chtěli udělat? No, možná chceme, aby naši psi byli tak nadšení, jak jen mohou být osobně. Spusťte program a přesvědčte se sami:

Obrázek 17:Náš pes přepíše svou třídní metodu

Nejprve Snoopy štěká jako obvykle:base.Speak() metoda volá Speak() metoda z nadřazeného Animal třída. Poté zbytek kódu odešle druhý řádek do konzole. Efektivně rozšiřujeme nadřazenou třídu přidáním nových funkcí pouze do podřízené třídy. Podívejte se, jak Cat třída není ovlivněna.

Samozřejmě, že kočky je notoricky obtížné dělat, jak si přejete, takže to pojďme reflektovat v kódu. Aktualizujte Cat třídy a přidejte následující přepsání metody, podobně jako Dog třída.

class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine(Name + " doesn't speak but just sits there wondering when you will feed it.");
    }
[remaining code omitted]

Na rozdíl od toho, co jsme dělali v Dog přepsat, nevoláme base.Speak() tentokrát metodou. Bez tohoto volání nikdy nespustíme Animal Speak() a zcela změnit, co se v této metodě děje. Podívejte se sami:

Obrázek 18:Naše kočka také překonala svou třídní metodu

Všimněte si také, že když jsme přestavěli naše třídy, nezměnili jsme kód Program.cs. Tyto změny slouží jako dobrý příklad toho, proč je objektová orientace velmi účinnou technikou; zcela jsme změnili základní kód, aniž bychom se dotkli programu, který tento kód používá. [Objektová orientace nám umožnila izolovat mnoho detailů implementace a jednoduše vystavit malou sadu rozhraní, se kterými program potřebuje komunikovat.]

Kde jsme ohýbali pravidla

Než skončíme, chci poukázat na několik věcí, především na některé „špatné“ věci, které jsme udělali.

Nejprve v našich třídách voláme Console.WriteLine() metodou přímo. Toto použití není dobrým návrhovým rozhodnutím, protože třídy by měly být nezávislé na tom, jak výstupy z tříd vydáváme.

Lepším přístupem by bylo místo toho vrátit data z tříd a poté je vydat jako součást programu. Potom bychom programu umožnili rozhodnout, co udělá s výstupem, spíše než s třídami, což nám umožní používat stejné třídy v mnoha různých typech programů, ať už jsou to webové stránky, Windows Forms, QT nebo konzolové aplikace.

Za druhé, v našich pozdějších příkladech byly všechny vlastnosti public . Zveřejnění těchto vlastností oslabuje bezpečnostní aspekt objektové orientace, kde se snažíme chránit data ve třídách před manipulací zvenčí. Ochrana vlastností a metod se však rychle stává složitou, když se zabýváte dědictvím, takže jsem se této komplikaci chtěl vyhnout, alespoň v této úvodní fázi.

Konečně nemá smysl mít Sound nastavit jako součást podřízeného konstruktoru vůbec, protože jsme tento kód také duplikovali. Bylo by lepší vytvořit konstruktor v nadřazené třídě, který akceptoval jak jméno, tak zvuk, a pak jej volat jako součást podřízeného konstruktoru. Nicméně znovu, kvůli jednoduchosti jsem v této fázi nechtěl zavádět různé podpisy konstruktorů.

Takže i když jsme se toho v tomto bonusovém tutoriálu hodně naučili, můžete vidět, že je stále co učit. Zatím se tím však příliš netrápte. Můžete si najít chvilku a poblahopřát si, že jste celou cestu sledovali. Pokud jste dodrželi všechny kroky, naučili jste se:

  • Proč používáme objektovou orientaci
  • Jak vytvářet třídy
  • Jak vytvářet vlastnosti a metody
  • Jak vytvářet konstruktory a co dělají
  • Jak využít dědičnost a proč je to výkonná funkce objektové orientace

Doufám, že se vám tato lekce navíc líbila a máte zájem prozkoumat další. Nezapomeňte se s námi podívat na nový obsah na blogu Atlantic.Net a zvažte jeden z našich špičkových virtuálních privátních hostingových serverů.
.


Linux
  1. Jak spravovat a vypisovat služby v Linuxu

  2. Jak na to:Replikace a konfigurace DRBD

  3. Jak na to:Programování soketů v Pythonu

  1. Jak duální bootování Linuxu a Windows

  2. Jak nainstalovat Elasticsearch a Kibana na Linux

  3. Jak rozdělit a naformátovat disk v systému Linux

  1. Co je to chroot jail a jak jej používat?

  2. Jak nainstalovat a používat Helm v Kubernetes

  3. Jak nainstalovat a používat Linux Screen?