静的メソッド継承の正しい代替手段は何ですか?


83

静的メソッドの継承がC#でサポートされていないことを理解しています。また、開発者がこの機能の必要性を主張する多くの議論(ここを含む)を読みました。典型的な応答は「静的なメンバーの継承が必要な場合、設計に欠陥があります」です。

OK、OOPは静的継承についても考えたくないので、それに対する私の明らかな必要性は私の設計のエラーを示していると結論付けなければなりません。しかし、私は立ち往生しています。私はこれを解決するためにいくつかの助けを本当にいただければ幸いです。これが課題です...

複雑な初期化コードをカプセル化する抽象基本クラス(Fruitと呼びましょう)を作成したいと思います。一部のコードは仮想メソッド呼び出しに依存するため、このコードをコンストラクターに配置することはできません。

Fruitは、他の具象クラス(Apple、Orange)に継承されます。各クラスは、インスタンスを作成および初期化するために、標準のファクトリメソッドCreateInstance()を公開する必要があります。

静的メンバーの継承が可能であれば、ファクトリメソッドを基本クラスに配置し、派生クラスへの仮想メソッド呼び出しを使用して、具象インスタンスを初期化する必要がある型を取得します。クライアントコードは、Apple.CreateInstance()を呼び出すだけで、完全に初期化されたAppleインスタンスを取得できます。

しかし、明らかにこれは不可能なので、同じ機能に対応するために私のデザインをどのように変更する必要があるかを誰かに説明してもらえますか。


2
「このコードの一部は仮想メソッド呼び出しに依存するため、このコードをコンストラクターに配置することはできません」というステートメントが正しくないことを指摘したいと思います。コンストラクター内で仮想メソッドを呼び出すことができます。(仮想メソッドが呼び出されるときに部分的に初期化されたクラスを持つことができるため、呼び出しを慎重に設計する必要がありますが、サポートされています。)詳細については、Ravadreの回答を参照してください。
ルーベン

回答:


61

1つのアイデア:

public abstract class Fruit<T>
    where T : Fruit<T>, new()
{
    public static T CreateInstance()
    {
        T newFruit = new T();
        newFruit.Initialize();  // Calls Apple.Initialize
        return newFruit;
    }

    protected abstract void Initialize();
}

public class Apple : Fruit<Apple>
{
    protected override void Initialize() { ... }
}

そして、そのように呼び出します:

Apple myAppleVar = Fruit<Apple>.CreateInstance();

追加のファクトリクラスは必要ありません。


9
私見ですが、ジェネリック型をクラスレベルに配置するのではなく、CreateInstanceメソッドに移動することをお勧めします。(私が私の答えでしたように)。
Frederik Gheysels 2009

1
あなたの好みが記されています。あなたの好みの簡単な説明をいただければ幸いです。
マットハムスミス2009

9
そうすることで、クラスの残りの部分を「汚染」することはありません。typeパラメーターは、Createメソッドでのみ必要です(少なくともこの例では)。その次に、私は次のように思います。Fruit.Create<Apple>(); 次に読みやすくなります:Fruit <Apple> .Create();
Frederik Gheysels 2009

2
Appleコンストラクターをパブリックより小さくすることはできません。これは、コンストラクターがパブリックコンストラクターを持っwhere T : Fruit<T>, new()ているT必要があるためです。@ MattHamsmith-を削除するまでコードはコンパイルされませんprotected Apple() { }。私はすでにVSでそれをテストしました。
Tohid 2013

1
@ Tohid-あなたは正しいです。編集しました。これにより、Fruit CreateInstance静的ファクトリメソッドを使用せずにAppleを構築できるようになりますが、それは避けられないようです。
Matt Hamsmith 2014年

17

ファクトリメソッドをタイプから移動し、独自のファクトリクラスに配置します。

public abstract class Fruit
{
    protected Fruit() {}

    public abstract string Define();

}

public class Apple : Fruit
{
    public Apple() {}

    public override string Define()
    {
         return "Apple";
    }
}

public class Orange : Fruit
{
    public Orange() {}

    public override string Define()
    {
         return "Orange";
    }
}

public static class FruitFactory<T> 
{
     public static T CreateFruit<T>() where T : Fruit, new()
     {
         return new T();
     }
}

しかし、私がこれを見ているとき、Createメソッドを独自のFactoryクラスに移動する必要はありません(ただし、関心の分離が望ましいと思います)。Fruitクラスに入れることができます。

public abstract class Fruit
{

   public abstract string Define();

   public static T CreateFruit<T>() where T : Fruit, new()
   {
        return new T();
   }

}

そして、それが機能するかどうかを確認するには:

    class Program
    {
        static void Main( string[] args )
        {
            Console.WriteLine (Fruit.CreateFruit<Apple> ().Define ());
            Console.WriteLine (Fruit.CreateFruit<Orange> ().Define ());

            Console.ReadLine ();
        }        
    }

1
コードはコンパイルされません。where T:new()句が必要です。しかし、これは私の正確な複製です。
John Gietzen 2009

1
静的なFruitFactoryクラスを作成することもできますが、インスタンス化可能なFruitFactoryクラスとともにIFruitFactoryインターフェイスを使用することをお勧めします。最終的にFruitFactoryクラスが1つだけになり、そのCreateFruitメソッドはそのオブジェクトインスタンスを参照しないため、静的メソッドである可能性もありますが、フルーツを作成する複数の方法を提供する必要が生じた場合、またはフルーツの作成が必要になった場合インスタンスメソッドを使用すると、状態を維持する機能が必要になる場合があります。
スーパーキャット2011年


4

私はこのようなことをします

 public abstract class Fruit() {
      public abstract void Initialize();
 }

 public class Apple() : Fruit {
     public override void Initialize() {

     }
 }

 public class FruitFactory<T> where T : Fruit, new {
      public static <T> CreateInstance<T>() {
          T fruit = new T();
          fruit.Initialize();
          return fruit;  
      }
 } 


var fruit = FruitFactory<Apple>.CreateInstance()

3

ザ・ WebRequest.NET BCLクラスとその派生型は、この種の設計を比較的適切に実装する方法の良い例を表しています。

このWebRequestクラスには、とを含むいくつかのサブクラスがHttpWebRequestありFtpWebReuestます。現在、このWebRequest基本クラスもファクトリタイプであり、静的Createメソッドを公​​開しています(ファクトリパターンで必要とされるように、インスタンスコンストラクタは非表示になっています)。

public static WebRequest Create(string requestUriString)
public static WebRequest Create(Uri requestUri)

このCreateメソッドは、の特定の実装を返します。WebRequestクラスの、URI(またはURI文字列)を使用して、作成して返すオブジェクトのタイプを決定します。

これにより、次の使用パターンの最終結果が得られます。

var httpRequest = (HttpWebRequest)WebRequest.Create("http://stackoverflow.com/");
// or equivalently
var httpRequest = (HttpWebRequest)HttpWebWebRequest.Create("http://stackoverflow.com/");

var ftpRequest = (FtpWebRequest)WebRequest.Create("ftp://stackoverflow.com/");
// or equivalently
var ftpRequest = (FtpWebRequest)FtpWebWebRequest.Create("ftp://stackoverflow.com/");

個人的には、これはこの問題に取り組むための良い方法だと思います。実際、.NETFrameworkの作成者が好む方法のようです。


3

まず第一に、仮想化できる静的初期化子がないからといって、オーバーロードされる可能性のある「標準」メンバーメソッドを使用できないわけではありません。第二に、コンストラクターから仮想メソッドを呼び出すことができ、それらは期待どおりに機能するため、ここでは問題はありません。第三に、ジェネリックスを使用してタイプセーフなファクトリを作成できます。
コンストラクターによって呼び出されるファクトリ+メンバーInitialize()メソッドを使用するコードを次に示します(保護されているため、オブジェクトの作成後に誰かが再度呼び出すことを心配する必要はありません)。


abstract class Fruit
{
    public Fruit()
    {
        Initialize();
    }

    protected virtual void Initialize()
    {
        Console.WriteLine("Fruit.Initialize");
    }
}

class Apple : Fruit
{
    public Apple()
        : base()
    { }

    protected override void Initialize()
    {
        base.Initialize();
        Console.WriteLine("Apple.Initialize");
    }

    public override string ToString()
    {
        return "Apple";
    }
}

class Orange : Fruit
{
    public Orange()
        : base()
    { }

    protected override void Initialize()
    {
        base.Initialize();
        Console.WriteLine("Orange.Initialize");
    }

    public override string ToString()
    {
        return "Orange";
    }
}

class FruitFactory
{
    public static T CreateFruit<T>() where T : Fruit, new()
    {
        return new T();
    }
}

public class Program
{

    static void Main()
    {
        Apple apple = FruitFactory.CreateFruit<Apple>();
        Console.WriteLine(apple.ToString());

        Orange orange = new Orange();
        Console.WriteLine(orange.ToString());

        Fruit appleFruit = FruitFactory.CreateFruit<Apple>();
        Console.WriteLine(appleFruit.ToString());
    }
}

1
一般に、コンストラクターから仮想メソッドを呼び出さないようにするのが最善です。blogs.msdn.com/abhinaba/archive/2006/02/28/540357.aspxを
TrueWill

それは本当です、それはトリッキーかもしれませんが、それは可能であり、常にそうあるべきであるように機能します、問題は、「それがすべきである」という意味を理解することです。一般に、私はこれ以上複雑なメソッドを呼び出さない傾向があります(非仮想メソッドでさえ、例外をスローできるように構築されていることが多く、ctorが何かをスローするのは好きではないため)。 、しかしそれは開発者の決定です。
マルチンDeptuła

0

最善の方法は、呼び出す必要のあるフルーツクラスに仮想/抽象Initialiseメソッドを作成してから、外部の「フルーツファクトリ」クラスを作成してインスタンスを作成することです。


public class Fruit
{
    //other members...
    public abstract void Initialise();
}

public class FruitFactory()
{
    public Fruit CreateInstance()
    {
        Fruit f = //decide which fruit to create
        f.Initialise();

        return f;
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.