субота, 13. фебруар 2016.

Interfejsi u C# programskom jeziku



Kao što vam je poznato, klasa ne može da nasleđuje istovremeno više osnovnih klasa. Ali zato može da implementira više interfejsa, istovremeno. Dok osnovna klasa definiše šta ona u stvari predstavlja, interfejs definiše šta klasa radi. Interfejs uopšte ni ne zna šta klasa predstavlja, nije uopšte zavisna od toga ali se interfejsi opet mogu dodati klasi i koristiti u mnogim situacijama. Interfejs je najjednostavnije rečeno ugovor koji kompletno odvaja imena i potpise metoda, propertija, indeksera, delegata i događaja od njihove implementacije. Ali interfejs ne može da sadrži polja. Na taj način osiguravate da klasa koja je u interakciji kroz određeni interfejs nema pristup internim podacima objekta. Interfejsi jesu slični apstraktnim klasama, samo što ne mogu da sadrže ništa osim navedenih potpisa i nije retkost da se interfejsi koriste zajedno sa apstraktnim klasama da bi se gradila programerska radna okruženja koja se proširuju. Interfejs, kao i apstraktna klasa se ne može instancirati. Implementacija interfejsa se u potpunosti prepušta implementirajućoj klasi. Svaki član interfejsa za razliku od klasa se podrazumeva da je javni, svaki član mora biti javni i svaki član se mora implementirati u istoj implementirajućoj klasi. Ali najvažnija osobina interfejsa je da klase mogu implementirati više interfejsa kao i što interfejs može da poziva druge interfejse. Kada se iz jednog interfejsa pozivaju drugi interfejsi, onda se to naziva re-implementation.





( Interfejsi i apstraktne klase zahtevaju implementaciju )


Programerima kojima je poznat koncept COM interfejsa, treba da znaju da postoji razlika. U C# programskom jeziku interfejsi se ne izvode iz klase IUnknown. Oni predstavljaju ugovor izražen u vidu funkcija radnog okvira .Net Framework-a i za razliku od COM interfejsa, interfejsi u C# ne predstavljaju nikakvu vrstu binarnog standarda. Interfejsi se definišu sa ključnom rečju interface i u nazivu interfejsa je uvek poželjno staviti prefiks ’I’ kako bi se znalo da je reč o interfejsu. To je jednostavno nepisano pravilo i praksa.

interface IBankAccount
{
    void Withdraw(decimal amount);
    void PayIn(decimal amount);
    decimal Balance { get; }

}

Navedeni interfejs sadrži dve metode i jedan properti koji je ograničen samo na čitanje. Obe metode i properti se moraju implementirati u klasi koja poziva interfejs. Interfejs se može implementirati implicitno ili eksplicitno. Eksplicitna implementacija se uglavnom primenjivala u situacijama kad ste sigurni da neki drugi interfejs može sadržavati isti naziv metode kao u ovakvom slučaju:

public interface IEmployee
{
    void DisplayEmployee();

}

public interface ICompany
{
    void DisplayEmployee();

}

Onda bi ste metode jedino mogli da razlikujete i pozivate kastingom:

Program program = new Program();
((IEmployee)program).DisplayEmployee();
((ICompany)program).DisplayEmployee();

Zato je eksplicitno povezivanje bolje, dok implicitno se sve ređe koristi čak ga i ne preporučuju. Često se dešava da programeri mešaju ova dva načina implementacije i kad govore o implicitnoj implementaciji u stvari misle na eksplicitnu i obratno. Zato dobro pogledajte oba primera implementacije interfejsa. U velikim projektima interfejsi itekako imaju veliku vrednost i primenu i često će te se susretati sa obe implementacije.

Implicitno implementiranje interfejsa

Kao što sam već naveo, implicitna implementacija se sve ređe koristi ali bez obzira kao programer treba da znate da prepoznate obe implementacije. Pogledajte naredni kod koji se sastoji od jednog interfejsa kog implicitno implementiraju dve klase.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceImplicitImplementation
{
    interface IBankAccount
    {
        void Withdraw(decimal amount);
        void PayIn(decimal amount);
        decimal Balance { get; }

    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceImplicitImplementation
{
    class SaverAccount
    {
        private decimal balance;

        public void PayIn(decimal amount)
        {
            balance += amount;

        }

        public void Withdraw(decimal amount)
        {
            if (balance >= amount)
            {
                balance -= amount;

            }
            else
            {
                Console.WriteLine("Europe Bank Saver Account: Withdraw attempt faild.");

            }
        }

        public decimal Balance { get; }

        public override string ToString()
        {
            return String.Format("Europe Bank Saver Account: Balance = {0,6:C}", balance);

        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceImplicitImplementation
{
    class GoldAccount
    {
        private decimal balance;

        public void PayIn(decimal amount)
        {
            balance += amount;

        }

        public void Withdraw(decimal amount)
        {
            if (balance >= amount)
            {
                balance -= amount;

            }
            else
            {
                Console.WriteLine("America Bank Gold Account: Withdraw attempt faild.");

            }
        }

        public decimal Balance { get; }

        public override string ToString()
        {
            return String.Format("America Bank Gold Account: Balance = {0,6:C}", balance);

        }
    }
}

using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceImplicitImplementation
{
    class Program
    {
        public void DisplayEmployee() => Write("k");

        static void Main(string[] args)
        {
            string FirstName = "Manuel";
            string LastName = "Radovanovic";

            WriteLine(Environment.NewLine + new String('-', 20) + Environment.NewLine);

            WriteLine(FirstName + " " + LastName);

            WriteLine(Environment.NewLine + new String('-', 20) + Environment.NewLine);

            SaverAccount europeAccount = new SaverAccount();
            GoldAccount americaAccount = new GoldAccount();

            europeAccount.PayIn(250);
            WriteLine(europeAccount.ToString());

            europeAccount.PayIn(500);
            WriteLine(europeAccount.ToString());

            europeAccount.Withdraw(1000);
            WriteLine(europeAccount.ToString());

            europeAccount.Withdraw(400);
            WriteLine(europeAccount.ToString());

            WriteLine(Environment.NewLine + new String('-', 20) + Environment.NewLine);

            americaAccount.PayIn(550);
            WriteLine(americaAccount.ToString());

            americaAccount.Withdraw(700);
            WriteLine(americaAccount.ToString());

            americaAccount.Withdraw(125);
            WriteLine(americaAccount.ToString());

            WriteLine(Environment.NewLine + new String('-', 20) + Environment.NewLine);

            ReadKey();

        }             
    }
}
        
Kao što možete videti u klasama se metode moraju implementirati kao javne dok je potpis metoda u interfejsu podrazumevano javni. Kada pokrenete navedeni program dobićete sledeće rezultate:

--------------------

Manuel Radovanovic

--------------------

Europe Bank Saver Account: Balance = $250.00
Europe Bank Saver Account: Balance = $750.00
Europe Bank Saver Account: Withdraw attempt faild.
Europe Bank Saver Account: Balance = $750.00
Europe Bank Saver Account: Balance = $350.00

--------------------

America Bank Gold Account: Balance = $550.00
America Bank Gold Account: Withdraw attempt faild.
America Bank Gold Account: Balance = $550.00
America Bank Gold Account: Balance = $425.00

--------------------


Kako izgleda prethodni program možete pogledati i na video-u:




( C# 6.0 Tutorial - Fundamentals - 39. Interface Implicit Implementation )

Eksplicitno implementiranje interfejsa


Da bi vam bilo što jasnije, uzećemo isti prethodni program i prepraviti ga da klase implementiraju eksplicitno interfejs. Eksplicitnim implementiranje interfejsa izbegavate iste nazive metoda koje bi se mogle desiti ukoliko bi neka od navedenih klasa implemetirala još neki interfejs sa nekim istim nazivom jedne od metoda koje ste već implementirali. To se radi tako što navedete naziv interfejsa svakoj metodi koju implementirate.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceExplicitImplementation
{
    interface IBankAccount
    {
        void Withdraw(decimal amount);
        void PayIn(decimal amount);
        decimal Balance { get; }

    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceExplicitImplementation
{
    class SaverAccount : IBankAccount
    {
        private decimal balance;

        void IBankAccount.PayIn(decimal amount)
        {
            balance += amount;

        }

        void IBankAccount.Withdraw(decimal amount)
        {
            if (balance >= amount)
            {
                balance -= amount;

            }
            else
            {
                Console.WriteLine("Europe Bank Saver Account: Withdraw attempt faild.");

            }
        }

        decimal IBankAccount.Balance { get; }

        public override string ToString()
        {
            return String.Format("Europe Bank Saver Account: Balance = {0,6:C}", balance);

        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceExplicitImplementation
{
    class GoldAccount : IBankAccount
    {
        private decimal balance;

        void IBankAccount.PayIn(decimal amount)
        {
            balance += amount;

        }

        void IBankAccount.Withdraw(decimal amount)
        {
            if (balance >= amount)
            {
                balance -= amount;

            }
            else
            {
                Console.WriteLine("America Bank Gold Account: Withdraw attempt faild.");

            }
        }

        decimal IBankAccount.Balance { get; }

        public override string ToString()
        {
            return String.Format("America Bank Gold Account: Balance = {0,6:C}", balance);

        }
    }
}

using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace InterfaceExplicitImplementation
{
    class Program
    {
        static void Main(string[] args)
        {
            string FirstName = "Manuel";
            string LastName = "Radovanovic";

            WriteLine(Environment.NewLine + new String('-', 20) + Environment.NewLine);

            WriteLine(FirstName + " " + LastName);

            WriteLine(Environment.NewLine + new String('-', 20) + Environment.NewLine);

            IBankAccount europeAccount = new SaverAccount();
            IBankAccount americaAccount = new GoldAccount();

            europeAccount.PayIn(250);
            WriteLine(europeAccount.ToString());

            europeAccount.PayIn(500);
            WriteLine(europeAccount.ToString());

            europeAccount.Withdraw(1000);
            WriteLine(europeAccount.ToString());

            europeAccount.Withdraw(400);
            WriteLine(europeAccount.ToString());

            WriteLine(Environment.NewLine + new String('-',20) + Environment.NewLine);

            americaAccount.PayIn(550);
            WriteLine(americaAccount.ToString());

            americaAccount.Withdraw(700);
            WriteLine(americaAccount.ToString());

            americaAccount.Withdraw(125);
            WriteLine(americaAccount.ToString());

            WriteLine(Environment.NewLine + new String('-', 20) + Environment.NewLine);

            ReadKey();

        }
    }
}

Kao što vidite kod eksplicitnog implementiranje metoda, svakom nazivu metode je dodat naziv interfejsa i odvojen tačkom ’.’ i tako nema konfuzije kako god da se zovu druge metode drugih interfejsa koje takođe treba implementirati. Kad pokrenete navedeni program rezultat je apsolutno isti kao u prethodnom programu, samo je implementacija interfejsa drugačija.

--------------------

Manuel Radovanovic

--------------------

Europe Bank Saver Account: Balance = $250.00
Europe Bank Saver Account: Balance = $750.00
Europe Bank Saver Account: Withdraw attempt faild.
Europe Bank Saver Account: Balance = $750.00
Europe Bank Saver Account: Balance = $350.00

--------------------

America Bank Gold Account: Balance = $550.00
America Bank Gold Account: Withdraw attempt faild.
America Bank Gold Account: Balance = $550.00
America Bank Gold Account: Balance = $425.00

--------------------


Kako izgleda prethodni program možete pogledati i na video-u:


( C# 6.0 Tutorial - Fundamentals - 40. Interface Explicit Implementation )

Kad se koristi jednostavna klasa, kad apstraktna klasa a kad interfejs?

Samo programerskim iskustvom možete pretpostaviti kad vam je najbolje da koristite jednostavnu klasu, kad je bolje da klasu proširite apstraktnom klasom ili interfejsom. Retko se dešava da programer izabere samo jedan ili drugi način za rešenje nekog problema, već je češća situacija da je rešenje kombinacija različitih pristupa. Do nekih promena dolazi i testiranjem. Na primer, ukoliko postoji razumna količina zajedničkih funkcionalnosti ili kad imate mnoštvo istog koda koje je najbolje grupisati na jednom mestu, onda vam savetujem da koristite apstraktnu klasu. Apstraktne klase nameću strožiju strukturu koda i definišu podrazumevani oblik realizovanja. Međutim ako poslovna logika nalaže mnoštvo klasa i njihovo nasleđivanje tada su interfejsi rešenje. Ukoliko nemate potrebe za proširivanjem i nadogradnjama poslovanja, onda je najbolje klasu ostaviti na nivou jednostavne klase. Sve ostalo je na nivou poslovne logike.