Как обрабатывать статические поля, которые варьируются в зависимости от реализации класса - PullRequest
4 голосов
/ 16 сентября 2008

Я постоянно сталкиваюсь с этой проблемой. Предположим, я делаю интерфейс командной строки (Java или C #, проблема та же, я думаю, я покажу C # здесь).

  1. Я определяю интерфейс ICommand
  2. Я создаю абстрактный базовый класс CommandBase, который реализует ICommand, чтобы содержать общий код.
  3. Я создаю несколько классов реализации, каждый из которых расширяет базовый класс (и за счет расширения интерфейса).

Теперь - предположим, что интерфейс указывает, что все команды реализуют свойство Name и метод Execute ...

Для Name каждый из моих экземпляров классов должен возвращать строку, которая является именем этой команды. Эта строка («HELP», «PRINT» и т. Д.) Является статической для соответствующего класса. То, что я хотел бы сделать, это определить:

публичная абстрактная статическая константная строка Name;

Однако (к сожалению) вы не можете определить статические элементы в интерфейсе.

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


UPDATE:

  1. Я не могу заставить форматирование кода работать должным образом (Safari / Mac?). Извинения.
  2. Пример, который я использую, тривиален. В реальной жизни иногда существуют десятки реализующих классов и несколько полей этого полустатического типа (т. Е. Статических для реализующего класса).

  3. Я забыл упомянуть - в идеале я хочу иметь возможность запрашивать эту информацию статически:

    имя строки = CommandHelp.Name;

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

Ответы [ 11 ]

2 голосов
/ 16 сентября 2008

Вы можете использовать атрибуты вместо полей.

[Command("HELP")]
class HelpCommand : ICommand
{
}
2 голосов
/ 16 сентября 2008

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

public abstract class MyBaseClass
{
  public abstract string Name { get; protected set; }
}

public class MyClass : MyBaseClass
{
  public override string Name
  {
    get { return "CommandName"; }
    protected set { }
  }
}

(Обратите внимание, что защищенный набор предотвращает изменение имени внешним кодом.)

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

1 голос
/ 16 сентября 2008
public interface ICommand {
      String getName();
}

public class RealCommand implements ICommand {
     public String getName() {
           return "name";
     }
}

Все просто. Зачем иметь статическое поле?


Obs .: Не используйте поле в абстрактном классе, которое должно быть инициировано в подклассе (как предложение David B ). Что если кто-то расширяет абстрактный класс и забывает инициировать поле?

0 голосов
/ 23 октября 2008

Что-то вроде этого:

abstract class Command {
   abstract CommandInfo getInfo();
}

class CommandInfo {
  string Name;
  string Description;
  Foo Bar;
}

class RunCommand {
  static CommandInfo Info = new CommandInfo() { Name = "Run", Foo = new Foo(42)  };

  override commandInfo getInfo() { return Info; }
}

Теперь вы можете получить статическую информацию:

RunCommand.Info.Name;

А у вас базовый класс:

getInfo().Name;
0 голосов
/ 16 сентября 2008

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

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

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

Итак, в качестве примера, давайте предположим, что есть некоторая фактическая логика, связанная с данными, и что она достаточно обширна и / или достаточно сложна, чтобы обеспечить витрину для базовой классификации. Если оставить в стороне, использует ли логика базового класса какие-либо детали имплементации или нет, у нас есть проблема обеспечения статической инициализации реализации. Я бы рекомендовал использовать защищенный реферат в базовом классе, чтобы заставить все реализации создавать необходимые статические данные, которые будут проверяться во время компиляции. Все IDE, с которыми я работаю, делают это очень быстро и легко. Для Visual Studio требуется всего несколько щелчков мышью, а затем просто существенно изменить возвращаемое значение.

Возвращаясь к весьма специфической природе вопроса и игнорируя многие другие проблемы проектирования ... Если вы действительно должны придерживаться всего этого характера статических данных и все же применять их в рамках характера проблемы .. Определенно используйте метод над свойствами, поскольку существует множество побочных эффектов, позволяющих использовать свойства. Используйте статический член в базовом классе и используйте статический конструктор в реализациях, чтобы установить имя. Теперь имейте в виду, что вы должны проверять имя во время выполнения, а не во время компиляции. По сути, метод GetName в базовом классе должен обрабатывать то, что происходит, когда реализация не устанавливает свое имя. Это может привести к исключению, из-за которого становится совершенно очевидно, что что-то не так с реализацией, которая была вызвана надеждой при тестировании / QA, а не у пользователя. Или вы можете использовать рефлексию, чтобы получить имя реализации и попытаться сгенерировать имя ... Проблема с рефлексией заключается в том, что она может влиять на подклассы и создавать ситуацию с кодом, которую трудно понять и поддерживать разработчику младшего уровня. ..

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

0 голосов
/ 16 сентября 2008

[Предлагаемый ответ № 3 из 3]

Я еще не пробовал, и на Java это было бы не очень хорошо (я думаю?), Но я мог бы просто пометить свои классы Атрибутами:

[CammandAttribute (Name = "HELP")]

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

0 голосов
/ 16 сентября 2008

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

interface A { abstract static NAME }
class B { NAME = "HELP" }
class C { NAME = "PRINT" }

Тогда откуда этот код может знать, на какое ИМЯ ссылаться:

void test(A a) {
    a.NAME;
}

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

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

Тем не менее, гораздо лучшим решением является использование перечислений:

public enum Command {
    HELP { execute() }, PRINT { execute() };
    abstract void execute();
}

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

0 голосов
/ 16 сентября 2008

[Предлагаемое решение № 2 из 3]

  • Создание приватной переменной-члена.
  • Определите абстрактное имя свойства в интерфейсе.
  • Реализуйте свойство в базовом классе следующим образом:

    public string Name
    { 
      get {return Name;}
    }
    
  • Заставить все реализации передавать имя в качестве аргумента конструктора при вызове конструктора абстрактного базового класса:

    public abstract class CommandBase(string commandName) : ICommand
    {
      name = commandName;
    }
    
  • Теперь все мои реализации устанавливают имя в конструкторе:

    public class CommandHelp : CommandBase(COMMAND_NAME) {}
    

Преимущества:

  • Мой код доступа централизован в базовом классе.
  • Имя определяется как константа

Недостатки

  • Имя теперь является переменной экземпляра - каждый экземпляр моих командных классов делает новую ссылку, а не разделение статической переменной.
0 голосов
/ 16 сентября 2008

Что я обычно делаю (псевдо):

abstract class:

private const string nameConstant = "ABSTRACT";

public string Name
{
   get {return this.GetName();}
}

protected virtual string GetName()
{
   return MyAbstractClass.nameConstant;
}

----

class ChildClass : MyAbstractClass
{
   private const string nameConstant = "ChildClass";

   protected override string GetName()
   {
      return ChildClass.nameConstant;
   }
}

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

0 голосов
/ 16 сентября 2008

просто добавьте свойство name к базовому классу и передайте его в конструктор базового класса, а в качестве имени команды передайте конструктору из производного класса

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...