Как переопределить значение свойства суперкласса, используя индивидуальную реализацию?

#c#

#c#

Вопрос:

У меня есть «Сущность» суперкласса в проекте логического уровня в существующем 3-уровневом «основном» коде приложения, от которого наследуются 2 конкретных класса «RiskEntity» и «PolicyEntity» (в том же базовом проекте). Суперкласс обладает общедоступным свойством «Loss», которое не переопределяется двумя подклассами. Это свойство используется другим кодом в рамках этого основного проекта. Реализация этого свойства ссылается на другие классы / свойства в рамках этого основного проекта.

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

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

Альтернативные предложения также приветствуются. Спасибо.

Комментарии:

1. Является ли свойство виртуальным? Определено ли свойство в интерфейсе?

2. Можете ли вы объяснить, почему вы не можете / не хотите делать это с помощью стандартного полиморфизма?

3. @Charles — ну, «стандартное» поведение реализовано так. Индивидуальное поведение рассматривается как нечто большее, поэтому в идеале, чтобы индивидуальный код не существовал в основном проекте. Структура суперкласса / подкласса по-прежнему уместна. На самом деле это не третий подкласс, который мне нужен, а скорее реализация замены средства получения суперкласса. Если то, что я пытался описать, может быть достигнуто с помощью стандартного полиморфизма … я все еще пытаюсь придумать, как.

4. Я попытаюсь перефразировать / упростить этот вопрос. Я думаю, это сводится к следующему: у меня есть базовый проект среднего уровня C #, содержащий весь код, необходимый для запуска (3-уровневого) приложения в стандартном режиме. Для конкретного сценария мне нужно использовать весь код / классы в этом проекте C #, но с измененной реализацией только одного или двух методов. Я не хочу добавлять этот пользовательский код в свой основной проект, и я не хочу дублировать / разветвлять весь проект только для этого пользовательского выпуска. Возможно ли это? Спасибо!

5. У вас есть доступ к суперклассу? То есть, вы можете вставить некоторую логику в конструктор суперкласса (и / или свойство Loss) и повторно развернуть сборку?

Ответ №1:

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

Во-первых, интерфейс стратегии и реализация по умолчанию в вашем основном проекте:

 public interface ILossCalculator
{
   decimal CalculateLoss(); //maybe needs parameters, perhaps even the Entity itself?
}

public class DefaultLossCalculator : ILossCalculator
{
   public DefaultLossCalculator(Dependency a, OtherDependency b)
   {
       //just an example, but here we inject the other classes that the 
       //default implementation requires to do the calculation.
   }

   public decimal CalculateLoss()
   {
       //default implementation ...
       return totalLoss;
   }
}
  

Далее, Entity класс и производные классы, снова в основном проекте.

 public abstract class Entity
{
   private ILossCalculator _lossCalculator;

   protected Entity(Dependency a, OtherDependency b)
   {
       _lossCalculator = new DefaultLossCalculator(a, b);
   }

   protected Entity(ILossCalculator lossCalculator)
   {
       _lossCalculator = lossCalculator;
   }

   public decimal Loss
   {
      get
      {
         return _lossCalculator.CalculateLoss();
      }
   }
}

public class RiskEntity : Entity
{
    public RiskEntity(Dependency a, Dependency b) : base(a, b)
    {
    }

    public RiskEntity(ILossCalculator lossCalculator) : base(lossCalculator)
    {
    }

    //rest of implementation
}

//Other derived class(es) omitted - you get the idea, though...
  

Клиентский код, который создает Entity производные объекты, теперь может подключаться к различным реализациям ILossCalculator интерфейса, даже если реализация определена вне основного проекта. Чтобы добиться выбора ILossCalculator реализации для использования из конфигурации, я бы, вероятно, создал Factory реализацию, которую вы использовали бы для создания Entity подклассов, которые можно было бы настроить так, чтобы при Entity создании она использовала пользовательскую реализацию ILossCalculator . Представление клиента было бы примерно таким:

 var factory = new EntityFactory();
factory.LossCalculationStrategy(() => new MyCustomLossCalculator());

var entity = factory.CreateRiskEntity(); //returns a RiskEntity constructed with the custom loss calculator
  

Комментарии:

1. Это очень приблизило меня к тому, чего я хотел. Основное отличие в моей реализации заключается в том, что фабрика просматривает настройки конфигурации и использует отражение (Assembly. Загрузка с последующей сборки. CreateInstance) для создания экземпляра требуемого ILossCalculator. Еще раз спасибо.

Ответ №2:

Я не уверен, что очень хорошо понимаю ваши требования, но это звучит очень похоже на хорошую задачу для фреймворка inversion of control (IOC). Моя команда использует Castle Windsor для МОК.

Вкратце, фреймворк позволяет вам настроить репозиторий на возврат объекта типа X для выполнения запроса на объект типа Y (где X реализует или наследует от Y). Вы можете определить эту связь в коде или в файле .config, который должен соответствовать вашему требованию «Я бы хотел, чтобы приложение было настраиваемым».

В зависимости от требуемой разницы в способе получения свойства, вы можете использовать декоратор (см. http://en.wikipedia.org/wiki/Decorator_pattern ) обернуть объект или, возможно, подкласс конструктором копирования. Рекомендация «предпочесть композицию» была бы в пользу декоратора. Я бы рассмотрел опцию подкласса только в том случае, если вам нужен доступ к защищенным элементам в вашей индивидуальной реализации.

Комментарии:

1. Спасибо за это. Я принял другой ответ, но ваш второй абзац, в частности, соответствует тому, что мне было нужно. Мы можем принять что-то вроде Castle Windsor, если будет больше случаев для использования этого (в будущем нам может понадобиться больше «подключаемого модуля»), но на данный момент я внедряю зависимость в значительной степени в соответствии с другим ответом.