Ohjelmoinnin peruskurssien laaja oppimäärä Luento 17: Scalan case class ja trait, sisäluokat, enumeraatiot Riku Saikkonen (merkityt ei-laajan kurssin kalvot: Otto Seppälä) 24. 2. 2011
Sisältö 1 Hahmonsovitus ja case class Scalassa 2 Scalan moniperintä: trait 3 Sisäluokat Javassa ja Scalassa 4 Enumeraatiot Javassa ja Scalassa 5 Lisää suunnittelumalleja
Mikä case class? (SbE 7.1) case class on muuten kuin tavallinen luokka, mutta: olioita luodessa ei tarvita new:tä metodit equals, hashcode ja tostring ovat valmiina valmis tostring tulostaa konstruktorikutsun näköisen tekstin valmis equals vertaa olion rakennetta rekursiivisesti case-luokkien kanssa voi käyttää Scalan hahmonsovitusta tarkoitettu luokille, jotka kuvaavat rakenteista dataa, usein niin että rakenteessa on useita vaihtoehtoja case-luokkaa ei yleensä peritä eikä sen kenttiä muuteta luomisen jälkeen Esimerkkikoodia abstract class Expr case class Number(n: Int) extends Expr case class Sum(e1: Expr, e2: Expr) extends Expr Sum(Number(1), Number(2)) == Sum(Number(1), Number(2)) true Sum(Sum(Number(1), Number(2)), Number(3)).toString "Sum(Sum(Number(1),Number(2)),Number(3))" (SbE 7.1)
Hahmonsovitus Scalassa (SbE 7.2) hahmonsovituksella voi sovittaa jotakin arvoa eri hahmoihin ja samalla antaa nimiä hahmon osille (ks. luento 5) syntaksi Scalassa on yleensä: arvo match { case hahmo-1 => koodia case hahmo-2 => koodia... hahmot käydään läpi järjestyksessä (kuten ifelse-if -ketju) usein hahmoilla myös luodaan uusia paikallisia muuttujia tunnistetun rakenteen osista (esimerkki alla) tämä on Scalan vastine switchille (mutta paljon monipuolisempi) Jatkoa edelliseen esimerkkiin (SbE 7.2) def eval(e: Expr): Int = e match { case Number(n) => n case Sum(l, r) => eval(l) + eval(r) eval(sum(sum(number(1), Number(2)), Number(3))) 6
Hahmot ja vahdit hahmo voi olla esimerkiksi: yksittäinen vakio case 0 =>... muuttuja ja luokka case x: Int =>... case-luokan konstruktorisyntaksin näköinen rakenne case Sum(Number(x), y) =>... pelkkä muuttujannimi tai _ sopii mihin tahansa lisäksi hahmossa voi olla vahti (guard) eli tarkistettava ehto: case Number(x) if x < 0 =>... case Sum(x,y) if x == y =>... muuttujannimen käyttäminen hahmossa tekee uuden muuttujan val x = 3; val y = 5; y match { case x => x+1 6 olemassaolevia muuttujia pitää käyttää vasta vahdissa: y match { case z if z == x => z+1; case _ => 0 4
Esimerkki case-luokista ja hahmonsovituksesta Symbolisen lausekkeen sievennystä simplify.scala abstract class Expr { def simplifystep: Expr = this match { case Sum(Num(0), x) => x.simplify // sievennyssääntöjä case Sum(x, Num(0)) => x.simplify case Product(Num(1), x) => x.simplify case Product(x, Num(1)) => x.simplify case Product(Num(0), _) => Num(0) case Product(_, Num(0)) => Num(0) case Sum(x, y) if x == y => Product(Num(2), x.simplify) case Sum(x, y) => Sum(x.simplify, y.simplify) // rekursio case Product(x, y) => Product(x.simplify, y.simplify) case x => x def simplify: Expr = { val r = this.simplifystep if (this == r) r else r.simplify // toista kunnes ei muutu case class Num(v: Int) extends Expr case class Var(name: String) extends Expr case class Sum(e1: Expr, e2: Expr) extends Expr case class Product(e1: Expr, e2: Expr) extends Expr Product(Sum(Num(0), Var("x")), Num(5)) Product(Var("x"), Num(5))
catch käyttää hahmonsovitusta myös Scalan trycatch-rakenne käyttää hahmonsovitusta hahmonsovituksen kohteena on heitetty poikkeusolio (Exception-luokan perillinen) Poikkeusesimerkki (osittainen) try { // lukee esim. rivin ''12 54'' kahteen muuttujaan val s = readline() filter { "1234567890 ".contains(_) val parts = s split ' ' filter {!_.isempty val y = parts(0).toint val x = parts(1).toint... catch { case e: IndexOutOfBoundsException => // hahmo println("i didn't understand.") // koodi case e: NumberFormatException => // toinen hahmo println("i didn't understand.") // toinen koodi
Option-luokan hahmonsovitus Scalan Option-luokkaa voi käyttää myös hahmonsovituksella Optionin perii kaksi case-luokkaa: Some(x) ja None Esimerkki Option-hahmonsovituksesta (osittainen) def findit(a: Int): Option[Int] = { var r: Option[Int] = None data foreach { item => if (found(item, a)) r = Some(item) r findit(4) match { case Some(item) =>... item... case None =>...
Parien ja monikoiden hahmonsovitus Scalan parit esim. (1,2) ja monikot esim. (1,2,3) on myös toteutettu case classeilla tämä syntaksi muuntuu valmiin case-luokan konstruktorikutsuksi, esim. Tuple2[Int,Int](1,2) matchin hahmoissa voi siis käyttää niitä: case (0, 0) =>... case (k:int, v:string) =>... case (x, y, z) =>... niillä on lisäksi metodit _1, _2, _3 jne., joilla monikon osia voi käsitellä val t = (4,5) t._1 + t._2 9 ja parin voi hajottaa osiin muuttujia määritellessä: val (x,y) = (3,5)
Sisältö 1 Hahmonsovitus ja case class Scalassa 2 Scalan moniperintä: trait 3 Sisäluokat Javassa ja Scalassa 4 Enumeraatiot Javassa ja Scalassa 5 Lisää suunnittelumalleja
trait rajapintana Javan rajapintojen vastine Scalassa on trait mutta traitissa voi olla myös metodien toteutuksia traitin syntaksi on sama kuin classin trait T {...:n toteuttaminen/periminen: class C extends T class C extends T with T2 (toteuttaa rajapinnat T ja T2) trait U extends T trait U extends T with T2 Rajapintaesimerkki (osittainen) trait Movable { def move(dx: Int, dy: Int) trait Selectable { def select() def deselect() class MyButton extends Selectable {... class MyWindow extends Movable with Selectable {... class MovableButton extends MyButton with Movable {...
Mihin muuhun traitia käytetään? koska traitissa voi olla metodien toteutuksia, sillä voi tehdä yksinkertaista moniperintää erityisesti ns. mixinejä mixin on termi luokalle, jonka perimällä toinen luokka saa lisättyä itseensä tietyn ominaisuuden osa tai kaikki metodeista on valmiina mixinissä, osan voi joutua tekemään itse periessä yleensä lisättävä ominaisuus on jonkin verran irrallinen muun luokan toiminnasta esimerkki: Scalan Ordered on trait, jossa on valmiina mm. metodit < ja <=, jotka kutsuvat compare-metodia yleensä Scalan traiteilla toteutetaan joko Java-tyylisiä rajapintoja tai pieniä mixin-luokkia
Miten traitin moniperintä toimii? traitin perivä luokka voi käyttää traitin metodeja suoraan ja ylikirjoittaa toteutuksia override-avainsanalla (samoin kuin luokkia periessä) ajattelutapa: metodit kopioidaan traitista luokkaan jos luokka perii useamman traitin, joissa on sama metodi: class A extends B with C with D kutsuttava metodi valitaan traiteista oikealta vasemmalle traitin sisällä super valitsee seuraavan vaihtoehdoista ajattelutapa: with X lisää sen edellä olevaan luokkaan X:n koodin, kuin koodi olisi kopioitu X:stä joten with X with Y lisää ensin X:n ja sitten Y:n trait voi myös periä luokan jolloin traitin koodista voi käyttää luokan metodeja mutta traitin voi ottaa mukaan vain tämän luokan aliluokkiin
Lisää traiteista Scalassa traitin voi luokan perimisen lisäksi lisätä yksittäiseen olioon konstruktoria kutsuttaessa oikeastaan tässä tehdään sisäluokka, joka perii nimetyn luokan ja toteuttaa traitin syntaksi: new-lausekkeen loppuun with trait class C {... trait T { def f() = {... val c1 = new C(...) val c2 = new C(...) with T c2.f() vielä muutama trait-yksityiskohta: abstraktia metodia toteuttaessa override ei ole pakollinen traitin konstruktorilla ei voi olla argumentteja
Sisältö 1 Hahmonsovitus ja case class Scalassa 2 Scalan moniperintä: trait 3 Sisäluokat Javassa ja Scalassa 4 Enumeraatiot Javassa ja Scalassa 5 Lisää suunnittelumalleja
Sisäluokat Sisäluokat ovat toisten luokkien luokkamäärittelyn sisällä määriteltyjä luokkia. Sisäluokkia on käytännössä neljää tyyppiä jäsenluokat (member classes) staattiset sisäluokat paikalliset sisäluokat anonyymit sisäluokat Esimerkki sisäluokan käytöstä ovat mm. java APIn HashMaprakenne joka tallentaa avain-arvo-parin Map.Entry tyyppiseen olioon. 16:54 (ei-laajan kurssin kalvo)
Sisäluokat Staattiset sisäluokat (Static nested classes) Muuten kuin tavallisia luokkia, mutta pystyvät lukemaan luokan staattisia kenttiä ja suorittamaan sen staattisia metodeja Eivät liity mihinkään ulompaan olioon! Joissakin tilanteissa tämä on erittäin kätevää Jäsenluokat (inner classes) Jäsenluokan olio liittyy aina tiettyyn ulomman luokan olioon. (enclosing instance) Se voi kutsua kaikkia tämän olion metodeita ja lukea sekä asettaa sen kaikkia kenttiä. Ulommalla oliolla on vastaavat oikeudet sisempään olioon. Ulomalla oliolla voi olla mikä tahansa määrä sisempiä olioita 16:54 (ei-laajan kurssin kalvo)
Sisäluokat Paikalliset sisäluokat (local class) Metodin tms. lohkon sisällä esiteltyä nimettyä luokkaa kutsutaan paikalliseksi sisäluokaksi. Paikallisen sisäluokan tyyppi on näkyvissä vain esittelylohkonsa sisällä. Paikallinen sisäluokka näkee ulkoluokan kentät ja metodit, mutta myös sen metodin joka sisäluokan olion loi, vakiomuuttujat Tämä estää sisäluokkaa vaikuttamasta metodin muuttujiin, jolloin vältetään mahdolliset rinnakkaisuusongelmat Vastaavasti vakiomuuttujien arvot ovat varmasti selvillä Lisäksi tämä toimii myös kun metodin suoritus on jo päättynyt Usein metodin parametrista voi tehdä vakion final-määreellä sisäluokkaa ajatellen. 16:54 (ei-laajan kurssin kalvo)
Paikalliset sisäluokat ja ympäröivät muuttujat miksi Javassa paikallinen sisäluokka näkee ympäröivästä metodista vain vakiomuuttujat? sisäluokan oliohan voidaan tallettaa jonnekin ja sitä voidaan käyttää ympäröivän metodin paluun jälkeen käytännössä sisäluokka saa automaattisesti kopion niistä ympäröivän metodin muuttujista, joita se käyttää final-muuttujilla kopion tekeminen ei haittaa (paitsi kuluttaa hiukan muistia), mutta muuten muuttujan kopioituminen olisi sekavaa, joten Java kieltää sen tämä rajoitus tulee virtuaalikoneen kutsupinon toteutuksesta kutsupino on tavallinen pino: metodin palatessa osa siitä poistetaan esim. Schemessä kutsupino on (ellei kääntäjä optimoi) linkitetty lista kehyksiä, joten yksittäisiä kutsupinon kehyksiä voidaan pitää tallessa myös funktion paluun jälkeen Scala kiertää tämän ongelman siirtämällä alkuperäisen var-muuttujan uuteen olioon (ns. boxed): osoitin tähän on vakio, joten sen voi rauhassa kopioida sisäluokkaan
Sisäluokat Anonyymit sisäluokat (anonymous inner classes) Luokille voi rakentaa lennossa aliluokkia samantapaisella menetelmällä kuin tavallisestikin. Käytännössä anonyymi sisäluokka on paikallinen luokka jolle ei anneta erillistä nimeä. Vastaavasti voidaan kirjoittaa toteutus rajapinnalle Anonyymin sisäluokan toteutus kirjoitetaan välittömästi konstruktorikutsun perään. Toteutus on kuten tavallinen luokan toteutusosa. Anonyymin sisäluokan voi tehdä kaikkialla missä voi kutsua konstruktoria Comparable Comparable vertailija vertailija = = new new Comparable(){ Comparable(){ public public int int compareto(object compareto(object o){ o){...jotain...jotain koodia... koodia... ; ; huomaa huomaa puolipiste puolipiste 16:54 (ei-laajan kurssin kalvo)
Sisäluokat Anonyymit sisäluokat Graafisissa käyttöliittymissä käytetään tyypillisesti paljon anonyymejä sisäluokkia. Tyypillisesti nämä ovat tapahtumankuuntelijoita Pikkuasioiden hoitoon ei kannata käyttää omaa tiedostoa Koodi on editoitavissa siellä missä sitä tarvitaan Päästään tarvittaessa käsiksi ulkoluokkaan 16:54 (ei-laajan kurssin kalvo)
Sisäluokat Scalassa Scalan vastine sisäluokille: objecteja ja classeja voi laittaa sisäkkäin toimivat loogisesti (muuttujat ym. leksikaalisesti sidottuja) erikoisuus: class A { class B { tekee jokaiselle A-luokan oliolle oman B-luokan (sisempi class suoritetaan konstruktorissa) Scalassa sisäluokkia käytetään vähemmän kuin Javassa monikko (tuple) korvaa Map.Entry-tyyppiset sisäluokat metodin sisäinen funktio korvaa tapahtumankuuntelijat yms. luokat (paitsi jos käyttää Java-kirjastoja, jotka haluavat luokkia eivätkä Scala-funktioita) muidenkin funktioargumenttien ja anonyymien funktioiden tilalla käytettäisiin Javassa (esim. anonyymeja) luokkia: esim. Scalan sortby ottaa funktioargumentin
Sisältö 1 Hahmonsovitus ja case class Scalassa 2 Scalan moniperintä: trait 3 Sisäluokat Javassa ja Scalassa 4 Enumeraatiot Javassa ja Scalassa 5 Lisää suunnittelumalleja
Enumeroidut tyypit Enumeroitu tyyppi, enum on tyyppi, jonka kaikki (vakio)arvot ovat tiedossa jo tyyppiä määriteltäessä enum Paiva {MAANANTAI, TIISTAI, KESKIVIIKKO...jne... enum:in arvot ovat enumeroidun tyypin staattisia kenttiä Paiva tanaan = Paiva.TORSTAI; enumeroitu tyyppi on täysin tyyppiturvallinen Paiva-tyyppinen muuttuja ei voi saada mitään muita arvoja kuin listatut vakiot ja arvon null; Enum-määrittely on pitkälti samanlainen kuin tavallisen luokan...paitsi että käytetään avainsanaa enum...ja määrittelyn pitää alkaa vakioiden listalla...ja että enumit eivät voi käyttää perintää ja ovat aina final16:54 (ei-laajan kurssin kalvo)
Enumeroidut tyypit Lisää enumeista... Enum:it voivat täyttää rajapintoja ja ovat oletusarvoisesti jo Serializable sekä Comparable Enumit voivat sisältää kenttiä, metodeja, sisäluokkia ja jopa enumeja Enumeilla on aina seuraavat metodit: public static EnumType[] values() Palauttaa enum-vakiot määrittelyjärjestyksen mukaisessa taulukossa public static EnumType valueof(string name) palauttaa merkkijonon nimisen enum-vakion 16:54 (ei-laajan kurssin kalvo)
Enumeroidut tyypit Käytännössä aiemmin nähty lista oli joukko parametrittoman konstruktorin kutsuja enum Paiva {MAANANTAI, TIISTAI, KESKIVIIKKO...jne... Jokainen enumin arvo on viittaus olioon, jonka enumin alun vakiomäärittely on luonut. Jos enumille kirjoittaa konstruktorin, voi olioden luontia ohjata tarkemmin. kts. Seuraava kalvo 16:54 (ei-laajan kurssin kalvo)
public public enum enum Paiva Paiva { { MAANANTAI( Viikko MAANANTAI( Viikko alkaa ), alkaa ), TIISTAI( uh ), TIISTAI( uh ), KESKIVIIKKO( Puolivälissä ), KESKIVIIKKO( Puolivälissä ), TORSTAI( Enää TORSTAI( Enää vähän ), vähän ), PERJANTAI( Kohta PERJANTAI( Kohta on on viikonloppu ), viikonloppu ), LAUANTAI( Huh ), LAUANTAI( Huh ), SUNNUNTAI( Kohta SUNNUNTAI( Kohta alkaa alkaa työ ); työ ); private private String String viesti; viesti; Paiva(String Paiva(String viesti) viesti) { { this.viesti this.viesti = = viesti; viesti; public public String String tostring() tostring() { { return return this.viesti; this.viesti; Kutsuu konstruktoria System.out.println(Paiva.MAANANTAI.toString()); System.out.println(Paiva.MAANANTAI.toString()); 16:54 (ei-laajan kurssin kalvo)
Enumeroidut tyypit Vakio-olioiden toimintaa voi eriyttää toisistaan sisäluokkamäärittelyä muistuttavalla syntaksilla System.out.println( System.out.println( Paiva.MAANANTAI.onkoEka()); Paiva.MAANANTAI.onkoEka()); true true System.out.println( System.out.println( Paiva.TORSTAI.onkoEka()); Paiva.TORSTAI.onkoEka()); false false public enum Paiva { public enum Paiva { MAANANTAI( Viikko alkaa ){ MAANANTAI( Viikko alkaa ){ public boolean onkoeka() { public boolean onkoeka() { return true; return true;,, TIISTAI( uh ), TIISTAI( uh ), KESKIVIIKKO( Puolivälissä ), KESKIVIIKKO( Puolivälissä ), TORSTAI( Enää vähän ), TORSTAI( Enää vähän ), PERJANTAI( Kohta on viikonloppu ), PERJANTAI( Kohta on viikonloppu ), LAUANTAI( Huh ), LAUANTAI( Huh ), SUNNUNTAI( Kohta alkaa työ ); SUNNUNTAI( Kohta alkaa työ ); private String viesti; private String viesti; Paiva(String viesti) { Paiva(String viesti) { this.viesti = viesti; this.viesti = viesti; public String tostring() { public String tostring() { return this.viesti; return this.viesti; public boolean onkoeka() { public boolean onkoeka() { return false; return false; Korvaa (overrides) vastaavan metodin enumissa Maanantain osalta 16:54 (ei-laajan kurssin kalvo)
Enumeroidut tyypit Muuta: Enumeja voi verrata suoraan == operaattorilla ja käyttää switch-lauseissa Koska enumeja ei voi periä, niitä ei voi laajentaa muuttamatta alkuperäistä enumeroitua tyyppiä Paiva alkupaiva =... Paiva alkupaiva =... if (alkupaiva == Paiva.TIISTAI) {... if (alkupaiva == Paiva.TIISTAI) {... switch (alkupaiva) { switch (alkupaiva) { case MAANANTAI: case MAANANTAI: System.out.println( MA ); System.out.println( MA ); break; break; case TIISTAI : case TIISTAI :...... Este jatkokehitykselle (täytyy koskea olemassaolevaan luokkaan perinnän sijaan) 16:54 (ei-laajan kurssin kalvo)
Enumeraatiot Scalassa Esimerkki enumeraatioista case classeilla sealed abstract class Paiva case object Maanantai extends Paiva case object Tiistai extends Paiva... p match { case Maanantai =>...; case Tiistai =>...;... Scalassa ei ole Javan enumia sen sijaan voi käyttää argumentittomia case classeja case object on tehokkaampi kuin case class (enumeraatioarvon esittämiseen käytetään aina samaa oliota) avainsana sealed kertoo, että kaikki luokan perijät on määritelty samassa tiedostossa matchissa saa virheilmoituksen, jos unohtaa jonkin tapauksista toinen tapa on periä Scalan Enumeration-luokka silloin em. virheilmoitusta ei saa mutta Enumeration-luokassa on kokoelmarajapinnan metodeita (esim. foreach), joista voi olla apua
Sisältö 1 Hahmonsovitus ja case class Scalassa 2 Scalan moniperintä: trait 3 Sisäluokat Javassa ja Scalassa 4 Enumeraatiot Javassa ja Scalassa 5 Lisää suunnittelumalleja
Decorator Mahdollistaa lisäominaisuuksien lisäämisen olioon lennossa, muuttamatta alkuperäistä luokkaa. Perusidea on, että dekoraattori liittää johonkin alkuperäiseen toimintoon oman lisänsä ja pyytää sitten koristelemaansa oliota (oli se sitten dekoraattori tai varsinainen olio) suorittamaan alkuperäisen toiminnon. Lopulta varsinainenkin operaatio tulee tehtyä Koska sekä dekoraattorit että varsinainen koristeltava olio täyttävät saman rajapinnan, ovat dekoraattorit käyttävälle luokalle näkymättömiä. Reunustaja Värittäjä TodellinenOlio Käyttävä Luokka +operaatio() +omatoiminto() +operaatio() +omatoiminto() +operaatio() 10:03 (ei-laajan kurssin kalvo)
Decorator Edellisen esimerkin UML-kaavio Käyttävä Luokka TodellinenKomponentti +operaatio() <<interface>> Komponentti +operaatio( ) <<abstract>> Dekoraattori koristettava +operaatio() +omatoiminto() operaatio(){ this.omatoiminto(); koristettava.operaatio(); Koristelija Värittäjä Reunustaja 10:03 (ei-laajan kurssin kalvo)
Decorator ja trait eräs Scalan traitien käyttötarkoitus on juuri luokan koristelu ylimääräisillä ominaisuuksilla traiteilla siis voi tehdä Decorator-suunnittelumallin yksinkertaisemmin dekoraattoriluokasta tehdäänkin trait edellä ollutta koristettava.operaatio()-kutsua vastaisi traitin koodin sisällä tehtävä super.operaatio()
Strategy Oletetaan että on olemassa joukko erilaisia tapoja ratkaista jokin laskennallinen ongelma Strategy-mallissa kirjoitetaan ensin ratkaisulle yleinen toteutus jossa vaihtoehtoiset kohdat on ulkoistettu muiden luokkien tehtäväksi Vaihtoehtoisille kohdille on kirjoitetaan rajapinta jonka kautta yleinen toteutus käyttää ulkoistettuja osia. voidaan toteuttaa erilaisia ratkaisutapoja jotka toteuttavat k.o. rajapinnan kautta jonkin osan ratkaisua, mutta ei tarvitse kirjoittaa koko koodia Nyt ratkaisutapaa voidaan haluttaessa helposti muuttaa esim. tekstin jakaminen riveihin vaihtelee kielen mukaan esim. alkioiden vertailu keskenään järjestettäessä 10:03 (ei-laajan kurssin kalvo)
Strategy import java.util.*; public class StrategyExample{ public static void main(string[] args){ Arrays.sort(args, String.CASE_INSENSITIVE_ORDER); for (int i=0; i< args.length; i++) System.out.println(args[i]); /* Vaihdetaan järjestämisstrategiaksi toisen * merkin mukaan järjestäminen */ Arrays.sort(args, new Comparator(){ public int compare(object one, Object other){ return ((String)one).charAt(1)- ((String)other).charAt(1); public boolean equals(object other){ return this.equals(other); ); System.out.println(); for (int i=0; i< args.length; i++) System.out.println(args[i]); (ei-laajan kurssin kalvo) Tässä esimerkissä järjestämisalgoritmi on kirjoitettu Arraysluokkaan. Ulkoistettu osa algoritmia on vertailu, jonka koodari voi asettaa antamalla haluamansa Comparator-olion. Esimerkissä luodaan Comparator joka järjestää merkkijonot niiden toisen merkin mukaan. 10:03
Template Method Template method toimii muuten kuin Strategy, mutta puuttuvat osat algoritmia on kirjoitettu abstrakteiksi metodeiksi Metodit toteutetaan aliluokassa jonka jälkeen algoritmi on käyttökelpoinen 10:03 (ei-laajan kurssin kalvo)
Strategy ja Template Method funktioargumenteilla edelliset kaksi suunnittelumallia muistuttavat kovasti funktioargumenttien käyttöä abstraktioiden tekemisessä tosin molemmissa luokan avulla voidaan yhdistää useampi funktioargumentti yhteen kokonaisuuteen Strategy-idean voi toki yleistää esim. moduuleihin (useita vaihtoehtoisia moduuleja, joissa on sama rajapinta)