mboost-dp1

Spørgsmål til god OOP-skik


Gå til bund
Gravatar #1 - Spiderboy
15. jan. 2009 10:18
Mit spørgsmål er lidt svært at beskrive, så jeg spørger med et eksempel. Betragt følgende klasse:
class Test {

private int x;

public Test(int value) {
x = value;
}

public int getX() {
return x;
}

public int squared() {
return x*x;
}

}


Mit spørgsmål handler om linje 14. Hvis det skal være mest muligt korrekt, skal denne linje så være
        return x*x;

eller
        return getX()*getX();

?

I dette simple eksempel gør det ingen forskel, men man kunne forestille sig komplekse klasser med ikke-trivielle get/set-funktioner til felter (input-validering, parsing, osv.), hvor det kunne gøre en forskel.
Gravatar #2 - Jonasee
15. jan. 2009 11:31
I stede for
public int getX() {
return x;
}


bør du skrive

public int X{
get{
return x;
}
}


omkring dit spørgsmål så bør du nok brug den sidste, getX()*getX()
Gravatar #3 - zin
15. jan. 2009 12:05
#2: Dit første foreslag kan kun lade sig gøre i C#, så vidt jeg husker. Udfra syntaksen er det umuligt at sige om det er C# eller Java, som det kunne være, hvor get {} og set{} muligheden ikke forefindes.
#1: return x*x er den korrekte måde at gøre det på. Hvis du benytter getX()*getX() så vil du muligvis gøre det mere "OOP" rigtigt, men i et hvert programmatisk-mæssigt henseende vil det være hamrende forkert.
Gravatar #4 - Spiderboy
15. jan. 2009 12:12
#2, #3
Det er Java. Beklager, at jeg ikke gjorde tydeligt opmærksom på det. :-)

Og så skal det lige siges, at I dette spørgsmål ser jeg fuldstændig bort fra kodeoptimeringsbetragtninger og udelukkende vil vide, hvad der er mest "rigtigt".
Gravatar #5 - zin
15. jan. 2009 13:08
#4: Jeg vil påstå at, selvom du tænker ultra-OOP, så bør du, når du er i samme klasse, returnere variablen eller den omregnede variabel hellere end at returnere endnu en metode. F.eks. hvis du nu mente at variablen skulle behandles (altså ændres i objektet) Så burde du bruge en funktion - altså :


public int squared() {
x *= x;
return getX();
}

}

Men ellers ikke.
Tror det er en holdningssag, dog. :-)
Gravatar #6 - arne_v
15. jan. 2009 14:02
#1

Jeg vil sige x*x fremfor getX()*getX().

Fordi jeg betragter getX() som værende tiltænkt kode udenfor klassen og x som værende tiltænkt kode indenfor klassen med den angivne accessibility.

Og hvis man f.eks. lod getX() tælle antal access, så ville jeg ikke have intern implementations specifik brug talt med.
Gravatar #7 - arne_v
15. jan. 2009 14:03
#1

Bemærk iøvrigt at de to måder vil give forskelligt resultat, hvis objektet er en sub klasse af Test som har overridet getX med noget der retrunerer noget andet.
Gravatar #8 - arne_v
15. jan. 2009 14:06
#2,3,4

Spørgsmålet ligner nu klart Java i mine øjne. Per SUN og Microsoft coding conventions, så er det getX i Java og GetX i C#.
Gravatar #9 - arne_v
15. jan. 2009 14:06
#5

Den kode gør jo noget helt andet. Den ændrer objektets state.
Gravatar #10 - Spiderboy
15. jan. 2009 14:07
#5
Jeg tror godt jeg ved hvad du mener. Forskellen på x og getX() er vel for det meste et mentalt skel, hvor x jo er privat og beregnet til intern brug, mens getX() er offentlig og grænseflade til x.

Men jeg synes stadig ikke rigtigt, at det er klart hvad der er mest korrekt.

Men dette eksempel er måske heller ikke så godt, fordi det ingen forskel gør. Betragt i stedet for følgende (småfjollede) eksempel:
public class Test {

float dividend = 0;
float divisor = 1;

Test(float initDividend, float initDivisor) {
dividend = initDividend;
divisor = initDivisor;
}

public void setDividend(float value) {
dividend = value;
}

public void setDivisor(float value) {
if (value == 0) {
throw new ArithmeticException("Divisor can't be 0.");
}
divisor = value;
}

public float getQuotient() {
return dividend / divisor;
}

}


Her der der f.eks. input validation til forskel om klassen internt bruger

        dividend = initDividend;
divisor = initDivisor;


eller

        setDividend(initDividend);
setDivisor(initDivisor);

Gravatar #11 - Spiderboy
15. jan. 2009 14:11
arne_v (6) skrev:
Jeg vil sige x*x fremfor getX()*getX().

Fordi jeg betragter getX() som værende tiltænkt kode udenfor klassen og x som værende tiltænkt kode indenfor klassen med den angivne accessibility.

Det er også noget i den retning jeg forestillede mig det.

arne_v (7) skrev:
#1

Bemærk iøvrigt at de to måder vil give forskelligt resultat, hvis objektet er en sub klasse af Test som har overridet getX med noget der retrunerer noget andet.

Det er til gengæld en rigtig god pointe, som jeg slet ikke havde tænkt over. Et godt argument til at tilgå feltvariablen direkte.
Gravatar #12 - arne_v
15. jan. 2009 14:21
#11

Eller drysse lidt final ud over ens klasse.,
Gravatar #13 - arne_v
15. jan. 2009 14:24
#10

Jeg ville føle det fristende at flytte testet til getQuotient, fordi en divisor forskellig fra nul er ikke tilstrækkelig til at garantere at getQuotient vil returnere et tal.

PS: Brug af ens field og parameter navn og this. prefix på field er nærmest idiomatisk.
Gravatar #14 - Spiderboy
15. jan. 2009 14:39
arne_v (13) skrev:
Jeg ville føle det fristende at flytte testet til getQuotient (...)

Hehe ja, naturligvis - det eksempel blev lidt søgt. ;-)

Er der ved at være konsensus i tråden om, at svaret på #1 er x*x?
Gravatar #15 - Mort
15. jan. 2009 14:39
Nu er mit kendskab til Java minimalt, men set fra et objekt orienteret synspunkt vil jeg mene at du får størst fleksibilitet ved at kalde:
return getX()*getX();
. Begrundelsen er at du kan arve fra din Test klasse og i den nedarvede klasse kan du så override betydningen af getX() til feks at være:

public int getX() {
return anotherx;
}


Det at getX() betydningen er ændret bør så også betyde at dit kald til squared() bør være det.
Gravatar #16 - Spiderboy
15. jan. 2009 14:43
#15
Jeps, meget i stil med hvad #7 skrev. Den læste jeg som en advarsel mod en evt. utilsigtet effekt, men som du vender det, kan man jo sagtens udnytte det til en tilsigtet effekt.

I øvrigt behøver du ikke at kende noget til Java, da det er et OOP-spørgsmål og dermed sproguafhængigt. :-)
Gravatar #17 - myplacedk
15. jan. 2009 14:55
Det er mest OOP-agtigt at bruge getter-metoden. Men det er en puristisk tilgang, som man ikke kan bruge til meget i praksis.

Jeg tilgår normalt direkte. Men i mere komplekse situationer kan der ske sjove ting og sager i en getter. Fx. med en lazy initializer. Så benytter man getteren. Eksmpel:


public class Test {

int elefanter = -1;

public void setElefanter(int elefanter) {
this.elefanter = elefanter;
}

public int getElefanter() {
if (elefanter < 0) {
elefanter = Util.magic();
}
return elefanter;
}

public int getMeningsløs() {
int e = getElefanter;
return e*e;
}


(Så er der så mange andre ting i vejen med dette eksempel, men det viser værdien af at bruge en getter internt.)
Gravatar #18 - arne_v
15. jan. 2009 15:05
#16

#7 var også tiltænkt som en formentlig negativ egenskab.
Gravatar #19 - arne_v
15. jan. 2009 15:10
#17

I sådan et tilfælde er det nødvendigt at kalde get.

Men den form for lazy load er jo driven af performance optimering. Med den puristiske OO hat på, så bør en getter ikke ændre objektets state.

(jeg ved godt at den form for lazy load er helt standard i f.eks. Hibernate)
Gravatar #20 - zin
16. jan. 2009 08:05
#9: Det ved jeg. Det står også i teksten, hvis du ellers læste den. Jeg ved at en get-metode aldrig burde ændre noget, men derfor ændrede jeg også navnet på metoden. Dog burde den så ikke returnere noget, da det jo så er en set-metode.

#8: For det første; Det er nemt at sige, nu hvor det er blevet sagt at det er Java. For det andet; Nej. I min klasse hvor vi lærer C# bruger vi camelCasing, ligesom i Java - små bogstaver først. Med mindre du er Microsoft fanboy (undskyld udtrykket, begrundelse følger), så er sandsynligheden at du har en gammel hund til at undervise C#, der sandsynligvis er vant til camelCasing fra eksempelvis C, C++, Java eller andre sprog. Ergo ved du ikke at man benytter anden casing medmindre du selv går ind og søger på det.

#17: Jeg forstår ikke hvordan det kan være mest Objekt-Orienteret at benytte en get metode hellere end en variabel. Hverken eller af delene har noget med Objekter at gøre. Kun hvis du vil bruge nedarvning vil du have godt af, muligvis ikke at skulle rette så meget, men i mine øjne kunne man også bare tage sig sammen og rette i squared-metoden selv hellere end i get-metoden, efter som samme variabel sandsynligvis er i den nedarvede metode skal der jo nok være en get på både "anotherx" og "x", også vil du jo ikke bruge getX() til at hente anotherx, som #15 ellers nævner - det ville hellere ikke give nogen mening i programmatisk henseende at returnere "anotherx" i getX() metoden. Det vil man bruge getAnotherX() til, for pokker da - og så skal du alligevel rette i selve metoden.


Rant. Pis.
Gravatar #21 - myplacedk
16. jan. 2009 08:18
ZiN (20) skrev:
#17: Jeg forstår ikke hvordan det kan være mest Objekt-Orienteret at benytte en get metode hellere end en variabel.

Det hedder "encapsulation", og er en af de grundlæggende ting ved OOP.

ZiN (20) skrev:
Hverken eller af delene har noget med Objekter at gøre.

Spørgsmålet handler om OOP, så selvfølgelig har det det. Med puristik OOP er ALT klasser og objekter.

ZiN (20) skrev:
Kun hvis du vil bruge nedarvning vil du have godt af, muligvis ikke at skulle rette så meget, men i mine øjne kunne man også bare tage sig sammen og rette i squared-metoden selv hellere end i get-metoden,

Rette hvad? Mener du "kopiere get-metoden"?

ZiN (20) skrev:
efter som samme variabel sandsynligvis er i den nedarvede metode skal der jo nok være en get på både "anotherx" og "x", også vil du jo ikke bruge getX() til at hente anotherx, som #15 ellers nævner - det ville hellere ikke give nogen mening i programmatisk henseende at returnere "anotherx" i getX() metoden. Det vil man bruge getAnotherX() til, for pokker da - og så skal du alligevel rette i selve metoden.

Hva'? Nu snakker du om nedarvning, som overhovedet ikke er relevant for mit eksempel.


ZiN (20) skrev:
Rant. Pis.

It happens. ;-)
Gravatar #22 - zin
16. jan. 2009 10:01
#21:
Encapsulation kender jeg til. Det bruges dog kun uden for klasserne i OOP - ingen metoder skal have direkte adgang til variabler osv., men interne metoder har, selvfølgelig. Alt andet er da slam.

OOP = Objekt Orienteret Programmering. Det ved du vel. Her snakker vi interne metoder og variabler. OOP handler, som du selv siger, om Klasser og objekter, polymorfisme og den slags. IMHO har OOP derfor ikke særlig meget med omtalte eksempel at gøre, overhovedet.

Nedarvningsdelen var egentlig både til dig og #15, hvor nedarvning (som jo ER OOP) bliver brugt som argument for at benytte return getX()*getX(), fordi du så kan bruge getX() til at returnere en anden variabel (andetx) (se #15). Mit argument er så at du forhåbentlig aldrig vil benytte getX() til at returnere "andetx", men derimod getAndetx(), og så skal du alligevel rette i squared-metoden, som jeg vil mene er den bedste måde at lave en eventuel nedarvning på.
Gravatar #23 - myplacedk
16. jan. 2009 10:56
ZiN (22) skrev:
#21:
Encapsulation kender jeg til. Det bruges dog kun uden for klasserne i OOP - ingen metoder skal have direkte adgang til variabler osv., men interne metoder har, selvfølgelig. Alt andet er da slam.

Hvordan ved du med 100% sikkerhed, at der ikke engang i fremtiden sker et eller andet specielt i getteren eller setteren?

Spørgsmålet er, om det du reelt skal bruge, er X, eller resultatet af getX. Det vil næsten altid være det samme, men hvad nu hvis det pludselig ikke længere er? Så vil det højest sandsynligt være resultatet af getX du skal bruge, og derfor mener jeg, at det reneste design som udgangspunkt er at brugere getteren.

Men man kan jo ikke spå, og i praksis vil man når man ændrer på definitionen af X (fx. til lazy initialization) være nødt til at vurdere brugen af X alle steder i klassen.

Men nu har vi vist begge fået vores holdninger ud, så jeg tror jeg slutter her. ;-)
Gravatar #24 - plazm
16. jan. 2009 11:54
#23, uanset hvordan og hvorledes du ændrer på getX og setX, så skal den altid returnere samme værdi... at den interne repræsentation bliver skiftet er såvidt ligemeget, så spørgsmålet går mere på om getX og setX laver nogle statistikker, eller andet..
hvis man ændrer getX til at returnere noget andet end x, så beder man også selv om tæsk :)
Gravatar #25 - zin
16. jan. 2009 11:57
#23: Så overloader du squared() og ikke en simpel get-metode. Eller også laver du en metode, til det, du skal bruge (resultafx()) og benytter denne metode i squared() hvis du skulle få brug for det - men ærlig talt er du så langt ude i det tilfælde at du lige så godt kunne gøre det i squared()-metoden til at begynde med.
Gravatar #26 - arne_v
16. jan. 2009 13:54
ZiN (20) skrev:

#8: For det første; Det er nemt at sige, nu hvor det er blevet sagt at det er Java. For det andet; Nej. I min klasse hvor vi lærer C# bruger vi camelCasing, ligesom i Java - små bogstaver først. Med mindre du er Microsoft fanboy (undskyld udtrykket, begrundelse følger), så er sandsynligheden at du har en gammel hund til at undervise C#, der sandsynligvis er vant til camelCasing fra eksempelvis C, C++, Java eller andre sprog. Ergo ved du ikke at man benytter anden casing medmindre du selv går ind og søger på det.


I C bruger man normalt all lowercase med underscore.

I C++ bruger man ofte mixed case men med stort start bogstav.

Java er faktisk lidt speciel med mixed case med lille start bogstav.

Og coding convention for C# fra Microsofts side er helt klar.

Og at jeres underviser vælge at ignorere den slags er pinligt for uddannelses institutionen.

Hele pointen med coding convention er ikke at en bestemt convention er bedre end andre conventions. Pointen er at der er fordele ved at alle bruger samme convention.

Og man behøver såmænd ikke gennemsøge hele Microsofts web. Man kan nøjes med at konkludere udfra hvad MS selv har gjordt i .NET framework librariet.
Gravatar #27 - arne_v
16. jan. 2009 14:01
myplacedk (23) skrev:

Spørgsmålet er, om det du reelt skal bruge, er X, eller resultatet af getX. Det vil næsten altid være det samme, men hvad nu hvis det pludselig ikke længere er? Så vil det højest sandsynligt være resultatet af getX du skal bruge, og derfor mener jeg, at det reneste design som udgangspunkt er at brugere getteren.


Der har jeg så en anden tilgang.

square metoden og x field er i samme klasse - de bør matche i intended logic.

En getX metode i en anden klasse eventuelt lavet af en anden udvikler som eventuelt aldrig har set kilde teksten til denne klasse er derimod en helt ukendt størrelse.

Hvis den anden udvikler ikke har adgang til kilde teksten (og ikke gider decompile) så har han ingen mulighed for at vide at getX bruges i squared.

På den måde kan man faktisk sige at brug af x er mere encapsulated end getX.

Man kunne dog også med lige så stor ret hævde at problemet er at override af en ikke abstrakt metode og at udvikleren af denne klasse skulle bruge final hvis ikke den må overrides.
Gravatar #28 - Windcape
16. jan. 2009 19:13
arne_v (26) skrev:
Og at jeres underviser vælge at ignorere den slags er pinligt for uddannelses institutionen.
Det er desværre ret normalt.

Jeg har hjulpet mine medsturende på datamatiker i Århus med slåfejl en del gange. Slåfejl de ville havde undgået hvis de var blevet undervist i "god kode skik".

Det er heller ikke særlig typiske for certifikat kurser ser det ud til.
Ekstrem meget kode på internettet er ekstremt rodet, VB er en af storsynderne.
Gravatar #29 - zin
16. jan. 2009 20:21
#26: Men ud over coding conventionen som mindst en af brugerne her ikke kender til, og en mere kan bekræfte er meget normalt kan du ikke definitivt tyde hvilket sprog der bliver brugt. Syntaksen er den samme og man kan bruge hvilken som helst coding convention man har lyst til.
Det er lidt det samme som at sige at fordi 99% af alle kriminelle spiser brød, må brød lede til kriminalitet. Den passer i statistikken men logikken holder ikke.
Gravatar #30 - arne_v
17. jan. 2009 23:38
#29

Koden compiler i C#. Men koden er nydelig Java kode, men dårlig C# kode - ikke bare overtræde den C# naming convention, men som #2 var inde på bruger den ikke properties hvor den burde.
Gravatar #31 - arne_v
17. jan. 2009 23:48
#migselv

Og nu har jeg ævlet løs om Microsofts coding convention, så var det måske på tide med et link:

http://msdn.microsoft.com/en-us/library/ms229002.a...

og relevant underside er:

http://msdn.microsoft.com/en-us/library/ms229043.a...


Pascal Casing

The first letter in the identifier and the first letter of each subsequent concatenated word are capitalized. You can use Pascal case for identifiers of three or more characters. For example:

BackColor

Camel Casing

The first letter of an identifier is lowercase and the first letter of each subsequent concatenated word is capitalized. For example:

backColor



Do use Pascal casing for all public member, type, and namespace names consisting of multiple words.

Note that this rule does not apply to instance fields. For reasons that are detailed in the Member Design Guidelines, you should not use public instance fields.

Do use camel casing for parameter names.



Gravatar #32 - arne_v
18. jan. 2009 00:54
#31

Og når nu jeg er igang, så er her for Java.

http://java.sun.com/docs/codeconv/html/CodeConvTOC...

http://java.sun.com/docs/codeconv/html/CodeConvent...


Classes

Class names should be nouns, in mixed case with the first letter of each internal word capitalized. Try to keep your class names simple and descriptive. Use whole words-avoid acronyms and abbreviations (unless the abbreviation is much more widely used than the long form, such as URL or HTML).

class Raster;
class ImageSprite;



Methods

Methods should be verbs, in mixed case with the first letter lowercase, with the first letter of each internal word capitalized.

run();
runFast();
getBackground();



Variables

Except for variables, all instance, class, and class constants are in mixed case with a lowercase first letter. Internal words start with capital letters. Variable names should not start with underscore _ or dollar sign $ characters, even though both are allowed.

Variable names should be short yet meaningful. The choice of a variable name should be mnemonic- that is, designed to indicate to the casual observer the intent of its use. One-character variable names should be avoided except for temporary "throwaway" variables. Common names for temporary variables are i, j, k, m, and n for integers; c, d, and e for characters.


int i;
char c;
float myWidth;


Gå til top

Opret dig som bruger i dag

Det er gratis, og du binder dig ikke til noget.

Når du er oprettet som bruger, får du adgang til en lang række af sidens andre muligheder, såsom at udforme siden efter eget ønske og deltage i diskussionerne.

Opret Bruger Login