'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.
Maar waarom zit open() dan niet op de klasse Object?
[quote]
Omdat alles wat tussen Object en Meubel zit niet de mogelijkheid kan hebben om geopend te worden.
Ook niet alles dat tussen Meubel en Stoel zit.bronce schreef op woensdag 06 april 2011 @ 12:37:
[...]
Omdat alles wat tussen Object en Meubel zit niet de mogelijkheid kan hebben om geopend te worden.
[ Voor 12% gewijzigd door NMe op 06-04-2011 12:38 ]
'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.
Je ziet, je redeneert in cirkeltjes. Hij hoort niet op Object omdat je niet ieder Object open kunt doen. Maar hij mag wel op Meubel omdat je de keuze hebt om open() niet te overriden? Waarom mag hij dan niet op Object, ook daar kun je de keuze hebben om 'm niet te overriden.
[ Voor 80% gewijzigd door .oisyn op 06-04-2011 12:40 ]
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Inderdaad. En als je inheritance structuur groter en groter wordt, met meer ingewikkelde meubels dan krijgt je Pianokrukje ineens wel heel veel loze methodes die er eigenlijk niet op aangeroepen mogen worden. Wat mij betreft een slecht codevoorbeeld.
Dat is de meest onzinnige vraag ooit.
Waarom checkt Object niet of het een instantie van Kast is ?
Je hebt 2 stellingen gedaan:
1. Een methode mag niet in een klasse als die methode niet opgaat voor iedere instantie van die (sub)klasse.
2. Een methode mag wel in een klasse als je ervoor kunt kiezen om 'm niet te overriden.
Volgens 1. mag open() niet in Meubel, en volgens 2. mag open() wel in Object.
[ Voor 133% gewijzigd door .oisyn op 06-04-2011 12:42 ]
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Dat hoeft ook niet, je zou wel kunnen controleren of het object IOpenable implementeert en hem, zo ja, daarna openen.bronce schreef op woensdag 06 april 2011 @ 12:42:
Maar waarom checkt Object niet of het een instantie van Kast is om te bepalen of hij open mag?
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
1
2
3
4
5
6
7
8
9
10
11
| Set<Meubel> meubels = new HashSet<Meubel>(); meubels.add(new Meubel()); meubels.add(new Kast()); for (Meubel m : meubels) { m.plaats(); if (m instanceof Openable) { ((Openable)m).open(); } } |
nu moet de klasse die de collectie bevat bepalen of iets geopend mag worden.
in OO is het de bedoeling dat klassen zoveel mogelijk onafhankelijk zijn (ze hebben vaak een klein beetje afhankelijkheid).
Elke klassen moet ook een duidelijk doel hebben en niet veel verantwoordelijkheid.
Een Meubel (of een subklasse voor Meubels die geopend kunnen worden) moet bepalen of hij geopend mag worden niet de klasse met de collectie !!!!!!!
Waar precies checkt de klasse Meubel hier datzelfde?bronce schreef op woensdag 06 april 2011 @ 12:42:
Maar waarom checkt Object niet of het een instantie van Kast is om te bepalen of hij open mag?
1
2
3
4
5
6
7
8
9
10
11
12
13
| class Kamer { Set<Meubel> meubels = new HashSet<Meubel>(); meubels.add(new Meubel()); meubels.add(new Kast()); for (Meubel m : meubels) { m.plaats(); if (m instanceof Openable) { ((Openable)m).open(); } } } |
Dit is de code die Cheatah hierboven plaatste. Meubel heeft überhaupt geen kennis van het feit dat er een Openable is, laat staan dat hij de method open() implementeert. Kast kent Openable wel en implementeert die interface ook.
Nee, de Kast weet of hij geopend kan worden. Hij is immers Openable. De klasse bepaalt dan ook dat het mag. De collectie controleert alleen of die bepaling er wel of niet is.bronce schreef op woensdag 06 april 2011 @ 12:49:
nu moet de klasse die de collectie bevat bepalen of iets geopend mag worden.
in OO is het de bedoeling dat klassen zoveel mogelijk onafhankelijk zijn (ze hebben vaak een klein beetje afhankelijkheid).
Elke klassen moet ook een duidelijk doel hebben en niet veel verantwoordelijkheid.
Een Meubel (of een subklasse voor Meubels die geopend kunnen worden) moet bepalen of hij geopend mag worden niet de klasse met de collectie !!!!!!!
[ Voor 31% gewijzigd door NMe op 06-04-2011 12:52 ]
'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.
Maar waarom verpest je het dan door nog een check te doen in de Main klasse met de collectie?
Is het de verantwoordelijkheid van die klasse om te weten welk meubel geopend moet worden?
Zelfs zeven uitroeptekens maken dat statement niet waar. De collectie meubels moet juist wél weten welk meubel openable is, omdat de klasse meubel hier geen drol mee heeft te maken! Jij legt álle verantwoordelijkheden voor álle soorten meubels in één klasse, terwijl deze klasse juist generiek zou moeten zijn: van toepassing op alle meubels.bronce schreef op woensdag 06 april 2011 @ 12:49:
Een Meubel (of een subklasse voor Meubels die geopend kunnen worden) moet bepalen of hij geopend mag worden niet de klasse met de collectie !!!!!!!
In de klasse Meubel zet je alleen de dingen die gelden voor alle meubels, zoals bijvoorbeeld afmetingen en gewicht, en verdere specifieke kenmerken worden per exemplaar (bijvoorbeeld Kast) bepaald.
Kun je elk meubel openen? Nee. Moet Open() dan in Meubel? Nee. Je wringt jezelf hier in de meest onmogelijke bochten om maar Open te kunnen doen op alle meubels, terwijl dit inherent onmogelijk is (zie Stoel). Het voorbeeld (foreach (Meubel m in Meubels) {m.Open(); } ) is wellicht ongelukkig gekozen, omdat je zo'n constructie niet snel zult maken. Je hebt eerder een rij kasten die je wil Open()en, en daar pas je de collectie dan eventueel ook op aan.
https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...
Exact. En daarom mijn opmerking dat je je af moet vragen of die collectie zo wel handig is ontworpen. Door gewoon maar open() toe te voegen op Meubel creëer je niet alleen extra (en bovendien onjuiste) afhankelijkheden, het is symptoombestreiding, want het daadwerkelijke "probleem" (voor zover het een probleem is) zit bij de code die de collectie gebruikt.bronce schreef op woensdag 06 april 2011 @ 12:49:
nu moet de klasse die de collectie bevat bepalen of iets geopend mag worden.
in OO is het de bedoeling dat klassen zoveel mogelijk onafhankelijk zijn (ze hebben vaak een klein beetje afhankelijkheid).
En oh ja, aangezien jij daar vatbaar voor lijkt: !!!!!!!!!!!!
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Nou zijn er nog wel meer eigenschappen te bedenken dan Sitable en Openable het probleem hier is dus die afhankelijkheid als je iets wilt toevoegen of verwijderen moet dit op twee plaatsen hierdoor wordt je programma onderhoudsonvriendelijk en een onderhoudsprogrammeur weet misschien niet dat het op 2 plaatsen moet hierdoor krijg je een hoop errors als hij iets aanpast.
Ik ben het met je eens dat het niet de beste oplossing is om Meubel al die functionaliteit te geven. Maar het zorgt er wel voor de dat methode print onafhankelijk is en dat je die nooit meer hoeft te wijzigen.
class GameEntity
class Monster extends GameEntity implements Drawable
GameEntity bevat code om entiteiten persistent te maken e.d.
Oftewel, een 'monster' is een GameEntity (moet persistent zijn e.d.), maar implementeert ook Drawable omdat hij op het scherm getekend kan worden.
@jwgoverg: meen je werkelijk waar dat je in plaats van een Drawable interface een draw() method in GameEntity zou definieren hoewel niet alle game entities op het scherm getekend moeten worden?
Als je "sit()" in "Meubel" stopt moet je collection net zo goed een "CannnotSitException" af gaan vangen omdat je het aanroept op een meubel (kast) waarop je niet kunt zitten toch? Wat is volgens jou efficienter; vooraf kijken of een object wel een interface implementeert, of gewoon maar aanroepen die hap en kijken wat er gebeurt?bronce schreef op woensdag 06 april 2011 @ 13:05:
Ja maar de afhankelijkheid die creëert door de verantwoordelijkheid bij de collectie te leggen is ook niet het beste ontwerp. Stel ik voeg een klasse Stoel die die wil ik sitable maken dan moet ik een Stoel maken die Sitable implementeert en sit() overriden EN je moet in je collectie een check doen naar instanceof Sitable.
Nou zijn er nog wel meer eigenschappen te bedenken dan Sitable en Openable het probleem hier is dus die afhankelijkheid als je iets wilt toevoegen of verwijderen moet dit op twee plaatsen hierdoor wordt je programma onderhoudsonvriendelijk en een onderhoudsprogrammeur weet misschien niet dat het op 2 plaatsen moet hierdoor krijg je een hoop errors als hij iets aanpast.
Ik ben het met je eens dat het niet de beste oplossing is om Meubel al die functionaliteit te geven. Maar het zorgt er wel voor de dat methode print onafhankelijk is en dat je die nooit meer hoeft te wijzigen.
[ Voor 50% gewijzigd door Hydra op 06-04-2011 13:14 ]
https://niels.nu
Je moet linksom of rechtsom ergens toegeven. En dan is het een stuk netter om geen functionaliteit aan een klasse te hangen die niets doet. Dat nodigt alleen maar uit tot bugs.bronce schreef op woensdag 06 april 2011 @ 13:05:
Ja maar de afhankelijkheid die creëert door de verantwoordelijkheid bij de collectie te leggen is ook niet het beste ontwerp. Stel ik voeg een klasse Stoel die die wil ik sitable maken dan moet ik een Stoel maken die Sitable implementeert en sit() overriden EN je moet in je collectie een check doen naar instanceof Sitable.
Nou zijn er nog wel meer eigenschappen te bedenken dan Sitable en Openable het probleem hier is dus die afhankelijkheid als je iets wilt toevoegen of verwijderen moet dit op twee plaatsen hierdoor wordt je programma onderhoudsonvriendelijk en een onderhoudsprogrammeur weet misschien niet dat het op 2 plaatsen moet hierdoor krijg je een hoop errors als hij iets aanpast.
Verder is er nog een verschil tussen jouw voorstel en dat van Cheatah: dat van Cheatah weet dat hij met Kasten aan het schuiven is. Jouw code weet dat niet. Voor hetzelfde geld voer je vervolgens omdat je dat niet weet een Stoel aan een functie die alleen overweg kan met objecten die je kan openen. Dan heb je een probleem: in het beste geval klopt je code, en in het slechtste geval werkt je applicatie wel maar doet hij gekke dingen. En vooral dat laatste is heel lastig terug te vinden.
'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.
Is 'Open' common voor alle meubels ? Maw, kan je alle meubels openen ? Neen. Dus, Open() hoort niet thuis in meubel.
Zeg nu zelf, wat is cleaner / logischer:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| public abstract class Meubel
{
public abstract void Open();
}
public class Kast : Meubel
{
public override void Open()
{
Console.WriteLine ("kast geopend.");
}
}
public class Stoel : Meubel
{
public override void Open()
{
throw new NotSupportedException ("Je kan een stoel niet openen.");
}
}
public class Program
{
public static void Main()
{
// ...
foreach( Meubel m in meubels )
{
try
{
m.Open();
}
catch( NotSupportedException ex)
{
Trace.WriteLine (ex.Message);
}
}
}
} |
of
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| public class Meubel
{
// ...
}
public interface IOpenable
{
void Open();
}
public class Stoel : Meubel {}
public class Kast : Meubel, IOpenable
{
public void Open()
{
Console.WriteLine ("kast geopend.");
}
}
public class Program
{
public static void Main()
{
// geef me een collectie van alle meubels die ik kan openen.
var items = meubels.OfType <IOpenable>().ToList();
foreach( i in items )
{
i.Open();
}
}
} |
https://fgheysels.github.io/
Wij zitten thuis altijd op kastenHydra schreef op woensdag 06 april 2011 @ 13:10:
Als je "sit()" in "Meubel" stopt moet je collection net zo goed een "CannnotSitException" af gaan vangen omdat je het aanroept op een meubel (kast) waarop je niet kunt zitten toch?
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| void OpenSpecialization(Kast k); void OpenSpecialization(Meubel m); void OpenSpecialization(Tafel t); void Open(Meubel m) { OpenSpecialization(m as dynamic); } Kast k; Tafel t; Lamp l; Open(k); //voert over load met Kast class uit Open(t); //voert overload met Tafel class uit. Open(l); //Voert overload met Meubel uit aangezien er geen specifiekere case is. |
Meer info: http://blogs.msdn.com/b/s...spatch-via-c-dynamic.aspx
Edit: zo hoef je dus geen instanceof switch te gebruiken, de methode voor meubel kan bijvoorbeeld een stub zijn voor als er niets gebeurt. Verder werkt deze techniek omdat het keyword dynamic ervoor zorgt dat de overload pas at runtime geresolved wordt ipv at compile time, en at runtime weet de IL/JVM natuurlijk wel wat voor exact type een object heeft.
[ Voor 17% gewijzigd door roy-t op 06-04-2011 13:36 ]
Of je moet een dummy implementatie maken die niks doet. Ik het geval van je tafel is het namelijk helemaal niet logisch om er Open op aan te moeten roepen ( Behalve als er natuurlijk weer een la in zit o.i.d.
[ Voor 31% gewijzigd door Woy op 06-04-2011 13:39 ]
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
Idd, Je kan wel syntax schrijven waarbij je een Open method aanroept op gelijk welk object, maar als dat object geen Open method heeft, krijg je een RTE.Woy schreef op woensdag 06 april 2011 @ 13:36:
@roy-t: maar je houdt nog steeds het probleem dat je de call alleen mag doen als er een implementatie voor Open is, dus je zult alsnog eerst moeten controleren of IOpenable wel geïmplementeerd is. In dit geval schiet je er dus niks mee op.
https://fgheysels.github.io/
Maar je blijft dan een (dummy) implementatie maken voor Meubel (of meteen object), die nergens op slaat. In dit geval is het gebruik van instanceof IMHO nog steeds een stuk netter.roy-t schreef op woensdag 06 april 2011 @ 13:39:
Ja e nee, je fallback (de overload met meubel) zal gewoon niets doen, terwijl specifieke types die wel geopend willen worden een overload presenteren, dit maakt wel je publieke interface groter, en dat kan natuurlijk erg onwenselijk zijn. Een andere structurering van data types zou daar misschien bij helpen.
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
Je doet dan precies hetzelfde als wat jwgoverg doet: functionaliteit aanbieden die niets doet. Vind ik niet mooi, al is het met deze code IMO wel mooier/overzichtelijker dan de code van jwgoverg.roy-t schreef op woensdag 06 april 2011 @ 13:39:
Ja e nee, je fallback (de overload met meubel) zal gewoon niets doen, terwijl specifieke types die wel geopend willen worden een overload presenteren, dit maakt wel je publieke interface groter, en dat kan natuurlijk erg onwenselijk zijn. Een andere structurering van data types zou daar misschien bij helpen.
'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.
Niet helemaal; dat dynamic keyword zorgt voor duck-typing. Je gaat ervan uit dat het dynamic object een method open heeft, en heeft deze dat niet, dan krijg je een RTE voor de kiezen.NMe schreef op woensdag 06 april 2011 @ 13:48:
[...]
Je doet dan precies hetzelfde als wat jwgoverg doet: functionaliteit aanbieden die niets doet. Vind ik niet mooi, al is het met deze code IMO wel mooier/overzichtelijker dan de code van jwgoverg.
Eigenlijk zou je dat dus enkel mogen aanroepen als je weet dat het object een open method heeft. (Vooral handig bij COM interop binnen .NET).
https://fgheysels.github.io/
Ok, misschien moet ik mezelf wat verduidelijken:Hydra schreef op woensdag 06 april 2011 @ 12:05:
[...]
Inderdaad. Ik gebruik het zelf bijna nooit omdat ik het meestal niet nodig heb. In heel enkele gevallen heb je een collectie objecten die verder niet gerelateerd zijn waarvan je moet gaan uitvogelen van welk type ze zijn. Mag je mij gaan uitleggen hoe je dat kan doen zonder instanceof, dat visitor-pattern hierboven werkt dan niet.
Ten eerste heb ik nooit geclaimd dat instanceof per definitie evil is, alleen dat ik het lelijk vind voor objectoriëntatie waar behoorlijke abstractie beter op z'n plek is. Dogma's moeten ten alle tijden vermeden worden IMO (en ja ik snap de ironie van deze zin dus het heeft geen zin daarop in te gaan
Je hebt overigens wel gelijk, als je de situatie tegenkomt waarin je stuk voor stuk moet gaan uitvogelen welk runtime-type elk object in een collectie heeft dan kan je niet om de instanceof heen. Maar in mijn ervaring is dat vaak al een symptoom van slecht ontwerp, en dan steek ik liever mn tijd in het uit elkaar trekken van zo'n collection in een aantal collection objects die een type hebben die wat lager in die hierarchie zit, dan doorgaan met die slechte code.
Ten tweede is er nooit een "one size fits all" oplossing, de enige 100% generieke uitspraak die er gedaan kan worden is iets van de strekking "beslis a.d.h.v. de situatie welk patroon het meest geschikt is". Het probleem met deze uitspraak is natuurlijk dat er niets over specifieke situaties wordt gezegd.
Zelf zou ik t, als ik van scratch zou klussen, denk ik als volgt oplossen in productiecode (voor een supersimpel voorbeeld maakt het eigenlijk niet uit maar dit vind ik wel mooie code):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public abstract class FurnitureItem { private final String id; public FurnitureItem(String id) { super(); this.id = id; } public String getId() { return id; } public abstract void place(); } |
1
2
3
4
5
6
7
8
| public abstract class OpenableFurnitureItem extends FurnitureItem { public OpenableFurnitureItem(String id) { super(id); } protected abstract void onOpen(); } |
1
2
3
4
5
6
7
8
| public abstract class Seat extends FurnitureItem { public Seat(String id) { super(id); } protected abstract void onSitOn(); } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class Closet extends OpenableFurnitureItem { public Closet(String id) { super(id); } @Override protected void onOpen() { System.out.println("Opened " + getId()); } @Override public void place() { System.out.println("Placed " + getId() + " somehere"); // Some Closet-specific stuff } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class Couch extends Seat { public Couch(String id) { super(id); } @Override protected void onSitOn() { System.out.println(getId() + " is being sat on."); } @Override public void place() { System.out.println("Placed " + getId() + " somehere"); // Some Couch-specific stuff } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public class User { private String name; public User(String name) { super(); this.name = name; } public String getName() { return name; } public void sitOn(Seat seat) { System.out.println(this.name + " takes a seat on " + seat.getId()); seat.onSitOn(); } public void open(OpenableFurnitureItem ofi) { System.out.println(this.name + " opens " + ofi.getId()); ofi.onOpen(); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| public class Main { private static final int NUM_ITEMS = 3; public static void main(String[] args) { User user = new User("Jack Daniels"); OpenableFurnitureItem[] openables = new OpenableFurnitureItem[NUM_ITEMS]; Seat[] seats = new Seat[NUM_ITEMS]; for (int i = 0; i < NUM_ITEMS; i++) { openables[i] = new Closet("Closet " + i); seats[i] = new Couch("Couch " + i); } for (int i = 0; i < NUM_ITEMS; i++) { user.sitOn(seats[i]); seats[i].place(); user.open(openables[i]); openables[i].place(); System.out.println(); // for readability purposes } } } |
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| Jack Daniels takes a seat on Couch 0 Couch 0 is being sat on. Placed Couch 0 somehere Jack Daniels opens Closet 0 Opened Closet 0 Placed Closet 0 somehere Jack Daniels takes a seat on Couch 1 Couch 1 is being sat on. Placed Couch 1 somehere Jack Daniels opens Closet 1 Opened Closet 1 Placed Closet 1 somehere Jack Daniels takes a seat on Couch 2 Couch 2 is being sat on. Placed Couch 2 somehere Jack Daniels opens Closet 2 Opened Closet 2 Placed Closet 2 somehere |
Let op de 2 arrays in de main-methode: die maken het grootste verschil van alles, omdat ze ervoor zorgen dat ik de arrays van het type kan declareren die de operaties kunnen uitvoeren (OpenableFurnitureItem die geopend kan worden en Seat waar op gezeten kan worden) die je wil hebben.
:j
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
| public class Meubel { public void plaats() { Console.WriteLine("Meubel geplaatst"); } } class Kast : Meubel { public void open() { Console.WriteLine("Kast geopend"); } } class Bank : Meubel { } static class MeubelCheckerHelper { public static String openableMeubels = "meubeltestcase.Kast;meubeltestcase.Kledingkast"; public static bool isOpenableMeubel(Meubel meubel) { foreach (String meubelclass in openableMeubels.Split(';')) { if (meubel.GetType().ToString() == meubelclass) { return true; } } return false; } } class Program { static void Main(string[] args) { Kast meubel1 = new Kast(); Bank meubel2 = new Bank(); List<Meubel> meubels = new List<Meubel>(); meubels.Add(meubel1); meubels.Add(meubel2); foreach (Meubel m in meubels) { if (MeubelCheckerHelper.isOpenableMeubel(m)) { foreach (String meubelclass in MeubelCheckerHelper.openableMeubels.Split(';')) { if (meubelclass == "meubeltestcase.Kast") { Kast kast = (Kast)m; kast.open(); } } } } Console.ReadKey(); } } |
En mochten er dan teveel classes openable zijn kan je het beste even de classnames in een database zetten ipv alsstring.
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Edit: wat nog wel een leuke optie is (misschien) om instanceof wat te verbergen is om in de foreach een iterator op te vragen die uit een andere iterator (Vector<>/ array ofzo) alle objecten die Interface X implementeren terug geeft. In C# zou je dit met Linq kunnen doen (wat stiekem nog een abstractie is) maar in Java zou je dit altijd nog met een gewoon een eigen class die iterator extend en een ifje kunnen doen.
[ Voor 68% gewijzigd door roy-t op 06-04-2011 14:50 ]
Doet me denken aan de eeuwige eval == evil-discussie..oisyn schreef op woensdag 06 april 2011 @ 14:42:
Ah ja, ga strings zitten comparen. Mooie oplossing om maar geen instanceof te gebruiken
'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.
En de methode MeubelCheckerHelper is het toppunt van OO programmeren die makkelijk uit te bereiden is met bijvoorbeeld isSittableMeubel. Je zou van MeubelCheckerHelper ook nog een superclass kunnen maken en dan voor elke soort functie een subclass maken. Bijvoorbeeld MeubelSittableCheckerHelper die dan erft van MeubelCheckerHelper.
Ja, en dan gaan we even gaan refactoren en die class een andere naam geven, of we verhuizen ze naar een andere namespace .... En dan werkt het plots niet meer.Big Joe schreef op woensdag 06 april 2011 @ 14:56:
Het gaat erom dat het werkt, en ik durf te wedden dat de Ikea het ook op deze manier zou doen als ze programmeurs met ervaring hebben.
En de methode MeubelCheckerHelper is het toppunt van OO programmeren die makkelijk uit te bereiden is met bijvoorbeeld isSittableMeubel. Je zou van MeubelCheckerHelper ook nog een superclass kunnen maken en dan voor elke soort functie een subclass maken. Bijvoorbeeld MeubelSittableCheckerHelper die dan erft van MeubelCheckerHelper.
[ Voor 3% gewijzigd door whoami op 06-04-2011 14:59 ]
https://fgheysels.github.io/
Ervaring zegt niets, je kunt ook jaren ervaring hebben in het schrijven van slechte code.Big Joe schreef op woensdag 06 april 2011 @ 14:56:
Het gaat erom dat het werkt, en ik durf te wedden dat de Ikea het ook op deze manier zou doen als ze programmeurs met ervaring hebben.
Iedere class die eindigt op Helper is een toppunt van slecht OO programmeren. Wat doet een helper?En de methode MeubelCheckerHelper is het toppunt van OO programmeren die makkelijk uit te bereiden is met bijvoorbeeld isSittableMeubel. Je zou van MeubelCheckerHelper ook nog een superclass kunnen maken en dan voor elke soort functie een subclass maken. Bijvoorbeeld MeubelSittableCheckerHelper die dan erft van MeubelCheckerHelper.
Het is een ranzige workaround die nog steeds neerkomt op het controleren van het type. In plaats van netjes het resultaat van GetType te gebruiken en te kijken of de instantie een Kast is (*kuch* instanceOf) ga jij raar lopen doen met strings (wat als de namespace of erger nog de naam van de klasse verandert?).Big Joe schreef op woensdag 06 april 2011 @ 14:56:
Het gaat erom dat het werkt, en ik durf te wedden dat de Ikea het ook op deze manier zou doen als ze programmeurs met ervaring hebben.
Ja, want als je een ranzige methode in een even ranzige klasse stopt is het ineens het toppunt van OO?En de methode MeubelCheckerHelper is het toppunt van OO programmeren die makkelijk uit te bereiden is met bijvoorbeeld isSittableMeubel. Je zou van MeubelCheckerHelper ook nog een superclass kunnen maken en dan voor elke soort functie een subclass maken. Bijvoorbeeld MeubelSittableCheckerHelper die dan erft van MeubelCheckerHelper.
https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...
It works! Ship it!Big Joe schreef op woensdag 06 april 2011 @ 14:56:
Het gaat erom dat het werkt, en ik durf te wedden dat de Ikea het ook op deze manier zou doen als ze programmeurs met ervaring hebben.
Sorry, maar dit is geen goeie instelling en dat weet je zelf ongetwijfeld ook.
'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.
Dit zegt wel genoeg toch?
Wishlist Backpack Survivors op Steam !
Áls je het op een dergelijke manier wilt doen, doe het dan niet met een te splitten string met types, maar gewoon met een array van types (jeweetwel, System.Type), zodat je gewoon aan dat type object kunt vragen of een object daar een instantie van is.
I vote worst code ever. Wie maakt er een DailyWTF post van?
@Big Joe: schoenmaker, blijf bij je leest
[ Voor 85% gewijzigd door .oisyn op 06-04-2011 15:08 ]
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Dan maak je Kast toch gewoon final?!?!.oisyn schreef op woensdag 06 april 2011 @ 15:04:
Ook leuk hoe die code niet werkt met subclasses van Kast.
'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.
Verwijderd
Bij deze heb ik de tag aangevraagd....oisyn schreef op woensdag 06 april 2011 @ 15:04:
I vote worst code ever. Wie maakt er een DailyWTF post van?
Big Joe schreef op woensdag 06 april 2011 @ 14:38:
C#:
1 2 3 4 if (meubel.GetType().ToString() == meubelclass) { return true; }
Doe 't dan goed:
1
| return meubel.GetType().ToString().Trim().ToLowerInvariant().Equals("meubeltestcase.kast", StringComparison.InvariantCultureIgnoreCase); |
There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.
Je eigen tweaker.me redirect
Over mij
meubel.GetType().ToString() == "string" is natuurlijk veel mooier dan meubel instanceof string, duh!
'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.
To approve, or not to approve, that is the question..
Anyone who gets in between me and my morning coffee should be insecure.
To approve or not to approve == true, dûh. Zijn we hier nu programmeurs of niet?
Soultaker schreef op woensdag 06 april 2011 @ 15:28:
offtopic:
To approve or not to approve == true, dûh. Zijn we hier nu programmeurs of niet?
En hoe weet jij zo zeker dat het niet FileNotFound is?
Wishlist Backpack Survivors op Steam !
Het drawable voorbeeld vond ik mooi (omdat het praktisch is). Daarbij heet de interface namelijk een collectie aan drawables, de gamecontroller een collectie van gameentities en de ai een collectie van monsters.
De interface, gamecontroller en de ai hoeven deze types niet meer te checken. Ze accepteren namelijk geen instances van andere typen.
Wanneer is de "Kast.open" en "Stoel.zit" realistisch? Als je een grote schuur hebt met honderden meubels en je gaat kijken of ze allemaal nog werken. Dan is het best logisch dat als je voor een meubel staat, dat je even controleert watvoor meubel het is en afhankelijk daarvan even kijkt of het er goed uit ziet.
Dan weet je gelijk waarom je Openable en Sitable moet implementeren. Anders moet je voor "Stoel" en "Bank" opnieuw de testmethod aanmaken.
Bovenstaand lijkt me in de programmeer wereld niet vaak voorkomen, behalve als je dus voorbeeld code aan het schrijven bent.
(to apporove or not to approve) instanceof FileNotExist == falseofftopic:
En hoe weet jij zo zeker dat het niet FileNotExists is?
controleerWerking() zou dan best wel weer een methode kunnen zijn op Meubel, die afhankelijk van de meubel verschillend geïmplementeerd is.ReenL schreef op woensdag 06 april 2011 @ 15:32:
Wanneer is de "Kast.open" en "Stoel.zit" realistisch? Als je een grote schuur hebt met honderden meubels en je gaat kijken of ze allemaal nog werken. Dan is het best logisch dat als je voor een meubel staat, dat je even controleert watvoor meubel het is en afhankelijk daarvan even kijkt of het er goed uit ziet.
Hoeft niet per se. Kijk bijvoorbeeld eens naar Java3D, daar wordt alles gewoon in een grote boomstructuur gemikt.ReenL schreef op woensdag 06 april 2011 @ 15:32:
Het drawable voorbeeld vond ik mooi (omdat het praktisch is). Daarbij heet de interface namelijk een collectie aan drawables, de gamecontroller een collectie van gameentities en de ai een collectie van monsters.
Niet dat ik dat nou echt een fantastische API vind overigens, maar het is niet zonder meer zo dat je collecties altijd volledig uitgespecificeerd zijn.
[ Voor 34% gewijzigd door .oisyn op 06-04-2011 15:41 ]
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Verwijderd
Vanuit performance-perspectief zal dat ongetwijfeld een goed idee zijnReenL schreef op woensdag 06 april 2011 @ 15:32:
Wel geweldig dat we allemaal een hele theoretische case aan het oplossen zijn.
Het drawable voorbeeld vond ik mooi (omdat het praktisch is). Daarbij heet de interface namelijk een collectie aan drawables, de gamecontroller een collectie van gameentities en de ai een collectie van monsters.
Maar hoe krijg je een object in de juiste collecties?De interface, gamecontroller en de ai hoeven deze types niet meer te checken. Ze accepteren namelijk geen instances van andere typen.
Verwijderd
Dat is de reden waarom mensen die roepen dat je geen goto mag gebruiken en dat eval altijd vies is, niet al te serieus genomen moeten worden.Vinnienerd schreef op woensdag 06 april 2011 @ 16:46:
Mensen die gaan roepen dat instanceof ranzig is zijn bezig met "overcorrect programmeren". Abstract denken is mooi, maar soms moet je gewoon specifiek zijn
Ik denk dat de consensus is dat onervaren programmeurs er geen gebruik van zouden moeten maken, maar dat het gebruik in sommige gevallen wel is te verantwoorden. Om te bepalen of dat zo is, moet je uiteraard het nodige inzicht en de nodige ervaring hebben.
Maar dan nog vind ik instanceof van compleet ander kaliber dan goto en eval.
[ Voor 6% gewijzigd door Verwijderd op 06-04-2011 16:57 ]
Er zijn allerlei code constructs die je bij voorkeur niet zou moeten gebruiken. Maar soms wordt je inderdaad voor de keuze gesteld om toch maar de construct te gebruiken of er omheen te werken.Verwijderd schreef op woensdag 06 april 2011 @ 16:56:
Maar dan nog vind ik instanceof van compleet ander kaliber dan goto en eval.
Als je met nette en duidelijke code kunt vermijden dat je de constructs gebruikt of de reden dat je het uberhaupt checkt kunt voorkomen... graag. Maar als je om een enorme (maar wel nette) hoeveelheid code moet produceren enkel om hetzelfde probleem op te lossen? Sja, dan moet je je alsnog weer achter je oren gaan krabben. Misschien was de instanceof (of goto, eval, meerdere returns, etc) dan toch een beter idee.
Dit is een gevaarlijke vraagHydra schreef op woensdag 06 april 2011 @ 13:10:
Als je "sit()" in "Meubel" stopt moet je collection net zo goed een "CannnotSitException" af gaan vangen omdat je het aanroept op een meubel (kast) waarop je niet kunt zitten toch? Wat is volgens jou efficienter; vooraf kijken of een object wel een interface implementeert, of gewoon maar aanroepen die hap en kijken wat er gebeurt?
Desalniettemin zou ik in dit geval ook de instanceof-variant kiezen en hooguit bij wijze van micro-optimalisatie vol in het kritieke pad onderaan de lijst overwegen om te kijken of er een alternatieve - nog efficientere - methode is om het te doen
Dat schrijf ik toch ook? Of heb je het nu niet tegen mij?Verwijderd schreef op woensdag 06 april 2011 @ 16:56:
[...]
Maar dan nog vind ik instanceof van compleet ander kaliber dan goto en eval.
I think we have a winner. Dat is wel de ultieme reden dat het gewoon helemaal compleet fout is..oisyn schreef op woensdag 06 april 2011 @ 15:04:
Ook leuk hoe die code niet werkt met subclasses van Kast.
class ServiesKast extends Kast. Dat IS gewoon een kast, maar volgens het voorbeeld niet. Te slecht gewoon.
Ik bedoel efficienter vanuit een programmeerstandpunt, niet performance. Dat zou wel een vorm van premature optimization zijnACM schreef op woensdag 06 april 2011 @ 17:26:
Dit is een gevaarlijke vraagAls geen (of slechts enkele) van de objecten een exception opgooien (dus alles Sitable is) is het namelijk goed mogelijk dat de variant met exception-check efficienter is dan de instanceof-variant.
https://niels.nu
Zoals .oisyn al zei: het probleem zit waarschijnlijk helemaal niet in Meubel/Kast/Openable, maar in de collectie/array van het type Meubel en het beoogde gebruik daarvan.
Het feit dat je instanceof nodig hebt om bepaalde instanties in die lijst eruit te filteren, is eerder een indicatie van een probleem met die lijst, dan van een probleem met de types van die instanties.
Als je graag alle Kasten wilt openen, moet je een collectie van Kasten maken, geen collectie van Meubels.
[ Voor 4% gewijzigd door Herko_ter_Horst op 06-04-2011 19:44 ]
"Any sufficiently advanced technology is indistinguishable from magic."
Verwijderd
Ik had het niet zozeer tegen jou, ik was aan het voortborduren op de gedachteVinnienerd schreef op woensdag 06 april 2011 @ 17:28:
Dat schrijf ik toch ook? Of heb je het nu niet tegen mij?
Maar bij het toevoegen weet je nou eenmaal niet altijd wat het is, en dus zul je op dat punt moeten controleren wat het is ( met instanceof bijvoorbeeld ). Als er een duidelijk onderscheid te maken is bij het toevoegen, moet je dat zeker doen. Maar vroeger of later kom je vanzelf een situatie tegen waar je eigenlijk een te generieke collectie gebruikt.Herko_ter_Horst schreef op woensdag 06 april 2011 @ 18:52:
Als je graag alle Kasten wilt openen, moet je een collectie van Kasten maken, geen collectie van Meubels.
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
De vraag is of in zo'n geval de verantwoordelijkheden goed liggen. Moet de class die de generieke collectie van Meubels beheert, wel dezelfde class zijn die Kasten open doet?Woy schreef op woensdag 06 april 2011 @ 19:44:
[...]
Maar bij het toevoegen weet je nou eenmaal niet altijd wat het is, en dus zul je op dat punt moeten controleren wat het is ( met instanceof bijvoorbeeld ). Als er een duidelijk onderscheid te maken is bij het toevoegen, moet je dat zeker doen. Maar vroeger of later kom je vanzelf een situatie tegen waar je eigenlijk een te generieke collectie gebruikt.
"Any sufficiently advanced technology is indistinguishable from magic."
Hoe zie je het dan voor je? Natuurlijk is een Kamer ( o.i.d. ) niet de aangewezen class om meubels te openen, maar als er bijvoorbeeld een Persoon is die dat zou doen, dan moet die alsnog uit de kamer alle IOpenable objecten verkrijgen, en dan heb je daar alsnog iets van instanceof voor gebruiken.Herko_ter_Horst schreef op woensdag 06 april 2011 @ 19:51:
[...]
De vraag is of in zo'n geval de verantwoordelijkheden goed liggen. Moet de class die de generieke collectie van Meubels beheert, wel dezelfde class zijn die Kasten open doet?
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
Bij mij op mijn werk gebruiken we het waterval model en is dit niet van toepassingen. Klassendiagrammen en ontwerpen worden van tevoren opgezet met UML, zodat namen vaststaan en nooit zullen veranderen. Het is een kleine opoffering die je moet maken als je echt om de performance van je applicatie geeft.whoami schreef op woensdag 06 april 2011 @ 14:58:
[...]
Ja, en dan gaan we even gaan refactoren en die class een andere naam geven, of we verhuizen ze naar een andere namespace .... En dan werkt het plots niet meer.
[...]
![]()
Een helper is in dit geval een soort controller die de applicatie helpt om te bepalen welke meubelen een kast zijn. Dit is een veelgebruikte best-practice om de applicatie-logica, het data model en de presentatie-laag van elkaar te scheiden.Iedere class die eindigt op Helper is een toppunt van slecht OO programmeren. Wat doet een helper?
Zoals ik al eerder vertelde hoeft de naam van een namespace of klasse niet te veranderen als je van tevoren een goed ontwerp maakt. Mijn methode is een goede manier om op een snelle en efficiente manier te controleren welke classes de methode open() hebben.Het is een ranzige workaround die nog steeds neerkomt op het controleren van het type. In plaats van netjes het resultaat van GetType te gebruiken en te kijken of de instantie een Kast is (*kuch* instanceOf) ga jij raar lopen doen met strings (wat als de namespace of erger nog de naam van de klasse verandert?).
Verder vind ik het beneden peil dat jullie mijn code op zo'n negatieve manier afkraken. Ik zit al 15 jaar in het vak en heb nog nooit negatieve feedback gehad op mijn code!
En je denkt serieus dat een string vergelijking beter performt dan een simpele type comparison?Big Joe schreef op woensdag 06 april 2011 @ 20:12:
[...]
Het is een kleine opoffering die je moet maken als je echt om de performance van je applicatie geeft.
Ten eerste is het zeker geen snelle efficiënte manier, en ten tweede is het ook erg lastig te onderhouden. Ook al maak je nog zo'n goed ontwerp van te voren, in de toekomst zullen er altijd wijzigingen of toevoegingen aan gedaan worden. Met deze manier van werken is het gewoon wachten totdat het mis gaat.Zoals ik al eerder vertelde hoeft de naam van een namespace of klasse niet te veranderen als je van tevoren een goed ontwerp maakt. Mijn methode is een goede manier om op een snelle en efficiente manier te controleren welke classes de methode open() hebben.
Het is niet bedoeld om je persoonlijk aan te vallen, maar de manier van werken die jij voorstelt is echt geen voorbeeld van hoe je zou moeten werken. Je hebt totaal geen enkele vorm van compile-time safety, refactoring tools werken er niet goed op ( Het scheelt dat Resharper bijvoorbeeld ook in stings zoekt, maar die heeft daarbij alsnog te weinig informatie om het altijd goed te doen.Verder vind ik het beneden peil dat jullie mijn code op zo'n negatieve manier afkraken. Ik zit al 15 jaar in het vak en heb nog nooit negatieve feedback gehad op mijn code!
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
Tenminste, dat hoop ik dan. Ik bedoel:
Dat kan je gewoon niet serieus menen als je "al 15 jaar in het vak zit".Klassendiagrammen en ontwerpen worden van tevoren opgezet met UML, zodat namen vaststaan en nooit zullen veranderen.
...
Zoals ik al eerder vertelde hoeft de naam van een namespace of klasse niet te veranderen als je van tevoren een goed ontwerp maakt.
Waterval en klassendiagrammen hebben nog minder dan niets met de performance van je applicatie te maken.Big Joe schreef op woensdag 06 april 2011 @ 20:12:
[...]
Bij mij op mijn werk gebruiken we het waterval model en is dit niet van toepassingen. Klassendiagrammen en ontwerpen worden van tevoren opgezet met UML, zodat namen vaststaan en nooit zullen veranderen. Het is een kleine opoffering die je moet maken als je echt om de performance van je applicatie geeft.
Het heeft toch te maken met de data?Een helper is in dit geval een soort controller die de applicatie helpt om te bepalen welke meubelen een kast zijn. Dit is een veelgebruikte best-practice om de applicatie-logica, het data model en de presentatie-laag van elkaar te scheiden.
Totdat er een nieuw meubelstuk wordt geïntroduceerd wat toevallig ook geopend moet kunnen worden. In het geval van de eerder genoemde code die gebruik maakt van een nette interface hoef je het nieuwe meubel alleen deze interface laten implementeren, en de rest van de code blijft onaangeraakt. In jouw geval moet je de naam van de klasse (die overigens door voortschrijdend inzicht wel degelijk kan veranderen) copypasten en in een string zetten.Zoals ik al eerder vertelde hoeft de naam van een namespace of klasse niet te veranderen als je van tevoren een goed ontwerp maakt. Mijn methode is een goede manier om op een snelle en efficiente manier te controleren welke classes de methode open() hebben.
Je lost er een niet-bestaand probleem (het om een of andere reden niet willen gebruiken van runtime typechecking) mee op.
Dan laat je je code aan de verkeerde mensen zien. Ik wil mezelf niet onervaren noemen, maar zelfs ik zie dat jouw ontwerp gewoon inherent slecht is. Wat overigens natuurlijk niets zegt over de code die je in je dagelijkse kostwinning produceert.Verder vind ik het beneden peil dat jullie mijn code op zo'n negatieve manier afkraken. Ik zit al 15 jaar in het vak en heb nog nooit negatieve feedback gehad op mijn code!
https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...
Joehoe, 1996 belde, ze willen hun wereldbeeld terug!Big Joe schreef op woensdag 06 april 2011 @ 20:12:
Bij mij op mijn werk gebruiken we het waterval model en is dit niet van toepassingen. Klassendiagrammen en ontwerpen worden van tevoren opgezet met UML, zodat namen vaststaan en nooit zullen veranderen. Het is een kleine opoffering die je moet maken als je echt om de performance van je applicatie geeft.
...
Een helper is in dit geval een soort controller die de applicatie helpt om te bepalen welke meubelen een kast zijn. Dit is een veelgebruikte best-practice om de applicatie-logica, het data model en de presentatie-laag van elkaar te scheiden.
...
Zoals ik al eerder vertelde hoeft de naam van een namespace of klasse niet te veranderen als je van tevoren een goed ontwerp maakt.
Oeh, ik bied me bij deze vrijwillig aan voor het doen van een code review!Verder vind ik het beneden peil dat jullie mijn code op zo'n negatieve manier afkraken. Ik zit al 15 jaar in het vak en heb nog nooit negatieve feedback gehad op mijn code!
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Commentaar krijgen op je code kan vervelend zijn, maar je kunt er ook van leren. Ervaring zegt niet alles, zoals ik al eerder zei: je kunt ook ervaring hebben in het schrijven van slechte code. Nu kun je natuurlijk een prima programmeur zijn en zit je er in dit geval net naast, maar ik hoop dat je inziet waarom jouw voorgestelde oplossing hier niet klopt.
Dit kun je oplossen door de code aan te passen naar het volgende:Hydra schreef op woensdag 06 april 2011 @ 17:48:
[...]
I think we have a winner. Dat is wel de ultieme reden dat het gewoon helemaal compleet fout is.
class ServiesKast extends Kast. Dat IS gewoon een kast, maar volgens het voorbeeld niet. Te slecht gewoon.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
| public class Meubel { public void plaats() { Console.WriteLine("Meubel geplaatst"); } } class Kast : Meubel { public void open() { Console.WriteLine("Kast geopend"); } } class ServiesKast : Kast { new public void open() { Kast k = new Kast(); k.open(); } } class Bank : Meubel { } static class MeubelCheckerHelper { public static String openableMeubels = "meubeltestcase.Kast;meubeltestcase.ServiesKast"; public static bool isOpenableMeubel(Meubel meubel) { foreach (String meubelclass in openableMeubels.Split(';')) { if (meubel.GetType().ToString() == meubelclass) { return true; } } return false; } } class Program { static void Main(string[] args) { Kast meubel1 = new Kast(); ServiesKast meubel2 = new ServiesKast(); List<Meubel> meubels = new List<Meubel>(); meubels.Add(meubel1); meubels.Add(meubel2); foreach (Meubel m in meubels) { if (MeubelCheckerHelper.isOpenableMeubel(m)) { foreach (String meubelclass in MeubelCheckerHelper.openableMeubels.Split(';')) { if (meubelclass == "meubeltestcase.Kast") { Kast kast = (Kast)m; kast.open(); } } } } Console.ReadKey(); } } |
Het grote voordeel van interfaces is juist dat je nog een gedeelte van je abstractie bewaard en dat je er dus eventueel meerdere tegelijk kan invoeren. Dan heb je iets als "ZitbareMeubelKist implements Seatable, Openable, Rollable" wat je dan vervolgens kan testen op instanceof Seatable, danwel Openable of Rollable... Hoe meer van dat soort "labels" aan een object te hangen zijn, hoe lastiger het wordt om een starre hierarchie aan te houden.
Jouw string-check is enkel een vervanger voor de instanceof op een te diep niveau en voegt verder niet zoveel toe. Nouja, hoofdpijn zodra je dingen wilt refactoren. Want if(m instanceof Openable) vangt ook gelijk die te openen meubelkist af, terwijl jij er een elseif bij moet zetten.
Jouw code is nu equivalent aan if(m instanceof Kast), alleen dan zonder de daarvoor aangerijkte hulpmiddelen uit de taal zelf.
[ Voor 6% gewijzigd door ACM op 06-04-2011 20:52 ]
Je hebt gelijk, ik kwam erachter dat dit inderdaad niet klopte, hier is een nieuwe versie:Amras schreef op woensdag 06 april 2011 @ 20:47:
Waarom maakt een Servieskast een nieuwe Kast en opent deze, terwijl er wordt gevraagd om de Servieskast te openen?
In deze versie kan je categorieen aangeven, zoals een kast of deur. Als de applicatie dan ziet dat de classname het woord "Kast" bevat, dan weet hij dat de superclass een kast is.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| public class Meubel { public void plaats() { Console.WriteLine("Meubel geplaatst"); } } class Kast : Meubel { public void open() { Console.WriteLine("Kast geopend"); } } class ServiesKast : Kast { } class Bank : Meubel { } static class MeubelCheckerHelper { public static String openableMeubels = "kast;deur"; public static bool isOpenableMeubel(Meubel meubel) { foreach (String meubelclass in openableMeubels.Split(';')) { if (meubel.GetType().ToString().ToLower().Contains(meubelclass.ToLower())) { return true; } } return false; } } class Program { static void Main(string[] args) { Kast meubel1 = new Kast(); ServiesKast meubel2 = new ServiesKast(); List<Meubel> meubels = new List<Meubel>(); meubels.Add(meubel1); meubels.Add(meubel2); foreach (Meubel m in meubels) { if (MeubelCheckerHelper.isOpenableMeubel(m)) { foreach (String meubelclass in MeubelCheckerHelper.openableMeubels.Split(';')) { if (m.GetType().ToString().ToLower().Contains("kast")) { Kast kast = (Kast)m; kast.open(); } } } } Console.ReadKey(); } } |
[ Voor 4% gewijzigd door Big Joe op 06-04-2011 21:01 ]
Je hoeft alleen maar instanceof te doen als je te maken hebt met de generieke verzameling meubels. Als je een Opener class hebt die een collectie Openables beheert, kun je daar prima Kasten (implements Openable) aan toevoegen.Woy schreef op woensdag 06 april 2011 @ 19:57:
[...]
Hoe zie je het dan voor je? Natuurlijk is een Kamer ( o.i.d. ) niet de aangewezen class om meubels te openen, maar als er bijvoorbeeld een Persoon is die dat zou doen, dan moet die alsnog uit de kamer alle IOpenable objecten verkrijgen, en dan heb je daar alsnog iets van instanceof voor gebruiken.
Of je loopt te trollen, of je hebt een plaat voor je kop die ze in Japan graag zouden willen hebben om die reactors af te schermen...Big Joe schreef op woensdag 06 april 2011 @ 20:52:
[...]
Je hebt gelijk, ik kwam erachter dat dit inderdaad niet klopte, hier is een nieuwe versie:
Dit kun je niet serieus menen.
[ Voor 45% gewijzigd door Herko_ter_Horst op 06-04-2011 20:56 ]
"Any sufficiently advanced technology is indistinguishable from magic."
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
| using System; using System.Collections.Generic; namespace MeubelOO { interface IOpenable { void Open(); } interface ISittable { void SitOn(); } interface IRollable { void Roll(); } interface ILockable { void ToggleLock(); } abstract class Meubel { public double Width { get; set; } public double Height { get; set; } } class Kast : Meubel, IOpenable { public void Open() { Console.WriteLine(this.GetType().Name + ": Ik ben nu open."); } } class Dressoir : Kast { } class Archiefkast : Kast { } class Kantoorkast : Archiefkast, ILockable { public bool IsLocked { get; private set; } public void ToggleLock() { Console.WriteLine(this.GetType().Name + (IsLocked ? ": Ik ben van het slot gehaald." : ": Ik ben op slot gedaan.")); IsLocked = !IsLocked; } } class Stoel : Meubel, ISittable { public void SitOn() { Console.WriteLine(this.GetType().Name + ": Iemand zit nu op mij."); } } class Bureaustoel : Stoel, IRollable { public void Roll() { Console.WriteLine(this.GetType().Name + ": They see me rollin'"); } } class LuxeBureaustoel : Bureaustoel { } class Program { static void Main(string[] args) { var alleMeubels = new List<Meubel> { new Kast(), new Dressoir(), new Archiefkast(), new Kantoorkast(), new Stoel(), new Bureaustoel(), new LuxeBureaustoel() }; foreach (var meubel in alleMeubels) { if (meubel is IOpenable) { ((IOpenable)meubel).Open(); } if (meubel is ISittable) { ((ISittable)meubel).SitOn(); } if (meubel is IRollable) { ((IRollable)meubel).Roll(); } if (meubel is ILockable) { ((ILockable)meubel).ToggleLock(); } } Console.ReadKey(); } } } |
Kast: Ik ben nu open. Dressoir: Ik ben nu open. Archiefkast: Ik ben nu open. Kantoorkast: Ik ben nu open. Kantoorkast: Ik ben op slot gedaan. Stoel: Iemand zit nu op mij. Bureaustoel: Iemand zit nu op mij. Bureaustoel: They see me rollin' LuxeBureaustoel: Iemand zit nu op mij. LuxeBureaustoel: They see me rollin'
Ik zie echt niet in hoe je dit efficiënt kunt bereiken zónder te kijken of een bepaalde interface geïmplementeerd wordt. Dat gedoe met strings is wel enorm lelijk en zeker niet zaligmakend, zie mijn voorbeeldje met een Dressoir. Het is zeker een kast, maar er zit geen Kast in de naam.
We are shaping the future
Zo dus nu kan ik ook boeken in mijn KasteelBig Joe schreef op woensdag 06 april 2011 @ 20:52:
[...]
Je hebt gelijk, ik kwam erachter dat dit inderdaad niet klopte, hier is een nieuwe versie:
In deze versie kan je categorieen aangeven, zoals een kast of deur. Als de applicatie dan ziet dat de classname het woord "Kast" bevat, dan weet hij dat de superclass een kast is.
Maar hoe komt die Opener aan de collectie? Waarschijnlijk word er op een bepaald moment bedacht dat alle Openable objecten in een Kamer geopend moeten worden ( Of alleen de eerste Container die Openable is waar genoeg ruimte is om iets in op te bergen ). Bij het creëren van de objecten, of het toevoegen aan de Kamer weet je immers nog niet wat er allemaal met de objecten gaat gebeuren.Herko_ter_Horst schreef op woensdag 06 april 2011 @ 20:54:
[...]
Je hoeft alleen maar instanceof te doen als je te maken hebt met de generieke verzameling meubels. Als je een Opener class hebt die een collectie Openables beheert, kun je daar prima Kasten (implements Openable) aan toevoegen.
Nogmaals: Het is goed om te zorgen dat je collectie niet te generiek is, maar je ontkomt er gewoon niet altijd aan.
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
En inderdaad, zoals hierboven al gezegd, ga je dan nat bij een Kasteel. Seriously; je hebt drastisch (bij)scholing nodig als je ervan overtuigd bent dat dit goede code is. En zoals anderen al zeiden:je bent misschien best een goede programmeur, maar dit gaat nergens over en is echt beneden peil. Al zit je 50 jaar in 't vak; als je sinds dag 1 niet meer hebt bijgeleerd dan maakt 't je niet nu een goede programmeur. Het spijt me zeer. Op dit vlak moet je gewoon wat leren zo blijkt. Neem dat als een vent en leer er van en doe er iets mee. Daar word je sterker en beter vanBig Joe schreef op woensdag 06 april 2011 @ 20:52:
In deze versie kan je categorieen aangeven, zoals een kast of deur
1
| public static String openableMeubels = "kast;deur"; |
Dit is gewoon een poor-mans wannabe-interface-iets. Gebruik dan gewoon een interface; daar is 't voor.
[ Voor 19% gewijzigd door RobIII op 06-04-2011 22:04 ]
There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.
Je eigen tweaker.me redirect
Over mij
De Opener komt aan de collectie doordat objecten er bij creatie ingestopt worden. Dit is uiteraard afhankelijk van het domein. Je zult dus per domein moeten analyseren wat de juiste aanpak is. Instanceof is daarbij een indicatie dat er mogelijk nog iets scheef zit.Woy schreef op woensdag 06 april 2011 @ 21:49:
[...]
Maar hoe komt die Opener aan de collectie? Waarschijnlijk word er op een bepaald moment bedacht dat alle Openable objecten in een Kamer geopend moeten worden ( Of alleen de eerste Container die Openable is waar genoeg ruimte is om iets in op te bergen ). Bij het creëren van de objecten, of het toevoegen aan de Kamer weet je immers nog niet wat er allemaal met de objecten gaat gebeuren.
Nogmaals: Het is goed om te zorgen dat je collectie niet te generiek is, maar je ontkomt er gewoon niet altijd aan.
Stel dat we het hebben over een tekst-RPG waarin je van Kamer naar Kamer kan gaan. In een Kamer staan Meubels. In een Kamer zijn er ook objecten die open kunnen (c.q. waar de speler mee kan interacteren). Voor de beschrijving zal de Kamer een collectie van Meubels bevatten. Daarnaast bevat de Kamer (of mogelijk een andere klasse) een collectie met Openables. Hierin zitten niet alleen de Kasten (of andere Meubels-die-open-kunnen), maar bijv. ook Boeken, Ramen en Deuren.
Op het moment dat je de Kamer vult, kun je de Kast (die Openable implementeert) simpelweg in beide collecties stoppen.
Iedereen neemt nu die ene generieke collectie van meubels als uitgangspunt. Dan kom je niet zo gauw af van instanceof (of een variant daarop). Je moet naar de requirements kijken om te bepalen of die collectie daar wel terecht staat.
[ Voor 9% gewijzigd door Herko_ter_Horst op 06-04-2011 22:08 ]
"Any sufficiently advanced technology is indistinguishable from magic."
Hoe zie je dat vullen dan voor jeHerko_ter_Horst schreef op woensdag 06 april 2011 @ 22:02:
[...]
Op het moment dat je de Kamer vult, kun je de Kast (die Openable implementeert) simpelweg in beide collecties stoppen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| class Kamer { void Add(Meubel m) { meubels.Add(m); //Wat als m hier ook een IOpenable is? } void Add(IOpenable o) { openables.Add(o); //Hoe voeg je hier toe aan meubels? Je weet immers niet zeker of "o" ook een meubel is } } |
Tuurlijk, daar ben ik het met je eens. Als je bij creatie al weet in welke categorieën je het kan indelen, moet je dat zeker niet laten. Maar je komt dit soort situaties IMHO toch regelmatig tegen.Iedereen neemt nu die ene generieke collectie van meubels als uitgangspunt. Dan kom je niet zo gauw af van instanceof (of een variant daarop). Je moet naar de requirements kijken om te bepalen of die collectie daar wel terecht staat.
[ Voor 27% gewijzigd door Woy op 06-04-2011 22:14 ]
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
Iemand maakt die Kast. Die weet dus dat Kast zowel een Meubel als een Openable is en kan daarom beide methodes aanroepen.Woy schreef op woensdag 06 april 2011 @ 22:11:
[...]
Hoe zie je dat vullen dan voor je
C#:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Kamer { void Add(Meubel m) { meubels.Add(m); //Wat als m hier ook een IOpenable is? } void Add(IOpenable o) { openables.Add(o); //Hoe voeg je hier toe aan meubels? Je weet immers niet zeker of "o" ook een meubel is } }
"Any sufficiently advanced technology is indistinguishable from magic."
Dat zou kunnen, maar dat is ook foutgevoelig, immers kan er dan een Meubel in de kamer staan die Openable is, maar die door de Opener niet meer geopend kan worden. Als een class immers later alsnog Openable gaat implementeren word er geheid vergeten om die 2e Add aanroep toe te voegen. En je gaat er in dat geval ook van uit dat de gene die de Kast maakt ook degene is die hem in de Kamer plaatst. Misschien komt hij wel uit een MeubelFactory, en voor die class is het niet interessant dat het een Openable is, voor de Kamer blijkbaar wel, en dus zal die daarop moeten controleren.Herko_ter_Horst schreef op woensdag 06 april 2011 @ 22:15:
[...]
Iemand maakt die Kast. Die weet dus dat Kast zowel een Meubel als een Openable is en kan daarom beide methodes aanroepen.
Het staat IMHO netter om in de Kamer class een instanceof te gebruiken, dan om te verplichten dat hij 2x aan de Kamer toegevoegd moet worden.
Het is ook niet dat ik wil zeggen dat je altijd maar instanceof moet gebruiken. Het is best goed om je af te vragen of het op die plek wel de beste oplossing is. Maar ik moet toch zeggen dat ik toch wel regelmatig plekken tegenkom waar ik het gebruik.
[ Voor 25% gewijzigd door Woy op 06-04-2011 22:29 ]
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
En een dressoir? Die kan niet geopend worden? En een kastalein daar steek je maar een breekijzer in?Big Joe schreef op woensdag 06 april 2011 @ 20:52:
Je hebt gelijk, ik kwam erachter dat dit inderdaad niet klopte, hier is een nieuwe versie:
In deze versie kan je categorieen aangeven, zoals een kast of deur. Als de applicatie dan ziet dat de classname het woord "Kast" bevat, dan weet hij dat de superclass een kast is.
Ik hoop van harte dat je loopt te trollen. Als je echt denkt dat dit correct ben je gewoon de slechtste Java developer die ik ooit gezien heb, en ik heb een HOOP slechte Java code gezien.
https://niels.nu
Ok, wacht, je zegt iets te geven om de performance van een applicatie, maar toch ga je élke keer een enkele string splitten op ";", die in een array zetten, er overheen lopen, en de array vervolgens weer weggooien.Big Joe schreef op woensdag 06 april 2011 @ 20:12:
Het is een kleine opoffering die je moet maken als je echt om de performance van je applicatie geeft.
Vertel me eens, hoe heeft jouw aanpak precies geholpen met het verbeteren van de performance?
Als je al zo eigenwijs wilt zijn om je eigen "pattern" aan te houden, doe het dan alsjeblieft gewoon zo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| static class MeubelCheckerHelper { public static Type[] openableMeubels = { typeof(Kast), /* ... */ }; public static bool isOpenableMeubel(Meubel meubel) { foreach (Type meubelclass in openableMeubels) { if (meubelclass.IsInstanceOfType(meubel)) { return true; } } return false; } } |
Dan werkt het ook nog als 'meubel' een subclass is van Kast, je kunt naar hartelust refactoren, en het performt ook nog eens een stuk beter.
[ Voor 43% gewijzigd door .oisyn op 06-04-2011 23:22 ]
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Maar dan nog heb je het probleem dat je wel weet dat een specifiek meubel te openen is, maar weet je nog niet hoe je hem kunt openen. Dus zul je met reflection of dynamic types aan de gang moeten, of alsnog een interface IOpenable hebben waar je heen kunt casten..oisyn schreef op woensdag 06 april 2011 @ 23:10:
[...]
Dan werkt het ook nog als 'meubel' een subclass is van Kast, je kunt naar hartelust refactoren, en het performt ook nog eens een stuk beter.
1
2
3
4
5
| Meubel b = new Kast(); if(MeubelHelper.IsOpenable(b)) { b.Open();//Compiler error } |
Als je al weet dat het een Kast is dan heeft het geen nut om IsOpenable te doen, en als je het niet weet, dan kun je er niks mee dat hij Openable is ( Behalve als er dus een interface is
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
Java variant daarvan is.oisyn schreef op woensdag 06 april 2011 @ 23:10:
Als je al zo eigenwijs wilt zijn om je eigen "pattern" aan te houden, doe het dan alsjeblieft gewoon zo:
C#:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static class MeubelCheckerHelper { public static Type[] openableMeubels = { typeof(Kast), /* ... */ }; public static bool isOpenableMeubel(Meubel meubel) { foreach (Type meubelclass in openableMeubels) { if (meubelclass.IsInstanceOfType(meubel)) { return true; } } return false; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| static class MeubelCheckerHelper { public static Class[] openableMeubels = { Kast.class, /* ... */ }; public static bool isOpenableMeubel(Meubel meubel) { for(Class meubelclass: openableMeubels) { if (meubelclass.isInstance(meubel)) { return true; } } return false; } } |
Enige voordeel is dat deze code tenminste nog refactor vriendelijk is.....
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
@Janoz: Java kent die vorm van foreach toch niet?
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Je kan ook generics gebruiken!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| package area51; import java.util.LinkedList; import java.util.List; public class Kamer { public static abstract class Meubel {} public static interface Openable { void open(); } public static class Kast extends Meubel implements Openable { @Override public void open() { System.out.println("Kast is geopend"); } } public static class NarniaKast extends Kast { @Override public void open() { System.out.println("Je hebt narnia gevonden"); } } public static class Kist extends Meubel implements Openable { @Override public void open() { System.out.println("Ik kan ook een kist openen"); } } public static void main(String... args) { List<Kast> kasten = new LinkedList<Kast>(); kasten.add(new Kast()); kasten.add(new Kast()); kasten.add(new NarniaKast()); kasten.add(new Kast()); openMeubels(kasten); openMeubels(verzamelOpenableMeubels(new Kast(), new NarniaKast(), new Kist())); } public static <T extends Meubel & Openable> List<T> verzamelOpenableMeubels(T ...ts){ List<T> list = new LinkedList(); for (T t:ts) list.add(t); return list; } public static <T extends Meubel & Openable> void openMeubels(Iterable<T> list){ for (T t:list) t.open(); } } |
owkee, het verdient geen prijs en geeft een paar warnings maar het compiled en het runt
OMG ja daar had ik nog niet eens bij stilgestaan. Dat stuk dat Big Joe na de IsOpenable() dan alsnog gaat kijken of het een Kast is met een specifieke cast had ik ook helemaal gemist. Mijn god, wat heb je er dan überhaupt aanWoy schreef op donderdag 07 april 2011 @ 09:01:
Als je al weet dat het een Kast is dan heeft het geen nut om IsOpenable te doen, en als je het niet weet, dan kun je er niks mee dat hij Openable is ( Behalve als er dus een interface is)
The plot thickens...
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Wat is het nou? Hoe kan feedback leveren beneden pijl zijn? Dat jij nog nooit (negatieve) feedback hebt gehad, maakt nog niet dat jij gelijk hebt.Big Joe schreef op woensdag 06 april 2011 @ 20:12:
[...]
Verder vind ik het beneden peil dat jullie mijn code op zo'n negatieve manier afkraken. Ik zit al 15 jaar in het vak en heb nog nooit negatieve feedback gehad op mijn code!
Het betekent alleen dat 'ie geen mensen kent die op hetzelfde niveau of hoger programmeren. En dat is eerder slecht voor je ontwikkeling dan goed... Ik moet er niet aan denken dat ik een weekje voorbij laat gaan zonder kritiek van mijn collega's, laat staan 15 jaar zonder kritiek.Vinnienerd schreef op donderdag 07 april 2011 @ 12:18:
[...]
Wat is het nou? Hoe kan feedback leveren beneden pijl zijn? Dat jij nog nooit (negatieve) feedback hebt gehad, maakt nog niet dat jij gelijk hebt.
'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.
*kuch* feedback *kuch*Armageddon_2k schreef op donderdag 07 april 2011 @ 13:08:
Ach 't is Kaddafi toch ook prima afgegaan, en die zat een jaar of 40 prima op zn plek zonder commentaar
Sure ... Programma-code is een organisch iets. Requirements veranderen na verloop van tijd, en een naam die gisteren zinnig was, kan morgen plots helemaal nergens meer opslaan. Dan ga je mij niet zeggen dat je bij code-wijzigingen om die veranderende functionaliteit te implementeren, die class niet gaat refactoren en een andere naam geven ...Big Joe schreef op woensdag 06 april 2011 @ 20:12:
[...]
Bij mij op mijn werk gebruiken we het waterval model en is dit niet van toepassingen. Klassendiagrammen en ontwerpen worden van tevoren opgezet met UML, zodat namen vaststaan en nooit zullen veranderen. Het is een kleine opoffering die je moet maken als je echt om de performance van je applicatie geeft.
Of ... Jullie requirements veranderen nooit, en jullie code is gebeiteld in steen ? Eens gereleased komen er nooit nieuwe versies ?
Een ugly implementatie van een instanceof() operator dus ?Een helper is in dit geval een soort controller die de applicatie helpt om te bepalen welke meubelen een kast zijn.
https://fgheysels.github.io/
https://niels.nu
Woy schreef op donderdag 07 april 2011 @ 09:52:
offtopic:
@Janoz: Java kent die vorm van foreach toch niet?
Heeey ... Had Janoz nou eerst echt iets als "foreach" staan en heeft hij dat later gewijzigd zonder dat wij dat kunnen zien of bedoelde Woy nu dat hij de ":" manier van itereren niet kent?Janoz schreef op donderdag 07 april 2011 @ 09:58:
oepsie... Maar deze vorm wel (/me tikt altijd wel foreach om op die manier het eclipse template aan te roepen)
In geval Janoz, foei, in geval Woy, dat zit er in sinds 1.5.
En verder heeft .oisyn he-le-maal gelijk dat je het steeds parsen van strings om iets typeof/instanceof-achtigs te krijgen echt niet moet willen. Gewoon goed gebruik maken van interfaces.
Kijk, onderaan, bij normale stervelingen zie je tenminste wel of er iets in het bericht is gehackt.
[ Voor 17% gewijzigd door Killemov op 08-04-2011 21:47 ]
Hey ... maar dan heb je ook wat!
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'