2009-06-03 4 views
9

C#/ .Netの「新しい」プロパティの長所と短所

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

と次のサンプルOrderクラス:

// 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プロパティが継承されます。

CustomerOrdersがParcelDelivery戦略を使用して配信されなければならない場合、CustomerOrderクラスのDeliveryプロパティを「 new」と見なすことができます。 Orderと互換性がある(polymorph)ことを確認してください)

これにより、キャスティングを必要とせずにCustomerOrderでParcelDelivery戦略を直接使用できます。

このパターンの使用を検討しますか?なぜ/なぜですか?

を更新してください:これは、複数のプロパティに対してこれを使用したいので、ジェネリックを使用する代わりに、このパターンを思いつきました。これらのすべてのプロパティにジェネリック型引数を使用したくない

+2

あなたのシャドウイング方法がより厳しい前提条件とより弱い事後条件を持たない限り、このプラクティスは完全に受け入れられると思います。 –

+0

最初に、どうしてなぜDeliveryStrategyをキャストする必要がありますか?戦略パターンの使用のポイントは、ビヘイビアがインタフェースを実装するため、すべて同じメソッドを持つため、キャストする必要はありません。 –

答えて

6

これは良いパターンだと思います。結果をキャストする必要性を排除して派生型を明示的に使用することを容易にし、基本クラスの動作を「中断」しません。実際には、同様のパターンがBCLにおけるいくつかのクラスで使用され、例えばたDbConnectionクラス階層を見:

  • DbConnection.CreateCommand()SqlConnection.CreateCommand(たDbCommand
  • を返す)を使用して、ベースの実装を隠し'new'はSqlCommandを返します。
  • (他たDbConnection実装が同じことを行う)

あなたがたDbConnection変数を介して接続オブジェクトを操作するのであれば、CreateCommandがたDbCommandを返します。 SqlConnection変数を使用して操作すると、CreateCommandはSqlCommandを返し、SqlCommand変数に割り当てる場合はキャストを回避します。基本クラスから書き込み可能なプロパティを非表示にする「新しい」キーワードを使用して

+0

.NETフレームワークからのsimularケースを表示するための答えとして受け入れられました(ただし、SqlConnectionでは 'new'を使用しません) –

+1

"SqlConnectionで 'new'を使用していませんでした。これは、基本クラスと同じ名前とパラメータを持つが、戻り値の型が異なるメソッドを定義する唯一の方法です。私はReflectorでチェックしたようですが、Reflectorで表示されるコードは実際のソースコードではありません.ILで実際に生成されたものだけを表示できます。明らかに 'new'修飾子はILを放出しません(または少なくともReflector doesnそれを表示しないでください)。 –

+0

ソースコードでnewを使用すると、コンパイラが警告を出さないようにするだけです(とにかく変更する可能性があります) – ShuggyCoUk

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()) 
    { } 
} 
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と考えています。実装と使用の両面で複雑さが増しています。

一般的なルートを下りたくない場合は、(別の名前の)本当に新しいプロパティを導入したいと思います。

+0

戻り値の型に反差異があるC#4を待つことはできません。 – erikkallen

+0

共変の戻り値の型、反変なパラメータの型:)しかし、これはインターフェイスのためのもので、特定のシナリオでは私が知っている限りです。 –

+0

@Jon:私はこの正確なケースのためにプロパティセッターを保護しました。 基本クラスと派生クラスが連携してこれを引き離す必要があります。 –

1

戻り値の型を変更する必要がある理由はありますか?

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

あなたは戻り値の型の変更を必要とした場合は、なぜあなたはでしょう、そして、私は疑問に思うでしょう:存在しない場合、それは代わりに継承したクラスで定義する必要がありますので、私はちょうど配達のプロパティを仮想することを示唆しています戻り値の型では動作の変化が激しいことが必要です。それにかかわらず、そうなら、これはあなたのために働くことはありません。

あなたの質問に直接答えるためには、戻り値の型が基本クラスと異なることが必要な場合は記述したパターンのみを使用し、非常に控えめにしてください(オブジェクトモデルを分析して私が最初にすることができる他の何か)。そうでない場合は、上記のパターンを使用します。

3

は私の意見では悪い考えです。 newキーワードを使用すると、派生クラス内の基本クラスのメンバーをオーバーライドするのではなく、非表示にすることができます。つまり、基本クラス参照を使用するそのようなメンバーへの呼び出しは、引き続き派生クラスコードではなく基本クラスコードにアクセスします。 C#は 'virtual'キーワードを持っています。これにより、派生したクラスは単にそれを隠すのではなく、実際にoverrideの実装を可能にします。その相違について話す合理的に良い記事hereがあります。あなたは、C#にproperty covarianceを導入する方法として、メソッドの隠蔽を使用しようとしているようなあなたの場合には

、それが見えます。しかし、このアプローチには問題があります。

多くの場合、基本クラスを持っていることの価値は、あなたのコードのユーザーが多態の種類を扱うことを可能にすることです。誰かが基本クラスへの参照を使用してDeliveryプロパティを設定すると、実装はどうなりますか? DeliveryプロパティがParcelDeliveryのインスタンスでない場合、派生クラスは中断されますか?これは、この実装の選択についてあなた自身に尋ねる必要のある種類の質問です。基底クラスで配信プロパティはセッターを提供しなかった場合

は今、あなたはわずかに異なる状況があります。基本クラスのユーザーは、プロパティーを取得して設定することはできません。プロパティアクセスを基本クラスに戻すので、基本クラスを介したアクセスは引き続き機能します。ただし、派生クラスが封印されていない場合、そのクラスを継承するクラスは、独自のバージョンのDeliveryプロパティを隠すことによって同じタイプの問題を導入する可能性があります。他の記事のいくつかは、すでに述べたように

、あなたは別の配信プロパティタイプを達成するための方法として、ジェネリック医薬品を使用することができます。ジョンの例はこれを実証するのにはかなり良いです。 CustomerOrderから派生させ、Deliveryプロパティを新しいタイプに変更する必要がある場合は、ジェネリックスのアプローチに1つの問題があります。

ジェネリックスに代わるものがあります。あなたは本当にあなたのケースで設定可能なプロパティが必要かどうかを検討する必要があります。 Deliveryプロパティでセッターを取り除くと、Orderクラスを使用して導入された問題は直ちに消えます。コンストラクタパラメータを使用して配信プロパティを設定するので、すべてのオーダーに適切なタイプの戦略があることを保証できます。

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でマスクしてはいけません。あなたのインバリアントが違反されたときに投げてください。

可能であれば、読み取り専用フィールドは常に望ましいです。彼らは不変性をはっきりさせます。

+0

私はインターフェイスが提供する代替が好きですが、私はこのパターンがモデルをより複雑にすると信じています(同じクラスに2つのDeliveryプロパティを持つ)。 –

+0

共/逆分散がない場合は、消費者側(異なる名前/キャスト)またはプロバイダ(2つのプロパティ(鉱山またはシャドウイングのような相互に2つのプロパティ))で自分で行う必要があります。すべてのプロバイダシナリオでは、プロパティ(あなたは '新しい'とそれを見ることはありません) – ShuggyCoUk

1

あなたのソリューションは、あなたが思う通りのことをしません。それは動作するように見えますが、あなたの "新しい"方法を呼び出すことはありません。実際にあなたのCustomOrderクラスを使用するには、次のコードスニペットその後

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

::と呼ばれる方法を参照するためにいくつかの出力を追加するためにあなたのコードに次の変更を検討

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

う出力「注文"新しいメソッドは、特に多型を破ります。 Orderベースクラスの一部ではないCustomOrderに新しいDeliveryプロパティを作成します。カスタムオーダーをOrderのように使用すると、CustomOrderにのみ存在し、Orderクラスの一部ではないため、新しいDeliveryプロパティは呼び出されません。

あなたがしようとしているのは、上書きできないメソッドを上書きすることです。あなたがオーバーライドするプロパティを意味している場合、それは抽象ます

// 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; } 
    } 
} 
+0

それは私がそれが期待しているワットです。 Console.WriteLine( "Order")をConsole.WriteLine(delivery.GetType()。GetName())(またはそのようなもの)に変更してください;-) –

+0

delivery.GetType()はあなたにParcelDeliveryを与えますサブクラスの新しいDeliveryプロパティを呼び出すのではなく、Orderのコンストラクタに渡します。リフレクションを使用して現在地を確認するには、実際に呼び出すメソッドを次のように変更します。 Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod()。DeclaringType.FullName); – ZombieDev

0

基底クラスの仮想メンバーをシャドウするnewを使用したサブ派生型が適切にそれらを上書きすることはできませんので、悪い考えです。派生クラスでシャドーしたいクラスメンバが存在する場合、基本クラスメンバはabstractまたはvirtualと宣言されるべきではなく、代わりにprotected abstractまたはprotected virtualメンバを単に呼び出す必要があります。派生型は、適切なprotectedメンバを呼び出して結果を適切にキャストするもので、基底クラスのメソッドをシャドーできます。

関連する問題