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


84

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

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

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

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

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

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


2
"이 코드 중 일부는 가상 메서드 호출에 의존하기 때문에 생성자에 배치 할 수 없습니다."라는 문장이 올바르지 않다는 점을 지적하고 싶습니다. 생성자 내에서 가상 메서드를 호출 할 수 있습니다. (가상 메서드가 호출 될 때 부분적으로 초기화 된 클래스를 가질 수 있지만 지원되기 때문에 호출을 신중하게 설계해야합니다.) 자세한 내용은 Ravadre의 답변을 참조하십시오.
Ruben

답변:


62

하나의 아이디어 :

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
IMHO, 제네릭 형식을 클래스 수준에 두는 대신 CreateInstance 메서드로 이동하는 것이 좋습니다. (내 대답에서 한 것처럼).
Frederik Gheysels

1
귀하의 선호도가 기록됩니다. 귀하의 선호에 대한 간략한 설명이 더 감사하겠습니다.
Matt Hamsmith

9
그렇게함으로써 나머지 학급을 '오염'시키지 않습니다. type 매개 변수는 Create 메소드에서만 필요합니다 (이 예에서는 적어도). 그 다음에는 다음과 같이 생각합니다. Fruit.Create <Apple> (); 그러면 더 읽기 쉽습니다. Fruit <Apple> .Create ();
Frederik Gheysels

2
public 생성자 가 있어야 한다는 것을 지시 Apple하기 때문에 생성자를 public보다 적게 만들 수는 없습니다 . @Matt Hamsmith-제거 할 때까지 코드가 컴파일되지 않습니다 . 나는 이미 VS에서 테스트했습니다. where T : Fruit<T>, new()Tprotected Apple() { }
Tohid

1
@Tohid-당신이 맞습니다. 편집했습니다. 이것은 Apple이 Fruit CreateInstance 정적 팩토리 메소드를 사용하지 않고 생성되도록 노출하지만 피할 수없는 것처럼 보입니다.
Matt Hamsmith 2014 년

18

팩토리 메서드를 형식 밖으로 이동하고 자체 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 ();
        }        
    }

1
코드가 컴파일되지 않습니다. where T : new () 절이 필요합니다. 그러나 이것은 내 정확한 속임수입니다.
John Gietzen

1
정적 FruitFactory 클래스를 만들 수 있지만 인스턴스화 가능한 FruitFactory 클래스와 함께 IFruitFactory 인터페이스를 선호합니다. CreateFruit 메서드가 개체 인스턴스를 참조하지 않고 따라서 정적 메서드 일 수도있는 FruitFactory 클래스 하나만으로 끝날 수 있지만 과일을 만드는 여러 방법을 제공해야하거나 과일을 만드는 경우 상태를 유지하는 능력이 필요하기 때문에 인스턴스 메서드를 사용하면 유용 할 수 있습니다.
supercat 2011-06-27

4

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

FruitFactory<Banana>.Create();

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클래스는 등 여러 가지 하위 클래스를 가지고 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 제작자가 선호하는 방법 인 것 같습니다.


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

그것은 사실입니다. 까다로울 수 있지만, 가능하고 항상 작동해야합니다. 문제는 '해야한다'는 의미를 이해하는 것입니다. 일반적으로 나는 더 복잡한 메소드를 호출하는 것을 피하는 경향이 있습니다. (비 가상적이지 않은 경우에도 예외를 던질 수있는 방식으로 구성되기 때문에 예외를 던질 수 있고 제 행위자가 아무것도 던지는 것을 좋아하지 않기 때문입니다) ,하지만 그것은 개발자의 결정입니다.
Marcin Deptuła 09.09.04

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.