TIE-20200 Ohjelmistojen suunnittelu Luento 10: olioiden rakentelumalleja ja SOLID TIE-20200 Samuel Lahtinen 1
Ajankohtaista Harjoitustyössä välinäyttöjen ajanvaraus auki Viikkoharjoitukset ohi
Ohjelmassa tänään Kirjastoista ja plugineista kertausta SOLID, yleisiä olio-ohjelmoinnin suunnitteluperiaatteita
Ajoaikaiset kirjastot Mitä voidaan muuttaa ilman että kirjastoa käyttävää ohjelmaa tarvitsee päivittää? Toteutus: kunhan noudatetaan sopimussuunnittelun mukaisesti jälki- ja esiehtoja Rajapinta: Yleisohje, ei saa muuttaa Tarkemmin: Uusien ei-virtuaalisten funktioiden lisääminen mahdollista Vanhojen funktioiden muuttaminen, uusien virtuaalifunktioiden lisääminen myös kirjaston käyttäjät pitää kääntää uudelleen
Jaetut/dynaamiset kirjastot Ja asennukset Asennus tärkeää, minne kirjastokomponentit tulevat, että ohjelmat löytävät ne? vaihtoehtoja: Käyttöjärjestelmän hakemistoihin Ohjelman ajohakemiston alihakemistoon Erilliseen kirjastohakemistoon Mitä mukaan? C++, itse dll ja otsikkotiedostot, jos halutaan tukea muita kehittäjiä? Myös kehitystyökalun kirjastot Esim Qt:n kanssa http://doc.qt.io/qt-5/windows-deployment.html Yleensä asennusohjelma tarpeen/hyödyllinen
SOLID, hyviä periaatteita Viisi olio-ohjelmoinnin perusperiaatetta, muistisääntö S Single responsibility principle O Open/closed principle L Liskov substitution principle I Interface segregation principle D Dependency inversion principle Ei estä ketterää kehitystä tai muutakaan mukavaa, päin vastoin helpottaa näitä Muutokset, laajentaminen, päivittäminen helpompaa, voidaan toimia ketterästi ilman loppuun asti hiottua suunnitelmaa
Single responsibility principle Single responsibility principle (SRP) a class should have only a single responsibility. Vain yksi vastuualue/luokka Pohjautuu reason-to-change ajetelmaan, jokaisella luokalla pitäisi olla vain yksi huolehdittava asia, vain yksi syy, miksi luokan toteutusta pitäisi muuttaa. Jos luokalla kaksi tai useampi muutossyy, voi muutoksella yhden vastuualueen toimiin olla helposti vaikutusta toisen toimintaan (korjataan yhtä, rikotaan toinen) Uudelleenkäyttö ja eriyttäminen vaikeampaa, kun mukana tulee ylimääräistä kuormaa
Single responsibility principle, testaaminen jne. Testattavuus ja Single responsibility principle yksikkötestit jne. helpompi toteuttaa, kirjoittaa Virheiden löytäminen helpottuu, tiettyyn toimintoon liittyvät asiat samassa paikassa Virheiden korjaaminen helpompaa, vaikeampi hajottaa muuta toimivaa koodia
Single responsibility principle Saman luokan kaksi vastuuta (reason for change), muutos toiseen voi rikkoa toisen puolen toiminnallisuuden Vastuualueiden jako (esim. GUI-versio käyttää GeometrisenSuorakaiteen palveluita, laskentapuolella käytetään vain geom. osaa) Suorakaide +laskeala(): double +piirraruudulle() GUI Matemaattinen laskentaosuus GUIsovellus Vaarat: turha kompleksisuuden lisääminen, jos vastuualueet muuttuvat aina yhdessä, ei niiden eriyttämisestä ole iloa TIE-20200 Samuel Lahtinen Yleinen kompromissiratkaisu, tehdään erilliset rajapinnat, sama toteuttaja. Helpompi muuntaa ja korjata, jos tarve ilmenee.
Open-closed principle Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification You should be able to extend a classes behavior, without modifying it. Moduulit ja luokat: Ovat avoimia laajennuksille, toiminnallisuutta voidaan lisätä, rajapintaa laajentaa uusilla palveluilla Suljettu muutoksilta: moduulin tai luokan toteutusta ei saa muuttaa Koodi testattu, katselmoitu jne, sitä ei enää muuteta Kuulostaa ristiriitaiselta, ideana hyödyntää abstraktioita Voidaan käyttää hyväksi perintää, (abstrakteja) kantaluokkia Ohjelmat, joissa hyödynnetään open-closed periaatetta, muutetaan lisäämällä uutta koodia, ei muuttamalla olemassa olevaa Ei realistisesti mahdollista päästä tilanteeseen, jossa 100% koodista suljettuna muutoksilta pitää tehdä päätöksiä siitä, mihin panostetaan, missä ohjelman osissa tulee todennäköisimmin muutoksia? Abstraktiot, ja osan ohjelmakoodin sulkeminen.
Open-closed principle Vinkkejä noudattamisen helpottamiseen: Ei julkisia tai protected jäsenmuuttujia, ei globaaleja muuttujia Vältä: Olioiden omistaminen arvoina, arvoparametrien käyttö (C++) Viitteet, osoittimet, voidaan antaa käyttöön erikoistettu versio jne. Älä käytä dynamic_castiä tyyppitarkastusten tekemiseen, vain laajennettuun rajapintaan kiinni pääsemiseen (tai toteutetun rajapinnan toteuttamisen tarkastamiseen), esimerkki huonosta tavasta(taululle)
Liskov substitution principle Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. Derived classes must be substitutable for their base classes Mikä tahansa aliluokan instanssi kelpaa kantaluokkaolion tilalle (ilman että ohjelman toiminta kärsii/muuttuu virheelliseksi) C++-maailmassa: mikä tahansa viitteen tai osoittimen luokkaan ottavan funktion on pystyttävä käyttämään mitä tahansa luokan aliluokkaa ilman että funktion toteutuksessa täytyy tietää todellinen tyyppi (taas vääränlaiset tyyppimuunnokset jne.) Toiminnalliset aliluokat: Toiminnallinen lisävaatimus pelkän syntaksivaatimuksen lisäksi (semantiikka), ei pelkkä syntaktinen yhteensopivuus (samat funktion protyypit jne.)
Liskov substitution principle, Rikkomisen seuraamuksia Otetaan esimerkkinä (taas) suorakaide ja siitä peritty neliö, vektori ja järjestetty vektori, perustietovarasto ja tilaoptimoiva tietovarasto Päästään takaisin is-a-suhteen merkitykseen Is-a-suhdetta ja Liskovin periaatetta ei pidä rikkoman koodirivejä säästääkseen tai pelkän käsitteellisen sukulaisuuden takia Sopimussuunnittelu ja Liskov: aliluokat eivät saa rikkoa kantaluokan sopimuksia Vain periaatetta noudattamalla on mahdollista kirjoittaa luokkia käyttävää koodia ja luottaa siihen, että uudet lisäykset (uudet luokat) eivät hajota olemassa olevan koodin toimintaa void testaa( Suorakaide& s ) { s.asetakorkeus( 6 ); s.asetaleveys( 3 ); assert( s.leveys()*s.korkeus() == 18 ); } class Suorakaide { public: virtual void asetakorkeus( int k ); virtual void asetaleveys( int l ); int leveys() const; int korkeus() const; private: int korkeus_; int leveys_; }; class Nelio: public Suorakaide { public: virtual void asetakorkeus( int k ); virtual void asetaleveys( int l ); };
Interface segregation principle many client-specific interfaces are better than one general-purpose interface. Make fine grained interfaces that are client specific Clients should not be forced to depend upon methods they do not use Rajapinnan käyttäjän ei pidä olla riippuussuhteessa palveluihin/funktioihin joita se ei tarvitse/käytä Jaetaan isommat rajapinnat pienemmiksi roolien mukaan, saadaan roolirajapintoja (role interfaces) Isompi ohjelma, jossa paljon keskinäisiä riippuvuuksia ja isoja möhkälerajapintoja pienikin muutos heijastuu joka puolelle muutoksen tekijä joutuu tonkimaan muutettavat asiat muun turhan sälän seasta yksittäiseen asiaan liittyvän toteutuksen vaihtaminen vaatii copy-pastea vanhoista koodeista Uusien ominaisuuksien lisääminen vaatii uuden asian lisäämisen lisäksi vanhan huomioimista
Interface segregation Anti-versio periaatteesta, yksi iso möhkäleluokka, joka tarjoaa lähes kaikki toiminnot. Vaikea hallita, päivittää, muuttaa, jos rinnakkaisuutta, saadaan suorituskykyongelmat mukaan Lihavat rajapinnat (fat interfaces), rajapintojen saastuminen (interface pollution)
Asiakas 1 Asiakas 2 palvelu Asiakas 3 Asiakas 1 Asiakas 2 Asiakas 3 Rajapinta 1 Rajapinta 2 Rajapinta 3 Palvelun toteutus
Interface segregation principle Rajapintojen saastuminen ja perintä: Ajastus, ovi, ajastettu ovi Tuote, Peli, CD, housut Käyttäjä (sisäänkirjautumisessa): aliluokat, vieras, normi, admin Mitä kantaluokkaan, mitä erillisiin rajapintoihin, mitä aliluokkiin?
Dependency inversion principle A) High-level modules should not depend on low-level modules. Both should depend on abstractions. B) Abstractions should not depend upon details. Details should depend upon abstractions. http://www.objectmentor.com/resources/articles/dip.pdf Riippuvuudet abstraktioihin, ei konkreettisiin toteutuksiin Huonon suunnittelun kriteeristöä: Ei siirrettävää koodia, ohjelman rakenne täynnä riippuvuuksia, komponentteja ei voi uudelleen käyttää tai siirtää näiden takia Jäykkyys, yksittäinen muutos vaikuttaa moneen muuhun osaan Hauraus, muutos yhteen osaan rikkoo asioita muualla
Korkea taso (sovelluslogiikan kannalta) Abstraktio(kerros) Rajapinnat Matalamman tason toteutus
Dependency inversion principle Korkeamman tason komponentit eivät saa olla riippuvaisia matalan tason toteutusyksityiskohdista, jotain ratkaisutapoja: Dependency injection Adapter-pattern, fasadit, sillat yms. Kerrosarkkitehtuurit Rajapinnat C++ ja toteutus- ja otsikkotiedoston erottaminen ei tarkoita toteutusriippuvuuden poistumista Periaate tärkeimpiä tapoja tehdä koodista: Muunneltavampaa, kestävämpää muutoksille (toteutusten muuttaminen ei hajota koko ohjelmaa) Ylläpidettävämpää Helpommin uudelleen käytettävää
Yhteenveto SOLID-periaatteet, samoja tavoitteita ja ideoita kuin muissakin kurssilla opituissa tekniikoissa. Esim. suunnittelumallit tarjoavat hyväksi havaittuja ratkaisuja näiden saavuttamiseen Lähdemateriaalia ja lisäluettavaa: http://www.objectmentor.com/resources/articles/principles_and_patterns.pdf http://www.codeproject.com/articles/703634/solid-architecture-principlesusing-simple-csharp http://www.codemag.com/article/1001061 http://lassala.net/2010/11/04/a-good-example-of-liskov-substitution-principle/ TIE-20200 Samuel Lahtinen 23