How to access derived class members from an interface?
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?
You can't access the additional members of classes which implement an interface because you're only exposing IProduct
in the List of items. I'd add specific list types for each item in the shopping cart to the ShoppingCart class, and then you can expose a sequence of all the products in the cart for anything which only needs to use the IProduct interface:
public class ShoppingCart
{
public IList<Stamp> Stamps { get; }
public IList<Letter> Letters { get; }
public IList<Parcel> Parcels { get; }
public IEnumerable<IProduct> Products
{
get
{
return this.Stamps.Cast<IProduct>()
.Concat(this.Letters.Cast<IProduct>())
.Concat(this.Parcels.Cast<IProduct>());
}
}
}
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; }
}
Why can't I access the additional members of derived classes from a
List<IProduct>
?
That is because, IProduct
interface does not know about UnitPrice
, Destination
etc of the derived class properties.
Are you trying add the intelligence to calculate the Amount
to each of the derived class objects Stamp, Letter, Parcel ?
Then, I would say you need to redesign a bit and use the Decorator design pattern.
DerivedClass::Amount()
{
Base::Amount() +
//Amount logic based on derived class
}
The reason why you can't access additional members from a derived class is that you are using the interface in the List<> - therefore you'll only be able to access properties on that interface.
A pattern that might help you is the double-dispatch pattern.
Example below:
public interface IHandler
{
void Handle(Stamp stamp);
void Handle(Letter letter);
...
}
public class Handler : IHandler
{
public void Handle(Stamp stamp)
{
// do some specific thing here...
}
public void Handle(Letter letter)
{
// do some specific thing here...
}
...
}
public interface IProduct
{
string Name { get; }
int Quantity { get; set; }
float Amount { get; }
void Handle(IHandler handler);
}
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 void Handle(IHandler handler)
{
handler.Handle(this);
}
}
You can now program some specific functionality in the Handler - I'm guessing you want to calculate some kind of total price given things such as quantity * unit price or a weight & destination lookup table...
精彩评论