mboost-dp1

OO fun


Gå til bund
Gravatar #1 - arne_v
23. mar. 2022 01:18
Her er et meget simpelt (?) Java program.

Hvad udskriver det?


public class OOFun {
public static class A {
public void m(int v) {
System.out.println(v);
}
public void m(int u, int v) {
System.out.println(u + " " + v);
}
}
public static class B extends A {
public void m(int v) {
m(0, v);
}
}
public static class C extends B {
public void m(int u, int v) {
super.m(u + 1, v);
}
}
public static void main(String[] args) {
A o = new C();
o.m(2);
}
}

Gravatar #2 - arne_v
23. mar. 2022 13:19
#1

Hvis folk foretrækker C#:


using System;

public class OOFun
{
public class A
{
public virtual void M(int v)
{
Console.WriteLine(v);
}
public virtual void M(int u, int v)
{
Console.WriteLine(u + " " + v);
}
}
public class B : A
{
public override void M(int v)
{
M(0, v);
}
}
public class C : B
{
public override void M(int u, int v)
{
base.M(u + 1, v);
}
}
public static void Main(string[] args) {
A o = new C();
o.M(2);
}
}

Gravatar #3 - Claus Jørgensen
23. mar. 2022 14:54
Det burde vel udskrive "0 2"

Medmindre B.m(v) kalder C.m(u, v) i stedet for A.m(u, v), hvor det i så fald ender med at udskrive "1 2"
Gravatar #4 - arne_v
23. mar. 2022 14:57
#3

1 2

Det er hvad de skal og hvad både Java og C# versionen gør.

Gravatar #5 - Claus Jørgensen
23. mar. 2022 15:01
Gravatar #6 - arne_v
23. mar. 2022 15:01
#4

Men det er lidt sjovt at så releativt simpel kode:
- 4 klasser, 3 med 1 metode, 1 med 2 metoder
- 5 metoder, 4 med 1 linie kode, 1 med 2 linier kode
- alle linier kode er meget simple
kan være så ulæselig.
Gravatar #7 - arne_v
23. mar. 2022 15:03
#5

Samme resultat hvis Swift skriver 1 2. Ikke samme resultat hvis swift skriver 0 2.
Gravatar #8 - Claus Jørgensen
23. mar. 2022 15:05
#6

Sikkert derfor der er mange som mener at inheritance er ondskaben selv nu om dage :p

#7

Yup, 1 2
Gravatar #9 - arne_v
23. mar. 2022 15:15
#8

Ja.

Men det er faktisk et eksempel som jeg har brugt.

Naturligvis ikke i den form.

En lille database loader.

A.m(v) gemmer i databasen med database auto generate keys
A.m(u,v) gemmer i databasen med explicit keys
B.m(v) genererer keys i applikationen og kalder A.m(u,v) eller C.m(u,v)
C.m(u,v) putter data i kø og en anden tråd henter fra kø og kalder A.m(u,v)


Gravatar #10 - arne_v
23. mar. 2022 15:17
#8

For mange år siden blev Gosling spurgt om hvad han fortrød i Java og svaret var extends keyword.
Gravatar #11 - larsp
23. mar. 2022 15:28
#1 #2, ja den kode er til at blive rundtosset af.

Jeg har erfaret at encapsulation er et af de vigtigste principper for at skrive god kode. Hver modul, klasse, funktion osv. bør have så lille og veldefineret en grænseflade som mulig med resten af koden.

Når man nedarver er grænsefladen mellem baseklassen og nedarveren kompliceret og uigennemskuelig, og tilbøjelig til at gå i stykker ved kodeændringer.

Jeg har aldrig været fan af java's designfilosofi med at have en milliard bittesmå klasser der nedarver hinanden i en stor og filtret træstruktur. Jeg føler ikke det er en effektiv og vedligeholdelsesvenlig måde at skrive kode på.

Abstrakte interfaces er derimod en fantastisk god idé, der netop definerer en grænseflade helt klart.
Gravatar #12 - larsp
23. mar. 2022 15:48
arne_v (9) skrev:
En lille database loader.

A.m(v) gemmer i databasen med database auto generate keys
A.m(u,v) gemmer i databasen med explicit keys
B.m(v) genererer keys i applikationen og kalder A.m(u,v) eller C.m(u,v)
C.m(u,v) putter data i kø og en anden tråd henter fra kø og kalder A.m(u,v)

Hvorfor er A, B og C overhovedet forbundne med nedarvning? Det forekommer mig at være væsentligt forskellige ting der foregår, A er database tilgang, B har noget med application at gøre, C håndterer kø og multithreading. A, B og C bør være helt separate "ting".
Gravatar #13 - arne_v
23. mar. 2022 15:54
#12

Meget relevant spørgsmål.

Men meget apropos #11 - det drejer sig netop om indkapsling.

Der er en del kode - gammel kode fra 2006 - som bruger A.

Ved at lave det på den her måde erstatter jeg:

new A()

med:

new B()

eller

new C()

og så virker al koden med en noget anderledes implementation.

Gravatar #14 - Claus Jørgensen
23. mar. 2022 16:01
#11

Det er en fin tilgang til kode. Men konceptet dør desværre ligeså snart du skal arbejde med UI elementer -- en af de få steder hvor inheritance giver mening (og reducere kode mængden med 90%!)
Gravatar #15 - arne_v
23. mar. 2022 16:09
larsp (11) skrev:

#1 #2, ja den kode er til at blive rundtosset af.


Ja. På trods af at den umiddelbart virke rmeget simpel.

larsp (11) skrev:

Jeg har erfaret at encapsulation er et af de vigtigste principper for at skrive god kode. Hver modul, klasse, funktion osv. bør have så lille og veldefineret en grænseflade som mulig med resten af koden.


Jep.

larsp (11) skrev:

Når man nedarver er grænsefladen mellem baseklassen og nedarveren kompliceret og uigennemskuelig, og tilbøjelig til at gå i stykker ved kodeændringer.


Jeg ved ikke helt om jeg vil kalde det en grænseflade. Men det kan ihvertfald nemt blive uigennemskueligt. Det er svært at arve uden at være afhængig af implementationen.

Joshua Bloch har et klassisk eksempel i Effective Java i afsnittet Favor composition over inheritance.

larsp (11) skrev:

Jeg har aldrig været fan af java's designfilosofi med at have en milliard bittesmå klasser der nedarver hinanden i en stor og filtret træstruktur. Jeg føler ikke det er en effektiv og vedligeholdelsesvenlig måde at skrive kode på.


Hvor meget der er af implementations arv varierer mellem forskellige dele af Java. Swing har meget af det. Men andre dele har stort set intet.

Og jeg tror ikke at Java klasser er mindre en klasser i andre sprog. Men Java forventer at public klasser er i en separat fil, hvilket giver rigtigt mange små filer.

larsp (11) skrev:

Abstrakte interfaces er derimod en fantastisk god idé, der netop definerer en grænseflade helt klart.


Jep.

Og Gosling er helt enig.

Gravatar #16 - arne_v
23. mar. 2022 16:31
arne_v (15) skrev:

Men Java forventer at public klasser er i en separat fil, hvilket giver rigtigt mange små filer.


For en god ordens skyld.

Java som i Java Language Specification siger kun:


If and only if packages are stored in a file system (§7.2), the host system may
choose to enforce the restriction that it is a compile-time error if a class or interface
is not found in a file under a name composed of the class or interface name plus an
extension (such as .java or .jav) if either of the following is true:
...
• The class or interface is declared public (and therefore is potentially accessible
from code in other packages).


Men alle nyere Java implementationer (SUN/Oracle, OpenJDK, IBM, HP etc.) gemmer source code i fil system og enforcer den regel om public klasser i separate filer.


Gravatar #17 - arne_v
23. mar. 2022 16:37
#17

Og hvis nogen undrer sig over det med fil system.

JLS tillader eksplicit at gemme Java source code i en database.

For ca. 20 år siden havde jeg den tvivlsomme fornøjelse at arbejde med en sådan implementation.

Klasser som rækker i en tabel. Members af klasser som rækker i en anden tabel med en FK til den første tabel. Man valgte først en klasse og så et member, editerede og gemte member. Skulle man se hele klassen var det eksport til report!

Det koncept blev hurtigt droppet!!


Gravatar #18 - larsp
24. mar. 2022 06:23
#17 Wow, det lyder som hel.. på jord, jeg mener en rigtig enterprisey idé....

Med den rette IDE kunne det måske bringes til at virke. Men en noget misforstået idé, for et filsystem er jo allerede en database som er født med en hierarkisk struktur der giver god mening til kodefiler.
Gravatar #19 - larsp
24. mar. 2022 06:30
arne_v (15) skrev:
larsp (11) skrev:

Når man nedarver er grænsefladen mellem baseklassen og nedarveren kompliceret og uigennemskuelig, og tilbøjelig til at gå i stykker ved kodeændringer.


Jeg ved ikke helt om jeg vil kalde det en grænseflade.

Det er vel en grænseflade i den sproglige forstand. Én klasse klistrer sig op af en anden klasse. Alt der hvor der "klistres" er vel en grænseflade mellem to kode moduler / klasser.
Gravatar #20 - arne_v
24. mar. 2022 12:48
#18

Der var naturligvis en IDE til at styre hent og gem fra database og navigere "træet".

Og jeg kan røbe at leverandøren af det IDE var et meget stort IT firma kendt for sin 3 bogstavs forkortelse hvor det første bogstav er I.

:-)
Gravatar #21 - arne_v
24. mar. 2022 14:40
#19

Flere klasser som skal arbejde sammen men kun et objekt.
Gravatar #22 - arne_v
25. mar. 2022 00:15
arne_v (15) skrev:

Det er svært at arve uden at være afhængig af implementationen.

Joshua Bloch har et klassisk eksempel i Effective Java i afsnittet Favor composition over inheritance.


Måske er det værd lige at forklare eksemplet.

Hans eksempel bruger HashSet<> men det er nemmere med en container klasse som indeholder 3 int.

Interface:


public interface ThreeInt {
public void setOne(int ix, int v);
public void setAll(int[] v);
public int[] getData();
public String getId();
// +100 more methods
}


Ønske om at tælle antal kald til setOne og setAll.

Det virker oplagt at arve fra implementations klassen og så override de 2 metoder og tælle op der.

Resultatet er imidlertid aldeles upålideligt.


public class Test {
public static interface InstrumentedThreeInt extends ThreeInt {
public int getSetOneCalls();
public int getSetAllCalls();
}
public static class InstrumentedThreeIntA extends ThreeIntA implements InstrumentedThreeInt {
private int soc = 0;
private int sac = 0;
public void setOne(int ix, int v) {
soc++;
super.setOne(ix, v);
}
public void setAll(int[] v) {
sac++;
super.setAll(v);
}
public int getSetOneCalls() {
return soc;
}
public int getSetAllCalls() {
return sac;
}
}
public static class InstrumentedThreeIntB extends ThreeIntB implements InstrumentedThreeInt {
private int soc = 0;
private int sac = 0;
public void setOne(int ix, int v) {
soc++;
super.setOne(ix, v);
}
public void setAll(int[] v) {
sac++;
super.setAll(v);
}
public int getSetOneCalls() {
return soc;
}
public int getSetAllCalls() {
return sac;
}
}
public static void main(String[] args) {
InstrumentedThreeInt a = new InstrumentedThreeIntA();
a.setAll(new int[] { 1, 2, 3});
a.setOne(1, -2);
System.out.printf("%s : %d %d\n", a.getId(), a.getSetOneCalls(), a.getSetAllCalls());
InstrumentedThreeInt b = new InstrumentedThreeIntB();
b.setAll(new int[] { 1, 2, 3});
b.setOne(1, -2);
System.out.printf("%s : %d %d\n", b.getId(), b.getSetOneCalls(), b.getSetAllCalls());
}
}


udskriver:

A : 1 1
B : 4 1

Fordi ThreeIntA.setAll ser ud som:


public void setAll(int[] v) {
for(int i = 0; i < 3; i++) {
data[i] = v[i];
}
}


og ThreeIntB.setAll ser ud som:


public void setAll(int[] v) {
for(int i = 0; i < 3; i++) {
setOne(i, v[i]);
}
}


Valget af arv og override gør at resultatet afhænger af implementeringen og dermed bryder med indkapslingen.

Det virker fint med delegering:


public class Test2 {
public static interface InstrumentedThreeInt extends ThreeInt {
public int getSetOneCalls();
public int getSetAllCalls();
}
public static class InstrumentedThreeIntX implements InstrumentedThreeInt {
private int soc = 0;
private int sac = 0;
private ThreeInt real;
public InstrumentedThreeIntX(ThreeInt real) {
this.real = real;
}
public void setOne(int ix, int v) {
soc++;
real.setOne(ix, v);
}
public void setAll(int[] v) {
sac++;
real.setAll(v);
}
public int[] getData() {
return real.getData();
}
public String getId() {
return real.getId();
}
// +100 more methods
public int getSetOneCalls() {
return soc;
}
public int getSetAllCalls() {
return sac;
}
}
public static void main(String[] args) {
InstrumentedThreeInt a = new InstrumentedThreeIntX(new ThreeIntA());
a.setAll(new int[] { 1, 2, 3});
a.setOne(1, -2);
System.out.printf("%s : %d %d\n", a.getId(), a.getSetOneCalls(), a.getSetAllCalls());
InstrumentedThreeInt b = new InstrumentedThreeIntX(new ThreeIntB());
b.setAll(new int[] { 1, 2, 3});
b.setOne(1, -2);
System.out.printf("%s : %d %d\n", b.getId(), b.getSetOneCalls(), b.getSetAllCalls());
}
}


udskriver:

A : 1 1
B : 1 1

Imidlertid skal der laves en masse trivielle delegerende metoder.

Kotlin kan gøre det smartere:


interface InstrumentedThreeInt : ThreeInt {
fun getSetOneCalls(): Int
fun getSetAllCalls(): Int
}

class InstrumentedThreeIntX(val real: ThreeInt) : ThreeInt by real, InstrumentedThreeInt {
var soc: Int = 0
var sac: Int = 0
override fun setOne(ix: Int, v: Int): Unit {
soc++
real.setOne(ix, v)
}
override fun setAll(v: IntArray): Unit {
sac++
real.setAll(v)
}
override fun getSetOneCalls(): Int = soc
override fun getSetAllCalls(): Int = sac
}

fun main(): Unit {
val a = InstrumentedThreeIntX(ThreeIntA())
a.setAll(intArrayOf(1, 2, 3))
a.setOne(1, -2)
println("%s : %d % d".format(a.getId(), a.getSetOneCalls(), a.getSetAllCalls()))
val b = InstrumentedThreeIntX(ThreeIntB())
b.setAll(intArrayOf(1, 2, 3))
b.setOne(1, -2)
println("%s : %d % d".format(b.getId(), b.getSetOneCalls(), b.getSetAllCalls()))
}



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