6
\$\begingroup\$

I'm trying to figure out what is the best way to create some objects in my application. The application is already running, but I want to structure things well and clean.

These objects represents paths and they are complicated (they receive various parameters in their constructors).

There are four types of classes that represents a particular path:

  • Gpx
  • Road
  • TrackMarkers
  • JoinedMarkers

In the application there can exist only one path (Gpx, TrackMarkers, JoinedMarkers) and (if the user wants to) a Road (Road class represents the road to the path). A Road can't exist alone.

My MainActivity must have access to the data of these classes once created. I did not create a hierarchy of these classes (or implemented a creational patterns like factory method, abstract factory, builder etc...) and these classes (which are similar) contains duplicated code (except for JoinedMarkers that is simpler).

All the code of these classes:

Gpx

public class Gpx {
 /** lunghezza del tracciato gpx */
 private float length;
 /** elevazione media del tracciato gpx */
 private double averageElevation;
 /** lista di coordinate del tracciato */
 private List<LatLng> coordinates;
 /** file relativo a questo percorso gpx */
 private File file;
 /** contesto dell'activity principale */
 private Context context;
 public Gpx(File file, Context context) {
 coordinates = new ArrayList<LatLng>();
 this.file = file;
 this.file = file;
 build();
 }
 /**
 * Esegue la costruzione del tracciato, inizializzando i campi privati
 */
 private void build() {
 ParserAsyncTask parserAsyncTask = new ParserAsyncTask();
 parserAsyncTask.execute(file);
 }
 /**
 * AsyncTask che effettua il parsing del file gpx
 */
 private class ParserAsyncTask extends AsyncTask<File,Void,Void> {
 private ProgressDialog mDialog;
 protected void onPreExecute() {
 mDialog = ProgressDialog.show(context, "", "Caricamento...", true);
 }
 protected void onPostExecute(Void unused) {
 if(mDialog != null) {
 if(mDialog.isShowing()) {
 mDialog.dismiss();
 }
 }
 //calcolo la lunghezza del tracciato
 calculatesLength();
 }
 protected Void doInBackground(File... files) {
 //PARSING ALGORITHM, OMITTED
 }
 }
 private void calculatesLength() {
 for(int i=0; i < coordinates.size() - 1; i++) {
 LatLng firstLatLng = coordinates.get(i);
 LatLng secondLatLng = coordinates.get(i+1);
 length += Utilities.getInstance().getDistance(firstLatLng,secondLatLng);
 }
 }
 public float getLength() {
 return length;
 }
 public double getAverageElevation() {
 return averageElevation;
 }
 public List<LatLng> getCoordinates() {
 return coordinates;
 }
}

JoinedMarkers

public class JoinedMarkers {
 /** lunghezza del tracciato */
 private float length;
 /** lista di markers */
 private List<Marker> markers;
 public JoinedMarkers(List<Marker> markers) {
 this.markers = markers;
 calculatesLength();
 }
 private void calculatesLength() {
 for(int i=0; i < markers.size() - 1; i++) {
 LatLng firstLatLng = markers.get(i).getPosition();
 LatLng secondLatLng = markers.get(i+1).getPosition();
 length += Utilities.getInstance().getDistance(firstLatLng,secondLatLng);
 }
 }
 public float getLength() {
 return length;
 }
}

TrackMarkers

public class TrackMarkers {
 /** lunghezza del tracciato */
 private float length;
 /** elevazione media */
 private double averageElevation;
 /** modalità di viaggio */
 private String travelMode;
 /** lista di coordinate del tracciato */
 private List<LatLng> coordinates;
 /** lista di markers per costruire il percorso */
 private List<Marker> markers;
 /** tempo di percorrenza stimato **/
 private int duration;
 /** contesto dell'Activity principale */
 private Context context;
 /** timeout per la connessione */
 private int connectionTimeout;
 /** timeout per il download */
 private int dataTimeout;
 /** flag per il calcolo della lunghezza */
 private boolean flag;
 public TrackMarkers(Context context, String travelMode, List<Marker> markers,
 int connectionTimeout, int dataTimeout) {
 flag = false;
 this.travelMode = travelMode;
 this.markers = markers;
 this.context = context;
 this.connectionTimeout = connectionTimeout;
 this.dataTimeout = dataTimeout;
 coordinates = new ArrayList<LatLng>();
 //avvia la costruzione del tracciato
 build();
 }
 private void build() {
 for(int i = 0; i < markers.size() - 1; i++) {
 Marker firstMarker = markers.get(i);
 Marker secondMarker = markers.get(i + 1);
 //I extract latitude and longitude from the markers
 LatLng firstLatLng = new LatLng(firstMarker.getPosition().latitude, firstMarker.getPosition().longitude);
 LatLng secondLatLng = new LatLng(secondMarker.getPosition().latitude, secondMarker.getPosition().longitude);
 //Ogni volta si costruisce un URL aggiornato per la nuova coppia di markers
 String URL = Utilities.getInstance().makeURL(firstLatLng.latitude, firstLatLng.longitude,
 secondLatLng.latitude, secondLatLng.longitude, travelMode);
 if(i == markers.size() - 2) {
 flag = true;
 }
 MultipleTracksAsyncTask multipleTracksAsyncTask = new MultipleTracksAsyncTask(URL);
 multipleTracksAsyncTask.execute();
 }
 }
 private class MultipleTracksAsyncTask extends AsyncTask<Void,Void,String> {
 private ProgressDialog mDialog;
 private String URL;
 public MultipleTracksAsyncTask(String URL) {
 this.URL = URL;
 }
 protected void onPreExecute() {
 mDialog = ProgressDialog.show(context, "", "Loading...", true);
 }
 protected void onPostExecute(String result) {
 if(mDialog != null) {
 if(mDialog.isShowing()) {
 mDialog.dismiss();
 }
 }
 //effettuo il parsing del JSON
 if(result != null) {
 parseJSON(result);
 //se la computazione è finita, calcola la lunghezza
 if(flag) {
 calculatesLength();
 }
 }
 }
 public String doInBackground(Void... param) {
 JSONRequest jParser = new JSONRequest();
 String json = jParser.getJSONFromUrl(URL,connectionTimeout,dataTimeout);
 return json;
 }
 }
 private void parseJSON(String result) {
 //PARSING ALGORITHM OMITTED
 }
 private void calculatesLength() {
 for(int i=0; i < coordinates.size() - 1; i++) {
 LatLng firstLatLng = coordinates.get(i);
 LatLng secondLatLng = coordinates.get(i+1);
 length += Utilities.getInstance().getDistance(firstLatLng,secondLatLng);
 }
 }
 public float getLength() {
 return length;
 }
 public double getAverageElevation() {
 return averageElevation;
 }
 public int getDuration() {
 return duration;
 }
 public List<LatLng> getCoordinates() {
 return coordinates;
 }
}

Road

public class Road {
 /** lista di coordinate */
 private List<LatLng> coordinates;
 /** lunghezza del percorso*/
 private float length;
 /** tempo di percorrenza stimato */
 private int duration;
 /** URL per la richiesta HTTP */
 private String URL;
 /** contesto dell'Activity principale */
 private Context context;
 /** timeout per la connessione */
 private int connectionTimeout;
 /** timeout per il download */
 private int dataTimeout;
 public Road(Context context, String URL, int connectionTimeout,
 int dataTimeout) {
 coordinates = new ArrayList<LatLng>();
 this.context = context;
 this.URL = URL;
 this.connectionTimeout = connectionTimeout;
 this.dataTimeout = dataTimeout;
 build();
 }
 private void build() {
 PathToTrackAsyncTask pathToTrackAsyncTask = new PathToTrackAsyncTask();
 pathToTrackAsyncTask.execute();
 }
 private class PathToTrackAsyncTask extends AsyncTask<Void, Void, String> {
 private ProgressDialog mDialog;
 protected void onPreExecute() {
 mDialog = ProgressDialog.show(context, "", "Caricamento...", true);
 }
 protected void onPostExecute(String result) {
 if(mDialog != null) {
 if(mDialog.isShowing()) {
 mDialog.dismiss();
 }
 }
 if(result != null) {
 parseJSON(result);
 }
 }
 @Override
 protected String doInBackground(Void... params) {
 //Istanzio il JSONRequest
 JSONRequest jParser = new JSONRequest();
 //Ottengo la stringa JSON
 String json = jParser.getJSONFromUrl(URL,connectionTimeout,dataTimeout);
 //Ritorno la stringa per l'onPostExecute
 return json;
 }
 private void parseJSON(String result) {
 //PARSING ALGORITHM OMITTED (very similar to the previous class)
 }
 }
 public float getLength() {
 return length;
 }
 public int getDuration() {
 return duration;
 }
 public List<LatLng> getCoordinates() {
 return coordinates;
 }
}

UPDATE

AbstractAsyncTask

public abstract class AbstractAsyncTask extends AsyncTask<Void,Void,String> {
 private ProgressDialog dialog;
 private String URL;
 private Context context;
 private int connectionTimeout;
 private int dataTimeout;
 private List<LatLng> coordinates;
 public AbstractAsyncTask(Context context, String URL,
 int connectionTimeout, int dataTimeout, List<LatLng> coordinates) {
 this.context = context;
 this.URL = URL;
 this.connectionTimeout = connectionTimeout;
 this.dataTimeout = dataTimeout;
 this.coordinates = coordinates;
 }
 protected void onPreExecute() {
 dialog = ProgressDialog.show(context, "", "Loading...", true);
 }
 protected void onPostExecute(String result) {
 if(dialog != null) {
 if(dialog.isShowing()) {
 dialog.dismiss();
 }
 }
 if(result != null) {
 parseJSON(result);
 }
 }
 @Override
 protected String doInBackground(Void... params) {
 JSONRequest jParser = new JSONRequest();
 String json = jParser.getJSONFromUrl(URL,connectionTimeout,dataTimeout);
 return json;
 }
 /**
 * JSON parsing 
 */
 protected void parseJSON(String result) {
 try {
 final JSONObject json = new JSONObject(result);
 JSONArray routeArray = json.getJSONArray("routes");
 JSONObject routes = routeArray.getJSONObject(0);
 JSONArray legsArray = routes.getJSONArray("legs");
 JSONObject legsObject = legsArray.getJSONObject(0);
 JSONObject distanceObject = legsObject.getJSONObject("distance");
 JSONObject durationObject = legsObject.getJSONObject("duration");
 JSONObject overviewPolylines = routes.getJSONObject("overview_polyline");
 String encodedString = overviewPolylines.getString("points");
 ArrayList<LatLng> res = Utilities.getInstance().decodePoly(encodedString);
 coordinates.addAll(res);
 //for Road
 //length = distanceObject.getInt("value");
 //duration = durationObject.getInt("value");
 //for TrackMarkers
 //length += distanceObject.getInt("value");
 //duration += durationObject.getInt("value");
 updateLenghtAndDuration(distanceObject,durationObject);
 }
 catch (JSONException e) {
 e.printStackTrace();
 Toast.makeText(context, "Path is not available", Toast.LENGTH_SHORT).show();
 }
 }
 protected abstract void updateLenghtAndDuration(JSONObject distanceObject, JSONObject durationObject);
}

RoadAsyncTask

public class RoadAsyncTask extends AbstractAsyncTask {
 public RoadAsyncTask(Context context, String URL,
 int connectionTimeout, int dataTimeout, List<LatLng> coordinates,
 float length, int duration) {
 super(context, URL, connectionTimeout, dataTimeout, coordinates,
 length, duration);
 }
 @Override
 protected void updateLenghtAndDuration(JSONObject distanceObject, JSONObject durationObject) {
 try {
 length = distanceObject.getInt("value");
 duration = durationObject.getInt("value");
 } catch (JSONException e) {
 e.printStackTrace();
 }
 }
}

TrackMarkersAsyncTask

public class TrackMarkersAsyncTask extends AbstractAsyncTask {
 public TrackMarkersAsyncTask(Context context, String URL, int connectionTimeout, int dataTimeout, List<LatLng> coordinates, float length, int duration) {
 super(context, URL, connectionTimeout, dataTimeout, coordinates, length, duration);
 }
 @Override
 protected void updateLenghtAndDuration(JSONObject distanceObject, JSONObject durationObject) {
 try {
 length += distanceObject.getInt("value");
 duration += durationObject.getInt("value");
 } catch (JSONException e) {
 e.printStackTrace();
 }
 }
}
asked Jun 4, 2014 at 9:59
\$\endgroup\$
3
  • 2
    \$\begingroup\$ I'm a bit sad to see the (wasted) time to create localized documentation. It is a good thing that you document, but they should really be in English. \$\endgroup\$ Commented Jun 4, 2014 at 11:14
  • \$\begingroup\$ Can't you understand? \$\endgroup\$ Commented Jun 4, 2014 at 11:44
  • 3
    \$\begingroup\$ @skiwi Actually, the comments in Italian are pretty much redundant, and don't say much more than the English identifiers. That may be an ideal solution for a bilingual programming team: the code stays English, but there is some translation help for programmers who may understand Italian better. \$\endgroup\$ Commented Jun 4, 2014 at 11:50

2 Answers 2

7
\$\begingroup\$

Duplicated code

You have indeed some duplicated code, sometimes just a little off.
You can make abstract class for MultipleTracksAsyncTask and PathToTrackAsyncTask and move the duplicated code to there.

I should move to an private method of the abstract class.

if(mDialog != null) {
 if(mDialog.isShowing()) {
 mDialog.dismiss();
 }
}

Same for this but I should make an abstract method and make the implementation later cause it's for the 2 classes a little different :

if(result != null) {
 parseJSON(result);
}

So the result in the abstract class could be (You know what exactly the code does so make the names better for your cause):

@Override
protected void onPostExecute(String result) {
 updateMDialog();
 updateResult();
}

The same for your basic classes. I see lenght and context always popup(except for JoinedMarkers) and Road and TrackMarkers have even more in common.

Try abstracting this to an abstract class.

Dangerous situation

Now I guess you have overlooked it but you have a potential bug in your code.
In the class Gpx you have the List<LatLng> and calculates the distance in async task.
You don't provide a setter cause that will break your distance. The fault is that you have this getter.

public List<LatLng> getCoordinates() {
 return coordinates;
}

What's wrong with it?

Look at following code :

gpx.getCoordinates().clear();

Your list will be empty and your lenght will have incorrect value according this new list.
Program defensive and return an unmodifiable list back so no one can change it with the getter.

public List<LatLng> getCoordinates() {
 return Collections.unmodifiableList(coordinates);
}

Watch out: I put it here to point it out.
If after init the coordinates will not change anymore make it there unmodifiable.
So you won't constantly making a new unmodifiable list when you call the getter.

answered Jun 4, 2014 at 21:30
\$\endgroup\$
4
  • \$\begingroup\$ Oooh thanks! You are very helpful. I have updated the question and i added the class AbstractAsyncTask. Can you check it out and tell me if it's ok? \$\endgroup\$ Commented Jun 5, 2014 at 8:44
  • 1
    \$\begingroup\$ looking good, just watch out if the protected void parseJson(String result) is the same code for all implementations, otherwise seperate the duplicated code from the specific implementation code. If you want that users can't override some methods, make the methods final. \$\endgroup\$ Commented Jun 5, 2014 at 9:07
  • \$\begingroup\$ Yes the parsing is the same for the two classes. It's a bit different for the commented part, for which i created an abstract method. I updated again the question with the other two AsyncTask and i did some other little change too. It's right that the variables length and duration are protected? They are variables of the class (Road or TrackMarkers) that need to be updated by AsyncTask. However now the method onPostExecute() is the same for the two classes. \$\endgroup\$ Commented Jun 5, 2014 at 9:31
  • 1
    \$\begingroup\$ You are very correct with the protected. refactoring is good, abstracting also but don't go to abstract. If a method is in 2 implementations the same, and some other implementations not, so be it (certainly for 2 lines of code). If it's in all the implementations the same move it higher to remove the duplicated code. \$\endgroup\$ Commented Jun 5, 2014 at 10:06
1
\$\begingroup\$

You should not pass the Context to those classes (separation of concerns/ single responsibility principle). You could add the Context as a constructor parameter to the AsyncTask's; or maybe it would be better to define the AsyncTask's where the computation is launched (and the Context accessible) instead of as inner classes of your four classes.

I can't comment much about your four different classes since I lack domain knowledge and you did not explain much. However, they seem very similar so maybe you could get rid of some of them, or use inheritance.

answered Jun 4, 2014 at 21:01
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.