[Java] Code 'verbergen'

Pagina: 1
Acties:

  • bodiam
  • Registratie: December 2001
  • Laatst online: 31-12-2024
Okee, excuses voor de irritante topictitel, mijn creativiteit is even ergens anders mee bezig.

Ik had een vraagje: op dit moment ben ik bezig met een API te schrijven, welke berichten leest (parsed) en schrijft (serialized). Deze API komt beschikbaar voor diverse clients.

Een bericht kan er als volgt uitzien:
<message>
<id>4</id>
<text>hoi</text>
</message>

Ik wou dat ze zo simpel waren, maar het illustreert mijn probleem redelijk.

de class message ziet er als volgt uit:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Message {
  private int id;
  private String message;
  public Message(String message) {
    id = generateUniqueId(...);
    this.message = message;
  }
  public Message(String message, int id) {
    this.message = message;
    this.id = id;
  }
  // getters en setters
}


Probleem is echter dat mensen die de API gaan gebruiken, steeds met vragen komen, hoe ze een bericht moeten maken, vanwege de 2 constructors. (Opmerkingen als: dan moeten ze de documentatie maar lezen, of vertel het ze gaat hier niet echt op: Het domein is te omvangrijk en te ingewikkeld daarvoor).
"Clients" dienen bij het versturen (/serializen) van berichten de constructor Message(message) aan te roepen, en niet degene met het id. Deze is voor het inlezen (parsen) van binnen gekomen berichten.

Vraag is nu dus: wat is een goede manier om er voor te zorgen dat mijn Parser wel berichten kan maken met een niet gegenereerd id, en hoe kan ik voorkomen dat anderen (Serializers/Clients) dat niet kunnen? Ik had al een kleine oplossing:

Java:
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 Message {
  protected message;
  protected id;
  public Message(String message) {
    id = generateUniqueId();
    ..

  // set en get voor message
  public int getId() {
    return this.id;
  }
}

public class ParsedMessage extends Message {
  public ParsedMessage(String message, int id) {
    this.message = message;
    this.id = id;
  }

  public void setId( int id ) {  // niet per se nodig
    this.id = id;
  }
}

Probleem hierbij is dat dit echt heel veel werk kost. Er zijn een stuk of 50 a 60 domein klassen, dit gaan er op deze manier een stuk of 100 a 120 worden. Dit geeft een onoverzichtelijke bende (okee, meerdere packages zijn uiteraard mogelijk), en geeft me het idee dat ik zwaar ineffectief bezig ben.

Iemand suggesties? ;)

  • whoami
  • Registratie: December 2000
  • Laatst online: 15:31
Moet de constructor die door de serializer/deserializer opgeroepen wordt, public zijn ?

Indien ja, dan zou je toch nog kunnen overwegen om die constructor private te maken, en dan in die serializer mbhv reflection die constructor aan te roepen.

Je zou die constructors ook zowiezo kunnen 'verbergen' door gebruik te gaan maken van een factory class. Die factory class heeft dan bv 2 methods: een die je CreateMessage heet, en eentje die je CreateMessageFromSerializedStream oid heet.
Je kan dan die factory in dezelfde package opnemen als jouw class, en de constructor een 'beperktere' access modifier meegeven (internal in .NET, weet niet wat het in Java wordt).

https://fgheysels.github.io/


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Kun je niet bij het parsen eerst een id aanmaken? En dan pas het message object? Dus wat is de reden dat je bij het parsen niet meteen een message id kunt bepalen?

  • momania
  • Registratie: Mei 2000
  • Laatst online: 29-04 14:22

momania

iPhone 30! Bam!

Waarom gebruik je bij het parsen dan ook niet de setters ipv een constructor?
Dan kan je die constructor met id erin gewoon weg laten toch? :)

Verder kan je je clients gewoon een bepaalde interface aanbieden, ipv de implementatie.
Bijvoorbeeld door met het factory pattern om objecten aan te maken.

Neem je whisky mee, is het te weinig... *zucht*


  • bodiam
  • Registratie: December 2001
  • Laatst online: 31-12-2024
Alarmnummer schreef op dinsdag 27 september 2005 @ 14:44:
Kun je niet bij het parsen eerst een id aanmaken? En dan pas het message object? Dus wat is de reden dat je bij het parsen niet meteen een message id kunt bepalen?
Dat kan wel. Echter, iedereen die de API gebruikt kan dat ook, dus elke client kan zeggen:
Message m = new Message("Hoi Erik", 666);

terwijl dat echt niet de bedoeling is: alleen de berichtenparser mag de id's zetten (van de inkomende berichten). Bij uitgaande berichten is een uniek id vereist, welke automatisch gegenereert wordt door Message.

  • whoami
  • Registratie: December 2000
  • Laatst online: 15:31
momania schreef op dinsdag 27 september 2005 @ 14:45:
Waarom gebruik je bij het parsen dan ook niet de setters ipv een constructor?
Dan kan je die constructor met id erin gewoon weg laten toch? :)
Die constructor zorgt er echter wel voor dat je zeker bent dat jouw object in dat geval een id heeft...

https://fgheysels.github.io/


  • bodiam
  • Registratie: December 2001
  • Laatst online: 31-12-2024
whoami schreef op dinsdag 27 september 2005 @ 14:43:
Moet de constructor die door de serializer/deserializer opgeroepen wordt, public zijn ?

Indien ja, dan zou je toch nog kunnen overwegen om die constructor private te maken, en dan in die serializer mbhv reflection die constructor aan te roepen.
Das is een optie, maar op dit moment sta ik nog niet zo heel positief tegenover het gebruik van reflection, vanwege fouten die je tijdens het compilen niet kunt zien. Ik was daarom eigenlijk meer op zoek naar oplossing welke tijdens compile time wat fouten kan weergeven.
Je zou die constructors ook zowiezo kunnen 'verbergen' door gebruik te gaan maken van een factory class. Die factory class heeft dan bv 2 methods: een die je CreateMessage heet, en eentje die je CreateMessageFromSerializedStream oid heet.
Hebben we inderdaad ook overwogen. Dat is op zich nog helemaal geen rare gedachte, behalve dat er dan ook 60 factories gemaakt moeten worden. :(
Je kan dan die factory in dezelfde package opnemen als jouw class, en de constructor een 'beperktere' access modifier meegeven (internal in .NET, weet niet wat het in Java wordt).
Binnen Java wordt dat package private genoemd. Wellicht dat ik zo'n optie wel kies, maarja, ik ben lui, ik heb geen 'zin' om elke keer hetzelfde te moeten maken (lees: factories, extends classes, etc).

  • momania
  • Registratie: Mei 2000
  • Laatst online: 29-04 14:22

momania

iPhone 30! Bam!

whoami schreef op dinsdag 27 september 2005 @ 14:48:
[...]

Die constructor zorgt er echter wel voor dat je zeker bent dat jouw object in dat geval een id heeft...
true, 't is maar net op wat voor manier je het wil parsen :)

Anyway, als je niet wil dat een user bepaalde constructors gebruikt blijft een factory aanbieden het makkelijkst imo.

En ondanks dat men geen documentatie wil lezen, moet dat niet jouw probleem worden. Als gedocumenteerd is hoe iets dient te worden gebruikt en men houdt zich daar niet aan, moet men ook niet zeuren als iets niet werkt :)

Neem je whisky mee, is het te weinig... *zucht*


  • bodiam
  • Registratie: December 2001
  • Laatst online: 31-12-2024
whoami schreef op dinsdag 27 september 2005 @ 14:48:
[...]

Die constructor zorgt er echter wel voor dat je zeker bent dat jouw object in dat geval een id heeft...
Plus ik moet nog steeds de setId methode hebben, welke ook door een client aanroepen kan worden. Het is niet zo vanzelfsprekend dat een client de setId method niet mag aanroepen namelijk. Wellicht bij deze methode wel, maar er zijn per class een stuk of 20 attributen, keer 50 tot 60 klasses...das vrij veel, zeker omdat deze allemaal vrij domein specifiek zijn (lees: criptisch :))

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

bodiam schreef op dinsdag 27 september 2005 @ 14:47:
[...]
Dat kan wel. Echter, iedereen die de API gebruikt kan dat ook, dus elke client kan zeggen:
Message m = new Message("Hoi Erik", 666);

terwijl dat echt niet de bedoeling is: alleen de berichtenparser mag de id's zetten (van de inkomende berichten). Bij uitgaande berichten is een uniek id vereist, welke automatisch gegenereert wordt door Message.
Tip:
Je gaat soms hele complexe situaties creeeren op het moment dat je alles compiletechnisch wilt afdwingen. Soms kun je dit beter oplossen met een goed stuk documentatie.

  • momania
  • Registratie: Mei 2000
  • Laatst online: 29-04 14:22

momania

iPhone 30! Bam!

bodiam schreef op dinsdag 27 september 2005 @ 14:52:
[...]
Hebben we inderdaad ook overwogen. Dat is op zich nog helemaal geen rare gedachte, behalve dat er dan ook 60 factories gemaakt moeten worden. :(
Als je eclipse gebruikt oid, zullen daar vast wel mooie plugins voor zijn die dat voor je doen ;)
Zoveel werk kan dat nou ook weer niet zijn.

Neem je whisky mee, is het te weinig... *zucht*


  • bodiam
  • Registratie: December 2001
  • Laatst online: 31-12-2024
En ondanks dat men geen documentatie wil lezen, moet dat niet jouw probleem worden. Als gedocumenteerd is hoe iets dient te worden gebruikt en men houdt zich daar niet aan, moet men ook niet zeuren als iets niet werkt :)
Ha, mijn idee! Maar helaas werkt het zo niet. Het zou fijn zijn als mensen zeggen: Die API is zo duidelijk, het is onmogelijk om daar fouten mee te maken, ipv "$$#(#(#, werkt het niet. Nee, je mag die setter niet aanroepen, en die ook niet...en die, etc...".

  • momania
  • Registratie: Mei 2000
  • Laatst online: 29-04 14:22

momania

iPhone 30! Bam!

bodiam schreef op dinsdag 27 september 2005 @ 14:57:
[...]


Ha, mijn idee! Maar helaas werkt het zo niet. Het zou fijn zijn als mensen zeggen: Die API is zo duidelijk, het is onmogelijk om daar fouten mee te maken, ipv "$$#(#(#, werkt het niet. Nee, je mag die setter niet aanroepen, en die ook niet...en die, etc...".
Je hoeft ook niet als een leraar te zeggen wat er allemaal niet mag... zou lekker worden.

Stel je voor dat men bij de Integer in de javadocs moet aangeven bij equals(Object o) wat je allemaal _niet_ als object mag meegeven.. 8)7

Neem je whisky mee, is het te weinig... *zucht*


  • CyberSnooP
  • Registratie: Augustus 2000
  • Laatst online: 31-03 16:47

CyberSnooP

^^^^ schrijft --->

bodiam schreef op dinsdag 27 september 2005 @ 14:52:
Binnen Java wordt dat package private genoemd. Wellicht dat ik zo'n optie wel kies, maarja, ik ben lui, ik heb geen 'zin' om elke keer hetzelfde te moeten maken (lees: factories, extends classes, etc).
Je zit ook niet vast aan het factory pattern en je kunt (static) Creation Methods toevoegen aan de klasse zelf en je constructor verbergen, alsvolgt:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Message {
  private int id;
  private String message;

  private Message(String message, int id) {
    this.message = message;
    this.id = id;
  }

  public static createOutgoingMessage(String message) {
    return new Message(message, generateUniqueId(...));
  }

  public static createIncomingMessageWithId(String message, int id) {
    return new Message(message, id);
  }

  // getters en setters
}

Dit is nauwelijks extra werk en doordat je zelf namen mag kiezen voor je constructors is wordt het voor de gebruikers een stuk duidelijker.

(Deze oplossing heet Replace Constructors with Creation Methods in het boek Refactoring to Patterns van Joshua Kerievsky wat vol staat met dit soort oplossingen)

|_____vakje______|


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

CyberSnooP schreef op dinsdag 27 september 2005 @ 16:06:
[...]

Je zit ook niet vast aan het factory pattern en je kunt (static) Creation Methods toevoegen aan de klasse zelf en je constructor verbergen, alsvolgt:
Dit is niet veiliger dan het begin. Je kunt nu via een factory method alsnog een message aanmaken met willekeurige id en dat is precies hetgeen hij wou verhinderen.

Het zou eventueel opgelost kunnen worden met een package friendly constructor(alhoewel je met een shadow package ook erbij kan.. en je uiteraard niet kunt verhinderen dat iemand in de orginele package een 'bad' class erbij zet. En verder hebben protected constructors ook 0.0 toegevoegde functionaliteit aangezien niemand kan verhinderen dat je een subclass maakt die wel een public constructor heeft.

Daarom is mijn advies nog steeds: los dit op met een goed stuk documentatie. Als je programmeurs voor alles moet afschermen wat ze fout kunnen doen, kan je beter stoppen. Aangezien je uiteindelijk toch alles wel om zeep kunt helpen.

[edit]
Neemt trouwens niet weg dat factorymethods erg handig zijn en vaak veel duidelijker dan constructors omdat je de intentie beter kunt beschrijven.

[ Voor 64% gewijzigd door Alarmnummer op 27-09-2005 16:43 ]


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
Zoals ik het lees is dit een probleem in het design, niet zo zeer in het aantal niet-afgeschermde constructors. Je mengt de verantwoordelijkheid van een domeinspecifiek object (één van de 60 verschillende soorten berichten) met een technische verantwoordelijkheid (het identificeren van een berichtinstantie). Objectgeoriënteerd ontwerp leert dat dit niet verstandig is, omdat zoals je zelf al aangeeft dit onduidelijk is en onderhoudsgevoelig.

Je zou een hoofdklasse BaseMessage kunnen maken en alle messages hiervan kunnen laten erven. Het is dan vrij eenvoudig om één factory te maken die je deserializer kan bedienen. Je kunt ook met compositie werken, en een Id klasse maken, die als veld in iedere message klasse wordt opgenomen. Voor de Id klasse heb je opnieuw dan maar één factory nodig.

Beide methodes zijn niet fool-proof, omdat de ontwikkelaar altijd de factory zelf kan aanspreken. Het is alleen wel meer werk voor ze en het onderscheid is duidelijker. Ik zou persoonlijk er wél voor zorgen dat je als programmeur invloed kunt hebben op het id van een object, omdat je dit soort id's vaak gebruikt in unit-tests.
Pagina: 1