Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C#, Java, Ruby: Mikä on rajapinta (interface)?

Sivun loppuun

E1ss [27.03.2016 12:30:18]

#

Voisko joku siis selittää mitä toi rajapinta tarkoittaa. Oon yrittänyt lukea tosta mooc.fi sivulla mutta ne ei selitä kovin yksinkertaisesti.

Metabolix [27.03.2016 13:03:50]

#

Kyllä se varmaan MOOCista selviäisi, kun malttaisit lukea ja katsoa koodeja.

Rajapinta tarkoittaa jotain tiettyä ominaisuuksien joukkoa, jota voidaan ulkopuolelta hyödyntää.

Hyvä esimerkki rajapinnasta on USB-portti. Erilaisissa laitteissa on USB-portteja, ja laitteita voi siksi kytkeä USB-johdoilla toisiinsa. USB-laturi voi syöttää virtaa erilaisille laitteille sovitulla jännitteellä, ja silti laturin ei tarvitse tietää laitteista mitään, vaan riittää, että laturi toimii USB-standardin mukaan.

Alkeellisemmassa ohjelmoinnissa rajapinta voisi olla vaikka se, että erilaiset arvontavälineet tuottavat yhden kokonaisluvun. 6-sivuinen noppa toteuttaa tämän rajapinnan yhdellä tavalla, kolikko toisella tavalla.

import java.util.Random;

// Määritellään rajapinta Arpaväline: siihen kuuluu yksi metodi.
interface Arpaväline {
  public int arvo();
}

// Arpakuutio toteuttaa Arpaväline-rajapinnan.
class Arpakuutio implements Arpaväline {
  private static Random r = new Random();
  // Arpakuution pitää sisältää Arpaväline-rajapinnan mukainen metodi.
  // Arpakuution toteutus palauttaa luvun 1-6.
  public int arvo() {
    return r.nextInt(6) + 1;
  }
}

// Kolikko toteuttaa Arpaväline-rajapinnan.
class Kolikko implements Arpaväline {
  public static final int KRUUNA = 1;
  public static final int KLAAVA = 2;
  public static final int PYSTY = 3;
  private static Random r = new Random();
  // Kolikon pitää sisältää Arpaväline-rajapinnan mukainen metodi.
  // Kolikko palauttaa luvun 1 (kruuna) tai 2 (klaava) tai harvemmin 3 (pysty).
  public int arvo() {
    int n = 100;
    int tmp = r.nextInt(2 * n + 1);
    if (tmp < n) {
      return KRUUNA;
    }
    if (tmp < 2 * n) {
      return KLAAVA;
    }
    return PYSTY;
  }
}

// Testataan luokkia:
public class Testi {
  // Tämä metodi testaa minkä tahansa Arpavälineen.
  private static void testaa(Arpaväline a) {
    System.out.println("Testataan välinettä:");
    for (int j = 0; j < 10; ++j) {
      for (int i = 0; i < 25; ++i) {
        System.out.format("%d, ", a.arvo());
      }
      System.out.println(a.arvo());
    }
  }
  // Testataan ensin Kolikko ja sitten Arpakuutio.
  // Rajapinnan ansiosta molemmat käyvät samalle metodille.
  public static void main(String[] args) {
    testaa(new Kolikko());
    testaa(new Arpakuutio());
  }
}

E1ss [27.03.2016 13:14:22]

#

Kiitos tästä vastauksesta. Kiva kun täällä vastataan melkein heti. Alan pikku hiljaa hahmottamaan ideaa.

E1ss [29.03.2016 17:18:13]

#

Voisiko joku viellä selittä miksi tuota pitäisi käyttää koska edellisen esimerkin olisi voinut tehdä ilman sitä. Eli onko tämä pakollinen joissain tilanteissa?

Grez [29.03.2016 17:35:01]

#

Idea on lähinnä se, että voi tehdä yhtenäisen määrityksen, jota käyttää useampi luokka. Näin luokkaa voi vaihtaa ilman että sitä käyttävää koodia tarvitsee joka kerta kirjoittaa uudestaan. Lisäksi voidaan tehdä geneerisiä palikoita, jotka toimivat kaikille rajapinnan toteuttaville luokille.

Esimerkiksi IComparable (vertailtavissa oleva) rajapinnan toteuttavia luokkia voi järjestää Sortilla. Sortin ei tarvitse tietää mitään muuta luokista, kuin että sen toteuttavia olioita voi verrata keskenään.

Voit ajatella asiaa myöskin tuon USB-esimerkin kannalta. Toki voidaan tehdä laite joka ottaa jostain liitännästä sähköä ja siirtää tietoa, mutta jos jokainen toteuttaa sellaisen toisistaan riippumatta ilman mitään standardia, niin ne eivät toimi keskenään.

Metabolix [29.03.2016 17:40:24]

#

Miten sitten olisit tehnyt edellisen esimerkin ilman rajapintaa?

Rajapinnat eivät ole teknisesti välttämättömiä, mutta kyllä niihin Javassa käytännössä törmää, koska niitä on jo käytetty monin paikoin Javan standardikirjastossa ja muissa kirjastoissa.

Vaikka kaikki ohjelmat voisi tehdä myös ilman rajapintoja, usein koodia tulee enemmän tai siitä tulee jollain muulla tavalla sotkuista. Jos asiaa haluaa typerästi ajatella, niin onhan myös +-merkki tarpeeton, kun yhteenlaskun a+b voisi hyvin kirjoittaa muodossa -(-a-b).

E1ss [29.03.2016 19:28:19]

#

Grez toi sun selitys oli tosi hyvä.:D

spegi [29.03.2016 21:57:30]

#

Asiasta on käyty hyvä keskustelu ennenkin identtisellä otsikolla, tosin PHP:n näkökulmasta: https://www.ohjelmointiputka.net/keskustelu/27498-mikä-on-rajapinta-interface/sivu-1.

vinsentti [30.03.2016 06:55:01]

#

Tuossa vanhassa keskustelussa tuli ilmi, mistä interface-fiitserissä oliokielissä on kysymys: siitä, että kielen kehittäjillä on ollut kiire ja muutenkin vaikeaa perinnän kanssa.

jlaire [30.03.2016 09:15:05]

#

Luetunymmärtäminen on tärkeä taito se.

Grez [30.03.2016 10:45:27]

#

Vinsentti missäs viestissä se kävi ilmi.

Mun mielestä noissa on käsitteellinen ero vaikka jossain kielessä rajapinnat toteuttaisikin moniperintänä.

Jos mennään taas tohon USB-vertaukseen, niin käsityksellisesti rajapinta (interface) vastaa standardia ja perintä vastais ehkä enemmänkin sitä että kaikki laitevalmistajat käyttäis samaa USB-piiriä.

vinsentti [30.03.2016 11:19:57]

#

Grez kirjoitti:

Vinsentti missäs viestissä se kävi ilmi.

Keskustelun loppupuolikas kokonaisuudessaan. Siellä mainittiin myös trait-fiitseri, mitähän kaikkea muuta on keksitty. Sen seitsemät idiomit.

groovyb [30.03.2016 12:10:23]

#

Imho, Interfacen tarkoitus on määritellä implementoitavat osiot olioon. Ja tästä saadaan mm. seuraavat tärkeät edut:

Yksikkötestaus mockaamalla oikea toteutus pois, ylikirjoittamalla interfacen tarjoamat kilkkeet (vaikka rajaamalla servicen ulkoinen toteutus pois, yksikkötestauksessa data voi tulla dummy -datasta, eikä varsinaisesta tietokannasta)

Pakottaa kehittäjät toteuttamaan implementoitavat asiat, parantaen ylläpidettävyyttä ja koodin yhteneväisyyttä (vaikka pakottamalla DAL olioon id property implementoimalla interface, tarkoittaen että kaikki DAL luokat sisältävät samannimisen propertyn id:lle, eikä nimeäminen ole kehittäjän oma valinta)

Nopeuttaa kehitystä implementoimalla interface olioon, joka määrää yhtenevän toteutuksen tarvitsemat härpäkkeet (tästä esimerkkinä vaikka tuo IComparable tai IQueryable C# maailmasta)

vesikuusi [30.03.2016 18:56:51]

#

Töissä tulee tehtyä paljon juttuja, missä jokin toiminnon toteutus on voitava vaihtaa ajonaikaisesti (ja interface pidettävä samana). Ihan sama, onko vahva/heikko/staattinen/dynaaminen tyypitys, interface helpottaa elämää aika paljon.

Kiitos, interface.

Metabolix [30.03.2016 22:05:38]

#

Jos vinsentti haluaa tuoda keskusteluun jotain uutta, voisi vaikka selittää sitten, mikä olisi tälle ominaisuudelle parempi vaihtoehto. Itse ainakin pidän rajapintoja selvinä verrattuna esim. Go-kielen (ja PHP:n ilman tyypitystä) vaihtoehtoon eli siihen, että vain tehtäisiin funktioita ja toivottaisiin, että nimet menivät oikein.

vinsentti [31.03.2016 10:38:48]

#

Metabolix kirjoitti:

mikä olisi tälle ominaisuudelle parempi vaihtoehto.

Javan interface ei tuonut mitään uutta API-rajapintoihin, vaan on osa kielen perintäratkaisuja. Parempi vaihtoehto olisi sisällyttää kieleen perintä eikä erilaisia sijaisfiitsereitä ja siirtää asiat ohjelmoijien ratkaistavaksi erilaisin idiomein.

timoh [31.03.2016 11:34:50]

#

vinsentti kirjoitti:

Parempi vaihtoehto olisi toteuttaa perintä eikä erilaisia sijaisfiitsereitä ja siirtää asiat ohjelmoijien ratkaistavaksi erilaisin idiomein.

Eikö tuo olisi aika kömpelöä jos eri toteutuksilla ei ole mitään muuta yhteistä kuin itse runko?

vinsentti [31.03.2016 12:28:10]

#

timoh kirjoitti:

Eikö tuo olisi aika kömpelöä jos eri toteutuksilla ei ole mitään muuta yhteistä kuin itse runko?

En ole varma, mitä tarkoitat: mitään muuta yhteistä kuin rajapinta? Itse en tarkoita että ohjelmoijan pitäisi käyttää perintää, vaan sitä että Javan interface on kielen ratkaisua perinnän kysymyksiin.

timoh [31.03.2016 12:40:30]

#

Jep, konkreettisella toteutuksella ei ole väliä, vaan riittää tuntea pelkkä rajapinta mitä voi käyttää.

vinsentti [31.03.2016 12:45:25]

#

timoh kirjoitti:

(31.03.2016 12:40:30): Jep, konk­reet­ti­sella toteu­tuk­sella ei ole...

Tämä oli kaikissa(?) Javaa edeltäneissä oliokielissä samanlaisena. Ja Javan toisessa fiitserissä: abstract class. Interfacella Java lähti ratkomaan (moni)perinnän kysymyksiä.

Grez [31.03.2016 12:49:50]

#

Niin, vaikka monissa kielissä rajapintoja käytetään esimerkiksi paikkaamaan moniperinnän puutetta, niin eihän se tarkoita sitä että se olisi ainoa käyttötarkoitus rajapinnoille.

Mielestäni esimerkiksi jo mainitsemani C#:n IComparable on mainio esimerkki. Ei olisi mitään järkeä sisällyttää jotain toteutusta, jonka joutuisi jokaisessa luokassa joka tapauksessa ylikirjoittamaan.

vinsentti [31.03.2016 13:03:20]

#

Nämä kaikki jutut sopii aiheen Mikä on Java(?) interface alle. Kaikki nuo API-kysymykset olivat vanhaa kauraa, kun Java esiteltiin. Uutuutena Java toi interface moniperinnän. Muut uudet kielet ovat sitten seuranneet.

Metabolix [31.03.2016 16:40:12]

#

vinsentti, lopeta valitus ja esittele jokin parempi vaihtoehto tai myönnä, että olet vain kateellinen, kun omissa kielissäsi ei ole näin hyvää ominaisuutta.

groovyb [31.03.2016 17:50:02]

#

Mitä merkitystä sillä on, missä se on alunperin julkaistu? Kysymys koski mikä se on, mitä sillä tehdään ja miksi. Tähän kysymykseen mielestäni on vastattu. Ja mielestäni interface on oiva tapa toteuttaa se mitä sillä halutaan ajaa takaa - ja se on paljon muutakin kuin moniperinnän mahdollistaminen.

Interface määrittää abstraktilla tasolla mitä täytyy toteuttaa, toimien templatena oliolle. Implementoi sitten yhtä tai useampaa interfacea halutessaan liittää luokan tukemaan yleisiä toimintoja (esim tuo jo monesti mainittu IComparable - lisätään luokkaan tuki vertailulle).

Vastaavasti taasen abstraktin luokan tarkoitus on toteuttaa yksittäinen pohjaluokka samaa pohjaa käyttäville luokille, olkoon nyt sitten vaikka BaseModel DAL -luokille, joista ei kuulu luoda itsenäisiä luokkia. Ja näistä abstrakteista luokista luodaan se perintäketju, jos moniperintää tarvitaan. Ja tästä abstraktista pohjaluokasta saadaan peruskamat, joita ei tarvitse uudestaan toteuttaa, koska on jo pohjaluokassa toteutettu. Lisämainintana nyt se, että perintäketjutusta voidaan tehdä monestakin syystä, mutta usein kyse on pohjaluokista.

Toinen asia on käyttää interfacea puhtaasti Dependency injection -tarpeeseen, esimerkiksi rajaamalla ulkoinen integraatio ulos toteutuksesta, jolloin käyttötarve on ihan muuta kuin luokkien multiperinnän toteuttaminen.

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

namespace InterfaceTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var Svc = new Service<Foo>();
            //Data.Count == 0 koska "oikea" context ei sisällä mitään
            var data = Svc.Hae(x => x.id == 1);

            var dummyDB = new Context<Foo>()
            {
                new Foo()
                {
                    id = 1,
                    barfoo = "1",
                    foobar = "2"
                },
                new Foo()
                {
                    id = 2,
                    barfoo = "3",
                    foobar = "4"
                }
            };

            //Annetaan servicelle Dummy Context
            var MockedSvc = new Service<Foo>(dummyDB);
            //Service palauttaa datan dummysta, tässä tapauksessa datassa yksi item
            data = MockedSvc.Hae(x => x.id == 1);
            data.ForEach(WriteData);
            Console.ReadLine();
        }

        static void WriteData(Foo foo)
        {
            Console.WriteLine(string.Format("{0}: id = {1}", foo.ServiceInfo, foo.id.ToString()));
        }
    }

    public abstract class BaseModel
    {
        public int id { get; set; }
    }

    public abstract class ServiceModel : BaseModel
    {
        public string ServiceInfo { get; set; }
    }

    public class Foo : ServiceModel
    {
        public Foo()
        {
            this.ServiceInfo = this.GetType().ToString();
        }
        public string foobar { get; set; }
        public string barfoo { get; set; }
    }

    public class Service<T> where T : ServiceModel
    {
        private Context<T> _context;

        //Mock -tarkoitukseen
        public Service(IContext<T> context)
        {
            _context = context as Context<T>;
        }
        //Natiivikäyttöön
        public Service()
        {
            _context = new Context<T>();
        }
        //Haetaan kama, joko "oikeasta" contextista tai injektoidusta
        public List<T> Hae(Func<T, bool> predicate)
        {
            return _context.Find(predicate).ToList();
        }

    }

    //Tämän tarkoitus on simuloida jonkinsortin tietokantaa
    public class Context<T> : List<T>, IContext<T> where T : ServiceModel
    {
       public List<T> Find(Func<T, bool> predicate)
       {
            return this.Where(predicate).ToList();
       }
    }

    public interface IContext<T> : IList<T> where T : ServiceModel
    {
        List<T> Find(Func<T, bool> predicate);
    }
}

vinsentti [31.03.2016 20:15:28]

#

Metabolix kirjoitti:

(31.03.2016 16:40:12): vinsentti, lopeta valitus ja esittele jokin...

Onko tästä vaivaa?

fergusq [31.03.2016 20:49:57]

#

Oletteko te kuulleet ihanista dynaamisista kielistä kuten Ruby, joissa sovelletaan ankkatyypitystä. Se tekee ohjelmoinnista mukavaa. Kaikki nimet ovat niin loogisia, ymmärrettäviä ja arvattavia, ettei mitään turhia rajapintoja tai edes dokumentaatiota tarvita. Ja jos vahingossa sattuu käyttämään vaikkapa .render -metodin sijaan .print -metodia, niin ei se haittaa! Sekin tekee luultavasti jotain hyödyllistä, jonka näkee sitten kun sitä osaa koodista sattuu käyttämään. Se, että ainoa tapa tarkistaa jonkin olion metodit on kirjaston (esim. Rails) lähdekoodin tutkiminen, on vain hauskaa! Ja mitä sitten, vaikka jokin toinen implementaatio palauttaakin samoista metodeista aivan eri luokan olioita eri ominaisuuksilla: jännitystä elämään!

JaskaP [09.04.2016 23:51:18]

#

Metabolix kirjoitti:

Miten sitten olisit tehnyt edellisen esimerkin ilman rajapintaa?

Vaikka en mitään Javasta ymmärrä, niin näinhän tuon esimerkin voisi toisin tehdä:

abstract class Arpaväline {
  	abstract  int arvo();
}

// Arpakuutio toteuttaa Arpaväline-rajapinnan.
class Arpakuutio extends Arpaväline {
  // ...
}

class Kolikko extends Arpaväline {
  // ...
}

Joten vaadimme ankarasti parempaa esimerkkiä tai ainakin isommat tuopit!

Lisäys:

fergusq kirjoitti:

Oletteko te kuulleet ihanista dynaamisista kielistä kuten Ruby, joissa sovelletaan ankkatyypitystä.

Juu, dynaamisella ankka-kielellä kuten Ruby, se voisi mennä näin:

class Arpakuutio
	def arvo
      Random.rand(1..6)
   end
end

class Kolikko
	def arvo
      Random.rand(1..2)
   end
end

def testaa(a)
	puts "Testataan välinettä:"
	10.times { p 25.times.map { a.arvo } }
end

testaa Arpakuutio.new()
testaa Kolikko.new()

Kauheeta shittiä, eikös?? Mahtavan nerokas interface on nähtävästi ankkatyypityksessä täysin
tarpeeton.

jlaire [10.04.2016 02:27:34]

#

JaskaP kirjoitti:

Vaikka en mitään Javasta ymmärrä, niin näinhän tuon esimerkin voisi toisin tehdä:

Rajapinnan korvaaminen abstraktilla luokalla ei ole yleispätevä ratkaisu. Javassa luokka voi toteuttaa useamman kuin yhden rajapinnan, mutta periä suoraan vain yhdestä luokasta.

Ero rajapinnan toteuttamisella ja luokan perimisellä näyttää olevan monille yllättävän vaikea ymmärtää.

JaskaP kirjoitti:

Kauheeta shittiä, eikös??

Joo.

The Alchemist [10.04.2016 13:52:06]

#

JaskaP kirjoitti:

Metabolix kirjoitti:

Miten sitten olisit tehnyt edellisen esimerkin ilman rajapintaa?

Vaikka en mitään Javasta ymmärrä, niin näinhän tuon esimerkin voisi toisin tehdä:

Hyvin rankasti yksinkertaistetussa tilanteessa nuo kaksi tapaa ovat identtisiä eli voi sanoa, että ne ovat sama ratkaisu. Et ole esittänyt uutta tapaa asian tekemiseen, vaan olet esittänyt vaihtoehtoisen notaation sen ilmaisemiseen. Monimutkaisemmissa tilanteissa ratkaisut eivät enää olekaan vaihdannaisia, jolloin vastauksesi on pelkästään väärin.

groovyb [11.04.2016 00:22:53]

#

Ankkatyypittää voi esim C#:ssa dynamic -tyypillä, mutta ei se poissulje interfacen käyttämistä mitenkään. Se, että joku on mahdollista, ei tarkoita että se olisi se oikea tapa tehdä asioita.

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

namespace dynamictest
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 10; i++)
            {
                TestaaAnkalla(new Arpakuutio());
                TestaaAnkalla(new Kolikko());
                //VS
                TestaaInterfacella(new Arpakuutio());
                TestaaInterfacella(new Kolikko());
            }
            Console.ReadLine();

        }

        //Ankkaa siellä ja ankkaa täällä
        static void TestaaAnkalla(dynamic obj)
        {
            Console.WriteLine(obj.Arvo().ToString());
        }
        //Interfacella
        static void TestaaInterfacella(IArpominen obj)
        {
            Console.WriteLine(obj.Arvo().ToString());
        }

    }

    class Kolikko : IArpominen
    {
        public int Arvo()
        {
            return new Random(Guid.NewGuid().GetHashCode()).Next(1, 3);
        }
    }

    class Arpakuutio : IArpominen
    {
        public int Arvo()
        {
            return new Random(Guid.NewGuid().GetHashCode()).Next(1, 7);
        }
    }

    interface IArpominen
    {
        int Arvo();
    }
}

fergusq [11.04.2016 08:51:56]

#

Ankkakielten ongelma on se, että ei ole olemassa käytännössä yhtäkään sopivaa paikkaa rajapintojen dokumentoimiseen. Luokista Kolikko ja Arpakuutio ei voi mitenkään päätellä, että niillä on jotain tekemistä toistensa kanssa. On myöskin epäselvää, mitkä kaikki metodit pitää toteuttaa, jos aikoo luoda uuden saman rajapinnan toteuttavan luokan.

Dokumentaation puute aiheuttaa selviä ongelmia ohjelmoijalle. Olen itse törmännyt tähän ohjelmoidessani Railsilla nettisivuja. Ohjeet ovat surkeita. Käytän esimerkkinä respond_to-metodia.

Apidock.com -sivulla ensimmäinen ongelma on, ettei oikeaa metodia löydä. Jos kirjoittaa hakuun respond_to, saa yli kymmenen eri vaihtoehtoa. Osa näistä on luonnollisesti samoja (koska monet luokat toteuttavat sen rajapinnan, jossa respond_to on), mutta mistä minä sen voin tietää? Ja se, minkä luokan tai moduulin metodit näkyvät missäkin konteksteissa, ei ole ollenkaan itsestään selvää.

Tässä on esimerkki edellä mainitulta sivulta:

def index
  @people = Person.all

  respond_to do |format|
    format.html
    format.xml { render xml: @people }
  end
end

Koodista näkee kyllä mitä se tekee: lähettää sivun HTML:nä tai XML:nä. Mutta mikä tuo mystinen format oikein on? Entä jos haluaisin lähettää sivun YAML:ina tai JSON:ina? Missään kohtaa ei selvästi sanota, minkä tyyppinen format on, mitä rajapintaa se edustaa tai mitä metodeja sillä on. Oppaita on kyllä mielin määrin, mutta entä jos haluan tietää kaikki metodit?

Koodia tutkimalla selviää, että format on Collector-luokan olio, joka nimensä mukaisesti kokoaa kaikki mahdolliset tiedostotyypit dynaamisesti samaan olioon. Vastaus on siis, ettei kaikkia metodeja edes voi selvittää, sillä – hehehe – ne on generoitu suorituksenaikaisesti. Voisin ehkä tulostaa ne kaikki jonnekin lokiin, mutta koska kyseessä on palvelimelle ladattu palvelinohjelma, siinäkin on hirveästi työtä.

Lopputulos: ankkatyypitys tuhoaa tuottavuuden, sillä ohjelmoijan kaikki aika menee turhien asioiden selvittämiseen dokumentaation puuttuessa.

Grez [11.04.2016 10:01:01]

#

Koodaaminen on kivaa mutta dokumentointi tylsää. Eli uutta luodessa ankkatyypitetyt kielet on kivoja kun tarvii kirjoittaa mahdollisimman vähän. Ei tule edes sitä vähää tyypittämällä tehtyä dokumentaatiota mitä vahvasti tyypittäen tulisi. Eli uutta tehdessä ankkatyypitys on kivaa.

Ylläpito taas on sitä tuskallisempaa, mitä huonommin homma on dokumentoitu.

Vähän samasta syystä tekee mieli hakata päätä seinään kun joku toimija ilmoittaa että "meillä on tää xml-rajapinta" ja sitten toimittaa pelkkiä malli-XML:iä eikä schemoja ole olemassakaan....


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta