Главная » C# » Расширение системы предсказания результатов лотереи в Visual C# (Sharp)

0

В главе 10 применение интерфейса IProcessor было хорошим первым шагом, т. к. позволяло решить насущную проблему преобразования текстовой строки из одного формата в другой.  Но для  примера данной главы этого  интерфейса недостаточно, И нам нужно Добавить еще Два метода: Initialize () И Finalize ().

Мы хотим вычислить частоту выпадения каждого числа в лотерейных розыгрышах. Для обработки строки текста применяется метод IProcessor.Process(), а частоту выпадения можно подсчитать после обработки данных для всех розыгрышей. Стветственно, мы добавляем метод Finalizeo, который вызывается после считания всех строчек текста. Общепринятое соглашение по написанию кода гласит, что если у нас есть метод Finalizeo, то у нас также должен быть метод initialize о, который вызывается до обработки строчек текста.

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

public interface IProcessor { string Initialize();

string FinalizeO;

string Process(string input);

}

Такой код не позволен, т. к. он нарушает существующую функциональность. Теперь любой класс, реализующий интерфейс IProcessor, должен реализовывать методы initialize ()  и Finalize (), независимо от того, нужны ему эти методы или нет.

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

фейсы  и  производные  от  существующих  интерфейсов,  как  показано  в  следуем коде:

public interface IExtendedProcessor : IProcessor { string Initialize();

string Finalize();

}

Новый    интерфейс    IExtendedProcessor имеет    новые    методы     Initialize О и Finalize (), но наследует метод Process (). Старая функциональность так и имт один метод, а новая функциональность свободна реализовать любой интерфейс.

Добавление новых интерфейсов и методов не означает, что все будет работать как сейчас. Если мы посмотрим на исходный код, то увидим, что интерфейс IProcessor используется в классе Bootstrap. Поэтому, если мы хотим, чтобы интерфейс IExtendedProcessor распознавался, нам нужно обновить класс Bootstrap. Обноение класса Bootstrap не вызывает никаких проблем, т. к. это не влечет за собой необходимости обновления реализаций интерфейса iProcessor (или, по крайней мере, класс Bootstrap не должен требовать обновления реализаций интерфейса IProcessor).

Первоначальная реализация класса Bootstrap в сокращенном виде выглядит так:

public static class Bootstrap { public static void DisplayHelp() {

Console.WriteLinef"Нужна помощь? Прямо сейчас?");

}

public static void Process(string[] args, IProcessor processor) { TextReader reader = null;

TextWriter writer = null; if (args.Length ==0 ) {

reader = Console.In; writer = Console.Out;

}

// Несущественный код опущен для краткости.

writer.Write(processor.Process(reader.ReadToEnd()));

#if DEBUG OUTPUT

Console.WriteLinef"Argument count(" + args.Length + ")"); foreach (string argument in args) {

Console.WriteLine("Argument (" + argument + ")");

}

#endif

}

}

В   первоначальной  реализации   класса  Bootstrap для   чтения   входного  потока и записи выходного потока вызывается метод ProcessO. Так как методы initialize о и Finalize о должны вызываться до и после обработки строки, стветственно, то наиболее логично было бы разместить их до и после метода processor. Process (), как показано в следующем коде:

public static class Bootstrap { public static void DisplayHelp() {

Consble.WriteLine("Нужна помощь? Прямо сейчас?");

}

public static void Process(string!] args, IProcessor processor) { TextReader reader = null;

TextWriter writer = null; if (args.Length == 0) {

reader = Console.In; writer = Console.Out;

}

// Несущественный код опущен для краткости.

if (processor is IExtendedProcessor) { writer.Write(((IExtendedProcessor)processor).Initialize());

}

writer.Write(processor.Process(reader.ReadToEnd()));

if (processor is IExtendedProcessor) { writer.Write(((IExtendedProcessor)processor).Finalize());

}

#if DEBUG_OUTPUT

Console.WriteLine("Argument count(" + args.Length + ")"); foreach (string argument in args) {

Console.WriteLine( "Argument (" + argument + ")");

}

#endif

}

}

В данном решении интерфейс процессора проверяет, не является ли он  экземяром интерфейса IExtendedProcessor. Если является, то вызываются методы Initialize () HFinalize().

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

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

public interface lExtendedProcessor { string Initialize();

string FinalizeO;

string Process(string input);

}

[Obsolete("IProcessor is obsolete, plus used lExtendedProcessor ", true)] public interface IProcessor {

string Process(string input);

}

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

Интерфейс lExtendedProcessor не обращается к интерфейсу IProcessor и содеит метод ProcessO. Таким образом, больше нет никаких зависимостей, и вся функциональность должна использовать интерфейс IProcessor.

ПРИМЕЧАНИЕ

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

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

Реализация решения подсчета частоты вхождения номеров

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

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

Дл я  КОНСОЛЬНОГО Приложени я ДЛЯ статистически х вычислени й (FrequencyProcessor) требуетс я реализаци я интерфейс а iExtendedProcessor. Дале е приводитс я полны й исходны й  ко д  это й  реализации :

using System.10; using ReaderWriter;

using LottoLibrary;

namespace FrequencyProcessor

{

class LottoTicketProcessor : IExtendedProcessor { List<Ticket> _tickets = new List<Ticket>();

public string Process(string input) { TextReader reader = new StringReader(input);

while (reader.Peek() != -1) {

string lineOfText = reader.ReadLine();

string!] splitUpText = lineOfText.Split(new char[]{”}); string[] dateSplit = splitUpText[0].Split(1.’);

Ticket ticket =

new Ticket(new DateTime(

int.Parse(dateSplit[0]), int.Parse(dateSplit[1]), int.Parse(dateSplit[2])),

new int[] { int.Parse(splitUpText[l]), int.Parse(splitUpText[2]), int.Parse(splitUpText[3]), int.Parse(splitUpText[4]), int.Parse(splitUpText[5]), int.Parse(splitUpText[б]), int.Parse(splitUpText[7])};

„.tickets. Add (ticket) ;

}

return "";

}

#region IExtendedProcessor Members public string Initialize() {

return "";

}

int FrequencyOfANumber(int numberToSearch) { var query = from ticket in _tickets

where 1st.Numbers[0] == numberToSearch

|| 1st.Numbers[1] == numberToSearch

|| 1st.Numbers[2] == numberToSearch

|| lst.Numbers[3] == numberToSearch

|| 1st.Numbers[4] == numberToSearch

|| 1st.Numbers[5] == numberToSearch select 1st.Numbers;

return query.Count();

}

public string Finalize() {

StringBuilder builder = new StringBuilder(); for (int cl = 1; cl < 46; cl++) {

builder.Appendf"Number (" + cl + ") Found ("); int foundCount – 0;

foundCount += FrequencyOfANumber(cl); builder.Append("" + foundCount + ")\n");

}

return builder.ToStringf);

}

#endregion

}

}

Теперь разберемся, как работает эта реализация интерфейса IExtendedProcessor.

Заимствование кода для решения другой проблемы

Позаимствованный  код является  реализацией  метода  Processf). В  сокращенном виде она выглядит так:

public string Process(string input) { TextReader reader = new StringReader(input);

while (reader.Peek() != -1) {

string lineOfText = reader.ReadLine();

string[] splitUpText = lineOfText.Split(new char[] {”}); string[] dateSplit = splitUpText[0].Split(‘.’);

Ticket ticket = new Ticket(new DateTime(

int.Parse(dateSplit[0]),

II… abbreviated new int[] {

int.Parse(splitUpText[1]), II… abbreviated int.Parse(splitUpText[6]), int.Parse(splitUpText[7])};

_tickets.Add(ticket);

}

return "";

}

За исключением строки newtickets. Add (ticket); этот код идентичен коду реализи и метод а Text2Binary. Process о . Выделенны й жирны м шрифто м  ко д  добавляе т новые номера в список номеров предыдущих розыгрышей. После создания экземяров билетов их можно добавить в список билетов, которые можно обрабатывать.

ПОВТОРНОЕ       ИСПОЛЬЗОВАНИЕ      КОДА

Позаимствованный код демонстрирует повторное использование кода методом копования и вставки. Имейте в виду, что код,  который  ранее  использовался для  обротки двоичных объектов,  теперь применяется  в совершенно  ином  контексте.

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

Когда мы работали над  кодом  для  типа  Ticket, то  вы,  наверное,  не  предполагали, что этот код будет использован для другого приложения. В действительности,  я  и сам не думал, что этот код пригодится для решения другой проблемы.  Но  использование кода одного решения в другом решении — совсем не редкость и случается во многих моих  проектах.

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

Посмотрите на код первого решения задачи подсчета частоты вхождения  номеров:

string[] splitUpText = 1ineOfText.Split(new char[] {”}); frequency[int.Parse(splitUpText[0])] ++; frequency[int.Parse(splitUpText[l])] ++;

Является ли этот код минимальным? Нет. Несмотря на то, что количество строк кода минимально, сам код таковым не является. Если бы я захотел использовать этот же код

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

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

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

Использование языка LINQ

Чтобы вычислить частоту вхождения определенного номера, совсем не обязательно использовать язык LINQ. Фактически, вместо LINQ всегда можно использовать код на языке С#. Так зачем же тогда использовать LINQ? Затем, что он облегчает напание запросов для сложных поисков, независимых от источника данных. Для прера рассмотрим два варианта кода для подсчета частоты вхождения числа: пеый не подлежит повторному использованию, а второй можно  повторно использовать. Первый вариант реализует запрос  без  использования  LINQ,  а  втой — с использованием.

Сначала рассмотрим первый вариант:

int FrequencyOfANumberNotReusable(int numberToSearch) { int runningTotal = 0;

foreach (Ticket ticket in _tickets) {

if (ticket.Numbers[0]  == numberToSearch ticket.Numbers[1] == numberToSearch || ticket.Numbers[2] == numberToSearch || ticket.Numbers[3] == numberToSearch || ticket.Numbers[4] == numberToSearch || ticket.Numbers[5] == numberToSearch) {

runningTotal++;

}

}

return runningTotal;

}

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

Теперь рассмотрим второй вариант кода, с использованием выражения LINQ:

int FrequencyOfANumber(int numberToSearch) { var query = from ticket in _tickets

where ticket.Numbers[0] == numberToSearch

|| ticket.Numbers[1] == numberToSearch

|| ticket.Numbers[2] == numberToSearch

|| ticket.Numbers[3] == numberToSearch

|| ticket.Numbers[4] == numberToSearch

|| ticket.Numbers[5] == numberToSearch

• select ticket.Numbers; return query.Count();

}

В   выражении   LINQ  указано   много   конструктивов,   похожих   на  SQL-оператор

SELECT. Далее приводятся основные правила работы с LINQ:

•    все запросы LINQ должны иметь источник данных (from);

•    все запросы LINQ должны иметь фильтр (where); но если фильтр отсутствует, то подразумевается автоматический всевключающий фильтр;

•    все запросы LINQ должны иметь создатель набора данных результата (select).

Для исполнения выражения LINQ необходимо  иметь источник данных. Таким иочником может быть список объектов, документ XML или даже таблица реляцнной базы данных. В примере источником данных является список объектов, корый указывается оператором from:

from ticket in _tickets

С виду оператора from можно подумать, что это оператор foreach, но без типов. В самом деле, так оно и есть. Оператор from указывает обрабатывать элементы иочника данных в цикле и присваивать каждый элемент (Ticket) переменной ticket. Но обратите внимание на отсутствие информации о типе, что является оой из сильных сторон LINQ — мы можем сегментировать и фрагментировать данные, как нам требуется.

При получении каждого элемента нам нужно удостовериться, соответствует ли элемент нашим потребностям. В коде, не подлежащем повторному использованию, эта проверка выполняется с помощью оператора if . А в коде с LINQ для этого применяется оператор where, что идентично его эквиваленту в SQL. Посредством оператора where мы проверяем, отвечает ли элемент нашим критериям. В данном случае каждый номер в экземпляре Ticket проверяется на соответствие текущему обрабатываемому номеру.

Если оператор where возвращает true, то мы имеем совпадение, и нужно будет волнять какую-либо обработку. В коде, не подлежащем  повторному  использовию, это означает увеличение значения переменной runningTotai. В коде с LINQ целью является фильтрация набора данных (_tickets в нашем случае), поэтому применяется оператор select, чтобы создать новый набор выигравших  номеров. Этот набор данных содержит все розыгрыши с номером, который обрабатывается в настоящее время (numberToSearch), и если подсчитать все розыгрыши с этим номом, то мы получим частоту выпадения данного номера, значение которой и воращается.

Далее  приводится  версия  кода  на  С#,  которая  не  подлежит  повторному  исполованию:

List<int[]> FrequencyOfANumberNotReusable(int numberToSearch) { List<int[]> retval = new Listcint[]>();

foreach (Ticket ticket in _tickets) {

if (ticket.Numbers[0] == numberToSearch || ticket.Numbers[l] == numberToSearch || ticket.Numbers[2] == numberToSearch || ticket.Numbers[3] == numberToSearch || ticket.Numbers[4] == numberToSearch || ticket.Numbers[5] == numberToSearch) {

retval.Add(ticket.Numbers);

}

}

return retval;

}

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

Посмотрим, как можно вычислить частоту двух номеров кодом с помощью LINQ:

int FrequencyOfTwoNumbers(int numberlToSearch, int number2ToSearch) { var query = from ticket2in

from ticket in _tickets

where ticket.Numbers[0] == nmriberlToSearch

|| ticket.Numbers[1] == nmriberlToSearch

|| ticket.Nuiribers[2] == nmriberlToSearch

|| ticket-Numbers[3] =- nmriberlToSearch

|| ticket.Numbers[4] == nmriberlToSearch

|| ticket.Numbers[5] == numberlToSeaxch select ticket

where ticket2.Numbers[0] == number2ToSearch

|| ticket2.Numbers[1] == number2ToSearch

|| ticket2.Numbers[2] == number2ToSearch

|| ticket2.Numbers[3] == number2ToSearch

|| ticket2.Numbers[4]  == number2ToSearch

|| ticket2.Numbers[5]  == number2ToSearch select ticket2.Numbers;

return query.Count();

}

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

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

ПРИМЕЧАНИЕ

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

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

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

По теме:

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