Главная » C# » Определение интерфейсов серверной электронной таблицы в Visual C# (Sharp)

0

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

Определение интерфейса Debug

Так как код электронной таблицы был взят из промышленного приложения, то мы также рассмотрим фрагменты кода, демонстрирующие хорошие  навыки програирования. Далее приводится базовый интерфейс для всех моих интерфейсов, корый определен В сборке Devspace. Trader .Common:

public interface IDebug { bool Debug { get; set; }

}

Интерфейс IDebug имеет одно, булево, свойство Debug, значение которого можно устанавливать и извлекать. Назначением интерфейса IDebug является позволить компоненту  генерировать  вывод для  целей  отладки.  Одной  из основных  проблем с отладкой приложений, обрабатывающих большие объемы данных,  является  оеделение источника ошибки. Представьте себе, что при обработке нескольких миллионов записей, на записи, скажем, 900 001 происходит ошибка. Перспектива заниматься отладкой 900 000 записей, пока дойдем до ошибки, не выглядит очень привлекательной.  Поэтому  желательно  выяснить  причину  ошибки,  не  прибегая к отладчику. Вот здесь нам и пригодится интерфейс IDebug. Он предоставляет нам механизм, с помощью которого реализация может дать информацию о происходем процессе, поэтому, если нужно определить ошибку, это можно сделать, иссловав вывод данного интерфейса.

Далее приводится код, демонстрирующий, как использовать флаг Debug:

string[] baseType = typeToIstantiate.Split(new string[]

{ " [ [", "]]" }, StringSplitOptions.None); if (baseType.Length == 0) {

throw new Exception("Отсутствует базовый тип, чего не может быть.");

}

if (Debug) {

foreach (string str in baseType) {

GenerateOutput.Writef"Workbook.Load", "basetype(" + str + ")");

}

}

В первой строке кода буфер разбивается на отдельные элементы; буферы разделтся двойными квадратными скобками. Отдельные элементы буфера выводятся свойство м  Debug С ПОМОЩЬ Ю команд ы GenerateOutput. Write ().

СОВЕТ

Хотя я определил свою отладочную инфраструктуру, можно использовать другую ираструктуру, называющуюся log4net (http://logging.apache.org/log4net/) Это всбъемлющая инфраструктура, исследование которой может быть полезным.

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

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

Определение IWorksheetBase и интерфейсов Worksheet

Лист реализуется с помощью интерфейсов листа и  книги.  Не упускайте  из  виду, что одним из требований к реализации электронной таблицы является высокая скость вычислений. Это означает, что в случае электронной таблицы, содержащей числа, лучшей реализацией было бы использование типа double. Но если таблица содержит строковые данные, то лучшей реализацией будет использование типа string. На 11.2 показана организация таблицы, использующей как тип double, так И ТИП string.

На рис. 11.2 можно видеть, что книга обращается к листам типа worksheet<double> и Worksheet<string>, а в общем случае — к листу типа worksheet<BaseType>. Оюда, интерфейс, использующий обобщения .NET, можно было бы определить тим  образом:  определить общий лист и фактический тип  с  помощью  обобщений

.NET. Но с этим решением имеется проблема, заключающаяся в том, что книга оеделяет коллекцию смешанных типов.

Рис. 11.2. Организация таблицы, содержащей данные типа doubl e и  strin g

Можно   с   легкостью   впасть   в   заблуждение,   что   как  worksheet<double>, так и Worksheet<string> ЯВЛЯЮТСЯ ПРОИЗВОДНЫМИ ОТ Worksheet<BaseType> И, ТЭКИМ образом, они одного типа. Но это совсем не так, поскольку в .NET-обобщениях не конкретизированный тип — совсем не тип. Такой тип можно рассматривать, как "почти тип", но для работоспособной программы  все такие типы необходимо коретизировать .  На  рис .   11.2  ТИПЫ Worksheet<double> и  Worksheet<string> ЯВЛЯЮТ-

СЯ конкретизированными и, поэтому, двумя разными типами.  Но два разных типа листов усложняют работу с книгой, т. к. мы хотим иметь в ней лишь одну коллеию листов. Допустим на минуту, что интерфейс листа определен таким образом: interface IWorksheet<BaseType> { }

Тогда книга может ссылаться на лист, как к этой коллекции:

List<IWorksheet<BaseType>> _worksheets;

Но такая ссылка будет неполной, т. к. компилятору нужно знать, на что ссылается BaseType. Чтобы иметь возможность выбора, одним из решений могло бы быть не завершать  тип  BaseType, а  позволить  пользователю  книги  самому  решить  что к чему. В таком случае определение книги было бы следующим:

class Workbook<BasfeType> { List<IWorksheet<BaseType" _worksheets;

}

Данное решение выглядит неплохо, но в действительности оно означает переклывание ответственности с программиста на пользователя, т. к. оно совсем не рает проблему в рис.  П.2, а просто заставляет пользователя решить ее. Сущность проблемы заключается в том, что на рис. 11.2 обобщения .NET используются для определения листов конкретного типа, что означает смешанные типы, к которым должна обращаться книга. Иными словами, книга может содержать листы только определенного типа, как в следующем примере:

Workbook<string> workbookl; Workbook<double> workbook2,-

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

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

Для решения этой проблемы с листами необходимо применить объектнриентированный подход. Прежде всего, что такое лист? Это просто двумерная элеронная таблица, и это относится ко всем листам, независимо от типа их данных. Птому первым интерфейсом, который нужно определить, будет базовый лист:

using Devspace.Trader.Common;

public interface IWorksheetBase : IDebug { void Dimension(int rows, int cols);

int MaxRows { get; } int MaxCols { get; }

}

Определенный интерфейс IWorksheetBase имеет один метод и два свойства. Метод Dimension о используется для установки максимального значения строк и столов отдельных листов. А свойства MaxRows и MaxCols возвращают эти значения. Данные свойства и  метод не имеют ничего общего  с конкретным  типом данных листа, но интерфейс однозначно идентифицирует тип экземпляра как spreadsheet.

В коде книги список листов будет определен таким образом:

List<IWorksheetBase> „worksheets;

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

Для обращения к листу обычно  указываются необходимые строка и столбец. Но чтобы упростить объявление, применяется понятие координат листа sheetcoordinate, являющееся типом с членами данных, представляющими строку и столбец. Тип sheetcoordinate определяется следующим образом:

public struct Sheetcoordinate { public int Row;

public int Column;

public Sheetcoordinate(int row, int column) { if (row < 0) {

throw new ArgumentOutOfRangeException("Row is below zero");

}

if (column < 0) {

throw new ArgumentOutOfRangeException("Column is below zero");

}

Row = row; Column = column;

}

public SheetCoordinate OneUp { get {

return new SheetCoordinate(Row – 1, Column);

}

public SheetCoordinate OneDown { get {

return .new SheetCoordinate(Row + 1, Column);

}

• }

public SheetCoordinate OneLeft { get {

return new SheetCoordinate(Row, Column – 1);

}

}

public SheetCoordinate OneRight { get {

return new SheetCoordinate(Row, Column + 1);

}

}

}

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

Следующим шагом будет расширить определение листа и использовать обобщения

.NET для определения типа листа.  Далее приводится  полный исходный  код опре-

делени я  ТИПа Worksheet:

public interface IWorksheet<BaseType> : IWorksheetBase { void AssignColCalculationfint col,

Func<IWorksheet<BaseType>, int, int, BaseType > cb);

void AssignCellCalculation(int row, int col, Func<IWorksheet<BaseType>, int, int, BaseType > cb);

BaseType GetCellState(int row, int col);

void SetCellState(int row, int col, Basetype val); void AssignCellCalculation(SheetCoordinate coords,

Func<IWorksheet<BaseType>, int, int, BaseType > cb); BaseType GetCellState(SheetCoordinate coords);

void SetCellState(SheetCoordinate coords, BaseType val); void Calculate*);

void CalculateCol(int col); void CalculateRow(int row);

BaseType Calculate(int row, int col); BaseType Calculate(SheetCoordinate coords); BaseType [,] Data {get;}

}

Тип worksheet объявлен как общий тип .NET, где BaseType является параметром

.NET, представляющим тип листа. Так как worksheet является типом spreadsheet, он является подклассом интерфейса iworksheetBase, что позволяет ему быть частью смешанной коллекции экземпляров Worksheet. Интерфейс iworksheet довольно сложный и содержит много методов. Но в данном случае мы фокусирмся на концепции интерфейсов, а не на отдельных методах.

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

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

ПРИМЕЧАНИЕ

Метод создания общего типа .NET (такого как Worksheet), производного от общего не-.NET типа (например, iworksheetBase), позволяет идентифицировать общий тип, который мы пытаемся описать с определенной конкретностью в объявлении  общего типа .NET. С объектно-ориентированной точки зрения, не-.NET общий базовый тип (iworksheetBase) играет роль заполнителя, чтобы указывать, что данный тип коекции удовлетворяет определенному  критерию.

Определение интерфейса Workbook

Завершив определение интерфейсов iworksheeto и iworksheetBase, мы можем приступить к определению интерфейса самой книги. Интерфейс книги не будет обобщением .NET, т. к. книга будет содержать листы нескольких типов. Но как бет показано, мы можем оптимизировать этот интерфейс, чтобы облегчить работу с книгой.

На  данном  этапе,  давайте  рассмотрим  простой  интерфейс  iworkbook, без  типов обобщений .NET. Исходный код для его определения выглядит таким образом:

using Devspace.Trader.Common;

public interface iworkbook : IDebug {

IWorksheetBase this[string identifier] { get; set; } string Identifier { get; }

}

Интерфейс iworkbook определяет одно свойство — identifier, и индексатор this. Ожидается, что любой класс, реализующий интерфейс iworkbook, будет содержать ссылки на множественные экземпляры iworksheeto. Каким образом управлять этими ссылками, является ответственностью не интерфейса iworkbook, а его реалации.

ПРИМЕЧАНИЕ

Интерфейс iworkbook не предоставляет метод Clear () для сброса книги и удалия всех листов, на которые имеются ссылки. Казалось бы, вполне логично иметь мод clear ( ) , но в действительности в среде, предоставляющей сервис сборки муса, в этом методе нет абсолютно никакой необходимости. Если в книге больше нет надобности, просто не обращайтесь к ней, и сборщик мусора сделает все остальное. Это можно сравнить с употреблением настоящей столовой посуды или одноразовой. Настоящая посуда может казаться лучше, но ее могут разбить и после окончания трезы ее нужно мыть. А бумажную или пластиковую посуду после использования можно просто выбросить. Конечно же, с одноразовой посудой тоже имеются свои проблемы, такие как ее переработка, но этого нет в .NET, т. к. рециркуляция памяти осуществлтся автоматически.

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

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

Скажем, что все книги имеют конфигурационный лист со строковым идентификором configuration. Но вот пришел новый программист и решил, что ему больше нравится Configuration, т. е. с заглавной С. Это, казалось бы, небольшое измение вызовет проблемы, т. к. С# чувствителен к регистру идентификаторов. Рамотрим соответствующий пример:

IWorkbook workbook;

IWorksheetBase worksheetl = workbook!"configuration"]; IWorksheetBase worksheet2 = workbook["Configuration"];

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

public static class Worksheetldentifiers   {

public const string Configuration = "configuration";

}

IWorkbook workbook;

IWorksheetBase worksheetl = workbook[Worksheetldentifiers.Configuration]; IWorksheetBase worksheet2 = workbook[Worksheetldentifiers.Configuration];

Класс worksheetldentif iers все еще содержит жестко закодированный строковый буфер, но он локализирован в одном месте. Индексатор книги обращается к идеификатору в коде класса worksheetidentifiers. Поэтому при изменении класса worksheetldentif iers также изменяются идентификаторы, используемые индеатором. Таким образом, уменьшаются шансы, что ошибка при вводе идентификора подвесит приложение.

Теперь возвратимся к интерфейсу iworkbook, в частности к индексатору. Тип иексатора— IWorksheetBase. Хотя это и правильный тип, работать с ним несколо тяжеловато, т. к. IWorksheetBase является элементарным интерфейсом,  и, скее всего, не интерфейсом, который будет использоваться.  Интерфейсом,  который в  действительности  мы  хотим  использовать,  является  интерфейс  iworksheeto, и в этом заключается проблема. А именно, чтобы получить экземпляр worksheet, нам пришлось бы выполнить приведение типов, как показано в следующем коде:

IWorkbook workbook; IWorksheet<string> worksheet =

workbook[Worksheetldentifiers.Configuration] as IWorksheet<string>;

Жирным шрифтом выделен код для приведения типов, которое необходимо волнять при каждом обращении к экземпляру worksheet. Хотя сама операция предения  типов  не  представляет  никаких  трудностей,  она  довольно  громоздкая. Я лично предпочел  бы иметь возможность вызвать  свойство, метод  или индексор, который возвращает нужный мне тип.

В этом и заключается проблема, т. к. я хочу использовать индексатор, свойство или метод, как смешанный тип, а индексатор нельзя определять со смешанными типи. Можно определять только индексаторы постоянного типа. Чтобы лучше понять суть этой проблемы, посмотрите на следующее объявление книги, которое компируется без ошибок:

us ing Devspace.Trader.Common;

public interface IWorkbook<BaseType> : IDebug {

IWorksheet<BaseType> this[string identifier] { get; set; } string Identifier { get; }

}

В этом объявлении интерфейса iworkbook используется общий тип .NET, но тогда возникает проблема, что индексатор может возвращать экземпляры worksheet только одного типа, такого как double или string. Не забывайте, что у нас имеюя листы нескольких типов (см. рис. 11.2).

Мы хотим использовать объявление обобщения .NET уровня метода, как показано в следующем фрагменте кода:

public interface IMixedType {

Func<Datatype> this<Datatype>[string identifier] { get; set; }

}

Но с объявлением уровня метода имеется проблема — оно не компилируется. Паметр обобщения .NET можно объявить двумя способами. Первый способ — обвление на уровне типа — мы видели наиболее часто:

class MyType< GenericType> { }

Объявление на уровне типа означает, что при каждом использовании или указании типа для параметра обобщения .NET тип мутуре прикрепляется к определенному типу. Допустим, что тип мутуре объявлен следующим образом:

MyType<int> els = new MyType<int>();

Теперь мутуре привязан к типу int, и любые ссылки на GenericType в коде объекта мутуре станут типа int. Данная форма обобщений .NET позволяет решить многие проблемы, как было показано в главе 9.

Но в случае с iworkbook мы не хотим постоянного типа. Что мы хотим, так это чты тип коллекции мог содержать смешанные типы iworksheet. Этого можно диться, используя методы обобщений .NET следующим образом:

class MyType { GenericType Method< GenericType>() { … }>

Теперь параметр обобщения .NET ассоциирован с методом, а не с типом. Это ознает, что класс мутуре может смешивать типы, и мы можем иметь iwoKsheet раых типов. Но было бы это здорово, если бы у нас был еще и индексатор со сманными типами. Однако индексаторы и свойства обобщений .NET, которые не объявлены на уровне типа, не разрешаются. Поэтому использовать параметр обоения ..NET с индексатором или свойством не получится. Лично  я  считаю  это очень большой недоработкой создателей языка С#, т. к. это означает, что нам нуо писать код таким образом:

us ing Devspace.Trader.Common;

public interface iworkbook :  IDebug {

IWorksheet<BaseType> GetSheet<BaseType>(string identifier);

IWorksheetBase this[string identifier] { get;  set; } string Identifier { get; }

}

В этом модифицированном объявлении метод Getsheet () функционирует подобно части get индексатора, но обратите внимание на то, где объявляется параметр BaseType обобщения .NET — после идентификатора метода и  перед  первой  скоой. В случае с  iworkbook мы  используем  параметр  обобщения  .NET уровня  мета, чтобы позволить вызывающему коду определить тип экземпляров листов. Реизации   iworkbook не   нужно   ничего   делать,   кроме   как   выполнять   приведение к соответствующему типу. Параметры обобщений .NET уровня метода прекрасно подходят для работы со смешанными типами,  как в случае с  iworkbook.

Теперь  код для  получения листа, для  которого  прежде нужно было  выполнять  привение типа,  можно  переписать таким  образом:

iworkbook workbook; IWorksheet<string> worksheet =

workbook.GetSheet<string>(Worksheetldentifiers.Configuration);

Тем  не менее,  избавиться полностью от приведения типа нам  не удалось. Но теперь это делается для нас в реализации метода Getsheeto (), как показано в следующем коде: public IWorksheet<MethodBaseType>

GetSheet<MethodBaseType>(string identifier) { lock (_worksheets) {

IWorksheet<MethodBaseType> retval = null; if (_worksheets.ContainsKey(identifier)) {

retval = „worksheets[identifier] as IWorksheet<MethodBaseType>;

}

else {

retval = new Worksheet<MethodBaseType>(identifier);

_worksheets.Add(identifier, retval);

}

return retval;

}

}

Приведение типов выполняется кодом, выделенным жирным шрифтом. Теперь приведение осуществляется в методе и использует параметр обобщения .NET, обвленный  на уровне  метода.

Источник: Гросс  К. С# 2008:  Пер. с англ. — СПб.:  БХВ-Петербург, 2009. — 576 е.:  ил. — (Самоучитель)

По теме:

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