[c#] 정적 메서드 상속의 올바른 대안은 무엇입니까?

정적 메서드 상속이 C #에서 지원되지 않음을 이해합니다. 또한 개발자가이 기능이 필요하다고 주장하는 많은 토론 (여기 포함)을 읽었으며, 일반적인 응답은 “정적 멤버 상속이 필요한 경우 디자인에 결함이 있습니다”입니다.

좋아, OOP가 내가 정적 상속에 대해 생각하는 것을 원하지 않는다는 점을 감안할 때, 나는 그것이 나의 명백한 필요가 내 디자인의 오류를 가리킨다는 결론을 내려야한다. 그러나 나는 붙어 있습니다. 이 문제를 해결하는 데 도움을 주시면 감사하겠습니다. 여기에 도전이 있습니다 …

복잡한 초기화 코드를 캡슐화하는 추상 기본 클래스 (Fruit이라고합시다)를 만들고 싶습니다. 이 코드 중 일부는 가상 메서드 호출에 의존하기 때문에 생성자에 배치 할 수 없습니다.

Fruit은 다른 구체적인 클래스 (Apple, Orange)에 상속되며, 각 클래스는 인스턴스를 만들고 초기화하기 위해 표준 팩토리 메서드 CreateInstance ()를 노출해야합니다.

정적 멤버 상속이 가능하면 팩토리 메서드를 기본 클래스에 배치하고 파생 클래스에 대한 가상 메서드 호출을 사용하여 구체적인 인스턴스를 초기화해야하는 형식을 가져옵니다. 클라이언트 코드는 Apple.CreateInstance ()를 간단히 호출하여 완전히 초기화 된 Apple 인스턴스를 얻습니다.

그러나 이것은 분명히 불가능하므로 누군가 동일한 기능을 수용하기 위해 내 디자인을 변경해야하는 방법을 설명해 주시겠습니까?



답변

하나의 아이디어 :

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

추가 공장 수업이 필요하지 않습니다.


답변

팩토리 메서드를 형식 밖으로 이동하고 자체 Factory 클래스에 넣습니다.

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 클래스로 옮길 필요가 없습니다.

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


답변

create 메서드로 팩토리 클래스 (템플릿)를 생성하지 않는 이유는 무엇입니까?

FruitFactory<Banana>.Create();


답변

나는 이렇게 할 것이다

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


답변

그만큼 WebRequest.NET BCL 클래스 및 파생 유형은 이러한 종류의 디자인을 비교적 잘 구현할 수있는 방법에 대한 좋은 예를 나타냅니다.

WebRequest클래스는 등 여러 가지 하위 클래스를 가지고 HttpWebRequestFtpWebReuest. 이제이 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/");

개인적으로 이것이 문제에 접근하는 좋은 방법이라고 생각하며 실제로 .NET Framework 제작자가 선호하는 방법 인 것 같습니다.


답변

우선, 가상이 될 수있는 정적 이니셜 라이저가 없다고해서 오버로드 될 수있는 “표준”멤버 메서드를 가질 수 없다는 의미는 아닙니다. 둘째, 생성자에서 가상 메서드를 호출 할 수 있으며 예상대로 작동하므로 여기에 문제가 없습니다. 셋째, 제네릭을 사용하여 형식이 안전한 팩토리를 가질 수 있습니다.
다음은 생성자에 의해 호출되는 팩토리 + 멤버 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());
    }
}


답변

가장 좋은 방법은 호출해야하는 과일 클래스에 가상 / 추상 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;
    }
}