Nahoru
 

Interface v programování

V novějších jazycích jako C#, Java nebo TypeScript je možné narazit na klíčové slovo „rozhraní“ (anglicky interface). Jedná se o prostou pomůcku, která má při správném použití velkou moc. Lze jej chápat i jako speciální případ abstraktních tříd.

Polymorfní struktury a další užitečné funkce vyžadující nějaké sdílené rozhraní participujících tříd (tedy tříd, jež mají stejně pojmenované určité metody nebo atributy) se v dobách C++ realizovaly děděním z (nejčastěji) abstraktních tříd. O polymorfismu a abstraktních třídách si můžete přečíst v článku „Abstraktní třídy a polymorfismus“.

Jazyk C++ se řídil myšlenkou, že programátoři nedělají chyby a povolil vícenásobnou dědičnost (tedy možnost dědit z více tříd najednou). Pokud tedy chceme vytvořit „Roborybu“, můžeme jí dědit z tříd „Ryba“ a „Robot“. To bohužel mohlo způsobit spoustu problémů, hlavně v tzv. „diamantovém dědění“. Znamená to, že z třídy A můžeme dědit třídy B a C a poslední třída D bude dědit z tříd B a C naráz (viz. obrázek č. 2). Zmíněné diamantové dědění s sebou nese problémy s duplicitními atributy, přepisováním metod a spoustu dalšího. Program se poté stává v podstatě nepoužitelným.

Schéma dědění tříd Roboryba z tříd Robot a Ryba
Obrázek č. 1: Dědění ze dvou tříd, jež povolovalo například C++
Náčrt dědícího schématu zvaný diamantové dědění
Obrázek č. 2: Náčrt diamantového dědění

Nové jazyky jako C#, Java nebo TypeScript však dědění z více tříd nepovolují. Znamená to, že jim nemůžeme přikázat, aby implementovali dvě a více různých (abstraktních) tříd. Tyto jazyky proto přidaly nástroj zvaný „interface“, který vyřešil nejenom tento dědící problém, ale zároveň rozšířil možnosti jazyka o zajímavé funkčnosti.

Interface (česky „rozhraní“) je sada předpisů funkcí. Pokud nějaká třída implementuje rozhraní, musí také implementovat všechny předepsané funkce daným rozhraním. Rozhraní nemůže popisovat atributy ani obsahovat kód, jedná se opravdu pouze o předpis funkcí.

Ukažme si interface v praxi. Mějme interface zvaný „ICountable“, který obsahuje předepsané rozhraní pro jedinou metodu „Count()“. Tato metoda vrací počet. Počet čeho? To nevíme. Víme však, že jakákoliv třída, která implementuje rozhraní „ICountable“, bude umět vrátit počet, ať už je to počet nějakých uložených objektů nebo počet kachen na jezeře.

//TypeScript implementace rozhraní ICountable
interface ICountable
{
    /**
     * Metoda vrátí počet předmětů jako číslo.
     * */
    Count(): number;
}
//C# implementace rozhraní ICountable
interface ICountable
{
    int Count();
}

Ukažme si příklad použití „ICountable“ a vytvořme třídu „DuckPool“. Tato třída bude ukládat kachny. Necháme třídu implementovat interface „ICountable“ a donutíme ji tak implementovat metodu „Count()“.

//Třída DuckPool, která implementuje rozhraní ICountable
// POZOR: Nebude fungovat, nemá implementaci metody Count()
class DuckPool implements ICountable
{
    ducks = [];

    AddDuck(newDuck)
    {
        this.ducks.push(newDuck);
    }

    //Nemá implementaci Count() - error
}

//Třída DuckPool, která implementuje rozhraní ICountable
class DuckPool implements ICountable
{
    ducks = [];

    AddDuck(newDuck)
    {
        this.ducks.push(newDuck);
    }

    //Povinná implementace metody Count()
    // kvůli rozhraní ICountable
    Count()
    {
        return this.ducks.length;
    }
}

Výhod použití interface je mnoho. Jednou z nich je unifikace rozhraní. Všechny naše třídy, jež dokáží něco počítat, nyní budou mít metodu „Count()“. Nestane se tak, že v jedné třídě bude „Size()“, v jiné „Count()“ a v těch nejstarších „NumberOf()“. Určitě to ulehčí zmatky a práci s dokumentací v týmech.

Někdy se interface používá i jako dorozumívací zařízení. Pokud například nějaký modul ještě není dokončen, protože ho zadáváme jinému oddělení nebo firmě a potřebujeme jej ihned, lze si napsat rozhraní a vyvíjet i bez potřebného kódu. Následné přidání hotového modulu je směšně jednoduché, pokud splňuje zadané rozhraní.

Výhoda navazující na předmluvu o dědění z C++ spočívá v tom, že třída může sice dědit z jednoho rodiče, ale může implementovat libovolně mnoho rozhraní. Vzhledem k tomu, že interface není nic jiného než soubor předpisů metod, není potřeba se obávat duplicit metod, duplicit atributů nebo zmatků s konstruktory. Interface neobsahuje žádný funkční kód ani algoritmy, a tak je mnohem „pružnější“. Například v jazyku TypeScript se interface při kompilaci úplně zahazuje a užívá se pouze jako kontrola datových typů a kontrola správných implementací rozhraní u tříd.

Interface umožňuje elegantní implementaci polymorfismu. Například díky interface „ICountable“, lze vytvořit metodu „IsEmpty(ICountable)“, která přijímá datový typ „ICountable“ a řekne nám, zda je vstup prázdný (tedy počet je nula). Interface tedy můžeme používat i jako datový typ. Stejně jako u abstraktní třídy platí, že nemůžeme vytvořit instanci rozhraní „ICountable“, ale můžeme do ní přiřazovat. Za maskou datového typu „ICountable“ se samozřejmě bude skrývat nějaká jiná třída, která však obsahuje metodu „Count()“, jak předepisuje její „interface ICountable“. Dostáváme tak plnohodnotný polymorfismus se všemi jeho výhodami.

//Implementace funkce IsEmpty(ICountable)
//Parametr funkce testItem je typu ICountable
function IsEmpty(testItem: ICountable) 
{
    //Je nám jedno, co dostaneme, hlavně
    // že to implementuje interface ICountable
    return testItem.Count() == 0;
}

Podobný princip jako kód metody „IsEmpty(ICountable)“ využívá blok „using“ v jazyku C#. Blok „using“ přijme parametr typu „IDisposable“, což je interface, obsahující předpis pro funkci „Dispose()“. Tato funkce by měla „rozpustit“ danou třídu, tedy například třída pro operaci se soubory uzavře komunikační kanál nebo třída s asynchronní síťovou komunikací přeruší připojení. Jakmile dojde k výstupu z bloku „using“, je automaticky zavolána metoda „Dispose()“. Díky tomu, že třída implementuje „IDisposable“, C# ví, že tuto metodu může bezpečně volat. Jedná se o příklad toho, jak jazyk může využít interface pro vytvoření velice elegantního bloku, který programátorům ušetří bolehlav a zpřehlední kód.

//Zasílání emailu – třída SmtpClient implementuje interface IDisposable
using (var client = new SmtpClient(HOST, PORT))
{
    //... nějaké operace

    //Pošle se email
    client.Send(new MailMessage("from@email.com",
        "to@email.com", "Subject", "Message"));

    //... nějaké operace
}
//Výstupem z bloku using se zavolá metoda Dispose() proměnné client,
// což je nějaké ukončení spojení

Interface je nová pružná funkčnost umožňující snadné použití polymorfismu a správu kódu pro práci v týmu. I když se interface může zdát jako zbytečnost, určitě tomu tak není. Z osobních zkušeností mohu říci, že vnesením „těžkého“ používání rozhraní do mého každodenního kódu zlepšilo přehlednost a ušetřilo spoustu práce.