Note that there are some explanatory texts on larger screens.

plurals
  1. POMoving data between interfaces without violating SOLID principles?
    primarykey
    data
    text
    <p><strong>TL;DR:</strong> What's the best way to move data between interfaces without violating SOLID principles? </p> <hr> <p>I might be over-thinking this and I don't intend to be dogmatic in regard to SOLID principles; but I wanted to get some input. I've been refactoring a shopping cart to be more "SOLID" and there's a method that I wrote that seems like "code smell" to me (maybe it isn't). </p> <p>I have a <code>CartHelper</code> class that looks something like this (I've simplified it a bit for brevity):</p> <pre> public class CartHelper { IEnumerable Products; IEnumerable Subscriptions; // ...Other Class Methods... [HttpPost] public void AddItem(int productVariantID) { var product = ProductService.GetByVariantID(productVariantID); if (product != 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 = Subscriptions.Where(s => s.VariantID != productVariantID); Products = Products.Where(p => p.VariantID != productVariantID); CartService.RemoveItem(productVariantID); } public decimal GetCartTotalBeforeDiscount() { return Products.Sum(p => p.Price); } public IEnumerable GetCartItems() { var products = (from p in Products select new CartSummaryItem { ProductID = p.ProductID, Title = p.Title, Description = p.Description, Price = p.Price, // ...Assign other applicable properties here... } as ICartSummaryItem); return products; } // ...Other Class Methods... } </pre> <p>The part that seems like "code-smell" to me (and there might be more that's bad here) is the <code>GetCartItems()</code> method. Something about it seems funky to me, but I can't think of an alternative that is any better. </p> <p>I'm converting to <code>ICartItem</code> because there are some properties added that need to be passed to the view but they don't make sense on an <code>IStoreProduct</code> or an <code>IStoreSubscription</code> (Interface Segregation Principle).</p> <p>I've thought about adding <code>ConvertProductToCartItem()</code> and <code>ConvertSubscriptionToCartItem()</code> methods to the <code>CartHelper</code>, but that seems like a violation of Single Responsibility Principle. Would it make sense to have a CartItemFactory that accepts <code>IStoreProduct</code>s and <code>IStoreSubscription</code>s and for conversion? That seems like a lot of unnecessary overhead for such a simple conversion.</p> <p>One of the solutions that I came up with is to define an explicit cast method:</p> <pre> public class StoreProduct : IStoreProduct { public decimal Price { get; set; } public decimal Discount { get; set; } // ...Properties... public ICartItem ToCartItem() { // Will call explicit cast implementation return (CartItem) this; } // Explicit cast conversion for Product as CartItem public static explicit operator CartItem(StoreProduct product) { return new CartItem() { Price = product.Price, Discount = product.Price, SalePrice = Helper.CalculateSalePriceForProduct(product), // ...Assign other applicable properties here... }; } } </pre> <p>Which let's me change my <code>GetCartItems</code> method to this much cleaner implementation:</p> <pre> public IEnumerable GetCartItems() { return Products.Select(p => p.ToCartSummaryItem()); } </pre> <p>But the problem with this approach is that this also violates Single Responsibility Principle by coupling <code>ICartItem</code> and <code>CartItem</code> to the <code>StoreProduct</code> Class. I also considered an extension method instead of the cast conversion, but that's not any better or different.</p> <p>Should I just make my concrete <code>StoreProduct</code> class implement the <code>ICartItem</code> interface and put the cart-specific properties there? Maybe I should just rewrite the <code>CartHelper</code> so that it only has ICartItems (i.e., remove the <code>IProduct</code>s)? Both of those options seem like violations of the Single Responsibility Principle. Maybe the solution will become obvious after I get some sleep...</p> <p><strong>So, I guess what my question boils down to is: what's the best way to move data between interfaces without violating SOLID principles?</strong> </p> <p>Any suggestions? Maybe I should just move on and not worry about it (i.e., don't be dogmatic about SOLID)? Have I answered my own question? Maybe this belongs in programmers.stackexchange, I hope this isn't too subjective. </p> <hr> <p>Also, in case it's helpful, this is what my interfaces look like:</p> <pre> public interface IProductBase { int ProductID { get; set; } decimal Price { get; set; } string Title { get; set; } string Description { get; set; } // ... Other Properties... } public interface IStoreProduct : IProductBase { int VariantID { get; set; } decimal Discount { get; set; } // ... Other Properties... ICartItem ToCartItem(); } public interface ISubscription : IProductBase { SubscriptionType SubscriptionType { get; set; } // ... Other Properties... ICartItem ToCartItem(); } public interface ICartItem : IProductBase { decimal SalePrice { get; set; } // ... Other Properties... } </pre> <p><strong>Update:</strong> Added post attributes for clarity.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload