【问题标题】:How to access derived class members from an interface?如何从接口访问派生类成员?
【发布时间】:2010-12-19 11:24:46
【问题描述】:

我有三门课;实现接口 IProduct 的 Stamp、Letter 和 Parcel,它们也有一些自己的功能。

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

为什么我不能从 List&lt;IProduct&gt; 访问派生类的其他成员?

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

我曾想过使用泛型,但在这种情况下,我将不得不为每种特定类型的产品编写单独的代码。

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

这种问题没有任何设计模式。工厂模式?

【问题讨论】:

  • 我的问题是,如果你的接口只有成员而不是方法,那么它的意义何在?您只是在重新定义每个类中的那些成员。
  • 接口必须要有方法吗?
  • 不,这不是必须的,但通常您使用接口来遵守合同。合同通常是需要通过签署该合同的类来实现的方法。当您在接口中有签名者必须实现它们的方法时。当涉及到变量本身并且只有变量时,创建接口是否有意义?
  • 你说得对,JonH :)
  • 那些不是真正的变量或方法。它们是属性,是一系列方法(getter 和 setter)的语法细节。有一个通用的 i/f 为这种用途定义它们是完全合法和有用的——所有 impl 必须在给定上下文中有用的某些属性。典型的方法是还有一个通用的抽象基类 (BaseProduct),它实现了 IProduct 并且所有产品实现都派生自该类。

标签: c# generics interface derived-class


【解决方案1】:

您无法访问实现接口的类的其他成员,因为您只是在项目列表中公开IProduct。我会为购物车中的每个项目添加特定的列表类型到 ShoppingCart 类,然后你可以公开购物车中所有产品的序列,任何只需要使用 IProduct 接口的东西:

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

【讨论】:

  • 我们可以使用 OfType 扩展来达到同样的效果吗?
  • 可以,但是这种情况下效果是一样的
【解决方案2】:

这是因为您在 foreach 循环中将购物车中的每个项目转换为 IProduct。您需要做的是:

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

或者,现在可以在 C# 中使用这种更现代的语法,它结合了 is 运算符和变量声明:

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

您还重复了不必要的属性名称、数量和金额。您应该从 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; }
}

【讨论】:

    【解决方案3】:

    为什么我无法访问附加 派生类的成员 List&lt;IProduct&gt;?

    那是因为IProduct接口不知道派生类的UnitPriceDestination等属性。

    您是否尝试将计算Amount 的智能添加到每个派生类对象 Stamp、Letter、Parcel 中?

    然后,我会说你需要重新设计一下并使用Decorator design pattern

    DerivedClass::Amount()
    {
      Base::Amount() + 
      //Amount logic based on derived class
    }
    

    【讨论】:

      【解决方案4】:

      您无法从派生类访问其他成员的原因是您正在使用 List 中的接口 - 因此您只能访问该接口上的属性。

      双分派模式可能对您有所帮助。

      下面的例子:

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

      您现在可以在 Handler 中编写一些特定功能 - 我猜您想计算某种总价,例如数量 * 单价或重量和目的地查找表...

      【讨论】:

      • 感谢 Codebrain。您能否在 C# 中提供任何示例/链接或双分派模式?
      猜你喜欢
      • 2018-08-09
      • 2012-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-30
      • 2019-08-15
      • 2010-12-12
      • 2011-01-27
      相关资源
      最近更新 更多