abhrshのSeasarにっき

2004-08-22

Strategyパターン

S2でStrategyパターンを使って動的にアルゴリズムを変更するサンプルを作ってみました。

方針はひがさんが言われている

Strategyが静的なものなら、普通にDIするだけです。 問題は、Strategyがデータに応じて動的に変わる場合です。 その場合は、StrategyのFactoryDIするようにします。 そして、必要なときに、データをStrategyのFactoryに 渡して、適切なStrategyを手に入れるのです。

はてなダイアリー - ひがやすをのここだけの話

に従っているつもりです。

お題は「一覧を取得するときにいくつかのソート順を切り替える」です。

まずはインターフェースの定義。

public interface GetSampleFactory {
    public static final String ORDER_ASC = "orderAsc";
    public static final String ORDER_DESC = "orderDesc";

    GetSampleLogic create(String key);
}

public interface GetSampleLogic {
    List getSample();
}

public interface SampleDao {
    List getSampleOrderAsc();
    List getSampleOrderDesc();
}

StrategyにあたるのはGetSampleLogicで、GetSampleFactoryが適切なGetSampleLogicの実装を提供します。

また、GetSampleFactory#create(String)の引数に渡すことができる値は"orderAsc"と"orderDesc"です。

SampleDaoには昇順と降順の2種類のListを取得できるメソッドがあります。

そして実装。

public class GetSampleFactoryImpl implements GetSampleFactory {
    private Map instanceMap = new HashMap();

    public void setOrderAsc(GetSampleLogic orderAsc) {
        instanceMap.put(ORDER_ASC, orderAsc);
    }

    public void setOrderDesc(GetSampleLogic orderDesc) {
        instanceMap.put(ORDER_DESC, orderDesc);
    }

    public GetSampleLogic create(String key) {
        return (GetSampleLogic) instanceMap.get(key);
    }
}

public class GetSampleOrderAsc implements GetSampleLogic {
    private SampleDao sampleDao;

    public void setSampleDao(SampleDao sampleDao) {
        this.sampleDao = sampleDao;
    }

    public List getSample() {
        return sampleDao.getSampleOrderAsc();
    }
}

public class GetSampleOrderDesc implements GetSampleLogic {
    private SampleDao sampleDao;

    public void setSampleDao(SampleDao sampleDao) {
        this.sampleDao = sampleDao;
    }

    public List getSample() {
        return sampleDao.getSampleOrderDesc();
    }
}

public class SampleDaoImpl implements SampleDao {
    public List getSampleOrderAsc() {
        return Arrays.asList(new String[] { "aa", "bb", "cc" });
    }

    public List getSampleOrderDesc() {
        return Arrays.asList(new String[] { "cc", "bb", "aa" });
    }
}

GetSampleFactoryImplが提供するGetSampleLogicの実装はsetterメソッドで渡すようにしました。これで実装クラスへの依存関係はdiconファイルで設定することができます。

GetSampleOrderAscとGetSampleOrderDescはGetSampleLogicの実装クラスです。SampleDaoのメソッド呼び出し以外は全く同じですね。(もう少しロジックがあるサンプルの方が良かったかも)

SampleDaoImplはSampleDaoの実装クラスです。

それでは、実行してちゃんと動くことを確かめたいと思います。

diconファイルとmain実行クラスです。



"http://www.seasar.org/dtd/components.dtd">
<components>
    <component name="sampleDao" class="strategy.SampleDaoImpl"/>

    <component name="getSampleOrderAsc" class="strategy.GetSampleOrderAsc"/>
    <component name="getSampleOrderDesc" class="strategy.GetSampleOrderDesc"/>

    <component name="getSampleFactory" class="strategy.GetSampleFactoryImpl">
        <property name="orderAsc">getSampleOrderAsc</property>
        <property name="orderDesc">getSampleOrderDesc</property>
    </component>
</components>

public class GetSampleFactoryClient {
    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create("strategy/strategy.dicon");
        GetSampleFactory factory = (GetSampleFactory) container.getComponent(GetSampleFactory.class);

        GetSampleLogic getSampleOrderAsc = factory.create(GetSampleFactory.ORDER_ASC);
        System.out.println(getSampleOrderAsc.getSample());

        GetSampleLogic getSampleOrderDesc = factory.create(GetSampleFactory.ORDER_DESC);
        System.out.println(getSampleOrderDesc.getSample());
    }
}

そして実行。

[aa, bb, cc]
[cc, bb, aa]

ちゃんと動きました~。これでStrategyもバッチリ

今回の場合は、Strategy部分の実装はDelegateInterceptorだけでもいけそうです。後でそっちでも動かしてみたいと思います。

Strategyパターン 捕捉

Strategyパターンとは本質的には関係ないですが、Strategy部分の実装をDelegateIntercepterに任せた場合も試しました。

diconファイルだけ差し替えます。(GetSampleOrderAscとGetSampleOrderDescは必要なくなります)



"http://www.seasar.org/dtd/components.dtd">
<components>
    <component name="sampleDao" class="strategy.SampleDaoImpl"/>

    <component name="getSampleOrderAsc" class="strategy.GetSampleLogic">
        <aspect>
            <component class="org.seasar.framework.aop.interceptors.DelegateInterceptor">
                <property name="target">sampleDao</property>
                <initMethod name="addMethodNameMap">
                    <arg>"getSample"</arg>
                    <arg>"getSampleOrderAsc"</arg>
                </initMethod>
            </component>
        </aspect>
    </component>

    <component name="getSampleOrderDesc" class="strategy.GetSampleLogic">
        <aspect>
            <component class="org.seasar.framework.aop.interceptors.DelegateInterceptor">
                <property name="target">sampleDao</property>
                <initMethod name="addMethodNameMap">
                    <arg>"getSample"</arg>
                    <arg>"getSampleOrderDesc"</arg>
                </initMethod>
            </component>
        </aspect>
    </component>

    <component name="getSampleFactory" class="strategy.GetSampleFactoryImpl">
        <property name="orderAsc">getSampleOrderAsc</property>
        <property name="orderDesc">getSampleOrderDesc</property>
    </component>
</components>

これで実行。

[aa, bb, cc]
[cc, bb, aa]

問題ないですね。

でもこれ使うとdiconファイルが読みづらくなってしまうなぁ。既にインターフェースが定義してあるから、こういう場合はDelegateInterceptorのメリットは少ないのかも。

zhsujdyjbrwzhsujdyjbrw2012/02/17 18:05pXRu4K , [url=http://gywanprthcww.com/]gywanprthcww[/url], [link=http://aizuwtjagvqm.com/]aizuwtjagvqm[/link], http://mibdprhmwfku.com/

トラックバック - http://seasarproject.g.hatena.ne.jp/abhrsh/20040822

2004-08-01

[]reload

2.0.15でS2TestCaseにreloadメソッドが追加されました。私が以前提案したassertEquals(DataSet, DataSource)より使いやすそうです。

ただ、日付の値があるとうまく動かなかったので、とりあえず勝手にこれを修正してみようと思います。

調査したところ、DataRowImpl#equals(Object)メソッド内のColumnType#doEquals(Object, Object)の結果が怪しいので、ColumnTypeを正しく設定すれば良さそうだと思いました。

具体的には、DataTableImpl#setMetaData(DatabaseMetaData)メソッドでColumnTypeを更新してしまおうと思ってます。

そのための準備としてまず、DataColumnインターフェースに以下のメソッドを追加します。(DataColumnImplクラスに実装)

public void setColumnType(ColumnType);

次に、DatabaseMetaDataUtilにカラムのタイプを取得するメソッドを追加します。

public static Map getColumnTypeMap(DatabaseMetaData dbMetaData, String tableName) {
    tableName = convertIdentifier(dbMetaData, tableName);
    int index = tableName.indexOf('.');
    if (index >= 0) {
        tableName = tableName.substring(index + 1);
    }
    Map map = new HashMap();
    try {
        ResultSet rs = dbMetaData.getColumns(null, null, tableName, null);
        while (rs.next()) {
            map.put(rs.getString(4), new Integer(rs.getInt(5)));
        }
        rs.close();
    } catch (SQLException ex) {
        throw new SQLRuntimeException(ex);
    }
    return map;
}

このメソッドが返すMapには{カラム名:String, タイプ:Integer}が入っています。

そして、DataTableImpl#setMetaData(DatabaseMetaData)を以下のように修正します。

public void setupMetaData(DatabaseMetaData dbMetaData) {
    Set primaryKeySet = DatabaseMetaDataUtil.getPrimaryKeySet(dbMetaData,
            tableName_);
    Map columnTypeMap = DatabaseMetaDataUtil.getColumnTypeMap(dbMetaData,
            tableName_);
    for (int i = 0; i < getColumnSize(); ++i) {
        DataColumn column = getColumn(i);
        if (primaryKeySet.contains(column.getColumnName())) {
            column.setPrimaryKey(true);
        } else {
            column.setPrimaryKey(false);
        }
        if (columnTypeMap.containsKey(column.getColumnName())) {
            column.setWritable(true);
            Integer type = (Integer) columnTypeMap.get(column.getColumnName());
            column.setColumnType(ColumnTypes.getColumnType(type.intValue()));
        } else {
            column.setWritable(false);
        }
    }
    hasMetaData_ = true;
}

修正個所はcolumnTypeMapを取得する部分と、column.setColumnType()の部分です。

これで修正は終わりです。自分の環境では日付が含まれていてもちゃんとテストが通りました。

今回はColumnTypeを変更したかったのでDataColumnのインターフェースを変えてしまいました。

こんな方法で良いのかは分かりませんが、ColumnTypeが設定されるように修正してもらえないでしょうか>ひがさん

トラックバック - http://seasarproject.g.hatena.ne.jp/abhrsh/20040801

2004-07-20

[]S2.0.13 S2DaoV1.0.1リリース (via higayasuo)

BeanAssertとBeanReaderが取り込まれてました。ひがさんどうもです。

ソースを確認して、ちょっとだけ気になったところがありました。

BeanAssertクラスで定義されているメソッドが、

BeanAssert#assertEquals(DataSet expected, Object actual)
BeanAssert#assertBeanEquals(String message, DataSet expected, Object actual)

と、名称が異なってしまっています。assertBeanEqualsの方は使われてはいないようですが一応報告です。細かくてすみません。

あと、ついでに要望なのですがDbAssertも取り込んでもらえないでしょうか?

これです。http://d.hatena.ne.jp/abhrsh/20040704#p1

こっちはテストが劇的に簡単になるものではないですが、テストコードが簡潔で直感的になるのでとても有効だと思っています。

higayasuohigayasuo2004/07/21 06:40assertBeanEquals修正しておきます。DBAssertもS2TestCaseのインスタンスメソッドに組み込んで、簡潔にいけそうですね。assertXlsAndDbEquals(String message, String path)

abhrshabhrsh2004/07/21 15:32それもいいですね。assertDbEquals(String message, DataSet expected)っていうのもあると汎用的で良さそうな気がします。

トラックバック - http://seasarproject.g.hatena.ne.jp/abhrsh/20040720