NickResh777 Ответов: 1

Проблемы с наследованием, с#


Предположим, у меня есть базовый абстрактный класс Функция это выглядит примерно так:
public abstract class Function
{
   public abstract string Invoke(string[] args);
}

А еще у меня есть детский подкласс DeterministicFunction это выглядит примерно так:
public abstract class DeterministicFunction : Function
{
    private readonly Dictionary<string[], string> _cache;

    public sealed override string Invoke(string[] args)
    {
         if (args?.Length > 0)
         {
             string fnResult;
             if (_cache.TryGetValue(args, out fnResult))
                 return fnResult;

             fnResult = GetResult(args);
             _cache[args] = fnResult;
             return fnResult;
         }

         return GetResult(args);
    }

    protected abstract string GetResult(string[] args);
}

Все функции могут быть либо детерминированными, либо недетерминированными.
Детерминированная функция всегда возвращает один и тот же результат для одной и той же комбинации аргументов, поэтому я решил оптимизировать ее, поместив результат в кэш, чтобы, если дорогая функция вызывается с уже вызванными аргументами, она могла кэшировать результат и получать его быстрее. У меня также есть куча недетерминированных функций, связанных с датой и временем. Они недетерминированы, потому что возвращаются
разные результаты с каждым вызовом. Мой базовый класс для функций даты и времени:
public abstract class DateTimeFunction : Function
{
     private CultureInfo _culture;

     protected DateTimeFunction(CultureInfo culture)
     {
         _culture = culture;
     }

     protected CultureInfo Culture => _culture;
}

public class UtcNowFunction : DateTimeFunction
{
     // skip constructor code, too long to type

     public override string Invoke(string[] args)
     {
         DateTime now = DateTime.NowUtc;
         string result = now.ToString(Culture);

         if (args?.Length > 0)
         {
            string format = args[0];
            result = now.ToString(format, Culture);
         }

         return result;
     }
}

Мой вопрос заключается в следующем: нужно ли мне создавать базовый класс для всех недетерминированных функций? На данный момент я не вижу причин добавлять новый абстрактный уровень, но было бы логично разделить их на две основные группы. Если я создам новый абстрактный класс NonDeterministicFunction - там будет пусто. Что же мне делать??

Что я уже пробовал:

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

1 Ответов

Рейтинг:
11

David_Wimbley

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

Вы все еще можете использовать свой абстрактный класс в качестве базового класса в качестве опции.

Лично я бы реализовал его как интерфейс (метод invoke) и использовал абстрактный класс в качестве базового класса (если это необходимо), чтобы размещать только общие методы во всех классах, которые наследуются от него.

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

Поэтому я бы поместил Недетерминированную конкретную реализацию в интерфейс INonDeterministicFunctions, а остальное-в IDeterministicFunctions. Тогда любая общая логика войдет в базовый класс функций. Тогда любая общая логика, специфичная для недетерминированных функций, может перейти в недетерминированный базовый класс.

Если я правильно вас понял, это что-то вроде того, что я бы рефакторировал

public IFunction
{
    string Invoke(string[] args)
}

public abstract class BaseFunction
{
    // common logic shared across function types here
}

public abstract class NonDeterministicFunction : BaseFunction
{
   // common non-deterministic specific functionality here
}

public abstract class DeterministicFunction : BaseFunction
{
   // common deterministic specific functionality here
}


public abstract class DateTimeFunction : DeterministicFunction
{
     private CultureInfo _culture;

     protected DateTimeFunction(CultureInfo culture)
     {
         _culture = culture;
     }

     protected CultureInfo Culture => _culture;
}

public class UtcNowFunction : DateTimeFunction, IFunction
{
     // skip constructor code, too long to type

     public string Invoke(string[] args)
     {
         DateTime now = DateTime.NowUtc;
         string result = now.ToString(Culture);

         if (args?.Length > 0)
         {
            string format = args[0];
            result = now.ToString(format, Culture);
         }

         return result;
     }
}


Причина для интерфейса заключается в том, что он описывает реализацию того, каким должен быть класс для того, что наследуется от него. Учитывая, что реализация специфична для данного класса, мне кажется, что она лучше всего подходит в качестве интерфейса.

Вы можете обнаружить, что недетерминированные/детерминированные классы не нужны, и вы можете просто обойтись 1 классом базовых функций и иметь интерфейсы для детерминированных недетерминированных конкретных реализаций.

Но, как я уверен, вы знаете, есть 10 различных способов сделать все, так что мое предложение может быть не на 100% то, что вы хотите, и, возможно, чрезмерно спроектировано.


NickResh777

Спасибо! Об этих строках - если вы используете свой абстрактный класс функций в качестве базы как для недетерминированных, так и для детерминированных функций, вы подвергаете реализации, которая касается только одной стороны этих функций (например, детерминированные классы смогут видеть/реализовывать недетерминированные методы (потенциально), Если вы используете абстрактный класс функций для обоих типов детерминированных функций).
Но я ставлю детерминистическую логику строго в класс [детерминистической функции], а недетерминистическую логику в класс [Недетерминистической функции]-они не пересекаются, я имею в виду, что их взаимодействия не будут встречаться. Что касается интерфейса [IFunction] - да, я думал об этом - это выглядит аккуратно с точки зрения чистого интерфейса

David_Wimbley

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

Экс:

public class NonDeterministicFunction : BaseFunction 
// Skip code and stuff
public class MyFunction : NonDeterministicFunction, IFunction


Тогда у вас была бы общая логика из базовой функции, доступная MyFunction (теоретически).

BillWoodruff

+5

Maciej Los

5ed!