Ссылочный универсальный параметр - PullRequest
0 голосов
/ 17 февраля 2019

Например, у меня есть следующие классы:

1.

class MyClass1
{
    public MyClass1 Method()
    {
        ...
        return new MyClass1();
    }
}

class MyClass2
{
    public MyClass2 Method()
    {
        ...
        return new MyClass2();
    }
}

Методы имеют одинаковые тела, поэтому я хочу извлечь код и повторно использовать.

2.

abstract class MyClass
{

    protected void Method()
    {
        ...
    }
}

class MyClass1 : MyClass
{

    public MyClass1 Method()
    {
        base.Method();
        return new MyClass1();
    }
}

class MyClass2 : MyClass
{
    public MyClass2 Method()
    {
        base.Method();
        return new MyClass2();
    }
}

Однако, поскольку таких методов много, лучше вообще переместить методы в базовый класс MyClass:

3.

abstract class MyClass<T>: where T : MyClass<T>
{
    protected abstract T Create();

    public T Method()
    {
        ...
        return Create();
    }
}

class MyClass1 : MyClass<MyClass1>
{
    protected override MyClass1 Create() => new MyClass1();
}

class MyClass2 : MyClass<MyClass2>
{
    protected override MyClass2 Create() => new MyClass2();
}

Работает нормально, но контракт выглядит слишком странно.Конечно, я могу извлечь что-то вроде IMyInterface и вернуть его вместо класса.Однако я должен сохранить исходный контракт, поскольку он также содержит определенные методы.

Upd: Итак, странная вещь выделена жирным шрифтом - класс MyClass1 : MyClass <<strong> MyClass1 >

Ответы [ 3 ]

0 голосов
/ 25 февраля 2019

Это обычная так называемая проблема самовоспроизведения (у вас Method() должен возвращать тот же тип, что и объект, для которого она была вызвана).Ваше решение № 3 очень похоже на F-ограниченное количественное определение.Однако это C #, а не Java, поэтому мы можем сделать немного лучше, используя класс расширения.

Вы можете убедиться, что эти методы вызываются только для подклассов MyClass, добавив where T : MyClass, связанный сT.

// Put all your shared methods in generic classes in here.
public static class MyMethods
{
    public static T Method<T>(this T x) where T : MyClass
    {
        ...
    }
}

Ваши классы не сильно меняются, за исключением того, что им вообще не нужно упоминать Method (или другие общие методы).

public abstract class MyClass
{
    ...
}

public class MyClass1 : MyClass
{
    ...
}

public class MyClass2 : MyClass
{
    ...
}
0 голосов
/ 01 марта 2019

В некоторых редких ситуациях может потребоваться ограничение на самоссылающиеся типы, но я не уверен, что это одна из них.

Кажется, что вы хотите использовать фабричный (Создать) шаблон, а также заставить эти фабрики возвращать разные конкретные типы.Но в то же время вы говорите, что все эти конкретные типы имеют что-то общее, определенное базовым классом.

Обычный подход заключается в определении общих функций в интерфейсе (как вы предложили IMyInterface) и возврате его из метода Create.Это охватило бы полиморфный аспект конкретных классов.Тогда возникает вопрос, как захватить другие методы, которые реализованы в конкретных типах.Для этого можно просто определить дополнительные интерфейсы, которые охватывают различные кластеры функциональности, реализованные более чем одним из конкретных классов.

В той мере, в которой после этого у вас остались какие-то проблемы с функциональностью, я бы сказал, что обработать их путем приведения будет проще всего - в этот момент функциональность будет уникальной дляодин из конкретных классов.Если вы хотите летать с закрытыми глазами, вы можете использовать «динамический» тип вместо кастинга.

Кроме того, обычно метод Create не определяется в экземплярах объекта, другими словами, обычно объекты не являются их собственными фабриками.Обычно они либо статические, либо в отдельных фабричных классах.В текущей ситуации немного размышлений помогает справиться с тем фактом, что у вас есть несколько производных типов.Существуют различные способы сделать это, кроме того, что я покажу ниже.

Итак… возможно, что-то вроде этого:

public interface ICommonFunctionality
{
    void SomethingThatEveryoneCanDo();
    // ... other common functionality
}

public interface IAdditionalFunctionality1
{
    void SomethingThatAFewCanDo();
    // ... other special functionality
}
public interface IAdditionalFunctionality2
{
    void SomethingThatOthersCanDo();
    // ... other special functionality
}

public class MyClass : ICommonFunctionality
{
    static public ICommonFunctionality Create(Type derivedType)
    {
        if (!typeof(ICommonFunctionality).IsAssignableFrom(derivedType)) { throw new ArgumentException(); }
        return derivedType.CreateInstance() as ICommonFunctionality;
    }

    virtual public void SomethingThatEveryoneCanDo() { /* ... */  }
}

public class MyClass1 : MyClass, IAdditionalFunctionality1
{
    public void SomethingThatAFewCanDo() { /* ... */ }
}

public class MyClass2 : MyClass, IAdditionalFunctionality1, IAdditionalFunctionality2
{
    public void SomethingThatAFewCanDo() { /* ... */ }
    public void SomethingThatOthersCanDo() { /* ... */ }
}

public class MyClass3 : MyClass, IAdditionalFunctionality2
{
    public void SomethingThatOthersCanDo() { /* ... */ }
}

public static class TypeHelpers
{
    public static object CreateInstance(this Type type, bool required = true)
    {
        System.Reflection.ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
        if (required && ctor == null) { throw new InvalidOperationException("Missing required constructor."); }
        return ctor?.Invoke(null);
    }
}

PS Я сделал метод базового класса виртуальным, это в значительной степени необязательно в зависимости от вашей ситуации.

0 голосов
/ 17 февраля 2019

Да, немного странно иметь метод, который только создает.

Поскольку у вас есть 2 класса MyClass1 и MyClass2, которые имеют свои специфические разные методы, и распространен только базовый метод (который вы положили в базовый класс), я думаю, вы можете использовать шаблон абстрактной фабрики.

public class ClassFactory: IClassFactory
{
    public MyClass Create()
    {
         if (some condition)
              return new MyClass1;
         return new MyClass2;
    }
}

class MyClass
{
    protected string CommonLogic()
    {
         //common logic 
         return string;
    }

}

class MyClass1 : MyClass
{
    public object SpecificMethod()
    {
        CommonLogic();
        .....
    }

}

class MyClass2 : MyClass
{
    public object SpecificMethod2()
    {
        CommonLogic();
        .....
    }
}

В этом случае у вас не будет дублированного кода, и у вас будет какой-то класс, который будет отвечать за создание ваших классов и будет знать, когда и какой класс вернется.+ Вы легко воспользуетесь здесь IoC.

Надеюсь, мой ответ вам поможет.

...