Kode Review: Kontaktmanager

  • Themenstarter Gelöschtes Mitglied 32532
  • Beginndatum
G

Gelöschtes Mitglied 32532

Gast
Hallo,

ich wollte einen Kontaktmanager in Java schreiben und bin soweit gekommen, dass Daten aus einer .txt eingelesen (dort stehen Kontaktdaten in der Form "'Vorname' 'Nachname' 'Telefonnummer'") und in Objekte der Klasse "Contact" übertragen werden.

Ich kenne mich mit Java nicht sonderlich aus und wollte bevor ich weitermachen nun erstmal fragen, was ihr an dem Kode verbessern würdet in Bezug auf Übersichtlichkeit, was vereinfacht geschrieben werden kann usw.

Java:
public class Contact {
	
	private String forename;
	private String surname;
	private int phoneNumber;
	
	public Contact(String forename, String surname, int phoneNumber) {
		
		this.forename = forename;
		this.surname = surname;
		this.phoneNumber = phoneNumber;		
	}
	
	public String getForename() {
		return forename;
	}
	
	public String getSurname() {
		return surname;
	}

	
	public int getPhoneNumber() {
		return phoneNumber;
	}

Java:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ContactManager {
	
	static final String path = "data.txt";
	static final String REGEX = "^[a-zA-Z]+ [a-zA-Z]+ \\d*$";
	
	public static boolean dataIsWellFormed(final LinkedList<String> data) {
		
		boolean isWellFormed = true;
		
		Pattern pattern = Pattern.compile(REGEX);
		
		for (String dat : data) {
			
			Matcher matcher = pattern.matcher(dat);
			
			if (!matcher.find()) {
				isWellFormed = false;
				break;
			}			
		}
		
		return isWellFormed;
	}
	
	public static LinkedList<Contact> loadContacts(final String fileName) throws Exception {
		
		LinkedList<String> data = new LinkedList<String>();
		File file = new File(fileName);
		
		if (!file.exists()) 
		{
			try                   { file.createNewFile(); }
			catch (IOException e) { e.printStackTrace(); }
		}
		
		if (!file.isFile())	throw new Exception("Der angegebene Pfad ist keine Datei");
		if (!file.canRead()) throw new Exception("Die angegebene Datei kann nicht gelesen werden");
				
		try                   { BufferedReader in = new BufferedReader(new FileReader(file));
							    String zeile = null;
							    while ((zeile = in.readLine()) != null) data.add(zeile); }
		catch (IOException e) { e.printStackTrace(); }
		
		if (!dataIsWellFormed(data)) throw new Exception("Data is not well-formed.");
		else return initializeContacts(data);		
	}
	
	public static LinkedList<Contact> initializeContacts(final LinkedList<String> data) {
		
		LinkedList<Contact> contacts = new LinkedList<Contact>();
				
		for (String dat : data) {
			
			String[] help = dat.split(" ");
			String forename = help[0];
			String surname = help[1];
			String telephoneNumber = help[2];
			
			contacts.add(new Contact(forename, surname, Integer.parseInt(telephoneNumber)));			
		}
		
		return contacts;		
	}
	
	public static void main(String[] args) 
	{
		LinkedList<Contact> data = null;
		
		try                 { data = loadContacts(path); } 
		catch (Exception e) { e.printStackTrace(); }	
				
		for (Contact c : data) {
			System.out.println(c.getForename() + " " + c.getSurname() + " " + c.getPhoneNumber());
		}
	}
}

Ich bin mir z.B. unsicher ob die geschweiften Klammern direkt nach "if" oder Klassennamen oder in die darunter liegende Zeilen sollten.

Auch weiß ich nicht ob Zeile 75-82 so ok sind - sollte das ganze in den try-Block? Denn so flöge ja sowohl eine NullPointerException wenn die Daten nicht richtig in der .txt-Datei stehen und versucht wird die Kontakte auszugeben als auch eine "nicht richtig formatiert"-Exception. Andererseits soll doch so wenig wie möglich in den try-Block, oder?
 
Zuletzt bearbeitet von einem Moderator:
G

Gast2

Gast
- Deine Klasse Contact ist immutable, du kannst die Attribute also allesamt final machen.
- Warum ist deine Klasse ContactManager static? Würde ich so nicht machen.
- Du schreibst oft
Code:
LinkedList<String>
. Wenn du als Parameter ne Liste erwartest oder eine Liste zurückgibst, dann schreib lieber nur List<String> (also gegen das Interface), damit könntest du intern mal die Implementierungen tauschen und das hätte keinerlei Auswirkung auf deinen Code


Ich bin mir z.B. unsicher ob die geschweiften Klammern direkt nach "if" oder Klassennamen oder in die darunter liegende Zeilen sollten.
Ist Geschmackssache, in den code conventions steht glaube ich die Variante dass die Klammern in der selben Zeile stehen. Aber hauptsache du machst es überall einheitlich. Siehe
Code:
initializeContacts
und
Code:
main
.

Auch den try-catch Block würde ich anders formatieren.
Java:
try { 
    data = loadContacts(path); 
} catch (IOException e) {
    e.printStackTrace(); 
}
Warum fängst du überhaupt Exception ab? Da solltest du schon etwas konkreter werden. Die IOException kann fliegen wenn die Datei nicht lesbar ist oder nicht existiert. Du wirfst dann in loadContacts noch händisch ne Exception, auch das sollte man nicht machen. Wirf da lieber ne IllegalArgumentException oder so.
 
G

Gelöschtes Mitglied 32532

Gast
- Warum ist deine Klasse ContactManager static? Würde ich so nicht machen.

Warum würdest du sie denn nicht static machen? Es gibt doch nichts in der ContactManger-Klasse, was sich irgendwie verändern würde

Auch den try-catch Block würde ich anders formatieren.
Java:
try { 
    data = loadContacts(path); 
} catch (IOException e) {
    e.printStackTrace(); 
}
Warum fängst du überhaupt Exception ab? Da solltest du schon etwas konkreter werden. Die IOException kann fliegen wenn die Datei nicht lesbar ist oder nicht existiert.

Soll ich einfach eine "Exception e" abfangen?

Den Rest habe ich geändert - danke
 
G

Gast2

Gast
Naja, ich sehe jetzt keinen Grund warum der ContactManager zwingend static sein müsste. Wenn die Klasse static ist dann kanns nur genau einen ContactManager pro JVM geben, evtl. überlegst du später mal mehrere ContactManager zu haben (pro User einen oder so), dann kommt dir das in die Quere. Außerdem sind dann Abhängigkeiten zwischen deinen Klasse nicht klar wenn eine Klasse statisch auf den ContactManager zugreift (global state), aber das geht zuweit. Anfangs würde ich komplett auf static verzichten.

Es gibt doch nichts in der ContactManger-Klasse, was sich irgendwie verändern würde
Was hat das mit static zu tun?

Soll ich einfach eine "Exception e" abfangen?
Nein, genau das solltest du nicht tuen. loadContacts() sollte eine IOException werfen und vllt noch ne IllegalArgumentException wenn der
Code:
path
ungültig ist. Und genau diese beiden solltest du dann auch abfangen.
Code:
throws Exception
oder
Code:
catch (Exception e)
sollte man (fast) nie verwenden.
 
J

JohannisderKaeufer

Gast
Java:
static final String path = "data.txt";

Den Dateipfad fest zu verdrahten ist, vielleicht nicht unbedingt Sinnig, wenn er später beim Aufruf sowieso noch gesetzt wird.

Daher eher sowas in der main-Methode
[JAVA=77]data = loadContacts("data.txt");[/code]


Zweitens kann es auch Sinn machen
[JAVA=33]public static LinkedList<Contact> loadContacts(final String fileName) throws Exception {[/code]

mit sowas zu ersetzen

Java:
public static List<Contact> loadContacts(final InputStream inputStream) throws Exception {
...
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader reader = new BufferedReader(inputStreamReader);
...

Damit kannst du dann in der main-Methode
Mit File einen FileInputStream erstellen den du loadContacts übergibst.
Du kannst aber auch System.in übergeben und den Inhalt dann per Console eintippen.
Du kannst aber auch über eine URL und das Internet gehen um Daten einzulesen.
=> mehr Flexibilität, ohne am Programm rumschrauben zu müssen.

Solltest du irgendwann deine Contacte schreiben wollen, wäre ein OutputStream als Parameter angebracht äquivalent nur einen Dateinamen anzugeben.
Dann kannst du beispielsweise über einen FileOutputStream gehen und in eine Datei schreiben oder System.out und das Ergebnis in der Konsole betrachten.
 
B

...ButAlive

Gast
Was mir als erstes auffällt. ist dass du die Telefonnummer als
Code:
int
speicherst. Da gehen führende Nullen verloren. Du kannst nicht mehr unterscheiden ob 8912345 eine Nummer bei dir aus dem Ort ist oder ob 08912345 gemeint war , was eine Telefonnummer aus München wäre. Speicher also die Telefonnummer lieber als
Code:
String
.
 
G

Gelöschtes Mitglied 32532

Gast
Danke an euch beide.

@JohannisderKaeufer: Das hört sich sehr interessant an. Ich werde mich nun zuerst daran versuchen ein GUI zu erstellen und das Hinzufügen / Suchen / Löschen von Kontakten zu ermöglichen. Danach werde ich das mit dem Internet ausprobieren!

So sieht es nun nach dem Lösen von der statischen Klasse aus: (die Klasse Contact habe ich mal weggelassen und die main-Methode ausgelagert)

Java:
public class ContactManager {
	
	private final InputStream inputStream;
	private final String REGEX = "^[a-zA-Z]+ [a-zA-Z]+ \\d*$";
	
	public ContactManager(InputStream inputStream) {
		this.inputStream = inputStream;
	}
	
	private boolean dataIsWellFormed(final List<String> data) {
		
		boolean isWellFormed = true;
		Pattern pattern = Pattern.compile(REGEX);
		
		for (String dat : data) {	
			Matcher matcher = pattern.matcher(dat);
			if (!matcher.find()) {
				isWellFormed = false;
				break;
			}			
		}
		
		return isWellFormed;
	}
	
	public List<Contact> loadContacts() throws IOException, IllegalArgumentException {
		
		List<String> data = new LinkedList<String>();
		InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
						
		try { 
			BufferedReader reader = new BufferedReader(inputStreamReader);
		    String zeile = null;
		    while ((zeile = reader.readLine()) != null) data.add(zeile); 
		} catch (IOException e) { 
			e.printStackTrace(); 
		}
		
		if (dataIsWellFormed(data)) return initializeContacts(data);		
	    else throw new IllegalArgumentException("Data is not well-formed.");
	}
	
	private List<Contact> initializeContacts(final List<String> data) {
		
		List<Contact> contacts = new LinkedList<Contact>();
				
		for (String dat : data) {
			
			String[] help = dat.split(" ");
			String forename = help[0];
			String surname = help[1];
			String telephoneNumber = help[2];
			
			contacts.add(new Contact(forename, surname, telephoneNumber));			
		}
		
		return contacts;		
	}
}

Java:
public class entryPoint {

	public static void main(String[] args) throws IOException, IllegalArgumentException {
		
		String path = "data.txt";
		
		File file = new File(path);
		
		if (!file.exists()) {
			try { 
				file.createNewFile(); 
			} catch (IOException e) { 
				e.printStackTrace(); 
			}
		}
		if (!file.isFile())	throw new IllegalArgumentException("Der angegebene Pfad ist keine Datei");
		if (!file.canRead()) throw new IOException("Die angegebene Datei kann nicht gelesen werden");
				
		FileInputStream inputStream = new FileInputStream(file);
		
		ContactManager cM = new ContactManager(inputStream);		
		
		List<Contact> data = null;
		
		try { 
			data = cM.loadContacts(); 
		} catch (IOException | IllegalArgumentException e) { 
			e.printStackTrace(); 
		} 
						
		for (Contact c : data) {
			System.out.println(c.getForename() + " " + c.getSurname() + " " + c.getPhoneNumber());
		}
	}
}

Solltest du irgendwann deine Contacte schreiben wollen, wäre ein OutputStream als Parameter angebracht äquivalent nur einen Dateinamen anzugeben.
Dann kannst du beispielsweise über einen FileOutputStream gehen und in eine Datei schreiben oder System.out und das Ergebnis in der Konsole betrachten.

Ich weiß nicht ob ich dich ganz verstehe, meinst du, wenn ich selbst Kontakte erstellen möchte, dass ich dem Konstruktor auch noch den FileOutPutStream mitgebe?
 
Zuletzt bearbeitet von einem Moderator:
J

JohannisderKaeufer

Gast
Ich stelle mir vor das du später irgendwann mal Lust auf sowas hättest.

Du hast eine Liste mit Kontakten. Die hängt irgendwo im Speicher deines Systems.
Jetzt möchtest du das ganze abspeichern, um es später wieder einlesen zu können (Neustart des Systems etc.).
Also schreibst du das ganze in eine Datei, die das selbe Format hat, aus der du einliest.


Jetzt könntest du einfach einen Dateinamen übergeben z.B.
Java:
public void writeContacts(String filename) throws IOException

oder aber einen Stream übergeben.
Java:
public void writeContacts(OutputStream stream) throws IOException

Dieser Stream hat nun die Möglichkeit alles mögliche zu sein.
Java:
writeContacts(System.out); //Wäre die Konsole
writeContacts(new FileOutputStream(new File("dateiname")));//Wäre eine Datei
writeContacts(aServerSocket.getOutputStream());//Damit könntest du über das Netzwerk antworten.(Fehlt natürlich noch was vorne dran)

Den OutputStream würde ich allerdings nicht im Konstruktor übergeben, sondern der Methode, da ja auch die Möglichkeit besteht, das ich in verschiedenen Dateien die gleichen Daten speichern möchte.

In EntryPoint (Klassen immer GroßSchreiben) kann man Zeile 9 - 17 prinzipiell auch weglassen, da du nichts anderes machst als zu prüfen ob eine Exception geworfen werden würde um sie dann selbst zu werfen, dass würde auch automatisch geschehen.

Zeile 31 - 33 in Entrypoint könnte man auch weglassen, wenn man in ContactManager eine Methode writeContact hätte. Dann würde dort ein writeContacts(System.out); genügen (siehe weiter oben).


Statt Zeile 5 könnte man auch hingehen und data.txt als parameter beim Programmstart mitgeben.

Java:
String path = args[0];

java EntryPoint data.txt
 
G

Gelöschtes Mitglied 32532

Gast
Danke, ich habe das eingearbeitet. Nun stell sich mir eine weitere Frage zu dem Hinzufügen eines Kontaktes:

Wenn die Eingabe nicht der Syntax entspricht wird verworfen (-1), wenn der Kontakt noch nicht existiert wird eine 1 zurückgegeben und der Kontakt hinzugefügt, und wenn Vor und Nachname bereits existieren, der Kontakt also überschrieben würde, wird eine 0 zurückgegeben und es wird nichts gemacht. Bei z.B. einer 0 müsste dann in der Main gefragt werden "wollen Sie den Kontakt wirklich überschreiben?" und wenn dann ein "ja" folgt, würde aus der Main die Methode "overwriteContact" aufgerufen.

Ist das Auslagern dieser Aufrufe und Logik in die Main hin ok? Auch bin ich mir nicht sicher, ob dass mit der 1 / 0 / -1 guter Stil ist.

Java:
// 1: successful / 0: contact already exists / -1: unsuccessful
	public int addContact(String cS) {
		
		if (datIsWellFormed(cS)) {
			Contact contact = initializeContact(cS);
			if (existsContact(contact)) return 0;
			else {
				contacts.add(contact);
				return 1;
			}
		}
		else return -1;		
	}
	
	public void overwriteContact(String cS) {
		
		List<String> contactsS = new LinkedList<String>();
		for (Contact c :contacts) contactsS.add(c.getForename() + " " + c.getSurname());
		
		Contact contact = initializeContact(cS);
		
		int index = contactsS.indexOf(contact.getForename() + " " + contact.getSurname());
		
		contacts.remove(index);
		contacts.add(contact);
	}

Dann noch eine andere Frage zu:
Java:
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
outputStreamWriter.write(outputString);
Angenommen der outputString enthält \n 's, z.B. output="a a 1\nb b 2" Wenn der outputStream dann System.out entspricht, wird alles korrekt ausgegeben, d.h. "a a 1" und "b b 2" werden untereinander ausgegeben. Wenn aber outputStream = new FileOutputStream(file), dann werden die \n 's weder umgesetzt noch angezeigt: "a a 1b b 2" steht dann in der Datei - woran liegt das?
 
Zuletzt bearbeitet von einem Moderator:
B

bygones

Gast
Danke, ich habe das eingearbeitet. Nun stell sich mir eine weitere Frage zu dem Hinzufügen eines Kontaktes:

Wenn die Eingabe nicht der Syntax entspricht wird verworfen (-1), wenn der Kontakt noch nicht existiert wird eine 1 zurückgegeben und der Kontakt hinzugefügt, und wenn Vor und Nachname bereits existieren, der Kontakt also überschrieben würde, wird eine 0 zurückgegeben und es wird nichts gemacht. Bei z.B. einer 0 müsste dann in der Main gefragt werden "wollen Sie den Kontakt wirklich überschreiben?" und wenn dann ein "ja" folgt, würde aus der Main die Methode "overwriteContact" aufgerufen.

Ist das Auslagern dieser Aufrufe und Logik in die Main hin ok? Auch bin ich mir nicht sicher, ob dass mit der 1 / 0 / -1 guter Stil ist.
das klassische Problem eines TriStates.... Ich wuerde eher mit einem enum das regeln anstatt mit ints. Irgendwo hast du dann im code [c]if (addContact() == -1)[/c] was beim lesen nix hilft, da man nicht weiss was -1 bedeutet. [c]if (addContact() == CONTACT_EXISTS)[/c] ist sofort verstaendlich
 
M

Marcinek

Gast
Andererseits würde ich von der addContact Methode erwarten, dass sie ein Kontakt hinzufügt oder ihn ablehnt.
 

Ähnliche Java Themen

Neue Themen


Oben