9

考虑以下示例代码:

// delivery strategies
public abstract class DeliveryStrategy { ... }
public class ParcelDelivery : DeliveryStrategy { ... }
public class ShippingContainer : DeliveryStrategy { ... }

和以下示例订单类:

// order (base) class
public abstract class Order
{
    private DeliveryStrategy delivery;

    protected Order(DeliveryStrategy delivery)
    {
        this.delivery = delivery;
    }

    public DeliveryStrategy Delivery
    {
        get { return delivery; }
        protected set { delivery = value; }
    }
}

当我派生一种新类型的订单类时,它将继承 DeliveryStrategy 类型的 Delivery 属性。

现在,当必须使用 ParcelDelivery 策略交付 CustomerOrders 时,我们可以考虑在 CustomerOrder 类中的 Delivery 属性中使用“ new ”:

public class CustomerOrder : Order
{
    public CustomerOrder()
        : base(new ParcelDelivery())
    { }

    // 'new' Delivery property
    public new ParcelDelivery Delivery
    {
        get { return base.Delivery as ParcelDelivery; }
        set { base.Delivery = value; }
    }
}

(CustomerOrder 显然需要确保与 Order 兼容(多态))

这允许在 CustomerOrder 上直接使用 ParcelDelivery 策略,而无需强制转换。

你会考虑使用这种模式吗?为什么/为什么不?

更新:我想出了这种模式,而不是使用泛型,因为我想将它用于多个属性。我不想对所有这些属性使用泛型类型参数

4

8 回答 8

11

我更喜欢使类型通用:

public abstract class Order<TDelivery> where TDelivery : Delivery
{
    public TDelivery Delivery { ... }
    ...
}

public class CustomerOrder : Order<ParcelDelivery>
{
    ...
}

这确保了编译时的类型安全,而不是将其留给执行时。它还可以防止以下情况:

CustomerOrder customerOrder = new CustomerOrder();
Order order = customerOrder;
order.Delivery = new NonParcelDelivery(); // Succeeds!

ParcelDelivery delivery = customerOrder.Delivery; // Returns null

哎哟。

我认为new通常是最后的手段。它在实施和使用方面都增加了复杂性。

如果您不想走一般路线,我会介绍一个真正的新属性(使用不同的名称)。

于 2009-06-03T11:56:14.737 回答
7

我认为这是一个很好的模式。通过消除转换结果的需要,它可以更容易地显式使用派生类型,并且它不会“破坏”基类行为。实际上,在 BCL 中的某些类中使用了类似的模式,例如查看 DbConnection 类层次结构:

  • DbConnection.CreateCommand() 返回一个 DbCommand
  • SqlConnection.CreateCommand() 使用“new”隐藏基本实现以返回 SqlCommand。
  • (其他 DbConnection 实现也是如此)

因此,如果您通过 DbConnection 变量操作连接对象, CreateCommand 将返回 DbCommand ;如果您通过 SqlConnection 变量操作它,CreateCommand 将返回一个 SqlCommand,如果您将其分配给 SqlCommand 变量,则避免强制转换。

于 2009-06-03T12:33:49.310 回答
5

您可以使用泛型。

// order (base) class
public abstract class Order<TDeliveryStrategy> where TDeliveryStrategy : DeliveryStrategy
{
    private TDeliveryStrategy delivery;

    protected Order(TDeliveryStrategy delivery)
    {
        this.delivery = delivery;
    }

    public TDeliveryStrategy Delivery
    {
        get { return delivery; }
        protected set { delivery = value; }
    }
}

public class CustomerOrder : Order<ParcelDelivery>
{
    public CustomerOrder()
        : base(new ParcelDelivery())
    { }
}
于 2009-06-03T11:53:36.083 回答
3

在我看来,使用“new”关键字从基类中隐藏可写属性是一个坏主意。new 关键字允许您在派生类中隐藏基类的成员,而不是覆盖它。这意味着使用基类引用对此类成员的调用仍然访问基类代码,而不是派生类代码。C# 有 'virtual' 关键字,它允许派生类实际覆盖实现,而不是简单地隐藏它。这里有一篇相当不错的文章讨论了这些差异。

在您的情况下,您似乎正在尝试使用方法隐藏作为将属性协方差引入C# 的一种方式。但是,这种方法存在问题。

通常,拥有基类的价值在于允许您的代码的用户以多态方式处理类型。如果有人使用对基类的引用设置 Delivery 属性,您的实现会发生什么情况?如果 Delivery 属性不是 ParcelDelivery 的实例,派生类会中断吗?这些是你需要问自己关于这种实施选择的问题。

现在,如果基类中的 Delivery 属性没有提供 setter,那么您的情况会略有不同。基类的用户只能检索属性而不能设置它。由于您将属性访问路由回基类,因此通过基类进行的访问将继续有效。但是,如果您的派生类不是密封的,则从它继承的类可能会通过使用它们自己的版本隐藏 Delivery 属性来引入相同类型的问题。

正如其他一些帖子已经提到的那样,您可以使用泛型作为实现不同 Delivery 属性类型的一种方式。Jon 的例子很好地证明了这一点。如果您需要从 CustomerOrder 派生并将 Delivery 属性更改为新类型,则泛型方法存在一个问题。

有一个泛型的替代方案。您需要考虑您是否真的想要一个可设置的属性。如果去掉 Delivery 属性上的设置器,直接使用 Order 类引入的问题就消失了。由于您使用构造函数参数设置交付属性,因此可以保证所有订单都具有正确的策略类型。

于 2009-06-03T13:21:03.490 回答
1

是否有任何理由需要更改返回类型?如果没有,那么我建议只将 Delivery 属性设为虚拟,因此它必须由继承的类来定义:

public abstract class Order
{
    protected Order(DeliveryStrategy delivery)
    {
        Delivery = delivery;
    }

    public virtual DeliveryStrategy Delivery { get; protected set; }
}

public class CustomerOrder : Order
{
    public CustomerOrder()
        : base(new ParcelDelivery())
    { }

    public DeliveryStrategy Delivery { get; protected set; } 
}

如果您确实需要更改返回类型,那么我想知道为什么您需要对返回类型进行如此剧烈的行为更改。无论如何,如果是这样,那么这对您不起作用。

因此,要直接回答您的问题,如果需要返回类型与基类不同,我只会使用您描述的模式,并且非常谨慎(我会分析我的对象模型以查看是否还有其他东西我可以先做)。如果不是这种情况,那么我会使用上面描述的模式。

于 2009-06-03T12:11:05.113 回答
1

考虑这种方法:

public interface IOrder
{
    public DeliveryStrategy Delivery
    {
        get;
    }
}

// order (base) class
public abstract class Order : IOrder
{
    protected readonly DeliveryStrategy delivery;

    protected Order(DeliveryStrategy delivery)
    {
        this.delivery = delivery;
    }

    public DeliveryStrategy Delivery
    {
        get { return delivery; }
    }
}

然后使用

public class CustomerOrder : Order
{
    public CustomerOrder()
        : base(new ParcelDelivery())
    { }

    public ParcelDelivery Delivery
    {
        get { return (ParcelDelivery)base.Delivery; }
    }


    DeliveryStrategy IOrder.Delivery
    {
        get { return base.Delivery}
    }
}

这仍然远非完美(您的示例没有说明为什么基类根本需要了解交付策略,并且具有约束的通用性会更有意义,但这至少允许您使用相同的名称为财产并获得类型安全。

在您的示例中是没有意义的,如果某些东西不是正确的类型,您不应该用 null 掩盖它,因为您的不变量已被违反,您应该抛出。

在可能的情况下,只读字段总是更可取。它们清楚地表明了不变性。

于 2009-06-03T16:07:27.410 回答
1

您的解决方案没有按照您的想法执行。它似乎有效,但没有调用您的“新”方法。考虑对代码进行以下更改以添加一些输出以查看调用了哪个方法:

// order (base) class
public abstract class Order
{
    private DeliveryStrategy delivery;

    protected Order(DeliveryStrategy delivery)
    {
        this.delivery = delivery;
    }

    public DeliveryStrategy Delivery
    {
        get 
        {
            Console.WriteLine("Order");
            return delivery; 
        }
        protected set { delivery = value; }
    }
}

public class CustomerOrder : Order
{
    public CustomerOrder()
        : base(new ParcelDelivery())
    { }

    // 'new' Delivery property
    public new ParcelDelivery Delivery
    {
        get 
        {
            Console.WriteLine("CustomOrder");
            return base.Delivery as ParcelDelivery; 
        }
        set { base.Delivery = value; }
    }
}

然后以下代码片段实际使用您的 CustomOrder 类:

Order o = new CustomerOrder();
var d = o.Delivery;

将输出“订单”。新方法专门打破了多态性。它在 CustomOrder 上创建一个新的 Delivery 属性,该属性不属于 Order 基类。因此,当您像使用 Order 一样使用 CustomOrder 时,不要调用新的 Delivery 属性,因为它只存在于 CustomOrder 上,而不是 Order 类的一部分。

你想要做的是覆盖一个不可覆盖的方法。如果您的意思是该属性是可覆盖的,请将其抽象化:

// order (base) class
public abstract class Order
{
    private DeliveryStrategy delivery;

    protected Order(DeliveryStrategy delivery)
    {
        this.delivery = delivery;
    }

    public abstract DeliveryStrategy Delivery
    {
        get { return delivery; }
        protected set { delivery = value; }
    }
}

public class CustomerOrder : Order
{
    public CustomerOrder()
        : base(new ParcelDelivery())
    { }

    public override ParcelDelivery Delivery
    {
        get { return base.Delivery as ParcelDelivery; }
        set { base.Delivery = value; }
    }
}
于 2010-10-22T14:32:50.757 回答
0

使用new影子基类的虚拟成员是一个坏主意,因为子派生类型将无法正确覆盖它们。如果存在想要在派生类中隐藏的类成员,则不应将基类成员声明为abstractor virtual,而应简单地调用protected abstractorprotected virtual成员。protected派生类型可以使用调用适当成员并适当转换结果的方法来隐藏基类方法。

于 2012-02-24T21:15:19.227 回答