カテゴリー別アーカイブ: java

経路情報をパースする 設計 3

RouteJsonParserさんに離婚されて、一人で子供や孫たちの面倒を見ることになったRoutesさん、母子家庭で大変なので整理してみます。
今はこういった現状

public class Routes {
	private String summary = "";
	private List<Legs> legs = null;
	private String waypoint_order = "";
	private List<OverviewPolyline> overview_polyline = null;
	private String bounds = "";
	private String copyrights = "";
	private String warnings = "";
	class Legs {
		private List<Steps> steps = null;
		private Distance distance = null;
		private Duration duration = null;
		private ArrivalTime arrival_time = null;
		private String departure_time = ""; //Time オブジェクト?
		private String start_location = "";
		private String end_location = "";
		private String start_address = "";
		private String end_address = "";
		class Steps {
			private String html_instructions = "";
			private Distance distance = null;
			private Duration duration = null;
			private String start_location = "";
			private String end_location = "";
			private List<Steps> sub_steps = null;
			private TransitDetails transit_details = null;
			class TransitDetails {
				// 乗り換えについては後で設計する
			}
		}
		class Distance {}
		class Duration {}
		class ArrivalTime {}
	}
	class OverviewPolyline {}
	
	Routes() {}

}

最初に手がけるのは、子供たちを自立させます。とくに子供や孫を抱えてるものたち!

class Legs、class OverviewPolylineを外部クラスにします。
アトリビュートを眺めると、内部クラスとするべきものがない。
ただし、クラスにすべきものがいくつかある。
waypoint_order 、bounds 、warnings などは配列もしくはデータ構造を持っている。
現在、その構造やオブジェクトの分析は行なっていないので、Stringのままとして扱う。

そうすると
Routes.java

public class Routes {
	private String summary = "";
	private List<Legs> legs = null;
	private String waypoint_order = "";
	private List<OverviewPolyline> overview_polyline = null;
	private String bounds = "";
	private String copyrights = "";
	private String warnings = "";

	Routes() {}
}

class Routes の要件は JSONObject から必要な値を取得することである。
その要件で書くと

public class Routes {
	/**
	 * ログのタグ
	 */
	private final String LOG_TAG = this.getClass().getSimpleName();
	/**
	 * JSON TAGS
	 */
	public enum Tags {
		SUMMARY("summary"),  
		WAYPOINTORDER("waypoint_order"), 
		BOUNDS("bounds"),
		COPYRIGHTS("copyrights"), 
		WARNINGS("warnings"), 
		LEGS("legs"), 
		OVERVIEWPOLYLINE("overview_polyline");
		private String name;
		Tags(String name){this.name = name;}
		public String getName(){return this.name;}
	}
	/**
	 * JSON オブジェクト
	 */
	JSONObject jsonRoutes = null;
	/**
	 * コンストラクタ
	 * @param json RouteJsonParser からわたされる
	 */
	public Routes(JSONObject json) {
		jsonRoutes = json;
	}
	public String getSummary() throws JSONException {
		return jsonRoutes.getString(Tags.SUMMARY.getName());
	}
	public String getWaypointOrder() throws JSONException {
		return jsonRoutes.getString(Tags.WAYPOINTORDER.getName());
	}
	public String getBounds() throws JSONException {
		return jsonRoutes.getString(Tags.BOUNDS.getName());
	}
	public String getCopyrights() throws JSONException {
		return jsonRoutes.getString(Tags.COPYRIGHTS.getName());
	}
	public String getWarnings() throws JSONException {
		return jsonRoutes.getString(Tags.WARNINGS.getName());
	}
	public List<Legs> getLegsList() {
		List<Legs> list = new ArrayList<Legs>();
		JSONArray array;
		try {
			array = jsonRoutes.getJSONArray(Tags.LEGS.getName());
			for(int i=0;i<array.length();i++) {
				list.add( new Legs(array.getJSONObject(i)) );
			}
		} catch (JSONException e) {
			list.clear();
			Log.e(LOG_TAG, "getLegsList() " + e.getMessage() );
		}
		return list;
	}
	public List<OverviewPolyline> getOverviewPolylineList(){
		List<OverviewPolyline> list = new ArrayList<OverviewPolyline>();
		JSONArray array;
		try {
			array = jsonRoutes.getJSONArray(Tags.OVERVIEWPOLYLINE.getName());
			for(int i=0;i<array.length();i++) {
				list.add( new OverviewPolyline(array.getJSONObject(i)) );
			}
		} catch (JSONException e) {
			list.clear();
			Log.e(LOG_TAG, "getOverviewPolyline() " + e.getMessage() );
		}
		return list;
	}
}

ちゃっかり実装してますが、してないこととして見てください。(笑)
子供たちを自立させたので、内部クラスは存在しません。ただ、アトリビュートを enum Tags で宣言しています。
enum Tags を インターフェイスにしても問題はありません。
インターフェイスについてはここでは触れません。何かに機会があれば、書くかもしれませんが・・・

このクラスの要件はJSONObjectから取得するなので、set関数は存在しません。
すべて、get関数です。
コンストラクタだけは、RouteJsonParser から 子供たちの養育費(JSONObject)をもらっています。(笑)
内部クラスであった、子や孫たちも同じように設計していけば完了です。

これだと、アトリビュートが追加されたり、バリュー(値)に若干の変更があってもクラスを派生させるなり、直接改修するなりしても他のクラスには依存してないので問題ありません。
大規模な構造変化の場合は最初から作り直さなければなりませんが(笑)

内部クラスは、その他のクラスと依存関係がないものだけにしましょう。
そのようにすると、そのクラスだけで完結できます。ただし、例外的なものはありますが、ここではふれません。

Routesさんに後日聞いてみました。
「子供たちはどうしました?」
「みんな自立して働いてます。必要なときに養育費をわけてます(依存関係)」

経路情報をパースする 設計 2

RouteJsonParser の構造は、いまだと必要のないアトリビュートが存在している。

status、class Routes に関連する子や孫たちである。

ストーリを読んでみると、RouteJsonParserさんは大変だ。自分の仕事だけでなく、子供、孫まで面倒見てるんだね。
子供が生まれたらそのつど、かみさんにはまかせておけないから、RouteJsonParserさんが面倒見るんだ、しかもその孫たちまでも、もしかしたら曾孫も面倒見るのかな?

といった感じ!

JSONデータのバージョンが変わったらそのつど、RouteJsonParser を改修し複数のバージョンに対応していく。
・・・ありえん!
これは、内部クラスのひとつの弊害である。
お手伝いしたプロジェクトで、こんな事例によく遭遇した。

なので、RouteJsonParser を書き直します。

public class RouteJsonParser {
	/**
	 * 元になる経路情報 
	 */
	private JSONObject jsonRoute = null;
	/**
	 * パースされた経路情報
	 */
	private List<Routes> routes = null;
	/**
	 * パースできない場合のエラーコード
	 */
	private int error = 0;
	/**
	 * GoogleDirectionsAPI のバージョン
	 * 2014/10/01 現在リリースされているバージョン 1 を初期値とする
	 */
	private int verGoogleDirectionsAPI = 1;
	/**
	 * コンストラクタ
	 * Google Directions API のバージョンは 1
	 */
	public RouteJsonParser() {}
	/**
	 * コンストラクタ
	 * @param ver Google Directions API のバージョン
	 */
	public RouteJsonParser(int ver) {
		verGoogleDirectionsAPI = ver;
	}
	/**
	 * JSONデータをパースする
	 * @param jsonString JSON文字列
	 * @return パースした経路情報
	 */
	public List<Routes> parse(String jsonString) {
		// バージョンごとに対応、JSONデータをパースする
		switch(verGoogleDirectionsAPI) {
		case 1:
			break;

		default:
			
		}
		return routes;
	}
	/**
	 * パースされた経路情報を取得
	 * @return
	 */
	public List<Routes> getParseListt() {
		return routes;
	}
	/**
	 * JSONデータが有効であるか検査する
	 * @return true:有効 false:無効
	 */
	public boolean isSuccess() {
		return true;
	}
	/**
	 * ステータスコードの取得
	 * @return
	 */
	public String getStatus() {
		return "";
	}
	/**
	 * エラーコードを取得
	 * @return
	 */
	public int getError() {
		return error;
	}
}

だいぶすっきり、明確になりました。

ストーリーを読んでみると、
RouteJsonParserさんのお仕事はJSONデータをパースすることで、パースした内容はいつでも引き出せるように保持する。
パースするデータがバージョンの異なるデータが放り込まれても、生まれたときに対応するバージョンが決められているので、決められたことしかしない。なので、生まれる前にバージョンをきめといてね!
パースのお仕事が済んだら、うまくできたか報告することと、ちょっとだけパースした中身のステータスを教えてくれるそうだ.
また、パースできなかった原因も報告してくれるらしい。

こんな感じで、私には読めました。いかがですか?

ちなみに、RouteJsonParserさんに Routesさんのことを聞いてみました。
「俺、Routesさんと付き合ってるんだけど、よく知らないんだ」
とのお答えでした。
以前のソースでは結婚し子供まで作っていたのに・・・・

経路情報をパースする 設計

内部クラス、無名クラスのことでぼやいていたので、そんじゃ、どうすればいいか書いてなかった。
入社1年目のシステムエンジニアやプログラマに、教えていた方法で、経路情報のパース処理について設計、実装を書いておく。

JSONデータの構造は、こちら Google Directions API を参考にした

RouteJsonParser.java

import org.json.JSONObject;

/**
 * @author Ryan's Factory
 *
 */
public class RouteJsonParser {
	private JSONObject jsonRoute = null;
	private String status = "";
	private List<Routes> routes = null;
	class Routes {
		private String summary = "";
		private List<Legs> legs = null;
		private String waypoint_order = "";
		private List<OverviewPolyline> overview_polyline = null;
		private String bounds = "";
		private String copyrights = "";
		private String warnings = "";
	}
	class Legs {
		private List<Steps> steps = null;
		private Distance distance = null;
		private Duration duration = null;
		private ArrivalTime arrival_time = null;
		private String departure_time = ""; //Time オブジェクト?
		private String start_location = "";
		private String end_location = "";
		private String start_address = "";
		private String end_address = "";
	}
	class OverviewPolyline {
	}
	class Steps {
		private String html_instructions = "";
		private Distance distance = null;
		private Duration duration = null;
		private String start_location = "";
		private String end_location = "";
		private List<Steps> sub_steps = null;
		private TransitDetails transit_details = null;
	}
	class Distance {
	}
	class Duration {
	}
	class ArrivalTime {
	}
	class TransitDetails {
		// 乗り換えについては後で設計する
	}
	RouteJsonParser() {
	}
	public List<Routes> parse(String jsonString) {
		return routes;
	}
}

書かれていたドキュメントを信じて(?)そのままをクラス構造としている。
実装するアトリビュート、オブジェクトはこの段階では具体的に書けないが、少なくとも、Jsonであることがわかっているので、基本を String と JSONObject にしている。

ドキュメントには配列の記述があるが、配列のままでは、メモリアロケーションに問題がある。
どういうことかと簡単に説明すると、配列の要素数が可変する場合、そのつどアロケーションしなければならないので、ListかMapのほうが効率がよい。
List or Map のどちらにするか?
Mapのほうが扱いやすいが、格納順序も考慮しなければいけないようなので、Listが最適だろうと判断する。

その他のオブジェクトについて判明しているのは、内部クラスで記述している。
Javaの利点、クラス設計が考えているままを記述できる。
元のデータが、構造化および機能などが設計されているので、そのままを書ける。
現段階では、内部クラスだが、設計が進めば外部クラスにしなければならないオブジェクトが見えてくる。

実装がなくても、コンパイルができるので、ほかのチームに迷惑がかからない(笑)

コーディングは文章です。Javaという言語を用いて、相手が内容を理解できるように書くのが基本です。
ソースを見れば、書いた人間のひととなりがうかがえます。

読みやすいソースは、ストーリをそのまま受け入れられます。
読みにくいソースは、ストーリが混乱したままよくわかりません。

HttpClient の content-charset を変更する

無理やりキャラセットを変更する場合

		DefaultHttpClient httpClient = new DefaultHttpClient();
		HttpParams params = httpClient.getParams();
		params.setParameter("http.protocol.content-charset", CHARSET);

注意しながら行なうこと!
できる限り、他の方法で文字化けを解消するようにする。