Архитектурный / шаблонный вопрос - проблема при скрытии деталей реализации за интерфейсом - PullRequest
0 голосов
/ 23 декабря 2010

У меня есть пара классов BitMask и BitMaskLarge, которые оба реализуют интерфейс IBitMask, который предоставляет функциональность для выполнения побитовых логических операций и т. Д. Класс BitMask содержит частное поле _mask типа long, а класс BitMaskLarge содержит частный массив _masks длинных.

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

При создании IBitMask используется BitMaskFactory, который возвращает соответствующий тип на основе пропускной способности, передаваемой в конструктор.

Проблема возникает при установке бита в битовой маске с помощью метода SetBitIndex. Если бит находится за пределами текущей маски, BitMaskLarge просто добавляет еще один элемент в _masks и устанавливает соответствующий бит. Однако BitMask не может выходить за рамки 64 битов, поэтому необходимо преобразовать его в BitMaskLarge. Очевидно, что это невозможно при вызове bitmask.SetBitIndex (100) для класса BitMask, который должен установить бит на индекс 100.

Одним из возможных решений, которое я могу придумать, было бы сделать SetBitIndex внутренним и создать статический метод в BitMaskFactory, который возвращал новый или обновленный IBitMask. Это не идеально, так как это не естественно для использования. Если у кого-то есть лучшее решение этой проблемы, я хотел бы услышать это. Все идеи приветствуются, независимо от того, насколько фундаментальными являются изменения.

Ответы [ 6 ]

1 голос
/ 23 декабря 2010

Как насчет внешнего интерфейса и внутреннего класса? Класс внешнего интерфейса предоставил бы фактический фасад для использования клиентскими приложениями (роль, которую в настоящее время выполняет ваш IBitMask интерфейс). Внутренний класс будет обеспечивать фактическую реализацию. Затем внешний интерфейс просто делегировал бы все вызовы от клиентского приложения своему внутреннему внутреннему экземпляру и мог бы прозрачно заменить реализацию, если набор битов становится слишком большим для «маленькой» версии (или снова становится достаточно маленьким, чтобы быть представленным "версия):

 class BitMask {
     private IBitMaskData actual;
     public void SetBitIndex(int index) {
          actual = actual.SetBitIndex(index);
     }
 }

 // Stuff below is an implementation detail of class BitMask
 // and private

 interface IBitMaskData {
      ...
      IBitMaskData SetBitIndex(int index);
 }

 class SmallBitMask {
      IBitMaskData SetBitIndex(int index) {
          if( index < 64 ) { 
              // Set the bit actually...
              return this;
          } else {
              LargeBitMask newMask = new LargeBitMask(this);
              return newMask.SetBitIndex(index);
          }
      }
 }

 class LargeBitMask {
     ...
 }

(Если вы достаточно заботитесь о требованиях к производительности и памяти, чтобы различать большие и маленькие битовые маски, вам может не понравиться это решение только из-за дополнительных накладных расходов)

1 голос
/ 23 декабря 2010

Единственное различие между BitMask и BitMaskLarge, которое было описано, заключается в использовании поля _mask vs _masks.Если это действительно единственная разница, как насчет того, чтобы всегда возвращать BitMaskLarge с завода?Если вам не нужно больше одного лонга, тогда у вас будет только один лонг в массиве _masks.

1 голос
/ 23 декабря 2010

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

IBitMask mask = BitMaskFactory(...); // assume mask is now a BitMask
mask = mask.SetBitIndex(100); // now mask is a BitMaskLarge

Обеспечение неизменности экземпляров также обеспечивает безопасность потоков "бесплатно".

Если вы находите это неудобным, вы можете решить его, добавив еще один уровень абстракции:

class BitMaskImplementation : IBitMask {
    private IBitMask mask;

    // BitMaskImplementation defers all operations to mask,
    // and you can change the object stored inside mask without
    // your callers knowing anything about what happened.
}

Это лишит вас безопасности потока.

Обновление:

На самом деле вы не должны идти на все эти неприятности, если BitMaskLarge не намного дороже для вас, чем BitMask, и , и вы также ожидаете, что почти все маски просты BitMask s .

0 голосов
/ 23 декабря 2010

Две вещи для рассмотрения:

Во-первых, почему вы не можете просто использовать BitMaskLarge 100% времени?Если вы скажете «производительность», я скажу вам, что разница незначительна.

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

0 голосов
/ 23 декабря 2010

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

Конечно, это не произойдет автоматически, но это позволит вашему абоненту передумать, не возвращаясь к заводским настройкам.

0 голосов
/ 23 декабря 2010

Вы должны решить, хотите ли вы динамически растущие битовые маски или нет. Если вы хотите, то создайте один класс BitMask, который поддерживает рост до произвольных размеров. Он все еще может работать хорошо, если у вас <= 64 бита, не волнуйтесь. В противном случае вы должны указать фабрике, какую большую битовую маску вы хотите получить, а затем запретить устанавливать индексы более высокого уровня, чем тот, для которого предназначен экземпляр класса.

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