I have three classes; Stamp, Letter and Parcel that implement an interface IProduct and they also have some of their own functionality.
public interface IProduct
{
string Name { get; }
int Quantity { get; set; }
float Amount { get; }
}
public class Stamp : IProduct
{
public string Name { get { return "Stamp"; } }
public int Quantity { get; set; }
public float Amount { get; set; }
public float UnitPrice { get; set; }
}
public class Letter : IProduct
{
public string Name { get { return "Letter"; } }
public int Quantity { get; set; }
public float Amount { get; set; }
public float Weight { get; set; }
public string Destination { get; set; }
}
public class Parcel : IProduct
{
public string Name { get { return "Parcel"; } }
public int Quantity { get; set; }
public float Amount { get; set; }
public float Weight { get; set; }
public string Destination { get; set; }
public int Size { get; set; }
}
public static class ShoppingCart
{
private static List<IProduct> products = new List<IProduct>();
public static List<IProduct> Items { get { return products; } }
}
Why can't I access the additional members of derived classes from a List<IProduct>
?
ShoppingCart.Items.Add(new Stamp { Quantity = 5, UnitPrice = 10, Amount = 50 });
ShoppingCart.Items.Add(new Letter { Destination = "US", Quantity = 1, Weight = 3.5f });
ShoppingCart.Items.Add(new Parcel { Destination = "UK", Quantity = 3, Weight = 4.2f, Size = 5 });
foreach (IProduct product in ShoppingCart.Items)
{
Console.WriteLine("Name: {0}, Quantity: {1}, Amount: {2}", product.Name, product.Quantity, product.Amount);
}
I thought of using generics, but in that case I will have to write separate code for each specific type of product.
public static class ShoppingCart<T> where T : IProduct
{
private static List<T> items = new List<T>();
public static List<T> Items { get { return items; } }
}
ShoppingCart<Stamp>.Items.Add(new Stamp { Quantity = 5, Amount = 10, UnitPrice = 50 });
ShoppingCart<Letter>.Items.Add(new Letter { Destination = "US", Quantity = 1, Weight = 3.5f });
foreach (Stamp s in ShoppingCart<Stamp>.Items)
{
Console.WriteLine("Name: {0}, Quantity: {1}, Amount: {2}", s.Name, s.Quantity, s.Amount);
}
foreach (Letter l in ShoppingCart<Letter>.Items)
{
Console.WriteLine("Name: {0}, Destination: {1}, Weight: {2}", l.Name, l.Destination, l.Weight);
}
Isn't there any kind of design pattern for this kind of problem. Factory Pattern?
This is because you are casting each Item in the shopping cart as IProduct in your foreach loop. What you would need to do is something like:
foreach(IProduct product in ShoppingCart.Items)
{
if (product is Stamp)
{
var stamp = product as Stamp;
Console.WriteLine("Name: {0}, Quantity: {1}, Amount: {2}, UnitPrice: {3}", stamp.Name, stamp.Quantity, stamp.Amount, stamp.UnitPrice);
}
else if (product is Letter)
{
var letter = product as Letter;
Console.WriteLine("Name: {0}, Quantity: {1}, Amount: {2}, Weight: {3}, Destination: {4}", letter.Name, letter.Quantity, letter.Amount, letter.Weight, letter.Destination);
}
else if (product is Parcel)
{
var parcel = product as Parcel;
Console.WriteLine("Name: {0}, Quantity: {1}, Amount: {2}, Weight: {3}, Destination: {4}, Size: {5}", parcel.Name, parcel.Quantity, parcel.Amount, parcel.Weight, parcel.Destination, parcel.Size);
}
}
Alternatively, this more modern syntax now available in C#, which combines the is
operator with the variable declaration:
foreach(IProduct product in ShoppingCart.Items)
{
if (product is Stamp stamp)
{
Console.WriteLine("Name: {0}, Quantity: {1}, Amount: {2}, UnitPrice: {3}", stamp.Name, stamp.Quantity, stamp.Amount, stamp.UnitPrice);
}
else if (product is Letter letter)
{
Console.WriteLine("Name: {0}, Quantity: {1}, Amount: {2}, Weight: {3}, Destination: {4}", letter.Name, letter.Quantity, letter.Amount, letter.Weight, letter.Destination);
}
else if (product is Parcel parcel)
{
Console.WriteLine("Name: {0}, Quantity: {1}, Amount: {2}, Weight: {3}, Destination: {4}, Size: {5}", parcel.Name, parcel.Quantity, parcel.Amount, parcel.Weight, parcel.Destination, parcel.Size);
}
}
Also you are repeating unnecessary properties Name, Quantity and Amount. You should derive each of your classes from Product:
public class Stamp: Product, IProduct
{
public double UnitPrice { get; set; }
}
public class TransitProduct: Product, IProduct
{
public double Weight { get; set; }
public string Destination { get; set; }
}
public class Letter: TransitProduct, IProduct
{
}
public class Parcel: TransitProduct, IProduct
{
public double Size { get; set; }
}