ホームページ  >  記事  >  Java  >  Java および Ceylon オブジェクトの構築と検証のためのコードケースの共有

Java および Ceylon オブジェクトの構築と検証のためのコードケースの共有

黄舟
黄舟オリジナル
2017-03-28 11:06:571338ブラウズ

Java コードを Ceylon コードに変換するときに、一部の Java クラス コンストラクターが検証と初期化を混同する状況に遭遇することがあります。シンプルだが不自然なコード例を使用して、私が言いたいことを説明しましょう。

いくつかの悪いコード

次の Java クラスを考えてみましょう。 (おい、家でこのようなコードを書かないでください)

public class Period {
    private final Date startDate;
    private final Date endDate;
    //returns null if the given String
    //does not represent a valid Date
    private Date parseDate(String date) {
       ...
    }
    public Period(String start, String end) {
        startDate = parseDate(start);
        endDate = parseDate(end);
    }
    public boolean isValid() {
        return startDate!=null && endDate!=null;
    }
    public Date getStartDate() {
        if (startDate==null) 
            throw new IllegalStateException();
        return startDate;
    }
    public Date getEndDate() {
        if (endDate==null)
            throw new IllegalStateException();
        return endDate;
    }
}

おい、前にも警告したけど、これは不自然だよ。ただし、実際の Java コードでこのようなものが見つかることは、実際には珍しいことではありません。

ここでの問題は、入力パラメーターの検証 (非表示の parseDate() メソッド内) が失敗した場合でも、Period のインスタンスが引き続き取得されることです。しかし、取得した期間は「有効な」状態ではありません。厳密に言うと、どういう意味でしょうか?

そうですね、オブジェクトが一般的な操作に意味のある応答をできない場合、そのオブジェクトは非アクティブ状態にあると言えます。この例では、getStartDate() と getEndDate() は IllegalStateException をスローしますが、これは「意味がある」とは思えない状況です。

この例を別の側面から見ると、Period を設計するときに、safety タイプの障害が発生します。チェックされていない例外は、型システムの「穴」を表します。したがって、Period のより優れたタイプセーフ設計は、未チェック例外を使用しない設計になります。この場合、これは IllegalStateException をスローしないことを意味します。

(実際のコードでは、null をチェックしない getStartDate() メソッドに遭遇する可能性が高く、このコード行の後に NullPointerException が発生します。これはさらに悪いことです。)

できる上記の Period クラスを Ceylon スタイルのクラスに変換するのは簡単です:

shared class Period(String start, String end) {
    //returns null if the given String
    //does not represent a valid Date
    Date? parseDate(String date) => ... ;
    value maybeStartDate = parseDate(start);
    value maybeEndDate = parseDate(end);
    shared Boolean valid
        => maybeStartDate exists 
        && maybeEndDate exists;
    shared Date startDate {
        assert (exists maybeStartDate);
        return maybeStartDate;
    }
    shared Date endDate {
        assert (exists maybeEndDate);
        return maybeEndDate;
    }
}

もちろん、このコードにも元の Java コードと同じ問題が発生します。 2 つのアサート シンボルは、コードの型安全性に問題があることを私たちに訴えています。

Java コードを改善する

Java でこのコードを改善するにはどうすればよいでしょうか?さて、これは、非常に非難されている Java のチェック例外が非常に合理的な解決策となり得る例です。 Period を少し変更して、コンストラクターからチェック例外をスローすることができます:

public class Period {
    private final Date startDate;
    private final Date endDate;
    //throws if the given String
    //does not represent a valid Date
    private Date parseDate(String date)
            throws DateFormatException {
       ...
    }
    public Period(String start, String end) 
            throws DateFormatException {
        startDate = parseDate(start);
        endDate = parseDate(end);
    }
    public Date getStartDate() {
        return startDate;
    }
    public Date getEndDate() {
        return endDate;
    }
}

さて、この解決策では、非アクティブな状態で Period を取得することはなく、Period をインスタンス化するコードが実行します。コンパイラーは、無効な入力を処理する責任があります。この場合、DateFormatException 例外がキャッチされます。

try {
    Period p = new Period(start, end);
    ...
}
catch (DateFormatException dfe) {
    ...
}

これは、チェック例外の素晴らしく、完璧で、正しい使用法ですが、残念ながら、上記のようなチェック例外を使用する Java コードをほとんど見かけません。

セイロンのコードをさらに改善する

では、セイロンはどうでしょうか?セイロンにはチェック例外がないため、別の解決策を見つける必要があります。通常、Java で function を呼び出すとチェック例外がスローされる状況では、Ceylon はその関数を呼び出して共用体型を返します。クラス初期化子はクラス自体以外の型を返さないため、初期化と検証の混合ロジックをファクトリ関数に抽出する必要があります。

//returns DateFormatError if the given 
//String does not represent a valid Date
Date|DateFormatError parseDate(String date) => ... ;
shared Period|DateFormatError parsePeriod
        (String start, String end) {
    value startDate = parseDate(start);
    if (is DateFormatError startDate) {
        return startDate;
    }
    value endDate = parseDate(end);
    if (is DateFormatError endDate)  {
        return endDate;
    }
    return Period(startDate, endDate);
}
shared class Period(startDate, endDate) {
    shared Date startDate;
    shared Date endDate;
}

型システムに応じて、呼び出し元は DateFormatError を処理する義務があります:

value p = parsePeriod(start, end);
if (is DateFormatError p) {
    ...
}
else {
    ...
}

あるいは、特定の日付形式の実際の問題を気にしない場合 (作業中の初期化コードがその情報を失っていると仮定すると、これは可能です) )、DateFormatError の代わりに Null を使用できます:

//returns null if the given String 
//does not represent a valid Date
Date? parseDate(String date) => ... ;
shared Period? parsePeriod(String start, String end)
    => if (exists startDate = parseDate(start), 
           exists endDate = parseDate(end))
       then Period(startDate, endDate)
       else null;
shared class Period(startDate, endDate) {
    shared Date startDate;
    shared Date endDate;
}

ファクトリ関数を使用するアプローチは、一般に検証ロジックとオブジェクトの初期化の間の分離が優れているため、控えめに言っても優れています。これはセイロンで特に役立ちます。セイロンでは、オブジェクトのすべてのフィールドが 1 回だけ割り当てられるように、コンパイラがオブジェクトの初期化ロジックに非常に厳しい制限を追加します。

以上がJava および Ceylon オブジェクトの構築と検証のためのコードケースの共有の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。