私はREST APIのラッパーを書いており、以前に自分で尋ねる必要がなかった何かに遭遇しました。
このAPIはeコマーストランザクション用であり、SALEおよびRETURNエンドポイント(およびこの説明に重要ではないその他のエンドポイント)があります。
SALEリクエストとRETURNリクエストはほとんど同じですが、SALEリクエストには追加のプロパティがあります。
public class ReturnRequest
{
[JsonProperty("cashier_id")]
public string CashierId { get; set; }
[JsonProperty("amount")]
public decimal Amount { get; set; }
[JsonProperty("pos_id")]
public CustomClass PosId { get; set; }
}
public class SaleRequest
{
[JsonProperty("cashier_id")]
public string CashierId { get; set; }
[JsonProperty("amount")]
public decimal Amount { get; set; }
[JsonProperty("pos_id")]
public CustomClass PosId { get; set; }
[JsonProperty("Zip_code")]
public string ZipCode { get; set; }
}
もともとは、SALEリクエストPOCOにRETURNリクエストPOCOを継承させるだけでした。
public class ReturnRequest
{
[JsonProperty("cashier_id")]
public string CashierId { get; set; }
[JsonProperty("amount")]
public decimal Amount { get; set; }
[JsonProperty("pos_id")]
public CustomClass PosId { get; set; }
}
public class SaleRequest : ReturnRequest
{
[JsonProperty("Zip_code")]
public string ZipCode { get; set; }
}
しかし、私にはあまり直感的でも明確でもなかったように思われます(SALEとRETURNは別物です。なぜ別のものを継承するのですか?)
次に、共通のプロパティを基本抽象クラスに入れることにしました。
public abstract class BaseRequest
{
[JsonProperty("cashier_id")]
public string CashierId { get; set; }
[JsonProperty("amount")]
public decimal Amount { get; set; }
[JsonProperty("pos_id")]
public CustomClass PosId { get; set; }
}
public class ReturnRequest : BaseRequest
{
}
public class SaleRequest : BaseRequest
{
[JsonProperty("Zip_code")]
public string ZipCode { get; set; }
}
しかし、その結果、BaseRequestと本質的に同じクラスが残ります。
最初はこれを正当化しました。それは、クラスを単純で冗長なものにしたかったからです。それはクラス間の明確な違いを生み出し、私は他の(あるいは基本クラスでさえ)心配することなくSALEまたはRETURNを変更できます。
しかし、今私はこれを間違って考えているのだろうかと思っています。本質的に空白のクラスを持っているのは悪い考えですか?
編集:プロパティ名とタイプを少し変更して、プロパティのタイプのいくつかをより厳密に表現しました。
新しいものを継承して追加する私のお気に入りの例はこれです:
public class LoginException : System.ApplicationException{}
どうして?すばらしい説明のクラス名で私が言う必要があるすべてを言ったので。確かに、動的メッセージを追加したい場合は、コンストラクターを追加する必要があります。しかし、もしそうでなければ、なぜ私はそうすべきなのでしょうか?
意味上の問題はProp1
のような名前で隠されているため、この例ではこの明確さが欠けています。継承を合成と委任に置き換えるのが好きですが、この場合、これを行うと、次のようになります。
SaleRequest sr = new SaleRequest(new ReturnRequest());
今私は尋ねなければならない、それは意味論的に意味がありますか?返品をリクエストせずにセールをリクエストすることはできませんか?
確かに、「小道具」は「正しい」が、それらは単なるデータ型であるため、機械的には理にかなっています。彼らが意味のある名前を持っていたとしても、それは理にかなっていますか?
この問題を修正しようとすると、次のようになります。
SaleRequest sr = new SaleRequest(new BaseRequest());
これもまた、意味論的に貧血です。ベース?このクラスに何が属し、何が属していないかはわかりません。それは良い名前ではありません。
BaseRequest
にProp1
とProp2
(それらが何であれ)が属していることを明確に示す名前があり、BaseRequest
が明らかに一部であるものの名前を持っている場合他の2つの要求のうち、またはすべての場合、これで問題ありません。
現状では、このコードを見て何が起こっているのかわかりません。それは良いことではありません。
空のクラスでも問題ありません。しかし、良い名前を付けてください。それは本当に彼らが提供しなければならない唯一のものです。
はい、基本クラスの「空の」サブクラスを持つことは完全に合理的です。異なるがいくつかの点で類似した状況は、表現に違いがない場合でも、型をラップして型の区別を強制することです。例えば:
struct Seconds {
public Seconds(int v) { Value = v; }
public int Value { get; }
}
struct Meters {
public Meters(int v) { Value = v; }
public int Value { get; }
}
これらがint
s以外になると考える理由はないかもしれませんが、混同しないようにそれらをラップすることは依然として価値があります。
そうは言っても、特定のケースでは、BaseRequest
が意味のある概念に対応していることは明らかではありません。 LAYAWAYオプションを追加した場合でも、BaseRequest
は意味のあるスーパークラスになりますか?答えが「いいえ」の場合、おそらくSaleRequest
とReturnRequest
を完全に別個のクラスとして扱います。 (これらの一般的な機能をキャプチャするインターフェイスがあるかもしれませんが、それはBaseRequest
ではないかもしれません。)十分な利便性を追加すれば、BaseRequest
を純粋に内部的に実装の詳細として使用できます。この場合、おそらくそれは価値がなく、概念的には素晴らしいアイデアではありません(コードがわかりにくくなります)。
この場合、2つの異なるエンドポイントであるので、返されたモデルを共有せず、それぞれに独自のエンドポイントを持たせます。これが意味する場合でも、最初の出現では、「重複した」コードがいくつかあります。現在は同じかもしれませんが、さまざまな理由で変更される可能性があります。
「BaseRequest」を直接期待して機能するコードが作成されるまで、これは一般的な抽象概念ではありません。それらの類似性が偶然であると考えてください。