0

TL;DR: SOLID の原則に違反せずにインターフェイス間でデータを移動する最善の方法は何ですか?


私はこれを考えすぎているかもしれませんが、SOLID の原則に関して独断的になるつもりはありません。しかし、私はいくつかのインプットを得たかったのです。私はショッピングカートをより「しっかり」するようにリファクタリングしてきましたが、私が書いたメソッドは「コードの匂い」のように思えます (そうではないかもしれません)。

次のようなCartHelperクラスがあります (簡潔にするために少し簡略化しています)。

パブリック クラス CartHelper
{
    IEnumerable Products;
    IEnumerable サブスクリプション。

    // ...その他のクラスメソッド...

    [HttpPost]
    public void AddItem(int productVariantID)
    {
        var product = ProductService.GetByVariantID(productVariantID);

        if (製品 != null)
        {
            if (product.Type == (int)Constants.ProductTypes.Subscription)
                Subscriptions = Subscriptions.Concat(new [] { product });

            Products = Products.Concat(new [] { product });
            CartService.AddItem(productVariantID);
        }
    }

    [HttpPost]
    public void RemoveItem(int productVariantID)
    {
        サブスクリプション = Subscriptions.Where(s => s.VariantID != productVariantID);
        Products = Products.Where(p => p.VariantID != productVariantID);

        CartService.RemoveItem(productVariantID);
    }

    公開小数 GetCartTotalBeforeDiscount()
    {
        Products.Sum(p => p.Price) を返します。
    }

    public IEnumerable GetCartItems()
    {
        var products = (Products の p から
                        新しい CartSummaryItem を選択
                        {
                           ProductID = p.ProductID、
                           タイトル = p.タイトル、
                           説明 = p.説明、
                           価格 = p.価格、
                           // ...他の適用可能なプロパティをここに割り当てます...
                        }
                        ICartSummaryItem として);

        製品を返品します。
    }

    // ...その他のクラスメソッド...
}

私にとって「コード臭」のように見える部分 (そして、ここにはもっと悪い部分があるかもしれません) はGetCartItems()メソッドです。それについての何かが私にはファンキーに思えますが、それ以上の代替案は思いつきません.

ビューに渡す必要があるいくつかのプロパティが追加されていますが、または(インターフェイス分離の原則) ではICartItem意味がありません。IStoreProductIStoreSubscription

ConvertProductToCartItem()にとConvertSubscriptionToCartItem()メソッドを追加することを考えましたCartHelperが、それは単一責任の原則に違反しているようです。IStoreProducts とsを受け入れて変換する CartItemFactory を持つことは理にかなっていIStoreSubscriptionますか? このような単純な変換には、多くの不必要なオーバーヘッドがかかるようです。

私が思いついた解決策の 1 つは、明示的なキャスト メソッドを定義することです。

パブリック クラス StoreProduct : IStoreProduct
{
    公開小数価格{取得; 設定; }
    public decimal 割引 { get; 設定; }
    // ...プロパティ...

    public ICartItem ToCartItem()
    {
        // 明示的なキャスト実装を呼び出します
        return (CartItem) this;
    }

    // Product を CartItem として明示的にキャスト変換する
    public static explicit operator CartItem(StoreProduct 商品)
    {
        new CartItem() を返す
        {
            価格 = 商品.価格、
            割引 = 商品.価格、
            SalePrice = Helper.CalculateSalePriceForProduct(製品)、
            // ...他の適用可能なプロパティをここに割り当てます...
        };
    }
}

これにより、GetCartItemsメソッドをこのよりクリーンな実装に変更できます。

public IEnumerable GetCartItems()
{
    製品を返します。選択 (p => p.ToCartSummaryItem());
}

しかし、このアプローチの問題は、クラスへのカップリングICartItemによって単一責任の原則にも違反することです。キャスト変換の代わりに拡張メソッドも検討しましたが、それは優れているわけでも違いがあるわけでもありません。CartItemStoreProduct

具象StoreProductクラスにインターフェイスを実装させ、ICartItemそこにカート固有のプロパティを配置する必要がありますか? CartHelperICartItems のみを持つように (つまり、s を削除して) を書き直すべきIProductでしょうか? これらのオプションは両方とも、単一責任の原則に違反しているように見えます。たぶん、私が少し寝た後に解決策が明らかになるでしょう...

要するに、私の質問は、SOLID の原則に違反することなくインターフェース間でデータを移動する最善の方法は何かということだと思います。

助言がありますか?たぶん、私はそれについて心配する必要はありません (つまり、SOLID について独断的にならないでください)。私は自分の質問に答えましたか?これはプログラマー.stackexchange に属している可能性があります。主観的すぎないことを願っています。


また、役に立つ場合は、これが私のインターフェイスの外観です。

パブリック インターフェイス IProductBase
{
    int ProductID { get; 設定; }
    10 進価格 { get; 設定; }
    文字列のタイトル { get; 設定; }
    文字列 説明 { get; 設定; }
    // ... その他のプロパティ...
}


パブリック インターフェイス IStoreProduct : IProductBase
{
    int バリアント ID { get; 設定; }
    小数の割引{取得; 設定; }
    // ... その他のプロパティ...

    ICartItem ToCartItem();
}


パブリック インターフェイス ISubscription : IProductBase
{
    SubscriptionType SubscriptionType { get; 設定; }
    // ... その他のプロパティ...

    ICartItem ToCartItem();
}


パブリック インターフェイス ICartItem : IProductBase
{
    10進数のSalePrice {get; 設定; }
    // ... その他のプロパティ...
}

更新:わかりやすくするために投稿属性を追加しました。

4

3 に答える 3

1

SRPとは?

SRP、SOLID の S は単一責任の原則であることを強調したいと思います。クラス/オブジェクトには1つの責任しかなく、それ以上はないことを意味します。クラスが変更された場合、その理由は 1 つだけであり、それ以上の理由はありません。

IEnumerable?

第二に、なぜIEnumerable(の代わりにList) と を使用するのConcatですか? IEnumerableand Concatoverを使用する利点が見つかりませんList

の実装を見てみましょうCartHelper

まずはネーミングから、ですHelper。それは何ですか / ヘルパーとは何ですか? 責任は何ですか?

AddItem

に移るとAddItem、ロジックは次のようになります。

  1. 提供された製品 ID を使用して、サービスを使用して製品を取得します。
  2. 商品が存在し、商品タイプが の場合Subscription、 に追加しSubscriptionます。
  3. 最後に、Productリストに追加してCartService.

私の見解では、この方法はやりすぎです。ポイント1を別の場所に移した方が良いと思います。そうしないと、クライアントでさえ問題ありません。AddItem を受け入れてリストIProductBaseに追加するだけにしProductます。Subscriptionリストはどうですか?それをスクラップしてください、あなたはそれを必要としません。LINQを使用すると、 type プロパティを使用してリストwhereからサブスクリプションのサブセットを見つけることができます。Product

クライアントの観点から、指定された製品が検索中に見つからない場合、このメソッドはユーザーに何を提供できますAddItemか? throw例外も何もありません。クライアントは を追加するだけでproduct、製品が追加されたかどうかはわかりません。彼らは、すべてが完了し、そこでエラー処理が行われないことを期待するだけです。

何をしているのかわかりませんCartService。しかし、そのように実装したい場合は問題ないと思います。

項目を削除

と同様AddItemに、リストを破棄できますSubscription。商品IDやIProductBaseクラスの受け渡しに関しては、互いに比較可能です。

GetCartTotalBeforeDiscount

待ってください、このクラスは項目を追加したり、項目を削除したりして、合計を取得できますか? SRP に続いて、割引前の合計を取得するためのロジックを変更する場合は、このクラスを変更する必要があります。クラスを変更する理由は 2 つあります。1 つ目は、アイテム コレクションの管理方法 (追加/削除/取得) を変更する場合であり、2 つ目は、割引前の合計を計算する方法です。この責任を他のクラスに移してください。にメソッドを追加することが変換可能であると思われる場合はCartHelper、ファサード パターンを使用してください。

GetCartItems

次に、カートのアイテムを取得します。しかし、待ってください。単に返すのではなく、特定のクラスに変換しましたか? IProductBase各製品を特定のクラスに変換するだけの場合、インターフェイスは何に使用されますか? さらに、クラスを変更する理由が 3 つになり、変換が理由になりました。

では、この GetCartItems をどのように改善したいですか? に変更してofGetProductsを返すと、すべてがクラスで行われます。次に、製品変換用に次のようなものを作成します。IEnumerableIProductBaseinterfaces

class CartItemConverter : IProductConverter<ICartItem>{
    public IEnumerable<ICartItem> GetConverted(IEnumerable<IProductBase> products){
        // logic here
    }
}

今、あなたには改宗の自由があります。

Decorator注:この場合のように、他のより良いパターンがあるかもしれません。しかし、これは最も単純なものであり、コードと最も類似した構造を持っています。

于 2013-07-19T01:41:20.840 に答える
0

わかりましたので、あなたの提案 (Fendy、Andy、MrDosu) のおかげで、はるかにクリーンな Facade の実装を以下に示します。

public class Cart
{
    private List<ICartItem> CartItems;

    // ...Other Class Methods...

    [HttpPost]
    public void AddItem(int productVariantID)
    {
        var product = ProductService.GetByVariantID(productVariantID);

        if (product != null)
        {
            CartItems.Add(CartItemConverter.Convert(product));
            CartService.AddItem(productVariantID);
        }
    }

    [HttpPost]
    public void RemoveItem(int productVariantID)
    {
        CartItems.RemoveAll(c => c.VariantID == productVariantID);
        CartService.RemoveItem(productVariantID);
    }

    public IEnumerable<ICartItem> GetCartItems()
    {
        return CartItems;
    }

    // ...Other Class Methods...
}

アイテムがカートに追加された後は、カート関連のプロパティのみを気にするので、IStoreProduct と ISubscription への参照を削除できます。さらに情報を抽出する必要が生じた場合 (ここでは YAGNI が適用されます)、Adapter パターンを (ProductService 経由で) 使用して、必要なデータを取り込むことができます。

于 2013-07-19T16:22:41.107 に答える