Избегайте нарушения DRY с помощью абстрактного базового класса. Как это сделать? Ищем альтернативы / аргументы - PullRequest
0 голосов
/ 23 апреля 2020

(Примечание: одно решение, которое работает, вы можете найти в нижней части этого вопроса)

В настоящее время я пытаюсь провести рефакторинг обобщенного c класса / интерфейса, где у меня есть ковариантный и контравариантный параметр. Причина рефакторинга заключается в том, что на данный момент я должен реализовать для всех приведенных вариантов использования класс N раз.

Моя цель / идея заключалась в том, чтобы написать абстрактный класс, содержащий разделяемые функциональные возможности, и сделать один сложный метод абстрактным, который нуждается в конкретной реализации для каждого варианта использования. Абстрактный базовый класс по унаследованным причинам все еще будет наследовать от исходного интерфейса, который является обобщенным c one.

. Вы можете найти исходную структуру кода здесь: https://dotnetfiddle.net/ZgGihl

То, что я пытался сделать, это следующий фрагмент кода

https://dotnetfiddle.net/Aub8Ov

С этим способом связаны две проблемы:

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

Вопросы

  • Есть ли способ каким-либо образом решить проблему с помощью обобщений и ковариантов без введения нового интерфейса?
    interface test <out TInput, in TResult> 
    {
        void setResult(TResult result);
        TInput GetInput();
    }

    abstract class SemiConcreteTest<TInput, TResult> : test<TInput, TResult> 
    {
        public TResult field;

        public void setResult(TResult result)
        {
            field = result;
        }

        public abstract TInput GetInput();

    }

    //The UseCases

    ....

    class concreteTest_A<TResult> : SemiConcreteTest<int,TResult> 
    {
        public override int GetInput()
        {
            //Some information gathering
            throw new System.NotImplementedException();
        }


    }

    //UsesCase Specific Classes
    interface Itag
    {

    }

    class A : Itag{}
    class B : Itag{}
    class C : Itag{}

Этот путь все равно приведет к другой ссылке, как показано в примере. Первый параметр Generi c можно исключить, используя дженерики непосредственно для метода вместо класса, но это все равно оставит проблему, касающуюся контравариантного

public static void Main()
{
        SemiConcreteTest<int, Itag> a = new concreteTest_A<A>();
        SemiConcreteTest<short, Itag> b = new concreteTest_B<B>();
        SemiConcreteTest<long, Itag> c = new concreteTest_C<C>();
}

Мое решение, исключающее контравариантный параметр

Что бы я сделал лично, это угробил бы контравариантный параметр alltogther и заменил его соответствующим интерфейсом, но коллега попросил меня *1047*, если возможно, во избежание этого .

Моим решением было бы следующее (https://dotnetfiddle.net/iql682):

interface DitchTheContraVariant{}

//The legacy Interface
interface ITest 
{
    void setResult(DitchTheContraVariant result);
    TInput GetInput<TInput>();
}

Я решаю проблему, связанную с общей ссылкой, используя универсальный c метод. как видно выше в интерфейсе.

abstract class SemiConcreteTest : ITest {

    DitchTheContraVariant result;
    public void setResult(DitchTheContraVariant result){

        this.result = result;

    }

    public abstract TInput GetInput<TInput>();
}

//The UseCases
class concreteTest_A : SemiConcreteTest
{

    public override T GetInput<T>()
    {
        //Some information gathering
        throw new System.NotImplementedException();
    }


}

class A :DitchTheContraVariant {}
class B :DitchTheContraVariant {}
class C :DitchTheContraVariant {}



public static void Main()
{
    SemiConcreteTest a = new concreteTest_A();
    a = new concreteTest_B();
    a = new concreteTest_C();

    a.setResult(new A());
    int b = a.GetInput<int>();

    Console.WriteLine("Hello");
}

Но это привело бы к появлению нового интерфейса в базе кода, которого я стараюсь избегать, но я понятия не имею, как. Я не уверен, как продать мою версию моему коллеге.

1 Ответ

0 голосов
/ 23 апреля 2020

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

Определить абстрактный класс для вашего теста

abstract class SharedTest<TInput, TResult> : Itest<TInput, TResult>
{
    TResult result;

    public virtual void setResult(TResult result)
    {
        this.result = result;
    }

    public abstract TInput GetInput();
}

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

Это позволит реализовать ваши классы следующим образом:

class concreteTest_A : SharedTest<int,A> 
{
    public override int GetInput()
    {
        //Some information gathering
        throw new System.NotImplementedException();
    }        
}

class concreteTest_B : SharedTest<short, B>
{
    public override short GetInput()
    {
        //Some information gathering
        throw new System.NotImplementedException();
    }
}

class concreteTest_C : SharedTest<long, C>
{
    public override long GetInput()
    {
        //Some information gathering
        throw new System.NotImplementedException();
    }
}

Использование:

Itest<int, A> a = new concreteTest_A();
concreteTest_B b = new concreteTest_B();
SharedTest<long, C> c = new concreteTest_C();
...