Föreläsning 5. När skall implementationsarv användas? The Open-Closed Principle (OCP) Liskov Substitution Principle (LSP)

Relevanta dokument
Föreläsning 5. När skall man använda implementationsarv? När skall man använda implementationsarv?

Föreläsning 5. När skall man använda implementationsarv?

Principles of subclasses. Objekt-orienterad programmering och design Alex Gerdes, 2018

Principles of subclasses Objekt-orienterad programmering och design (DIT953) Niklas Broberg, 2018

JUnit. Ska kompletteras med kodexempel på JUnit. DD2385 Programutvecklingsteknik Några bilder till föreläsning 12 21/5 2012

Grundläggande programmering, STS 1, VT Sven Sandberg. Föreläsning 14

Instuderingsuppgifter läsvecka 2

TDA550 Objektorienterad programvaruutveckling IT, forts. kurs Övning vecka 2

UML. Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2016

Static vs Dynamic binding Polymorfism. Objekt-orienterad programmering och design (DIT953) Niklas Broberg, 2018

Exempel på användning av arv: Geometriska figurer

Static vs Dynamic binding Polymorfism. Objekt-orienterad programmering och design Alex Gerdes, 2016

Generics och polymorfism. Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2016

Föreläsning 5. Föreläsning 5. Klasser och objekt. Klasser och objekt. Klasser och objekt

Generics och polymorfism. Objekt-orienterad programmering och design (DIT953) Niklas Broberg / Johannes Åman Pohjola, 2018

Föreläsning 13 Innehåll

Föreläsning 5. Föreläsning 5

Föreläsning 8 - del 2: Objektorienterad programmering - avancerat

Kursombud. Objektorienterad modellering och diskreta strukturer / design. Agile? Designprinciper EDAF10 EDA061. Lennart Andersson. Grupper för projekt

Outline. Objektorienterad Programmering (TDDC77) Signatur. Klassen calculator. Överlagring (overloading) Arv (inheritance) Ahmed Rezine

Objektorienterad Programmering (TDDC77)

2I1049 Föreläsning 5. Objektorientering. Objektorientering. Klasserna ordnas i en hierarki som motsvarar deras inbördes ordning

Ett objekt... Exempel: Om ni tittar er runt i föreläsningssalen ser in många olika fysiska föremål:

Föreläsning 4. Klasser och objekt

Föreläsning 8. Arv. Arv (forts) Arv och abstrakta klasser

Objektorientering - Arv och polymorfi. Eric Elfving Institutionen för datavetenskap

Idag. statiska metoder och variabler. private/public/protected. final, abstrakta klasser, gränssnitt, delegering. wrapper classes

Föreläsning 5-6 Innehåll. Exempel på program med objekt. Exempel: kvadratobjekt. Objekt. Skapa och använda objekt Skriva egna klasser

Arv: Fordonsexempel. Arv. Arv: fordonsexempel (forts) Arv: Ett exempel. En klassdefinition class A extends B {... }

F8 - Arv. ID1004 Objektorienterad programmering Fredrik Kilander

Föreläsning 5-6 Innehåll

Föreläsning 3 Arvsmekanismen Variabler och typer Typer och subtyper

Tentamen. 2D4135 vt 2004 Objektorienterad programmering, design och analys med Java Torsdagen den 3 juni 2004 kl

TDDC76 - Programmering och Datastrukturer

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 2 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Laboration 1: Figurer i hierarki

Outline. Objektorienterad Programmering (TDDC77) Åsidosättning. Signatur. Åsidosättning. Abstrakta klasser. Ahmed Rezine.

Målen med OOSU. Objektorienterad programmering. Objektorienterad programmering. Karlstads Universitet, Johan Öfverberg 1

Arv innebär att man skapar en ny klass (subklass) utifrån en redan existerande klass (superklass, basklass).

Subklasser och arv Inledning till grafik (JFrame och JPanel). Något om interface. Objektorienterad programvaruutveckling GU (DIT011) Subklasser

Outline. Objektorienterad Programmering (TDDC77) Att instansiera en klass. Objekt. Instansiering. Åtkomst. Abstrakt datatyp.

Svaret kan ges i Javakod (eller i UML-klassdiagram). public class A { B minb;... } public class B { <B:s många variabler och metoder> } Lösning:

Classes och Interfaces, Objects och References, Initialization

PROGRAMMERINGSTEKNIK TIN212

Arv. Fundamental objekt-orienterad teknik. arv i Java modifieraren protected Lägga till och modifiera metoder med hjälp av arv Klass hierarkier

Idag. Exempel, version 2. Exempel, version 3. Ett lite större exempel

Arv. Objektorienterad och komponentbaserad programmering

Klasshierarkier - repetition

Classes och Interfaces, Objects och References Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2016

"Är en"-relation. "Har en"-relation. Arv. Seminarium 2 Relevanta uppgifter. I exemplet Boll från förra föreläsningen gällde

Objektorienterad Programmering (TDDC77)

SI-pass 4. Johan Brook och Jesper Persson. 25 september Diskutera och svara på om påståendena nedan är äkta sanningar eller listiga lögner.

Subtyping, co- och contra-variance. Objekt-orienterad programmering och design Alex Gerdes, 2016

TDA550 Objektorienterad programvaruutveckling IT, forts. kurs Övning vecka 3

Lösningsförslag. 1 Lösningsförslag. Uppgift 1

Konstruktion av klasser med klasser

Repetition av viktiga begrepp inom objektorienterad programmering

Typhierarkier del 1 Gränssnitt, ärvning mellan gränssnitt, ärvning mellan klasser

OOP Objekt-orienterad programmering

TDDE10 TDDE11, 725G90. Objektorienterad programmering i Java, Föreläsning 3 Erik Nilsson, Institutionen för Datavetenskap, LiU

Föreläsning 7. Muterbara och icke-muterbara klasser Delegering Gränssnittet för en klass Metoden equals()

Tentamen. DD2385 Programutvecklingsteknik vt 2013 Onsdagen den 22 maj 2013 kl Hjälpmedel: penna, suddgummi, linjal

Tentamen. 2D4135 vt 2005 Objektorienterad programmering, design och analys med Java Lördagen den 28 maj 2005 kl

1 Repetition av viktiga begrepp inom objektorienterad programmering

DI-institutionen Sid 1 av 6 Hans-Edy Mårtensson Sten Sundin

Föreläsning 9: Arv och UML

Separation of Concern. Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2016 Johannes Åman Pohjola, 2017

Innehåll. dynamisk bindning. och programmering CRC) u Arv, polymorfi och

TDDE10 m.fl. Objektorienterad programmering i Java Föreläsning 6 Erik Nilsson, Institutionen för Datavetenskap, LiU

Design Patterns. Objekt-orienterad programmering och design Alex Gerdes, 2016

public class BoundedCounter {

Det är principer och idéer som är viktiga. Skriv så att du övertygar examinatorn om att du har förstått dessa även om detaljer kan vara felaktiga.

Static vs Dynamic binding Override vs Overload. Objekt-orienterad programmering och design Alex Gerdes och Sólrún Halla Einarsdóttir, 2018

Laboration 1 - Grunderna för OOP i Java

Mutability och State. Objekt-orienterad programmering och design (DIT953) Niklas Broberg / Johannes Åman Pohjola, 2018

Kopiering av objekt i Java

Separation of Concern. Objekt-orienterad programmering och design (DIT953) Niklas Broberg / Johannes Åman Pohjola, 2018

LÖSNINGSFÖRSLAG Programmeringsteknik För Ing. - Java, 5p

TENTAMEN: Objektorienterad programmering. Läs detta! Börja varje hel uppgift på ett nytt blad. Skriv inte i tesen.

Objektorienterad programmering (OOP) Föreläsning 15 & 16. Klasser för olika slags fordon. Klasser och objekt

Återanvändning. Två mekanismer. Nedärvning av egenskaper (inheritance) Objekt komposition

Objektorienterad Programmering (TDDC77)

Programmering för språkteknologer II, HT2011. Rum

Subtyping, co- och contra-variance. Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2016 Johannes Åman Pohjola, 2017

TENTAMEN PROGRAMMERING I JAVA, 5P SOMMARUNIVERSITETET

Java-syntax (arv) Exempel: public class Crow extends Bird {... } Jämför med Lab 1: public class FirstApp extends Frame {... }

Imperativ programmering. Föreläsning 4

Arv (Inheritance) Multipelt arv finns i verkligheten. Överskuggning, metodbindning. Läsanvisning: ! Arv! Object, instanceof! Relationer!

ID1004 Laboration 3, 5-6 November 2012

public och private Obs: private inte skyddar mot access från andra objekt i samma klass.

Introduktion till arv

Klasshierarkier. Klasser kan byggas på redan definierade klasser

Ärvning av implementation. Ärvning av implementation, inklusive abstrakta klasser Hur ska vi ärva? När ska vi ärva?

Outline. Objektorienterad Programmering (TDDC77) Laborationsserie del två. Vad händer under HT2. Introduktion HT2 UML.

Objektorienterad programmering Föreläsning 12. Copyright Mahmud Al Hakim

F9 - Polymorfism. ID1004 Objektorienterad programmering Fredrik Kilander

Begreppet subtyp/supertyp i Java. Mera om generik. Generik och arv. Generik och arv. Innehåll

TENTAMEN: Objektorienterad programutveckling, fk. Läs detta! Uppgifterna är inte ordnade efter svårighetsgrad.

Lösningar till Fiktiv Tentamen på kursen. 2D4135 Objektorienterad programmering, design och analys med Java vt2004. Teoridel

TDDD78, TDDE30, 729A Typhierarkier del 2 Vad krävs? Hur fungerar det?

Transkript:

Föreläsning 5 När skall implementationsarv användas? The Open-Closed Principle (OCP) Liskov Substitution Principle (LSP) När skall man använda implementationsarv? Implementationsarv är en konstruktion som kan användas för att få elegant kod - lättläst - tydlig - utbyggbar - återanvändbar Men implementationsarv är också en konstruktion som ofta missbrukas, vilket leder till kod som blir - svårbegriplig - oflexibel - svår att underhålla.

Motiveringar för att använda implementationsarv Motiveringar till att använda implementationsarv: Återanvändning av kod Klass B har viss kod (data och metoder) som är identisk med kod i klass A. Is-a relation Varje objekt av klassen B är också ett objekt av klassen A. Vilket betyder att mängden av alla objekt av klassen B är en delmängd av alla objekt i klassen A. ( En bil är ett fordon ) Det publika gränssnittet Det publika gränssnittet för klass B inkluderar det publika gränssnittet för klass A. Polymorfism Det finns ett behov av att kunna byta ut basklassen mot underklasser, dvs kunna tilldela ett objekt av klassen B till en variabel som deklareras av klassen A. Implementationsarv kontra återanvändning av kod En av de verkliga fördelarna med implementationssarv är återanvändning av kod. Men är återanvändning av kod i sig en tillräcklig anledning att använda implementationsarv? Betrakta nedanstående UML-diagram: Dog -name: String -birthdate: Date +getname(): String +getbirthdate(): Date Person -employer: Company +getemployer(): Company Möjligheten att ärva kod som annars skulle behöva dupliceras är en användbar egenskap hos objektorienterade språk, och därför skall man alltid överväga möjligheten att använda implementationsarv. Men enbart återanvändning av kod rättfärdigar inte implementationsarv. En person är ingen hund!!

Implemationsarv kontra is-a -relation Antag att vi skall utveckla ett system där vi behöver modellera geometriska rektanglar och kvadrater. Från matematiken vet vi att en kvadrat är en speciell typ av rektangel. Således verkar det självklart att använda implementationsarv. -width: int -height: int Rectangle +Rectangle(int,int) +getwidth(): int +getheight(): int +getarea(): int +getperimeter(): int +setsize(int, int): void All kod i Rectangle återanvänds. Subklassen Square behöver endast implementera konstruktorn! Eller? Square +Square(int) }//getarea Implemationsarv kontra is-a -relation }//Square public class Rectangle { private int width, height; public Rectangle(int width, int height) { this.width = width; this.height = height; }//constructor public int getwidth() { return width; }//getwidth public int getheight() { return height; }//getheight public int getarea() { return width * height; }//getarea public int getperimeter() { return 2 * (width + height); }//getperimeter public void setsize(int width, int height) { this.width = width; this.height = height; }//setsize }//Rectangle public class Square extends Rectangle { public Square(int size) { super(size, size); }//constructor }//Square

Implementationsarv kontra is-a -relation Det finns dock ett allvarligt problem med denna design. Eftersom klassen Square ärver alla publika metoder i superklassen Rectangle, kommer klassen Square att ha metoden public void setsize(int width, int height) som har två parametrar. Anropet square.setsize(5, 10); gör att höjden och bredden på kvadraten square blir olika!! En setsize-metod på en kvadrat skall endast ha en parameter. Vad kan vi göra åt detta? Implementationsarv kontra is-a -relation En möjlighet är att överskugga setsize-metoden, t.ex kan vi i klassen Square ge metoden @Override public void setsize(int width, int height) { this.width = width; this.height = width; }//setsize Denna ändring garanterar att höjden och bredden av en kvadrat är lika stora. Men lösningen är dålig: åtkomstmodifierarna för instansvariablerna width och height i klassen Rectangle måste ändras från private till protected det är förvirrande att ange storleken på en kvadrat med två parametrar om en klient tror att en variabel r refererar till ett objekt av typen Rectangle, men i verkligheten refererar till ett objekt av typen Square ändra inte anropet r.setsize(5, 10); höjden av objektet.

The Open-Closed Principle (OCP) Att vi behöver ändra åtkomstmodifierarna för instansvariablerna i klassen Rectangle, när vi utvecklar subklassen Square innebär att vi bryter mot The Open-Closed Principle: Software modules should be both open for extension and closed for modification. (Bertrand Meyer) Superklassen Rectangle är inte sluten för förändringar när vi utvidgar med subklassen Square. Software modules should be both open for extensio for modification. (Bertrand Meyer) Implementationsarv kontra is-a -relation Att slippa ange storleken på en kvadrat med både bredden och höjden kan vi lösa genom att överlagra setsize-metoden i Square: public void setsize(int size) { this.width = size; this.height = size; }//setsize Men denna åtgärd löser inte grundproblemet med vår design.

The Principle of Least Astonishment Det är fortfarande möjligt att skriva: Rectangle r;... r = new Square(8);... r.setsize(5, 10);... Om användaren tror att r är av typen Rectangle vid anropet av r.setsize(5, 10); förväntar sig användaren att r har höjden 10 efter anropet. Inte att höjden är 5!! Om en klient tror att den har en referens till ett objekt av typen A, men i verkligheten har en referens till ett objekt som är av subtypen B, skall det inte bli några överraskningar när klienten sänder meddelanden till objektet. Liskov Substitution Principle (LSP) If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behaviour of P is unchanged when o1 is substituted for o2 then S is a subtype of T. (Barbara Liskov) LSP säger att det är acceptabelt att göra klassen S till en subklass av klassen T, om och endast om det för varje publik metod som finns både i T och S gäller att S's metod som indata godtar alla värden som T's metod godtar samt att S gör alla bearbetningar på denna indata som T gör (S får göra fler bearbetningar än T). Om en subtyp S till typen T uppfyller Liskov Substitution Principle säger vi att S är en äkta subtyp till T. I Java är det alltså möjligt att införa subtyper som inte äkta subtyper, vilket är en potentiell källa till buggar.

Liskov Substitution Principle (LSP) Metoden setsize(int width, int height) i klassen Rectangle har beteendet att ändra storleken på höjden oberoende av storleken på bredden. Metoden setsize(int width, int height) i klassen Square kan omöjligt ha detta beteende och fortfarande ha kvar egenskapen att vara en kvadrat. Från LSP följer således att en kvadrat INTE är en rektangel och att klassen Square därför inte skall implementeras som en subklass till klassen Rectangle. Har då matematikerna fel som säger att en kvadrat ÄR en rektangel? Nej, matematiskt betraktas alla geometriska figurer som icke-muterbara, d.v.s. när man matematiskt ändrar storleken på en rektangel skapas en helt ny rektangel. Våra klasser Rectangle och Square är muterbara, så vad LSP egentligen säger är att en muterbar kvadrat INTE är en muterbar rektangel. Implementationsarv kontra is-a-relation Hur är då en lämplig relation mellan en muterbar kvadrat och en muterbar rektangel? Ett sätt är att införa en tredje klass, som en gemensam superklass, och låta denna klass innehålla de gemensamma delarna. Rectangle {abstract} ImmutableRectangle MutableRectangle +setwidth(int): void +setheight(int): void MutableSquare +setsize(int): void ImmutableSquare

Behövs klassen Square? Innan man inför klassen Square skall man naturligtvis fråga sig om denna klass verkligen behövs. Vad tillför klassen Square som inte klassen Rectangle tillhandahåller? Är det nödvändigt att skilja mellan kvadrater och rektanglar? - Räcker det att klassen Rectangle tillhandahåller en metod issquare() för att avgöra om höjden och bredden är lika? is-a -relation och gemensamt gränssnitt Ett klassiskt exempel för att introducera begreppet arv är klasserna Person och Student. Uppenbarligen gäller att en student är en person. Ett objekt av klassen Student har alla egenskaper och alla beteenden som ett objekt av klassen Person har, såsom namn, födelsedag och adress. Klassen Student har dessutom ytterligare egenskaper såsom utbildningsnivå, linje, inskrivningsår och avklarade poäng. Man kan alltså här argumentera för att klassen Student skall vara en subklass till klassen Person. Men är detta en bra lösning? Person -name -address -birthday Student -grads -StudyProgramme -year -points

Implementation av klasserna Person och Student import java.util.date; public class Person { private String name; mport private java.util.date; String address; private Date birthdate; public class Person { public Person(String name, String address, Date birthdate) { private this.name String = name; import java.util.date; this.address = address; private String public class Student extends Person { this.birthdate = birthdate; private Grade grades; private }//constructor Date birthdate;... public getname() { public Person(String name, String address, public Student(Name Date birthdate) name, Address { address, return name; Date birthdate, Grade grades) { this.name }//getname = name; super(name, address, birthdate); public String getaddress() { import java.util.date; this.address return address; = address; this.grades = grades; }//constructor public class Student ex this.birthdate }//getaddress = birthdate; public Grade getgrades() { public Date getbirthdate() { private Grade grade return grades; }//constructor return birthdate; }//getgrades... }//getbirthdate public String getname() {... //more methods }//Student public Student(Nam return }//Person name; Date }//getname super(name, addr public String getaddress() { this.grades = grad is-a -relation och gemensamt gränssnitt Anta att klasserna Person och Student skall användas i ett system för att lagra uppgifter om studenter på ett universitet. Antag också att man i samma system vill lagra uppgifter om de anställda på universitetet. Således behövs ytterligare en klass Employee. Av samma skäl som gällde för en student kan man argumentera för att Employee skall vara en subklass till klassen Person. Student -grades Person -name -address -birthday Employee -salary

is-a -relation och gemensamt gränssnitt Vad händer om en student blir klar med sin examen och får en anställning på universitetet eller om en student under sin studietid får en deltidsanställning som övningsledare? Personen kommer att vara representerad av två objekt, ett Student-objekt och ett Employee-objekt. Det gemensamma datat som finns i Person-klassen dupliceras! Ändras denna data måste detta åtgärdas i båda objekten, annars leder det till att datat blir osynkroniserad! :Student name = "Sven Stare" address = "Storgatan 19" birthday ="24 december" grades = {master, phd} :Employee name = "Sven Stare" address = "Storgatan 19" birthday ="24 december" salary = 345678 Delegering alternativ till implementationsarv En bättre lösning än implementationsarv är i detta fall att se student och anställd som en roll som en person spelar. En sådan roll är ofta temporär, en person är inte alltid student eller anställd. På grund av den temporära karaktären hos rollerna (i vårt fall student och anställd) och den mera permanenta karaktären hos personen, är det inte lämpligt att låta klasserna Student och Employee vara subklasser till klassen Person. En bättre design är att använda delegering och låta klasserna Student och Employee ha ett objekt av klassen Person, till vilket de refererar. Delegering innebär att ett objekt utåt erbjuder en viss tjänst, men internt överlåter ansvaret för att utföra tjänsten till ett annat objekt. Student -grades -person Employee -salary -person Person -name -address

Delegering alternativ till implementationsarv I vårt exempel innebär delegering att vi får exakt ett objekt av klassen Person för varje verklig person, som är oberoende av alla olika roller som spelas av denna person. :Student person grades = {master, phd} :Employee person salary = 345678 :Person name = "John Doe" address = "Storgatan 19" birthday ="24 december" Den nya implementationen av klassen Student import java.util.date; public class Student { private Person person; private Grade grades;... public Student(Person person, Grade grades) { this.person = person; this.grades = grades; }//constructor public String getname() { return person.getname(); }//getname return grades; }//getgrades... }//Student public String getaddress() { return person.getaddress(); }//getaddress public Date getbirthdate() { return person.getbirthdate(); }//getbirthdate public Grade getgrades() { return grades; }//getgrades... }//Student

Slutsatser Ett likadant gränssnitt är inte tillräckligt för ett elegant arvsförhållande, ett konsistent beteende är också nödvändigt. Subklasser skall lägga till nya beteenden, inte ändra redan befintliga beteenden. En klass A som är identisk med en klass B, förutom att den har extra restriktioner på sitt tillstånd, skall inte vara en subklass till B. Gör så många klasser som möjligt icke-muterbara. Överväg alltid om en subklass som inte tillför något eget unikt beteende verkligen behövs. Använd aldrig arv utan att beakta Liskov Substitution Principle. Subklasser som införs skall introducera äkta subtyper. Implementationsarv = stark koppling Ett problem med implementationsarv är den hårda kopplingen som subklasser har med sina superklasser. Antag att vi har en klass SimpleRectangle enligt bredvidstående klassdiagrammet. Säg nu att vi har behov av en nya klass som har samma beteenden som klassen SimpleRectangle förutom att klassen måste hålla reda på hur många gånger bredden av rektangeln förändras. SimpleRectangle -width: int -height: int +getwidth(): int +getheight(): int +setwidth(int): void +setheight(int): void +setsize(int, int): void Här verkar implementationsarv lämpligt.

Implementationsarv = stark koppling Vi skapar en klassen, ChangeMeasuredRectangle, som är en subklass till SimpleRectangle. Vi inför en instansvariabel nrofchange i ChangeMeasuredRectangle för att hålla reda på hur många gånger bredden av rektangeln förändras. Vidare överskuggar vi metoderna setwidth och setsize, på så sätt att dessa metoder kontrollerar om bredden på rektangeln förändras och i så fall räknar upp instansvariabeln nrofchange. Metoderna setwidth och setsize i klassen ChangeMeasuredRectangle utökar alltså beteendet hos motsvarande metoder i klassen SimpleRectangle. SimpleRectangle -width: int -height: int +getwidth(): int +getheight(): int +setwidth(int): void +setheight(int): void +setsize(int, int): void ChangeMeasuredRectangle -nrofchange: int +getwidthchanges(): int +setwidth(int): void +setsize(int, int): void nrofchange++; Koden Eller? för ChangeMeasuredRectangle super.setsize(width, }//setsize public class ChangeMeasuredRectangle extends SimpleRectangle { }//ChangeMeasuredRecta private int nrofchange = 0; public ChangeMeasuredRectangle (int width, int height) { super(width, height); }//konstruktor public int getwidthchanges() { return nrofchange; }//getwidthchanges En snygg implementation! Eller? @Override public void setwidth(int width) { if (width!= getwidth()) { nrofchange++; super.setwidth(width); } }//setwidth @Override public void setsize(int width, int height) { if (width!= getwidth()) nrofchange++; super.setsize(width, height); }//setsize }//ChangeMeasuredRectangle

Ett litet testprogram Vi skriver ett litet testprogram och ser vad resultatet blir. public static void main(string[] args) { ChangeMeasuredRectangle r = new ChangeMeasuredRectangle(5, 2); r.setwidth(4); public r.setsize(6, static 7); void main(string[] args) { r.setwidth(8); ChangeMeasuredRectangle r.setsize(12, 9); r = new ChangeMeasur System.out.println("Antal ändringar: " + r.getwidthchanges()); r.setwidth(4); }//main r.setsize(6, 7); Testprogrammet ger utskriften: r.setwidth(8); Antal ändringar: 6 r.setsize(12, 9); Men vi har ju bara ändrat vidden på rektangeln 4 gånger!! System.out.println("Antal ändringar: " + r.getwidthc Vad är galet? För att ta reda på detta måste vi veta hur klassen }//main SimpleRectangle är implementerad. Koden för SimpleRectangle setwidth(width); setheight(height); }//setsize }//SimpleRectangle public class SimpleRectangle { private int width, height; public SimpleRectangle(int width, int height) { this.width = width; this.height = height; } public int getwidth() { return width; }//getwidth public int getheight() { return height; }//getheight public void setwidth(int width) { this.width = width; }//setwidth public void setheight(int height) { this.height = height; }//setheight public void setsize(int width, int height) { setwidth(width); setheight(height); }//setsize }//SimpleRectangle

Förklaring till felet Metoden setsize i klassen SimpleRectangle har följande utseende: public void setsize(int width, int height) { setwidth(width); setheight(height); }//setsize public void setsize(int width, int height) { Detta setwidth(width); innebär att vår implementation av metoden setsize i klassen ChangeMeasuredRectangle är felaktig! setheight(height); }//setsize @Override public void setsize(int width, int height) { if (width!= getwidth()) nrofchange++; super.setsize(width, height); }//setsize Varför är metoden felaktig?? @Override public void setsize(int width, int height) { Förklaring till felet Antag att metoden setsize i ChangeMeasuredRectangle anropas med ett värde på parametern width som innebär att bredden på rektangeln förändrats. Då inträffar följande: Metoden setsize i ChangeMeasuredRectangle ökar instansvariabeln nrofchange med 1 för att markera att bredden har förändrats. Sedan anropas metoden setsize i SimpleRectangle. Metoden setsize i SimpleRectangle anropar i sin tur metoden setwidth. Eftersom objektet som anropas har typen ChangeMeasuredRectangle kommer, på grund av dynamisk bindning, metoden setwidth i klassen ChangeMeasuredRectangle att anropas. Metoden setwidth i klassen ChangeMeasuredRectangle konstaterar att bredden har förändrats och ökar instansvariabeln nrofchange med 1. Förändringen räknas alltså två gånger!!

Korrekt implementering För den givna implementationen av klassen SimpleRectangle, skall en korrekt implementation av ChangeMeasuredRectangle inte överskygga metoden setsize. SimpleRectangle -width: int -height: int +getwidth(): int +getheight(): int +setwidth(int): void +setheight(int): void +setsize(int, int): void ChangeMeasuredRectangle -nrofchange: int +getwidthchanges(): int +setwidth(int): void Överskygga endast metoden setwidth Implementationsarv = stark koppling Om metoden setsize i klassen SimpleRectangle istället haft följande implementation: public void setsize(int width, int height) { this.width = width; this.height = height; }//setsize hade dock vår första implementation av klassen ChangeMeasuredRectangle varit korrekt! The yo-yo problem: När programutvecklarna måste hoppa fram och tillbaka bland klasserna i arvshierarkin och läsa koden för att förstå hur en förändring av implementationen i en superklass påverkar tillhörande subklasser.

Delegering ett alternativ till implementationsarv För att kunna göra ett korrekt implementationsarv måste utvecklaren som skriver subklassen känna till implementationsdetaljer i superklassen. Ofta är delegering en bättre lösning än implementationsarv: ger en svagare koppling måste användas då implementationen är okänd. ChangeMeasuredRectangle -rect: SimpleRectangle -nrofchange: int +getwidth(): int +getheight(): int +setwidth(int): void +setheight(int): void +setsize(int, int): void +getwidthchanges(): int SimpleRectangle -width: int -height: int +getwidth(): int +getheight(): int +setwidth(int): void +setheight(int): void +setsize(int, int): void if (width!= getwidt nrofchange++; Delegering ett alternativ till implementationsarv rect.setsize(width, h }//setsize }//ChangeMeasuredRecta public class ChangeMeasuredRectangle { private SimpleRectangle rect; private int nrofchange = 0; public ChangeMeasuredRectangle(int width, int height) { rect = new SimpleRectangle(width, height); }//constructor public int getwidth() { return rect.getwidth(); }//getwidth public int getheight() { return rect.getheight(); }//getheight public int getwidthchanges() { return nrofchange; }//getwidthchanges public void setwidth(int width) { if (width!= getwidth()) nrofchange++; rect.setwidth(width); }//setwidth public void setheight(int height) { rect.setheight(height); }//setheight public void setsize(int width, int height) { if (width!= getwidth()) nrofchange++; rect.setsize(width, height); }//setsize }//ChangeMeasuredRectangle

Designa för förändringar I en mer förutseende design hade klassen SimpleRectangle implementerat ett gränssnitt Rectangle. <<interface>> Rectangle +getwidth(): int +getheight(): int +setwidth(int): void +setheight(int): void +setsize(int, int): void ChangeMeasuredRectangle SimpleRectangle -nrofchange: int -rect: SimpleRectangle +getwidthchanges(): int You can choose your friends, but you are stuck w Implementationsarv kontra delegering Implementationsarv och delegering är olika två tekniker för att åstadkomma återanvändning av kod i enlighet med The Open- Closed Principle. Båda teknikerna har sitt berättingande och det finns situationer där den ena tekniken är att föredra framför den andra. Finns tvekan om vilken teknik som skall användas, skall man använda delegering! You can choose your friends, but you are stuck with your family.

Refactorering: Exempel - rita polygon Vi skall i detta exempel utgå från ett program med mycket bristfällig design och stegvis förbättra designen (refactoring). Programet är ett enkelt ritprogram för att rita ut polygoner som antingen är kvadrater, retanglar eller trianglar enligt figuren nedan. Vi kommer endast att beakta den del i programmet som gör själva ritandet av polygonen. } else if (currentpolygon.equals("rectangle")) Rita g.drawrect(currentcenter.x polygon: Ursprunglig design -20, currentcenter.y -10, 40, 20); } public class DrawPolygons { }//paint private ArrayList<String> polygonnames; //DrawPolygons private ArrayList<Point> centerpoints;... public void paint(graphics g) { for (int i = 0; i < polygonnames.size(); i++) { String currentpolygon = polygonnames.get(i); Point currentcenter = centerpoint.get(i); if (currentpolygon.equals("square")) g.drawrect(currentcenter.x -10, currentcenter.y -10, 20, 20); else if (currentpolygon.equals("triangle")) { g.drawline(currentcenter.x, currentcenter.y-10, currentcenter.x-10, currentcenter.y+10); g.drawline(currentcenter.x-10, currentcenter.y+10, currentcenter.x+10, currentcenter.y+10); g.drawline(currentcenter.x+10, currentcenter.y+10, currentcenter.x, currentcenter.y-10); } else if (currentpolygon.equals("rectangle")) g.drawrect(currentcenter.x -20, currentcenter.y -10, 40, 20); } }//paint }//DrawPolygons

Rita polygon: Ursprunglig design Datat som beskriver ett polygon finns på två ställen: -typen av polygon finns i listan polygonname i form av en String -centrumpunkten för polygonet finns i listan centerpoint som en Point Detta innebär att listorna lätt blir osynkroniserade. Tas ett element bort ur en lista måste tillhörande element tas bort i den andra listan. Metoden paint innehåller alla kod för att rita ut de olika polygonen. Om man vill införa nya typer av polygon (ovaler, cirklar eller hexagoner) måste implementationen av paint förändras. Designen följer definitivt inte Open-Closed principen! }//getname public Point getcenter() { Drawing polygon: Design 2 return center; }//getpoint }//PolygonData Sammanför datan som beskriver ett polygon till en egen klass. import java.awt.point; Refactoring: Extrahera klass public class PolygonData { private String name; private Point center; public PolygonData(String name, Point center) { this.name = name; this.center = center; }//constructor public String getname() { return name; }//getname public Point getcenter() { return center; }//getpoint }//PolygonData

Drawing polygon: Design 2 public class DrawPolygons { private ArrayList<PolygonData> polygons;... public void paint(graphics g) { Refactoring: Extrahera klass ublic class DrawPolygons { for (int i = 0; i < polygons.size(); i++) { Refactoring: private String ArrayList<PolygonData> currentpolygon = polygons.get(i).getname(); polygons; Point currentcenter = polygons.get(i). getcenter();... if (currentpolygon.equals("square")) public void g.drawrect(currentcenter.x paint(graphics -10, g) currentcenter.y { -10, 20, 20); else if (currentpolygon.equals("triangle")) { for (int g.drawline(currentcenter.x, i = 0; i < polygons.size(); currentcenter.y-10, i++) currentcenter.x-10, { currentcenter.y+10); String g.drawline(currentcenter.x-10, currentpolygon = polygons.get(i).getname(); currentcenter.y+10, currentcenter.x+10, currentcenter.y+10); Point g.drawline(currentcenter.x+10, currentcenter = polygons.get(i). currentcenter.y+10, getcenter(); currentcenter.x, currentcenter.y-10); if (currentpolygon.equals("square")) } else if (currentpolygon.equals("rectangle")) g.drawrect(currentcenter.x -20, currentcenter.y -10, currentcenter.y -10, 40, 20); -10, 20, 20); } else if (currentpolygon.equals("triangle")) { }//paint }//DrawPolygons g.drawline(currentcenter.x, currentcenter.y-10, currentcenter.x-1 g.drawline(currentcenter.x-10, currentcenter.y+10, currentcenter.x+10, currentcenter.y+10); Rita polygon: Design 2 Det vi har åstadkommit är att datat för ett polygon är samla på ett ställe och vi slipper därmed problemet att synkronisera två listor. I övrigt kvarstår bristerna från design 1. Designen följer definitivt inte Open-Closed principen! Information Expert: Den som har tillgång till nödvändig information för att lösa en uppgift är bäst lämpa att lösa uppgiften. Nu har klass PolygonData all data som berör ett polygon, varför denna klass är mest lämpad för att rita ut polygonen. Låt oss göra detta. Samtidigt döper vi om klassen PolygonData till Polygon

Rita polygon: Design 3 import java.awt.point; import java.awt.graphics; public class Polygon { private String name; import private java.awt.point; center; public Polygon(String name, Point center) { import java.awt.graphics; this.name = name; public class this.center Polygon = center; { }//constructor private public String getname() name; { return name; private Point center; }//getname public public Polygon(String Point getcenter() { name, Point center) { return center; this.name = name; }//getpoint this.center = center; }//constructor public String getname() { return name; }//getname public Point getcenter() { return center; }//getpoint public void draw(graphics g) { if (name.equals("square")) g.drawrect(center.x -10, center.y -10, 20, else if (name.equals("triangle")) { g.drawline(center.x, center.y-10, center.xpublic void draw(graphics g) { if (name.equals("square")) g.drawrect(center.x -10, center.y -10, 20, 20); else if (name.equals("triangle")) { g.drawline(center.x, center.y-10, center.x-10, center.y+10); g.drawline(center.x-10, center.y+10, center.x+10, center.y+10); g.drawline(center.x+10, center.y+10, center.x, center.y-10); } else if (name.equals("rectangle")) g.drawrect(center.x -20, center.y -10, 40, 20); }//draw }//Polygon Refactoring: Döp om klass Refactoring: Flytta metod Refactoring: Döp om Refactoring: Flytta Rita polygon: Design 3 public class DrawPolygons { private ArrayList<Polygon> polygons = new ArrayList<Polygon>();... public void paint(graphics g) { for (Polygon poly : polygons) { poly.draw(g); } }//paint... }//DrawPolygons Stödjer Open-Closed Principen

Rita polygon: Design 4 import java.awt.point; import java.awt.graphics; public class Polygon { private String name; port java.awt.point; private Point center; port java.awt.graphics; blic class this.name Polygon = name; { this.center = center; private }//constructor String name; private public Point String center; getname() { public Polygon(String name, Point center) { return name; Refactoring: Extrahera metoder Refactoring: Extrahera metod private void drawsquare(graphics g, Point center) { g.drawrect(center.x -10, center.y -10, 20, 20); } private void drawtriangle(graphics g, Point center) { g.drawline(center.x, center.y-10, center.x-10, center.y+10); public }//getname Polygon(String name, Point g.drawline(center.x-10, center) { center.y+10, center.x+10, center.y+10); this.name public Point = getcenter() name; { g.drawline(center.x+10, center.y+10, center.x, center.y-10); return center; } this.center = center; private void drawsquare(graphics g, Point }//getpoint private void drawrectangle(graphics g, Point center) { }//constructor public void draw(graphics g) { g.drawrect(center.x g.drawrect(center.x -20, center.y -10, 40, 20); -10, center.y -10, 20 if (name.equals("square")) } } public String drawsquare(g); getname() { }//Polygon else if (name.equals("triangle")) private void drawtriangle(graphics g, Poin return drawtriangle(g); name; g.drawline(center.x, center.y-10, center. }//getname else if (name.equals("rectangle")) drawrectangle(g); g.drawline(center.x-10, center.y+10, cen public }//draw Point getcenter() { g.drawline(center.x+10, center.y+10, ce return center; } }//getpoint private void drawrectangle(graphics g, Po Rita polygon: Design 4 Refactoring: Ersätt selektering med polymo Vi har nu åstadkommit att metoden paint följer Open-Closed principen. Men vi har flytta problemet till metoden draw i klassen Polygon. Denna metod bryter definitivt mot OCP! Om vi tänker efter en stund inser vi att ett objekt av klassen Polygon håller reda på vilken typ av polygon det är för att draw-metoden skall kunna särskilja denna typ från andra typer av polygon. De olika typerna av polygon är naturligtvis polygon, men de är ju också olika varandra. Alla är specialiserade polygon. De olika typerna av polygon är subtyper till typen Polygon. Refactoring: Ersätt selektering med polymorfism

Rita polygon: Design 5 Polygon {abstract} +draw(graphics): void Square +draw(graphics): void Triangle +draw(graphics): void Rectangle +draw(graphics): void Refactoring: Ersätt selektering med polymorfism Rita polygon: Design 5 import java.awt.point; import java.awt.graphics; public abstract class Polygon { private Point center; public Polygon(Point center) { this.center = center; }//constructor public Point getcenter() { return center; }//getcenter public abstract void draw(graphics g); }//Polygon @Override public void draw(graphics g) { g.drawrect(getcenter().x -10, getcenter( }//draw }//Square import java.awt.point; import java.awt.graphics; public class Square extends Polygon { public Square(Point center) { super(center); }//constructor @Override public void draw(graphics g) { g.drawrect(getcenter().x -10, getcenter().y -10, 20, 20); }//draw }//Square

Rita polygon: Design 5 import java.awt.point; import java.awt.graphics; public class Triangle extends Polygon { port public java.awt.point; Triangle(Point center) { super(center); port }//constructor java.awt.graphics; blic class @Override Triangle extends Polygon { public void draw(graphics g) { public g.drawline(getcenter().x, Triangle(Point center) getcenter().y-10, { getcenter().x-10, getcenter().y+10); super(center); g.drawline(getcenter().x-10, getcenter().y+10, getcenter().x +10, getcenter().y+10); g.drawline(getcenter().x +10, getcenter().y+10, getcenter().x, getcenter().y-10); }//constructor }//draw }//Triangle @Override import java.awt.point; import java.awt.graphics; public void draw(graphics public class Rectangle g) { extends Polygon { g.drawline(getcenter().x, public Rectangle(Point getcenter().y-10, center) { getcenter().x-10, getcente super(center); g.drawline(getcenter().x-10, getcenter().y+10, getcenter().x +10, getc }//constructor g.drawline(getcenter().x @Override +10, getcenter().y+10, getcenter().x, getcen }//draw public void draw(graphics g) { g.drawrect(getcenter().x -20, getcenter().y -10, 40, 20); Triangle }//draw import java.awt.point; }//Rectangle import java.awt.graphics; public class Rectangle extends Polygon { public Rectangle(Point center) { Rita polygon: Design 5 Vi har åstadkommet en design som uppfyller Open-Closed principen. Den existerande koden behöver inte ändras när ny funktionalitet läggs till, t.ex. som att kunna rita ovaler, cirklar och hexagoner. Vi har gått från en rigid centraliserad design, där all data och funktionalitet var samlad på ett ställe, till en objektorienterad design där data och funktionalitet har distribuerats till ändamålsenliga klasser, och där arv och polymorfism används för att skapa en tydlig, flexibel och utbyggbar struktur.

Rita polygon Design 1: Ursprunglig design DrawPolygons +paint(graphics): void * * String Point Design 2: String Point 1 1 PolygonData * DrawPolygons +paint(graphics): void Design 3 & 4: String Point 1 1 Polygon +draw(graphics): void * DrawPolygons +paint(graphics): void Design 5: Slutlig design Point 1 Polygon {abstract} +draw(graphics): void * DrawPolygons +paint(graphics): void Square +draw(graphics): void Triangle +draw(graphics): void Rectangle +draw(graphics): void