Lyhyt kuvaus olio-ohjelmistosuunnittelusta

Osoitetaan toteuttamalla roolipelien luokat

Johdanto

Useimmat nykyaikaiset ohjelmointikielet tukevat ja kannustavat olio-ohjelmointia (OOP). Vaikka näyttää siltä, ​​että viime aikoina näemme olevan hieman siirtymässä tästä, kun ihmiset alkavat käyttää kieliä, joihin OOP ei vaikuta kovin paljon (kuten Go, Rust, Elixir, Elm, Scala), useimmilla on kuitenkin esineitä. Suunnitteluperiaatteet, jotka aiomme esitellä tässä, koskevat myös muita kuin OOP-kieliä.

Onnistuessasi kirjoittamaan selkeä, korkealaatuinen, ylläpidettävä ja laajennettava koodi, sinun on tiedettävä suunnitteluperiaatteista, jotka ovat osoittautuneet toimiviksi vuosikymmenien kokemuksen aikana.

Tiedonanto: Käymämme esimerkki tulee olemaan Pythonissa. Esimerkkejä on todistaa asia ja ne voivat olla huolimattomia muilla, ilmeisillä tavoilla.

Kohdetyypit

Koska aiomme mallintaa koodiamme esineiden ympärille, olisi hyödyllistä erottaa toisistaan ​​niiden eri vastuualueet ja muunnelmat.

Kohteita on kolmen tyyppisiä:

1. Entiteettiobjekti

Tämä esine vastaa yleensä jotakin reaalimaailman kokonaisuutta ongelmatilassa. Oletetaan, että rakennamme roolipeliä (RPG), olioobjekti olisi yksinkertainen Heroluokkamme:

Nämä objektit sisältävät yleensä ominaisuuksia itsestään (kuten healthtai mana) ja ovat muokattavissa tiettyjen sääntöjen avulla.

2. Ohjausobjekti

Ohjausobjektit (joskus kutsutaan myös Manager-objekteiksi ) ovat vastuussa muiden objektien koordinoinnista. Nämä ovat objekteja, jotka hallitsevatja hyödyntää muita esineitä. Hieno esimerkki RPG-analogiamme olisi Fightluokka, joka hallitsee kahta sankaria ja saa heidät taistelemaan.

Taistelun logiikan yhdistäminen tällaiseen luokkaan tarjoaa sinulle useita etuja: joista yksi on toiminnan helppo laajennettavuus. Voit siirtää sankarin taistelemaan helposti muun kuin pelaajan hahmotyypin (NPC) edellyttäen, että se paljastaa saman API: n. Voit myös periä luokan helposti ja ohittaa osan toiminnoista tarpeidesi mukaan.

3. Rajaobjekti

Nämä ovat esineitä, jotka istuvat järjestelmän rajalla. Kaikki objektit, jotka ottavat syötteen toisesta järjestelmästä tai tuottavat sen toiseen järjestelmään - riippumatta siitä, onko kyseinen järjestelmä Käyttäjä, Internet vai tietokanta - voidaan luokitella rajaobjektiksi.

Nämä rajakohteet ovat vastuussa tiedon kääntämisestä järjestelmäämme ja siitä pois. Esimerkissä, jossa otamme Käyttäjä-komentoja, tarvitsemme rajaobjektin kääntääksesi näppäimistön syötteen (kuten välilyönnin) tunnistettavaksi toimialueeksi (kuten merkkihyppy).

Bonus: Arvo-esine

Arvo-objektit edustavat yksinkertaista arvoa toimialueellasi. Ne ovat muuttumattomia eikä heillä ole identiteettiä.

Jos sisällytämme heidät peliin, a Moneytai Damageluokka sopisi hyvin. Mainittujen esineiden avulla voimme helposti erottaa, etsiä ja virheenkorjauksen liittyvän toiminnallisuuden, kun taas primitiivisen tyypin - kokonaislukujoukko tai yksi kokonaisluku - naiivi lähestymistapa ei.

Ne voidaan luokitella alaryhmään Entityesineitä.

Keskeiset suunnitteluperiaatteet

Suunnitteluperiaatteet ovat ohjelmistosuunnittelun sääntöjä, jotka ovat osoittautuneet arvokkaiksi vuosien varrella. Niiden tarkka noudattaminen auttaa varmistamaan, että ohjelmisto on huippuluokkaa.

Abstraktio

Abstraktio on ajatus yksinkertaistaa käsite sen olennaisimpiin osiin jossain yhteydessä. Sen avulla voit ymmärtää käsitteen paremmin irrottamalla sen yksinkertaistettuun versioon.

Yllä olevat esimerkit havainnollistavat abstraktiota - katso miten Fightluokka on rakennettu. Käyttötapa on mahdollisimman yksinkertainen - annat sille kaksi sankaria argumenttina välitettäessä ja kutsut fight()menetelmän. Ei enempää eikä vähempää.

Koodin abstraktin tulee noudattaa vähiten yllätyssääntöä. Abstraktiosi ei saa yllättää ketään tarpeettomalla ja etuyhteydettömällä käyttäytymisellä / ominaisuuksilla. Toisin sanoen - sen pitäisi olla intuitiivinen.

Huomaa, että Hero#take_damage()toimintamme ei tee jotain odottamatonta, kuten poistaa hahmomme kuoleman jälkeen. Mutta voimme odottaa sen tappavan hahmomme, jos hänen terveytensä laskee alle nollan.

Kapselointi

Kapseloinnin voidaan ajatella asettavan jotain kapselin sisään - rajoitat sen altistumista ulkomaailmalle. Ohjelmistoissa sisäisten esineiden ja ominaisuuksien käytön rajoittaminen auttaa tietojen eheyttä.

Kapseloimalla mustan laatikon sisäinen logiikka ja se helpottaa luokkien hallintaa, koska tiedät mitä osaa muut järjestelmät käyttävät ja mitä ei. Tämä tarkoittaa, että voit helposti muokata sisäistä logiikkaa säilyttäen julkiset osat ja olla varma, ettet ole rikkonut mitään. Sivuvaikutuksena työskentely kapseloitujen toimintojen kanssa ulkopuolelta tulee yksinkertaisemmaksi, koska sinulla on vähemmän ajateltavia asioita.

Useimmilla kielillä tämä tapahtuu ns. Pääsymuuttujien kautta (yksityiset, suojatut ja niin edelleen). Python ei ole paras esimerkki tästä, koska sillä ei ole tällaisia ​​suoritusaikaan sisäänrakennettuja modifikaattoreita, mutta käytämme käytäntöjä tämän kiertämiseen. _Etuliitteen muuttujiin / menetelmät merkitsevät niitä olevan yksityisiä.

Kuvittele esimerkiksi, että muutamme Fight#_run_attackmenetelmäämme palauttamaan looginen muuttuja, joka osoittaa, onko taistelu ohi, eikä herätä poikkeusta. Tiedämme, että ainoa koodi, jonka olemme saattaneet rikkoa, on Fightluokan sisällä , koska teimme menetelmästä yksityisen.

Muista, että koodia vaihdetaan useammin kuin kirjoitetaan uudestaan. Kehittäjänä haluamasi joustavuus on se, että pystyt vaihtamaan koodisi mahdollisimman selkeillä ja pienillä vaikutuksilla.

Hajoaminen

Hajoaminen on kohteen jakaminen useiksi erillisiksi pienemmiksi osiksi. Mainitut osat on helpompi ymmärtää, ylläpitää ja ohjelmoida.

Kuvittele, että halusimme sisällyttää lisää RPG-ominaisuuksia, kuten harrastajia, mainosjakaumaa, varusteita ja hahmomääritteitä Hero:

Oletan, että voit kertoa, että tästä koodista on tulossa melko sotkuinen. Kohteemme Herotekee liikaa tavaraa kerralla, ja tästä koodista on tulossa melko hauras.

Esimerkiksi yksi kestävyyspiste on 5 terveyden arvoinen. Jos haluamme koskaan muuttaa tätä tulevaisuudessa, jotta se olisi 6 terveyden arvoinen, meidän on muutettava toteutusta useissa paikoissa.

Vastaus on hajottaa Heroobjekti useiksi pienemmiksi kohteiksi, joista kukin sisältää osan toiminnoista.

Nyt, kun hajoavasta sankarimme objektin toiminnallisuutta HeroAttributes, HeroInventory, HeroEquipmentja HeroBuffesineitä, lisäämällä tulevaisuudessa toiminnallisuus on helpompaa, enemmän kapseloitu ja parempia otetun. Voit kertoa, että koodimme on paljon puhtaampi ja selkeämpi siitä, mitä se tekee.

Hajoamissuhteita on kolme tyyppiä:

  • yhdistys- Määrittää löysän suhteen kahden komponentin välillä. Molemmat komponentit eivät ole riippuvaisia ​​toisistaan, mutta ne voivat toimia yhdessä.

Esimerkki:Hero ja Zoneesine.

  • aggregaatio - Määrittää heikon "on-a" -suhteen kokonaisuuden ja sen osien välillä. Pidetään heikkoina, koska osat voivat olla olemassa ilman kokonaisuutta.

Esimerkki:HeroInventory ja Item.

A HeroInventoryvoi olla monia Itemsja Itemvoi kuulua mihin tahansa HeroInventory(kuten kaupankäynnin kohteisiin).

  • sävellys - Vahva "on-a" -suhde, jossa kokonaisuus ja osa eivät voi olla olemassa ilman toisiaan. Osia ei voi jakaa, koska kokonaisuus riippuu tarkoista osista.

Esimerkki:Hero ja HeroAttributes.

Nämä ovat sankarin määritteet - et voi vaihtaa omistajaa.

Yleistys

Yleistäminen voi olla tärkein suunnitteluperiaate - se on prosessi, jolla erotetaan jaetut ominaisuudet ja yhdistetään ne yhteen paikkaan. Kaikki meistä tietävät toimintojen käsitteestä ja luokan perimisestä - molemmat ovat eräänlainen yleistys.

Vertailu saattaa selventää asioita: vaikka abstraktio vähentää monimutkaisuutta piilottamalla tarpeettomia yksityiskohtia, yleistys vähentää monimutkaisuutta korvaamalla useita entiteettejä, jotka suorittavat samanlaisia ​​toimintoja yhdellä rakenteella.

Annetussa esimerkissä olemme yleistäneet yhteisen Heroja NPC luokkamme toiminnallisuuden yhteiseksi esi-isäksi nimeltä Entity. Tämä saavutetaan aina perinnöllä.

Sen sijaan, että meidän NPCja Heroluokkamme toteuttaisivat kaikki menetelmät kahdesti ja rikkovat DRY-periaatetta, vähennimme monimutkaisuutta siirtämällä niiden yhteisen toiminnallisuuden perusluokkaan.

Ennakkovaroituksena - älä liioittele perintöä. Monet kokeneet ihmiset suosittelevat sinun suosivan sommittelua perinnön sijaan.

Amatööriohjelmoijat käyttävät usein väärin perintöä, todennäköisesti siksi, että se on yksi ensimmäisistä OOP-tekniikoista, jotka he ymmärtävät yksinkertaisuutensa vuoksi.

Sävellys

Sommittelu on periaate yhdistää useita esineitä monimutkaisemmiksi. Käytännössä sanottu - se luo esineiden esiintymiä ja käyttää niiden toiminnallisuutta sen sijaan, että periisit sen suoraan.

Kohdetta, joka käyttää sommittelua, voidaan kutsua yhdistetyksi . On tärkeää, että tämä yhdistelmä on yksinkertaisempi kuin ikäisensä summa. Yhdistämällä useita luokkia yhdeksi haluamme nostaa abstraktiotasoa korkeammalle ja tehdä objektista yksinkertaisemman.

Komposiittiobjektin API: n on piilotettava sen sisäiset komponentit ja niiden välinen vuorovaikutus. Ajattele mekaanista kelloa, sillä siinä on kolme kättä ajan näyttämiseen ja yksi säätönuppi - mutta sisällä on kymmeniä liikkuvia ja toisistaan ​​riippuvia osia.

Kuten sanoin, koostumus on suositeltavampi kuin perintö, mikä tarkoittaa, että sinun pitäisi pyrkiä siirtämään yhteiset toiminnot erilliseen objektiin, jota luokat sitten käyttävät - sen sijaan, että sijoitat sen perimäsi perusluokkaan.

Valaistaan ​​mahdollinen ongelma toiminnallisuuden liiallisesta perimisestä:

Lisäsimme juuri liikettä peliin.

Kuten opimme, koodin kopioinnin sijasta käytimme yleistystä laittaaksemme move_rightja move_leftfunktiot Entityluokkaan.

Okei, nyt entä jos haluaisimme tuoda kiinnikkeet peliin?

Kiinnikkeiden on myös siirryttävä vasemmalle ja oikealle, mutta heillä ei ole kykyä hyökätä. Ajattele sitä - heillä ei ehkä ole edes terveyttä!

Tiedän, mikä ratkaisusi on:

Siirrä movelogiikka yksinkertaisesti erilliseen MoveableEntitytai MoveableObjectluokkaan, jolla on vain kyseiset toiminnot. MountLuokka voi sitten periä se.

Mitä sitten teemme, jos haluamme kiinnikkeitä, joilla on terveyttä, mutta jotka eivät voi hyökätä? Enemmän jakamista alakategorioihin? Toivon, että näet, kuinka luokkahierarkiamme alkaa monimutkaistua, vaikka liiketoimintalogiikkamme onkin edelleen melko yksinkertainen.

Hieman parempi lähestymistapa olisi abstraktio liikelogiikka osaksi Movementluokkaa (tai jotain parempaa nimeä) ja ilmentää se luokissa, jotka sitä tarvitsevat. Tämä pakkaa toiminnallisuuden hienosti ja tekee siitä uudelleenkäytettävän kaikenlaisissa kohteissa, joihin ei ole rajoitettu Entity.

Hurraa, sävellys!

Kriittisen ajattelun vastuuvapauslauseke

Vaikka nämä suunnitteluperiaatteet on muotoiltu vuosikymmenien kokemuksen kautta, on silti äärimmäisen tärkeää, että pystyt ajattelemaan kriittisesti ennen periaatteen sokeaa soveltamista koodiin.

Kuten kaikki asiat, liikaa voi olla huono asia. Joskus periaatteet voidaan viedä liian pitkälle, voit olla liian fiksu heidän kanssaan ja päätyä johonkin, jonka kanssa on todella vaikea työskennellä.

Insinöörinä tärkein piirteesi on arvioida kriittisesti paras tapa ainutlaatuiseen tilanteeseesi, ei sokeasti noudattaa ja soveltaa mielivaltaisia ​​sääntöjä.

Koheesio, kytkeminen ja huolenaiheiden erottaminen

Koheesio

Koheesio edustaa vastuiden selkeyttä moduulissa tai toisin sanoen - sen monimutkaisuutta.

Jos luokkasi suorittaa yhden tehtävän eikä mitään muuta tai sillä on selkeä tarkoitus - tällä luokalla on suuri yhteenkuuluvuus . Toisaalta, jos se on jonkin verran epäselvää toiminnassaan tai sillä on enemmän kuin yksi tarkoitus - sillä on heikko yhteenkuuluvuus .

Haluat, että luokkasi ovat hyvin koheesioisia. Heillä pitäisi olla vain yksi vastuu, ja jos sait heille enemmän - saattaa olla aika jakaa se.

Kytkentä

Yhdistäminen kuvaa eri luokkien yhdistämisen monimutkaisuuden. Haluat, että kursseillasi on mahdollisimman vähän ja yksinkertaisia ​​yhteyksiä muihin luokkiin, jotta voit vaihtaa ne tulevissa tapahtumissa (kuten verkkokehysten muuttaminen). Tavoitteena on löysä kytkentä .

Monilla kielillä tämä saavutetaan käyttämällä paljon käyttöliittymiä - ne tiivistävät logiikkaa käsittelevän luokan ja edustavat eräänlaista sovitinkerrosta, johon mikä tahansa luokka voi liittää itsensä.

Huolenaiheiden erottaminen

Huolenaihe (SoC) on ajatus siitä, että ohjelmistojärjestelmä on jaettava osiin, jotka eivät ole päällekkäisiä toiminnallisuutensa kanssa. Tai kuten nimikin sanoo - huoli - Yleinen termi mitään, joka tarjoaa ratkaisun ongelmaan - on eroteltava eri paikkoihin.

Verkkosivu on hyvä esimerkki tästä - sen kolme tasoa (Tiedot, Esitys ja Käyttäytyminen) on jaettu kolmeen paikkaan (HTML, CSS ja JavaScript).

Jos tarkastelet uudelleen RPG- Heroesimerkkiä, huomaat, että sillä oli alussa monia huolenaiheita (käytä buffeja, laske hyökkäysvaurioita, käsittele varastoja, varusta kohteita, hallitse määritteitä). Erotimme nämä hajotukset hajoamisen kautta yhtenäisempiin luokkiin, jotka abstraktit ja kapseloivat yksityiskohdat. Luokkamme Herotoimii nyt yhdistelmäkohteena ja on paljon yksinkertaisempi kuin aikaisemmin.

Loppuratkaisu

Tällaisten periaatteiden soveltaminen saattaa näyttää liian monimutkaiselta niin pienelle koodinpätkälle. Totuus on, että se on välttämätöntä jokaiselle ohjelmistoprojektille, jonka aiot kehittää ja ylläpitää tulevaisuudessa. Tällaisen koodin kirjoittamisella on hiukan yleiskustannuksia alusta alkaen, mutta se maksaa useita kertoja pitkällä aikavälillä.

Nämä periaatteet varmistavat, että järjestelmämme on enemmän:

  • Laajennettava : Korkea koheesio helpottaa uusien moduulien käyttöönottoa huolimatta etuyhteydettömistä toiminnoista. Alhainen kytkentä tarkoittaa, että uudessa moduulissa on vähemmän tavaraa yhdistettävissä, joten se on helpompi toteuttaa.
  • Ylläpidettävä : Matala kytkentä varmistaa, että yhden moduulin muutos ei yleensä vaikuta muihin. Korkea yhteenkuuluvuus varmistaa, että järjestelmävaatimusten muutos edellyttää mahdollisimman pienen luokkamäärän muuttamista.
  • Uudelleenkäytettävä : Korkea koheesio varmistaa, että moduulin toiminnot ovat täydelliset ja hyvin määritellyt. Matala kytkentä tekee moduulista vähemmän riippuvaisen muusta järjestelmästä, mikä helpottaa uudelleenkäyttöä muissa ohjelmistoissa.

Yhteenveto

Aloitimme ottamalla käyttöön joitain perustason korkean tason objektityyppejä (Entiteetti, Raja ja Ohjaus).

Sitten opimme keskeiset periaatteet mainittujen esineiden jäsentämisessä (abstraktio, yleistys, koostumus, hajoaminen ja kapselointi).

Seuraavaksi otimme käyttöön kaksi ohjelmiston laatumittaria (yhdistäminen ja yhteenkuuluvuus) ja opimme näiden periaatteiden soveltamisen eduista.

Toivon, että tämä artikkeli antoi hyödyllisen yleiskuvan joistakin suunnitteluperiaatteista. Jos haluat kouluttaa itseäsi tällä alalla, suosittelen joitain resursseja.

Lisälukemat

Suunnittelumallit: Uudelleenkäytettävien olio-ohjelmistojen elementit - kiistatta alan vaikuttavin kirja. Hieman päivätty sen esimerkeissä (C ++ 98), mutta mallit ja ideat ovat edelleen erittäin merkityksellisiä.

Testien ohjaama kasvava olio-ohjelmisto - hieno kirja, joka osoittaa, miten tässä artikkelissa (ja muissa) esitettyjä periaatteita voidaan käytännössä soveltaa projektin läpi.

Tehokas ohjelmistosuunnittelu - Huippuluokan blogi, joka sisältää paljon muutakin kuin suunnittelutietoja.

Ohjelmistosuunnittelu ja -arkkitehtuurin erikoistuminen - Suuri 4 videokurssin sarja, joka opettaa sinulle tehokasta suunnittelua koko sovelluksen ajan projektissa, joka kattaa kaikki neljä kurssia.

Jos tämä yleiskatsaus on ollut informatiivinen sinulle, harkitse sitä, kuinka paljon ansaitsit ansaitsemiasi tapoja, jotta useammat ihmiset voivat kompastua siihen ja saada siitä arvoa.