How to access derived class members from an interface?

时光怂恿深爱的人放手 提交于 2020-08-19 05:42:45

问题


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?


回答1:


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; }
}



回答2:


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>());
        }
    }
}



回答3:


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
}



回答4:


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...



来源:https://stackoverflow.com/questions/1783035/how-to-access-derived-class-members-from-an-interface

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!