Προς το περιεχόμενο

Προτεινόμενες αναρτήσεις

Δημοσ.

Καλησπέρα. Θέλω να ρωτήσω αν υπάρχει κάποιος τρόπος στην Java μέσα σε μια κλάση να μην ορίσουμε εξαρχης τον τύπο ενός πεδίου αλλά αυτός να εξαρτάται από μια μεταβλητή εκτός κλάσσης πχ στον constructor. Ας πούμε αν μια boolean μεταβλητή είναι true τότε το πεδίο α είναι int , αλλιώς το πεδίο α είναι char. Ακούγεται λογικά αδύνατο αλλά όποια άλλη βοήθεια ειναι ευπρόσδεκτη. Ευχαριστώ!

Δημοσ.

Χωρις να το εχω δοκιμασει, μπορεις να δηλωσεις στην κλαση το πεδιο ως Object γενικό και στον constructor αναλογα με την τιμη της boolean να κανεις cast το Object στον τυπο που θέλεις.

Δημοσ.

Ναι υπο προυποθέσεις. Με primitive types όχι δεν γίνεται. Αλλά με Objects γίνεται, π.χ :

	public void doSomething() {
                boolean b = true;
		Object a;

		if ( {
			a = new Integer(5);
		} else {
			a = new Character('a');
		}

		System.out.println(a);

	}
Δημοσ.

Νομίζω είναι σκόπιμο να πούμε πως ναι μεν ο Zakk σου έδωσε μια "μηχανική" λύση στο πρόβλημά σου, αλλά αυτό που πας να κάνεις είναι κλασικό παράδειγμα που οδηγεί σε κακό κώδικα (όχι ότι δε θα δουλέψει ή οτι άμα το κάνεις θα βγει μπαμπούλας να σε φάει -- απλά είναι μια "βρώμικη" λύση η οποία θα πρέπει να αποφεύγεται όταν σχεδιάζεις τη δομή μιας σοβαρής εφαρμογής).

 

Ο λόγος είναι πως αν το κάνεις αυτό, τότε κατα 99% στη συνέχεια θα έχεις το παρακάτω πρόβλημα: άντε και έβαλες μέσα στο πεδίο μιας τιμή όποιου τύπου σου αρέσει. Όταν έρθει η ώρα να τη χρησιμοποιήσεις τι θα κάνεις;

 

Αν η χρήση πρόκειται να γίνει περνώντας την τιμή σε κάποια άλλη method που παίρνει object (όπως πχ η println) τότε όλα καλά. Αν όμως αυτό δε σε καλύπτει τότε τι κάνεις;

 

Προφανώς δε μπορείς να καλέσεις καμία method και γενικότερα να κάνεις τίποτα άλλο με την τιμή αυτή εκτός από equals() όσο είναι typed σαν object. Άρα πρέπει να την κάνεις cast στον πραγματικό της τύπο. Αλλά πώς θα το κάνεις αυτό; Θα πρέπει να γράψεις κώδικα σαν τον παρακάτω:

if (val instanceof Integer) {
    // κάνε κάτι με το ((Integer)val).intValue()
}
else if (val instanceof Character) {
    // κάνε κάτι με το ((Character)val).charValue()
}

Αυτό είναι ήδη άσχημο γιατί στην ουσία θα πρέπει να "αντιγράψεις" όλες τις περιπτώσεις που υπάρχουν μέσα στον constructor που έθεσε τιμή στη val. Αν ποτέ κάνεις κάποια σχετική αλλαγή στον constructor θα πρέπει μετά να πας και στον παραπάνω κώδικα να κάνεις την αντίστοιχη αλλαγή αλλιώς μόλις έγραψες ένα bug.

 

Το ακόμα πιο άσχημο είναι πως αυτή την υποχρέωση μόλις την ανέλαβες σε κάθε ένα σημείο όπου χρειάζεσαι να κάνεις κάτι με τη val ξεχωριστά. Δεν μπορείς να βάλεις τη λογική "από val σε val.intValue() ή σε val.charValue() ανάλογα" μέσα σε κάποια method γιατί έχεις πάλι το ίδιο πρόβλημα που είχες αρχικά: τι θα επιστρέφει αυτή η method?

 

Όταν λοιπόν χρειάζεσαι να κάνεις κάτι τέτοιο χωρίς να γεμίσεις τον κώδικα με τέτοιους κανιβαλισμούς η σωστή λύση είναι να χρησιμοποιήσεις κάποιο κατάλληλο design pattern. Ποιό; Εξαρτάται από το τι ακριβώς σκοπεύεις να κάνεις -- αυτό όμως δεν το ξέρουμε γιατί δε μας είπες τι πρόβλημα έχεις, μας είπες μόνο ποιά φαντάζεσαι πως είναι η λύση του. Αυτό στην πράξη σε βάζει σε ένα λούκι το οποίο μπορεί να είναι το λάθος λούκι, αλλά δε θα το μάθεις ποτέ εκτός αν βρεθεί κάποιος να σου πει με δική του πρωτοβουλία.

 

Ελπίζω τα παραπάνω να τα βρεις χρήσιμα στο μέλλον.

  • Like 4
Δημοσ.

@defacer Καταρχάς να σε ευχαριστήσω για τις πολύ χρήσιμες συμβουλές. Η αλήθεια είναι οτι με βοηθάει η λύση των παιδιών αλλά πραγματικά όπως είπες μόνο στον constructor. Στον υπόλοιπο κώδικα θα χρειαστώ αυτούς ακριβώς τους κανιβαλισμούς που είπες. Οπότε καταφεύγω σε διαφορετική περίπτωση κατά πάσα πιθανότητα. Όσον αφορά το πρόβλημα: Η κλάση μου έχει ενα πεδίο

currentValue 

το οποίο μπορεί να ειναι αριθμός από 1-9 ή A-I. Η επιλογή δίνεται στον χρήστη προφανώς. Με βάση την επιλογή του χειρίζομαι την currentValue για συγκρίσεις εμφανίσεις κτλ. Οπότε υποθέτω μια 'καλή τεχνική ' θα ήταν εξαρχης με κάποια boolean να δω τι θέλει ο χρήστης και αναλόγως να αποκλίσω την μια περίπτωση και να χειρίζομαι το currentValue σαν int ή char. Η αμέσως επόμενη επιλογή θα ήταν με HashMap αλλά εκεί θα γινόταν χειρότερος χαμός. Αυτό είναι το πρόβλημα μου και όποια λύση ευπρόσδεκτη και πάλι.

Ευχαριστώ και πάλι για τις συμβουλές

Δημοσ.

Εγώ αυτό που καταλαβαίνω από τα παραπάνω είναι πως έχεις ένα πεδίο currentValue το οποίο μπορεί να πάρει 18 διαφορετικές τιμές. Το γεγονός ότι οι 9 από αυτές τις τιμές αντιστοιχούν σε κάτι που ο χρήστης βλέπει σαν "ψηφίο" ενώ οι άλλες 9 αντιστοιχούν σε κάτι που ο χρήστης βλέπει σαν "γράμμα" δε δημιουργεί καμία απολύτως υποχρέωση σε σένα για το πώς θα αναπαραστήσεις αυτές τις 18 τιμές μέσα στην class σου.

 

Μπορείς μια χαρά να χρησιμοποιήσεις int (1-18) ή char ('1' - '9' και 'A' - 'I') ή ακόμα καλύτερα μια enum για να αποθηκεύσεις αυτή την τιμή και να κάνεις τη μετατροπή από/προς κάτι εκτυπώσιμο μόνο στο σημείο όπου έχεις πάρε δώσε με το χρήστη. Τι θα ήταν καταλληλότερο; Εξαρτάται. Τι αναπαριστούν αυτές οι τιμές;

Δημοσ.

@defacer Καταρχάς να σε ευχαριστήσω για τις πολύ χρήσιμες συμβουλές. Η αλήθεια είναι οτι με βοηθάει η λύση των παιδιών αλλά πραγματικά όπως είπες μόνο στον constructor. Στον υπόλοιπο κώδικα θα χρειαστώ αυτούς ακριβώς τους κανιβαλισμούς που είπες. Οπότε καταφεύγω σε διαφορετική περίπτωση κατά πάσα πιθανότητα. Όσον αφορά το πρόβλημα: Η κλάση μου έχει ενα πεδίο

currentValue 

το οποίο μπορεί να ειναι αριθμός από 1-9 ή A-I. Η επιλογή δίνεται στον χρήστη προφανώς. Με βάση την επιλογή του χειρίζομαι την currentValue για συγκρίσεις εμφανίσεις κτλ. Οπότε υποθέτω μια 'καλή τεχνική ' θα ήταν εξαρχης με κάποια boolean να δω τι θέλει ο χρήστης και αναλόγως να αποκλίσω την μια περίπτωση και να χειρίζομαι το currentValue σαν int ή char. Η αμέσως επόμενη επιλογή θα ήταν με HashMap αλλά εκεί θα γινόταν χειρότερος χαμός. Αυτό είναι το πρόβλημα μου και όποια λύση ευπρόσδεκτη και πάλι.

Ευχαριστώ και πάλι για τις συμβουλές

 

Στην προκειμένη περίπτωση, καλό είναι να χειρίζεσαι το input σου σαν char και κατα περίπτωση να το ελέγχεις και να το "μετατρέπεις" σε αριθμό όπου χρειάζεται.

 

Προφανώς η λύση που έδωσα ήταν quick n dirty, αλλά δεν είχα εικόνα του ευρύτερου προβλήματος.

Δημοσ.

Το γενικότερο project ( που είναι το πρώτο μου μεγάλο project ) είναι η υλοποίηση του παιχνιδιού Sudoku με GUI. Το currentValue είναι η τιμή σε ένα κελί του πίνακα Sudoku που όλοι ξέρουμε. Έχουμε και εναλλακτική που ονομάζεται Wordoku και αντι για νούμερα 1-9 έχουμε γράμματα Α-Ι. Αυτό αναπαριστούν. Δεν ήθελα να δώσω το project εξαρχής γιατί ήθελα να το παλέψω αρκετά μόνος για μια καλή σχεδίαση. Ευχαριστώ όμως και πάλι για τις συμβουλές σας.


Η λύση του enum πάντως μου φαίνεται η πιο σωστή. Για τις συγκρίσεις κτλ που χρειάζομαι θα την χειρίζομαι σαν αριθμός και όπου έχει αλληλεπίδραση με τον χρήστη με μια boolean isWordoku θα έχω την κατάλληλη έξοδο . 

Δημοσ.

Σε αυτή τη περίπτωση ισως ειναι καλυτερο να κρατάς εσωτερικά στο πρόγραμμα τα νούμερα 1-9 και απλα οταν εμφανίζεις τον πίνακα, αν προκειται για Wordoku να βαζεις Α οπου 1 κτλ. Με αυτο δεν θα έχεις θέμα αν θες να το κάνεις και με άλλα στοιχεια, πχ χρώματα, ή σύμβολα.

  • Like 1
Δημοσ.

Ακόμα καλύτερα είναι αυτή η ιδέα να υλοποιηθεί πολυμορφικά και όχι με isWordoku. Έχεις ένα interface ISudokuBoard και δύο class SudokuBoard και WordokuBoard οι οποίες διαφέρουν μόνο σε κάποια method η οποία αντιστοιχίζει τους αριθμούς 1-9 σε εκτυπώσιμα σύμβολα. Μ' αυτό τον τρόπο όλα σου τα σχετικά if συμπυκνώνονται σε ένα και μόνο σημείο, εκεί που κάνεις board = new BlahBlahBoard().

 

Επιπλέον αν στο μέλλον θέλεις να κάνεις άλλες παραλλαγές (π.χ. 4x4 με 16 αριθμούς) είναι πολύ εύκολο εφόσον θα έχεις απομονώσει τη σχετική λογική στο ISudokuBoard (βασικά μάλλον σε περισσότερα από 1 interfaces αλλά ας βάλουμε κάπου μια τελεία προς το παρόν).

  • Like 3
Δημοσ.

Ευχαριστώ πολύ όλους και πάλι για τις χρήσιμες συμβουλές. Ακολουθώ προς το παρόν την μέθοδο enum και στην πορεία μπορεί να το αλλάξω. Καλή συνέχεια!

  • 1 μήνα μετά...
Δημοσ.

Ξαναεπαναφέρω το θέμα γιατί θα εκτιμούσα λίγη βοήθεια από σας. Παραθέτω ένα πρόχειρο διάγραμμα κλάσεων και εξηγώ:

Classes.png
 
Όπως είπες @defacer υλοποίησα δυο abstract κλάσεις Sudoku και Wordoku που έχουν μοναδική διαφορά στην υλοποίηση της getElement(). Οι κλάσεις αυτές όμως έχουν και μια abstract method isAcceptedValue() την οποία πρέπει να υλοποιήσουν οι κλάσεις 1 και 2. Το θέμα μου είναι πώς θα συσχετίσω τις κλάσεις αυτές έτσι ώστε να έχω κάποια τέτοια μορφή:
Wordoku s1 = new Class1();
Sudoku s2 = new Class2();
Sudoku s3 = new Class1();
...

Αφού προφανώς δεν έχω δυνατότητα πολλαπλής κληρονομικότητας που πρέπει να δηλώσω την μέθοδο isAcceptedValue() για να έχω αυτό που θέλω? Ή με ποιον τρόπο θα μπορουσα να οργανωθώ καλύτερα? 

Χονδρικά αυτό που θέλω είναι:

-Δυο διαφορετικές υλοποιήσεις της getElement(). 

-Δυο κλάσεις που υλοποιούν διαφορετικά την isAcceptedValue() αλλά περιέχουν και την getElement().

 

 

Συγγνώμη για τις πολλές ερωτήσεις και όποια βοήθεια θα εκτιμηθεί πολύ!Δεν ζητάω κάτι έτοιμο απλά λίγη βοήθεια για να δω αμα είμαι στον σωστό δρόμο. 

 

Δημοσ. (επεξεργασμένο)

Ωραία τώρα αρχίζεις και μπαίνεις σε πιο βαθειά νερά. Γενικότερα το multiple inheritance στη Java το προσομοιώνουμε συνήθως με Composition.

Θα πάρεις αρχικά μια γεύση απο το Strategy Pattern. Θα φτιάξεις ένα Interface π.χ. Acceptable το οποίο θα ορίζει μια μέθοδο isAcceptedValue() ως εξής:

public interface Acceptable {
	public boolean isAcceptedValue();
}

public class Wordoku extends Board {

	private Acceptable acceptable;
	
	//Για να μην μπορεί να την καλέσουν
	private Wordoku() {
	}
	
	public Wordoku(Acceptable acceptable) {
	    this.acceptable = acceptable;
	}
	
	@Override
	public Element getElement() {
	     //....implementation
	}
	
	public boolean isAcceptedValue() {
		return acceptable.isAcceptedValue();
	}

}


public class Sudoku extends Board {

	private Acceptable acceptable;
	
	//Για να μην μπορεί να την καλέσουν
	private Sudoku() {
	}
	
	public Sudoku(Acceptable acceptable) {
	    this.acceptable = acceptable;
	}
	
	@Override
	public Element getElement() {
	     //....different implementations
	}
	
	public boolean isAcceptedValue() {
		return acceptable.isAcceptedValue();
	}
}

public class Class1 implements Acceptable {

   public boolean isAcceptedValue() {
          //implementation 1
   }

}


public class Class2 implements Acceptable {

   public boolean isAcceptedValue() {
          //implementation 2
   }
}

Απο ότι θα παρατηρήσεις τα Sudoku, Wordoku class δεν είναι πλεον Abstract...δεν υπάρχει και λόγος. Ενδεχομένως την isAcceptedValue να την βάλεις και στο Board class ως abstract(ανάλογα τι θέλεις να πετύχεις). Οπότε ο τρόπος που θα φτιάχνεις τα class σου είναι ουσιαστικά αυτός:

Board b1 = new Sudoku(new Class1());

Board b2 = new Sudoku(new Class2());

Board b3 = new Wordoku(new Class1());

Board b4 = new Wordoku(new Class2());

Το Acceptable είναι ουσιαστικά το "Strategy" που επιλέγουμε.

 

Αυτή είναι ως συνήθως μια σχετικά quick n dirty λύση. Τώρα αυτό μπορούμε να το επεκτείνουμε κι'άλλο και να γίνει ακόμα καλύτερο το Pattern, και ενδεχομένως να χρησιμοποιήσουμε και Enums. Διάβασε οπωσδήποτε το Effective Java του Joshua Bloch και ρίξε μια ματιά στα Strategy και Factory Pattern.

 

Το έγραψα επι-τόπου οπότε ενδεχομένως να υπάρχει κανα συντακτικό λαθάκι ;).


Ακόμα πιο όμορφο θα ήταν το εξής

public enum Acceptable {


        METHOD1 {
		public boolean isAcceptedValue();
			//implementation1
			return true;
		}
	},
	METHOD2 {
		public boolean isAcceptedValue();
			//implementation2
			return false;
		}		
	}
			
	};
	
	public abstract boolean isAcceptedValue();
}

Οπότε ουσιαστικά μετά μπορείς να κάνεις:

Board b1 = new Sudoku(Acceptable.METHOD1);

Το οποίο είναι λίγο πιο όμορφο.

 

Αλλά και πάλι...ανάλογα τι θέλεις να κάνεις ;)

Επεξ/σία από ZAKKWYLDE
  • Like 2

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα
  • Δημιουργία νέου...