Hyvästi, olio-ohjelmointi

Olen ohjelmoinut oliokeskeisiä kieliä vuosikymmenien ajan. Ensimmäinen käyttämäni OO-kieli oli C ++ ja sitten Smalltalk ja lopulta .NET ja Java.

Minua yritettiin hyödyntää perinnöllisyyden, kapseloinnin ja polymorfismin eduista. Paradigman kolme pylvästä.

Olin innokas saamaan uudelleenkäyttölupauksen ja hyödyntämään viisautta, jonka ovat hankkineet ennen minua tulevat uuteen ja jännittävään maisemaan.

En voinut pidättää jännitystäni ajatuksessa kartoittaa reaalimaailman esineeni luokkani ja odotin koko maailman laskevan siististi paikalleen.

En olisi voinut olla enemmän väärässä.

Perintö, ensimmäinen pylväs pudota

Ensi silmäyksellä perinnöllisyys näyttää olevan oliopohjaisen paradigman suurin hyöty. Kaikilla yksinkertaisilla esimerkkeillä muotohierarkioista, jotka esitetään esimerkkinä äskettäin indoktrinoiduille, näyttää olevan loogista järkeä.

Ja uudelleenkäyttö on päivän sana. Ei ... tee siitä vuosi ja ehkä koskaan.

Nielisin tämän kokonaan ja ryntäsin ulos maailmaan uutta tietoaani.

Banaani-apinan viidakon ongelma

Uskonnon sydämessäni ja ratkaistavien ongelmien kanssa aloin luokkahierarkioiden rakentamisen ja koodin kirjoittamisen. Ja kaikki oli kunnossa maailman kanssa.

En koskaan unohda sitä päivää, jolloin olin valmis maksamaan takaisin lupauksen uudelleenkäyttöön perimällä nykyiseltä luokalta. Tämä oli hetki, jota olin odottanut.

Uusi projekti tuli mukana ja ajattelin takaisin tuolle luokalle, josta olin niin rakastettu viimeisessä projektissani.

Ei ongelmaa. Uudelleenkäyttö pelastamiseen. Ainoa mitä minun täytyy tehdä, on yksinkertaisesti tarttua luokka toiseen projektiin ja käyttää sitä.

No… oikeastaan… ei vain tuo luokka. Tarvitsemme vanhemman luokan. Mutta ... Mutta siinä se on.

Uhh ... Odota ... Näyttää siltä, ​​että tarvitsemme myös vanhemman vanhemman ... Ja sitten ... Tarvitsemme KAIKKI vanhemmista. Okei ... okei ... käsittelen tätä. Ei ongelmaa.

Ja hienoa. Nyt sitä ei käännetä. Miksi?? Voi, näen ... Tämä esine sisältää tämän toisen objektin. Joten tarvitsen myös sen. Ei ongelmaa.

Odota… en tarvitse vain sitä esinettä. Tarvitsen esineen vanhemman ja sen vanhemman vanhemman ja niin edelleen ja niin edelleen jokaisen sisältämän objektin ja KAIKKI vanhempien kanssa mitä ne sisältävät heidän vanhempiensa, vanhempiensa, vanhempiensa kanssa ...

Ugh.

Erlangin luoja Joe Armstrong on upea tarjous:

Objektorientoituneiden kielten ongelma on, että heillä on kaikki tämä epäsuora ympäristö, jota he kantavat mukanaan. Halusit banaania, mutta mitä sait, oli gorilla, joka piti banaania ja koko viidakon.

Banaani-apinan viidakon ratkaisu

Voin kesyttää tämän ongelman, koska en luota hierarkioita, jotka ovat liian syviä. Mutta jos perintö on avain uudelleenkäyttöön, niin kaikki mekanismin asettamat rajoitukset rajoittavat varmasti uudelleenkäytön hyödyt. Oikea?

Oikea.

Joten mitä tehdä huono esineorientoitunut ohjelmoija, joka on auttanut Kool-apua terveellisesti?

Sisällytä ja delegoi. Lisää tästä myöhemmin.

Diamond-ongelma

Ennemmin tai myöhemmin seuraava ongelma takaa sen ruman ja kielestä riippuen ratkaisemattoman pään.

Useimmat OO-kielet eivät tue tätä, vaikka tällä näyttää olevan loogista järkeä. Mikä on niin vaikeaa tämän tukemisessa OO-kielillä?

Kuvittele seuraava pseudokoodi:

Luokan PoweredDevice {
}
Class Scanner perii PoweredDeviceltä {
  toiminnon aloitus () {
  }
}
Class Printer perii PoweredDevice-laitteelta {
  toiminnon aloitus () {
  }
}
Class Copier perii skannerilta, tulostimelta {
}

Huomaa, että sekä skanneriluokka että tulostinluokka toteuttavat toiminnon nimeltä start.

Joten minkä aloitusfunktion kopiokoneluokka perii? Skanneri? Tulostin? Se ei voi olla molemmat.

Timanttiratkaisu

Ratkaisu on yksinkertainen. Älä tee sitä.

Kyllä se on oikein. Useimmat OO-kielet eivät anna sinun tehdä tätä.

Mutta, mutta ... entä jos minun on mallinnettava tämä? Haluan uudelleenkäyttöni!

Sitten sinun on oltava ja delegoitava.

Luokan PoweredDevice {
}
Class Scanner perii PoweredDeviceltä {
  toiminnon aloitus () {
  }
}
Class Printer perii PoweredDevice-laitteelta {
  toiminnon aloitus () {
  }
}
Luokkakopiokone {
  Skanneriskanneri
  Tulostin
  toiminnon aloitus () {
    printer.start ()
  }
}

Huomaa tässä, että Kopiokone-luokka sisältää nyt tulostimen ja skannerin esiintymät. Se siirtää käynnistystoiminnon tulostinluokan toteutukseen. Se voidaan delegoida yhtä helposti skanneriin.

Tämä ongelma on jälleen yksi murtuma perintöpilariin.

Hauras pohjaluokkaongelma

Joten teen hierarkioita mataliksi ja estämään niitä suhdannevaihteluista. Ei timantteja minulle.

Ja kaikki oli kunnossa maailman kanssa. Se on kunnes ...

Eräänä päivänä koodini toimii ja seuraavana päivänä se lakkaa toimimasta. Tässä on kicker. En vaihtanut koodiani.

No, ehkä se on virhe ... Mutta odota ... Joku muuttui ...

Mutta sitä ei ollut koodissani. Osoittautuu, että muutos oli luokassa, jonka perin.

Kuinka perusluokan muutos voi rikkoa koodini?

Näin ...

Kuvittele seuraava perusluokka (Se on kirjoitettu Java-kielellä, mutta sen pitäisi olla helppo ymmärtää, jos et tunne Java: ta):

tuo java.util.ArrayList;
 
julkisen luokan ryhmä
{
  yksityinen ArrayList  a = uusi ArrayList  ();
 
  public void add (Object element)
  {
    a.add (elementti);
  }
 
  public void addAll (objektielementit [])
  {
    varten (int i = 0; i 

TÄRKEÄÄ: Huomaa kommentoitu koodirivi. Tätä linjaa muutetaan myöhemmin, mikä rikkoa asiat.

Tämän luokan rajapinnassa on 2 toimintoa, add () ja addAll (). Lisää () -toiminto lisää yhden elementin ja addAll () lisää useita elementtejä kutsumalla lisätoimintoa.

Ja tässä on johdettu luokka:

julkinen luokka ArrayCount laajentaa Array: ta
{
  yksityinen laskenta = 0;
 
  @Ohittaa
  public void add (Object element)
  {
    super.add (elementti);
    ++ count;
  }
 
  @Ohittaa
  public void addAll (objektielementit [])
  {
    super.addAll (elementit);
    count + = elementit.pituus;
  }
}

ArrayCount-luokka on erikoistuminen yleiseen Array-luokkaan. Ainoa käyttäytymisero on, että ArrayCount pitää elementtien lukumäärää.

Katsotaanpa näitä molempia luokkia yksityiskohtaisesti.

Array add () lisää elementin paikalliseen ArrayList.
Array addAll () kutsuu paikallisen ArrayList-lisäyksen jokaiselle elementille.

ArrayCount add () soittaa vanhempiensa add (): lle ja lisää sitten määrän.
ArrayCount addAll () soittaa vanhempiensa addAll (): lle ja lisää sen jälkeen määrän elementtien lukumäärällä.

Ja kaikki toimii hyvin.

Nyt murtautuva muutos. Kommentoitu koodirivi Base-luokassa muutetaan seuraavaan:

  public void addAll (objektielementit [])
  {
    varten (int i = 0; i 

Perusluokan omistajan kannalta se toimii edelleen ilmoitetulla tavalla. Ja kaikki automatisoidut testit läpäisevät edelleen.

Mutta omistaja ei tiedä Derived-luokkaa. Ja Derived-luokan omistaja on töykeässä heräämisessä.

Nyt ArrayCount addAll () kutsuu vanhempiensa addAll (), joka kutsuu sisäisesti add (): ta, jonka Derived-luokka on YLITTÖINEN.

Tämä aiheuttaa laskennan kasvattamisen joka kerta, kun johdetun luokan lisäystä () kutsutaan, ja sitten sitä kasvatetaan JATKA uudelleen niiden alkioiden lukumäärällä, jotka lisättiin johdetun luokan lisäykseen ().

Se on laskettu kahdesti.

Jos niin voi tapahtua, ja niin tapahtuu, johdetun luokan kirjoittajan on TIETOA kuinka perusluokka on toteutettu. Ja heille on tiedotettava jokaisesta muutoksesta perusluokassa, koska se voi rikkoa johdetun luokan ennakoimattomilla tavoilla.

Ugh! Tämä valtava halkeama uhkaa ikuisesti kallisarvoisen perintöpylvään vakautta.

Hauras pohjaluokan ratkaisu

Jälleen kerran sulje ja siirry pelastamaan.

Käyttämällä Contain ja Delegate siirrymme White Box -ohjelmoinnista Black Box ohjelmointiin. White Box -ohjelmoinnin avulla meidän on tarkasteltava perusluokan toteutusta.

Black Box-ohjelmoinnilla voimme olla täysin tietämättömiä toteutuksesta, koska emme voi syöttää koodia Base-luokkaan ohittamalla yhtä sen toiminnoista. Meidän on vain huolehdittava rajapinnasta.

Tämä suuntaus on häiritsevä…

Perinnön oli tarkoitus olla valtava voitto uudelleenkäytölle.

Objektisuuntautuneet kielet eivät tee sisällön ja siirron tekemistä helpoksi. Ne on suunniteltu tekemään perintö helpoksi.

Jos olet kuin minä, olet alkanut ihmetellä tätä perintöasiaa. Mutta mikä tärkeintä, tämän pitäisi horjuttaa luottamustasi luokituksen voimaan hierarkioiden kautta.

Hierarkiaongelma

Joka kerta, kun aloitan uudessa yrityksessä, kamppailen ongelman kanssa luotaessa paikkaa yrityksen asiakirjojen, esim. työntekijöiden käsikirja.

Luonko asiakirjat-kansion ja luon sen jälkeen yrityksen nimisen kansion?

Vai luonko kansiota nimeltä Yritys ja sitten siinä kansiota nimeltä Asiakirjat?

Molemmat toimivat. Mutta mikä on oikein? Mikä on paras?

Luokkahierarkioiden idea oli, että oli olemassa perusluokkia (vanhempia), jotka olivat yleisempiä ja että johdetut luokat (lapset) olivat erikoistuneempia versioita noista luokista. Ja vieläkin erikoistuneempi, kun me edetämme perintöketjussa. (Katso yllä oleva muotohierarkia)

Mutta jos vanhempi ja lapsi voisivat mielivaltaisesti vaihtaa paikkoja, niin selvästi jotain on vialla tässä mallissa.

Hierarkiaratkaisu

Mikä vika on ...

Kategoriset hierarkiat eivät toimi.

Joten mitkä hierarkiat ovat hyviä?

Suojarakennus.

Jos katsot todellista maailmaa, näet säilytys- (tai yksinoikeuden) hierarkioita kaikkialla.

Mitä et löydä, on kategoriset hierarkiat. Anna sen upota hetkeksi. Objektisuuntainen paradigma perustui todelliseen maailmaan, joka oli täynnä esineitä. Mutta sitten se käyttää rikki mallia, nimittäin. Kategoriset hierarkiat, joissa ei ole reaalimaailman analogiaa.

Mutta todellinen maailma on täynnä säilytyshierarkioita. Upea esimerkki säilytyshierarkiasta on sukat. Ne sijaitsevat sukkalaatikossa, joka sisältyy lipastosi yhteen laatikkoon, joka sisältyy makuuhuoneeseesi, joka sisältyy taloon jne.

Kiintolevyn hakemistot ovat toinen esimerkki säilytyshierarkiasta. Ne sisältävät tiedostoja.

Joten miten me sitten luokittelemme?

No, jos ajattelet yritysasiakirjoja, sillä ei melko ole väliä mihin ne laitoin. Voin laittaa ne asiakirjakansioon tai Stuff-kansioon.

Tapa luokittelen sen tunnisteilla. Tunnistan tiedoston seuraavilla tunnisteilla:

Asiakirja
Yhtiö
Käsikirja

Tunnisteissa ei ole järjestystä tai hierarkiaa. (Tämä ratkaisee myös timanttiongelman.)

Tunnisteet ovat analogisia rajapintojen kanssa, koska asiakirjaan voi liittyä useita tyyppejä.

Mutta niin monien halkeamien vuoksi näyttää siltä, ​​että perintöpilari on pudonnut.

Hyvästi, perintö.

Kapselointi, toinen pylväs putoamiseen

Ensisilmäyksellä kapselointi näyttää olevan toiseksi suurin hyöty olio-ohjelmoidusta ohjelmoinnista.

Objektitilamuuttujat on suojattu pääsyltä ulkopuolelta, ts. Ne on kapseloitu esineeseen.

Meidän ei enää tarvitse huolehtia globaaleista muuttujista, joita kuka tietää-kuka käyttää.

Kapselointi on turvallinen muuttujillesi.

Tämä kotelointitapa on uskomaton!

Eläköön kapselointi ...

Se on kunnes ...

Viiteongelma

Tehokkuuden vuoksi esineet siirretään toimintoihin, jotka EI ole niiden arvon, mutta viitteiden perusteella.

Mitä tämä tarkoittaa, että toiminnot eivät välitä objektia, vaan lähettävät sen sijaan viitteen tai osoittimen objektille.

Jos objekti ohitetaan viitaten objektinrakentajaan, rakentaja voi laittaa kyseisen objektiviitteen yksityiseen muuttujaan, joka on suojattu koteloinnilla.

Mutta ohitettu esine ei ole turvallinen!

Miksi ei? Koska jollakin muulla koodinpalalla on osoitin esineelle, nimittäin. koodi, joka kutsui rakentajaa. Sillä PITÄÄ olla viittaus esineeseen, muuten se ei voinut siirtää sitä rakentajalle?

Viiteratkaisu

Rakentajan on kloonattava kuljettu esine. Eikä matala klooni, mutta syvä klooni, ts. Jokainen esine, joka sisältyy siirrettyyn esineeseen, ja jokainen esine näissä esineissä ja niin edelleen ja niin edelleen.

Niin paljon tehokkuudesta.

Ja tässä on kicker. Kaikkia esineitä ei voida kloonata. Joillakin on käyttöjärjestelmän resursseja, jotka tekevät kloonaamisesta hyödytöntä parhaimmillaan tai pahimmillaan mahdotonta.

Ja kaikilla yksittäisillä valtavirran OO-kielillä on tämä ongelma.

Hyvästi, kapselointi.

Polymorfismi, kolmas pylväs putoamiseen

Polymorfismi oli esineorientoituneen kolminaisuuden punapäätärinen tytärpuoli.

Se on eräänlainen ryhmän Larry Fine.

Hän oli siellä kaikkialla, missä he menivät, mutta hän oli vain tukihahmo.

Ei ole niin, että polymorfismi ei ole hienoa, vaan se, että sinun ei tarvitse esinekeskeistä kieltä saadaksesi tämän.

Rajapinnat antavat sinulle tämän. Ja ilman kaikkia OO: n matkalaukkuja.

Ja rajapintojen kanssa ei ole rajaa kuinka monelle erilaiselle käyttäytymiselle voit sekoittaa.

Joten ilman paljon vaihetta, olemme jättäneet hyvästit OO-polymorfismiin ja tervetuloa rajapintapohjaiseen polymorfismiin.

Broken lupaukset

No, OO varmasti lupasi paljon alkuaikoina. Ja nämä lupaukset annetaan edelleen naiiville ohjelmoijille, jotka istuvat luokkahuoneissa, lukevat blogeja ja osallistuvat verkkokursseille.

Kesti vuosia ymmärtää kuinka OO valehteli minulle. Minäkin olin laajasilmäinen, kokematon ja luottavainen.

Ja minä poltin.

Hyvästi, olio-ohjelmointi.

Joten mitä sitten?

Hei, toiminnallinen ohjelmointi. On ollut niin mukavaa työskennellä kanssasi viime vuosina.

Vain joten tiedät, etten ota lupauksiasi nimellisarvoon. Minun täytyy nähdä se uskoa siihen.

Kerran palanut, kahdesti ujo ja kaikki.

Sinä ymmärrät.

Jos pidit tästä, napsauta alla olevaa, jotta muut ihmiset näkevät tämän täällä Mediumissa.

Jos haluat liittyä web-kehittäjien yhteisöön, joka oppii ja auttaa toisiaan kehittämään web-sovelluksia Elm-ohjelman funktionaalisen ohjelmoinnin avulla, tutustu Facebook-ryhmään, Opi Elm-ohjelmointi https://www.facebook.com/groups/learnelm/

Oma Twitter: @cscalfani