Prøver lige at forklare det på en anden måde:
Der er ingen fordele så at sige for de dækker over to forskellige ting. Vil dog meget hellere mangle abstrakte klasser end interfaces.
Vi skal lave et program der kan fortælle tiden baseret på ure. Vi tænker at en ur har to visere, en til timer og en til minutter. Så der er noget fællestræk vi kan smide i en abstrakt klasse i stedet. Alt underklasserne skal gøre er at fortælle os hvor viserne er:
- public abstract class Watch {
-
- //Returner vinklen som viserne peger på
- public abstract double getHourHand();
- public abstract double getMinuteHand();
-
- //Dette er den vi virkelig ønsker og den er implementeret her
- //TimeFormat er en eller anden måde at repræsentere tid på det er den
- // slags vi returnerer. Men det er irrelevant for eksemplet
- public TimeFormat getTime() {
- double hourHand = getHourHand();
- double minuteHand = getMinuteHand();
- //Omregn vinklerne til timer og minutter
- //Konstruer et TimeFormat baseret på det
- //Returner det timeformat
- }
- }
Succes. Vi har sparet alle fremtidige implementører af urer besværet med at skulle omregne til tid. De kan nu nemt lave deres eget:
- public class CasioWatch extends Watch {
- @Override
- public double getHourHand() {
- //Det kan være de har en hardware enhed de kan læse fra der måler rotationen på et tandhjul
- //eller noget. Men alt de skal gøre her er at finde vinklen.
- }
-
- @Override
- public double getMinuteHand() {
- //Samme her
- }
- }
På samme måde kan Rolex lave deres. Siden de alle arver fra Watch har de jo også getTime. Vores system kender kun til superklassen Watch. Så vores system ved kun der er nogen Watch'es, og den kan kalde getTime på dem. Men hov, hvad nu hvis vi har brug for et digital ur? Vores system kender kun til Watch så vi er tvunget til at bruge en halvfærdig implementation der arbejder med visere, i stedet for 'segmenter' som et digital ur bruger. Men det kan vi stadig gøre ved lidt grimt kodning:
- public class DigitalWatch extends Watch {
- //Vi er nødt til at implementere getHourHand og getMinuteHand (siden denne klasse ikke er abstrakt)
- //Selvom det ikke giver mening for et DigitalWatch at have visere, fordi vores system
- //Håndterer kun Watch'es
-
- @Override
- public double getHourHand() {
- //Meget vigtig! Det er en vinkel der returneres her ikke et time antal.
- //Hvilken vinkel giver det mening at returnerne? Det ved jeg ikke hvad en analog
- //ur ville sige? Puhaa det bliver noget rod.
-
- //Men hey lad os bruge vores getTime som vi overskrive om lidt til det
-
- //Kald getTime
- //Udtræk timer og omregn til en vinkel
- //Returner den vinkel
- }
-
- @Override
- public double getMinuteHand() {
- //Ligesom i getHourHand
- }
-
- @Override
- public TimeFormat getTime() {
- //Find timerne baseret på på oplyste segmenter i tallene eller noget
- //Konsturer TimeFormat
- //Returner det
- }
- }
Det bliv da noget værre rod. Vi skulle implementere viserne siden vi ikke kan have abstrakte metoder i en ikke-abstrakt klasse. I stedet for at de 2 metoder gjor det simplere skulle vi nu udover dem også selv implementere getTime. Vi har ikke vundet noget ved arve fra Watch hvilket er et krav. I stedet er vi blevet straffet med to overflødige metoder vi ikke havde brug for. Problemet her er at da vi lavede vores abstrakte klasse antog vi visse fællestræk som alle vores underklasser vil have. Som så mange andre steder i udviklingsverdenen viste det sig at være forkert. Alle der ikke skulle implementere et analog ur bliver straffet. Dette inkluderer implementation af et AtomUr som kunden også vil have understøttet i morgen.
Nåh hvad kan vi gøre ved det. Igen læg mærke til at pointen med Interfaces og Abstrakte klasser er vidt forskellige i dette afsnit. Hvad er det egenligt et ur skal kunne? Det skal blot kunne fortælle tiden. Men det er heller ikke forkert at alle analog ure, og alle digitale urer har visse fællestræk som vi kan tage fordel af. Det er bare ikke ALLE ure som deler disse fællestræk. Lad os bruge vores nylig fundne viden til at refaktore koden:
Alt et ur skal kunne er at fortælle tiden. Lad os lave et interface til det:
- public interface Watch {
- public TimeFormat getTime();
- }
I stedet for at bruge den abstrakte klasse Watch (den tidligere), bruger vi nu interfacet Watch. Hvad ved vi om analoge urer? De kan fortælle tiden og har visere. Lads os ændre vores gamle watch til at repræsentere dette:
- //Vi implementerer interface Watch siden vi kan fortælle tiden
- public abstract class AnalogWatch implements Watch {
-
- //Returner vinklen som viserne peger på
- public abstract double getHourHand();
- public abstract double getMinuteHand();
-
- //Samme implementation før - det virkede jo fint for analoge ure
- //Casio uret som arver fra denne behøver heller ikke ændres.
- public TimeFormat getTime() {
- double hourHand = getHourHand();
- double minuteHand = getMinuteHand();
- //Omregn vinklerne til timer og minutter
- //Konstruer et TimeFormat baseret på det
- //Returner det timeformat
- }
- }
Ligeledes med digitale ure. Vi behøver ikke længere arve fra AnalogWatch og blive straffet for det. Vi implementerer bare Watch direkte:
- public abstract class DigitalWatch implements Watch {
-
- //Et fællestræk ved digital ure kunne være at de havde et skes 8-taller (digital tal), som består af 7 segmenter.
- //Dvs. de linjestykker der kan lyse som viser tallet.
- //Returner alle segmenter i rækkefælge med true hvis oplyst med false hvis ej.
- public abstract bool[] getSegments()
-
- @Override
- public TimeFormat getTime() {
- //Find timerne baseret på på oplyste segmenter i tallene via. getSegments
- //Konsturer TimeFormat
- //Returner det
- }
- }
Et digitalt ur kunne nedarve fra den og behøvede blot at fortælle hvilke segmenter der er oplyste. De behøver ikke implementere getTime igen. Bemærk her at abstrakte klasser gør det mere bekvemmeligt at implementere nye ure, mens det er interfaces der gør det mulige at have andre ure på. Vi kan sagten implementere nye ure uden at gøre brug af abstrakt og de vil virke i vores system siden det kun gør brug af objekter af typen Watch:
- public class KukuuWatch implements Watch {
- @Override
- public TimeFormat getTime() {
- //Beregn hvor træt fuglen inde i uret er
- //Konstruer og returner et TimeFormat baseret på det.
- }
- }
-
- public class AskJacobWatch implements Watch {
- @Override
- public TimeFormat getTime() {
- //Jakobs tidsberegning opstår på magisk vis
- //jeg kan derfor ikke forklare implementationen
- //Men den returnerer et TimeFormat til sidst.
- }
- }
Håber det forklarede det bedre.
(Sidenote: Den måde abstrakte klasser bliver brugt på her, er et eksempel på mønsteret Template method pattern.)
Indlæg senest redigeret d. 21.06.2013 14:39 af Bruger #14645