タグ付け出来る辞書をTDDで書いてみた。

TDDの練習として、ふと閃いた「タグ付け出来る辞書」を作ってみました。

仕様

細かい事は決めていなかったのですが、下記のようなのをイメージ。

  • Gmailのタグ付けのように、あるデータに対して複数のタグを付けられる
  • タグは後から変更(追加・削除)出来る
  • key - value - tags なデータ構造?

その他、勉強用の仕様として、下記を追加。

  • 総称型を使う
  • assertThatを使う
  • 可変長引数を使う

とりあえず、コードが書きたかったので、思いつきで書いてみました。

コード
package sample.tagmap;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.junit.matchers.JUnitMatchers.*;

import java.util.Arrays;

import org.junit.Before;
import org.junit.Test;

public class TagMapTest {
	TagMap<String, String> map;

	@Before
	public void setUp() throws Exception {
		map = new TagMap<String, String>();
	}

	@Test
	public void 単一タグを持つマップを作成する() throws Exception {
		map.put("key", "value", "tag");
		assertThat(Arrays.asList(map.getTags("key")), hasItem("tag"));
	}

	@Test
	public void 複数のタグを持つマップを作成する() throws Exception {
		map.put("key", "value", "tag1", "tag2");
		assertThat(Arrays.asList(map.getTags("key")), hasItems("tag1", "tag2"));
	}

	@Test
	public void データを削除したらタグも削除されていること() throws Exception {
		map.put("key", "value", "tag");
		map.remove("key");
		assertThat(map.getTags("key"), nullValue());
	}

	@Test
	public void タグを設定・削除できること() throws Exception {
		map.put("key", "value");

		map.putTags("key", "tag1", "tag2");
		assertThat(Arrays.asList(map.getTags("key")), hasItems("tag1", "tag2"));

		map.putTags("key", "tag10", "tag20");
		assertThat(Arrays.asList(map.getTags("key")), hasItems("tag10", "tag20"));

		map.putTags("key", (String[]) null);
		assertThat(map.getTags("key"), nullValue());
	}
}
package sample.tagmap;

import java.util.HashMap;

/**
 * キーに対してタグを付けることが出来るMap
 *
 * @author sinsoku
 *
 * @param <K>
 *            Key
 * @param <V>
 *            Value
 */
@SuppressWarnings("serial")
public class TagMap<K, V> extends HashMap<K, V> {
	HashMap<K, String[]> tagmap = new HashMap<K, String[]>();

	/**
	 * 指定されるキーに関連付られたタグを返します。
	 *
	 * @param key
	 *            指定されるタグが関連付けられるキー
	 * @return 指定されたキーに関連したタグ。またはこのキーのタグが無かった場合は<code>null</code>
	 */
	public String[] getTags(K key) {
		return tagmap.get(key);
	}

	/**
	 * 指定されたキーに指定された値とタグをこのマップに関連付けます。マップが以前にこのキーのマッピングを保持していた場合、古い値が置き換えられます。
	 *
	 * @param key
	 *            指定される値、タグが関連付けられるキー
	 * @param value
	 *            指定されるキーに関連付けられる値
	 * @param tags
	 *            指定されるキーに関連づけれらるタグ
	 * @return 指定されたキーに関連した値。または、キーのマッピングが無かった場合は<code>null</code>
	 */
	public V put(K key, V value, String... tags) {
		tagmap.put(key, tags);
		return super.put(key, value);
	}

	@Override
	public V remove(Object key) {
		tagmap.remove(key);
		return super.remove(key);
	}

	/**
	 * キーに対して複数のタグを付ける事が出来る。マップが以前にこのキーのタグを保持していた場合、古い値が置き換えられます。
	 *
	 * @param key
	 *            指定されるタグが関連付けられるキー
	 * @param tags
	 *            指定されるキーに関連付られるタグ
	 * @return 指定されたキーに関連したタグ。またはこのキーのタグが無かった場合は<code>null</code>
	 */
	public String[] putTags(K key, String... tags) {
		return tagmap.put(key, tags);
	}

}

テストが少ないような気もしますが、タグに関するテストだと設定・追加・削除ぐらいなんだよな・・・

とりあえず、適当実装でこんな感じになりました。
まだ、継承元のHashMapのメソッドを適切にオーバーライドしていないので、clear()とか呼ばれると
タグだけ残るようなバグもあります。
また明日にでも弄ってみます。

追加機能

ここまで書いてから、追加機能として下記のような機能を考えてみた。

  • タグのついているkeyを検索出来る。
  • 複数のタグを指定して、タグの付いているkeyを検索出来る
  • clone()出来る。

また、テストとして

  • 総称型を確認するテストを書く。


現在の実装方法だと、タグからkeyを検索するのが難しいので、データ構造を見直す必要がありそう。
全体的にLRUキャッシュのよりはヌルイ題材ですし、TDDでやるには手頃だったかな。