web-dev-qa-db-ja.com

ToStringと暗黙の演算子の設計が異なるのは悪い設計ですか?

私は現在、stringsを翻訳するための複数のメソッドを提供する必要がある翻訳サービス用のラッパーAPIを開発しています。

Task<string> ITranslator.TranslateAsync(string phrase, Language from, Language to)

ICollection<string>

Task<ICollection<string>> ITranslator.TranslateAsync(ICollection<string> phrases, Language from, Language to)

およびドキュメント全体:

Task<Stream> ITranslator.TranslateDocumentAsync(string file, Language from, Language to)

Languageクラスは次のようになります

public sealed class Language
{
    [JsonProperty("language")]
    public string TwoLetterISOLanguageName { get; internal set; }

    [JsonProperty("name")]
    public string NativeName { get; internal set; }

    internal Language()
    {
    }

    /* Provided in case users want to display these in a ComboBox or similar. */
    public override string ToString() => NativeName;
}

クラスのインスタンスは、ITranslator.GetSupportedLanguagesAsyncの呼び出しによってのみ取得できます。

ただし、サポートされている言語をAPIのユーザーに強制的にクエリさせたくはありません。また、fromではなくtoタイプのstringおよびLanguageパラメーターを使用するメソッドを使用して、翻訳要求をより簡潔にする方法を提供します。 "de"などの言語コード。

そのソリューションの私の問題は、Translate *メソッドが2倍になるため、APIが不必要に乱雑になり、これらのメソッドに関するドキュメントのほとんどが複製されることです。

Languageクラスにimplicit operator関数を導入してインスタンスをTwoLetterISOLanguageCodeに変換し、メソッドのオーバーロードを不要にすることを最後に考えましたが、Languageクラスには既にToStringがあるため、設計が悪いかどうかはわかりません代わりにNativeNameを生成するメソッド。

このimplicit operatorの追加は悪いデザインでしょうか?

2
user354327

文字列は、言語を識別するのにまったく不適切なデータ構造です。これは、文字列内のテキストの解釈方法を指定する必要があるためです( "French"、 "französisch"、 "franzoesisch"、 "Français"、 "Francais"、 "FR"のうち、言語を指定しますか?)。また、文字列が言語を表すかどうかはまったく明確ではありません。

そのため、アプリケーションは、ユーザーのロケールで言語を指定するユーザー入力の文字列、または言語を指定することになっている他の文字列を、可能な最も早い時点で「Language」インスタンスに変換し、Languageの有効なインスタンスかどうかを取得して、文字列ができるだけ早い段階で言語を表さなかった問題。

そして今、あなたの全体の問題はなくなりました。

"Language"クラス自体では、Languageオブジェクトの文字列を返すメソッドが何を返すかが完全に明確ではありません。 2文字のISOコードを返す関数がある場合は、呼び出し元が2文字のISOコードを返すことがわかるように名前を付けます。多くの言語には、ロギング/デバッグの目的で使用される文字列を生成する標準関数があります。

"NativeName"は既に不明確です:英語、フランス語、ドイツ語の "NativeName"は "English"、 "Français"、 "Deutsch"ですか(これらはareのネイティブ名であるため)?または、ユーザーがフランス語の場合、ローカライズされた名前は「Anglais」、「Français」、「Allemand」のようになる可能性がありますか?

いくつかの自動変換を行おうとすると、この問題をひどく悪化させるだけです。

3
gnasher729

私の考えでは、APIで言語を指定する正しい方法は列挙型です。列挙型は、離散値の完全なリストです。サポートされていない値はコンパイルできないため、これはコンパイル時にガードレールとしても機能します。 JSONには列挙型がないため、インピーダンスの不一致がありますが、JSONライブラリは文字列から列挙型にアップ/ダウンコンバートできます。

文字列表現の意図が述べられていないため、ToString()はコードの匂いを少し獲得しました。ロギング、デバッグ、またはエンドユーザーへの表示用ですか?表示に必要な場合は、コードを読みやすくするためにDisplayNameメンバーを含めます。それをローカライズする必要がある場合もあります。

呼び出し元が言語クラスの完全なコンテンツに関与する必要があるべきではありません。代わりに、Languageインスタンスを取得するために辞書を保持します。

public enum LanguageIdentifier {
    en,
    de // more ISO identifiers you support ...
}

internal class Language {
    LanguageIdentifier Identifier { get; set; }
    string DisplayName { get; set; }
    // ...
}

public static class Translator {
    internal static Dictionary<LanguageIdentifier, Language> LanguageMap = 
        new Dictionary<LanguageIdentifier, Language>();
}
2
Martin K

それはガイドラインに違反しています

すべての文字列が有効な言語であるとは限りません。1つの文字列をキャストすると、実行時エラーが発生する可能性があります。 MSによると キャストタイプのガイドライン

何が起こっているのかを開発者が理解するのは非常に難しいため、暗黙的キャストから例外をスローしないでください。

暗黙の演算子がスローする可能性があるため、おそらく優れた設計ではありません。

別の方法として、静的なFrom___パターン、例えば.

translator.TranslateAsync(text, Language.FromCultureString("en-us"), Language.FromCultureString("tlh"));

暗黙的な変換のアイデアと結婚している場合は、おそらくカルチャーにそれを提供できます(カルチャーから言語への完全なマッピングがあると想定)。これにより、呼び出し元はこれを行うことができます。

translator.TranslateAsync(text, new Culture("en-us"), new Culture("tlh"));
1
John Wu

ToString()は、1つのことを伝えることのみを目的としています。

https://docs.Microsoft.com/en-us/dotnet/api/system.object.tostring?view=netframework-4.8

これを使用すると、Languageクラスはおそらく本来の意図に反することになります。

使用されている言語を説明するために使用することができます。たとえば、EN "English"またはFR "French"が返されます。

0
Jon Raynor