Розширення системи передбачення результатів лотереї в Visual C # (Sharp)

У чолі 10 застосування інтерфейсу IProcessor було хорошим першим кроком, т к дозволяло вирішити нагальну проблему перетворення текстового рядка з одного формату в іншій Але для прикладу даної глави цього інтерфейсу недостатньо, І нам потрібно Додати ще Два методи: Initialize () І Finalize ()

Ми хочемо обчислити частоту випадання кожного числа в лотерейних розіграшах Для обробки рядка тексту застосовується метод IProcessorProcess (), а частоту випадання можна підрахувати після обробки даних для всіх розіграшів Стветственно, ми додаємо метод 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() {

ConsoleWriteLinef Потрібна допомога Прямо зараз )

}

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

TextWriter writer = null if (argsLength ==0 ) {

reader = ConsoleIn writer = ConsoleOut

}

/ / Несуттєвий код опущений для стислості

writerWrite(processorProcess(readerReadToEnd()))

#if DEBUG OUTPUT

ConsoleWriteLinef&quotArgument count(&quot + argsLength + &quot)&quot) foreach (string argument in args) {

ConsoleWriteLine(&quotArgument (&quot + argument + &quot)&quot)

}

#endif

}

}

У початковій реалізації класу Bootstrap для читання вхідного потоку і записи вихідного потоку викликається метод ProcessO Так як методи initialize про і Finalize про повинні викликатися до і після обробки рядка, стветственно, то найбільш логічно було б розмістити їх до і після методу processor Process (), як показано в наступному коді:

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

ConsbleWriteLine (Потрібна допомога Прямо зараз”)

}

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

TextWriter writer = null if (argsLength == 0) {

reader = ConsoleIn writer = ConsoleOut

}

/ / Несуттєвий код опущений для стислості

if (processor is IExtendedProcessor) { writerWrite(((IExtendedProcessor)processor)Initialize())

}

writerWrite(processorProcess(readerReadToEnd()))

if (processor is IExtendedProcessor) { writerWrite(((IExtendedProcessor)processor)Finalize())

}

#if DEBUG_OUTPUT

ConsoleWriteLine(&quotArgument count(&quot + argsLength + &quot)&quot) foreach (string argument in args) {

ConsoleWriteLine( &quotArgument (&quot + argument + &quot)&quot)

}

#endif

}

}

У даному рішенні інтерфейс процесора перевіряє, чи не є він екземяром інтерфейсу IExtendedProcessor Якщо є, то викликаються методи Initialize () HFinalize ()

При розширенні інтерфейсів і розширенні класів, які використовують старі і ниє інтерфейси одночасно, має місце збереження зворотної сумісності Зворотна сумісність є важливою концепцією, т к вона має на увазі можливість поступового додавання нової функціональності в працює додаток без порушення при цьому старої функціональності

Збереження зворотної сумісності є нелегким завданням, і існує момент, коли додавання нової функціональності стає занадто складним Досягнувши цієї стадії, необхідно порушити зворотну сумісність слідуючи чином:

public interface lExtendedProcessor { string Initialize()

string FinalizeO

string Process(string input)

}

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

string Process(string input)

}

У прикладі зворотна сумісність порушується з тієї причини, що атрибут obsolete асоціюється з інтерфейсом IProcessor Тому звернення будь-якого класу або інтерфейсу до інтерфейсу IProcessor викликає помилку компілятора Помилка викликається другим параметром атрибута obsolete, яким є лю true Якщо при зверненні до інтерфейсу опустити цей параметр, то визивтся попередження, а не помилка

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

ПРИМІТКА

Порушення зворотної сумісності є важливим кроком, т к код починає жоваться, що його працездатність була десь порушена, і необхідний про модіфіровать відповідну ділянку коду Залежно від характеру порушення, Необхідні е модифікації можуть бути істотними або незначними Але інда порушення коду не можна уникнути, і в такому випадку необхідно забезпечити, щоб скарг и коду про внесені порушеннях був і дуже гучними У противно м випадку можливе виникнення несподіваних помилок

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

Ми хочемо, щоб наше рішення прочитував текстові дані, перетворював їх в двійкові обєкти, виконувало певні статистичні обчислення і водило результати в текстовому форматі Що цікаво в цьому рішенні, так це те, що ми вже створили деякі його частини Згадайте, що в чолі 10нам потрібно

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

Дл я консольні додатки я ДЛЯ статистично х обчислень й (FrequencyProcessor) Требуетса я реализаци я інтерфейс а iExtendedProcessor Дале е пріводітс я повні й вихідними й ко д це й реалізації:

using System10 using ReaderWriter

using LottoLibrary

namespace FrequencyProcessor

{

class LottoTicketProcessor : IExtendedProcessor { List&ltTicket&gt _tickets = new List&ltTicket&gt()

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

while (readerPeek() = -1) {

string lineOfText = readerReadLine()

string] splitUpText = lineOfTextSplit(new char[]{‘}) string[] dateSplit = splitUpText[0]Split(1’)

Ticket ticket =

new Ticket(new DateTime(

intParse(dateSplit[0]), intParse(dateSplit[1]), intParse(dateSplit[2])),

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

„tickets Add (ticket)

}

return &quot"

}

#region IExtendedProcessor Members public string Initialize() {

return &quot"

}

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

where 1stNumbers[0] == numberToSearch

|| 1stNumbers[1] == numberToSearch

|| 1stNumbers[2] == numberToSearch

|| lstNumbers[3] == numberToSearch

|| 1stNumbers[4] == numberToSearch

|| 1stNumbers[5] == numberToSearch select 1stNumbers

return queryCount()

}

public string Finalize() {

StringBuilder builder = new StringBuilder() for (int cl = 1 cl &lt 46 cl++) {

builderAppendf&quotNumber (&quot + cl + &quot) Found (&quot) int foundCount – 0

foundCount += FrequencyOfANumber(cl) builderAppend(&quot&quot + foundCount + &quot)\n&quot)

}

return builderToStringf)

}

#endregion

}

}

Тепер розберемося, як працює ця реалізація інтерфейсу IExtendedProcessor

Запозичення коду для вирішення іншої проблеми

Запозичений код є реалізацією методу Processf) У скороченому вигляді вона виглядає так:

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

while (readerPeek() = -1) {

string lineOfText = readerReadLine()

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

Ticket ticket = new Ticket(new DateTime(

intParse(dateSplit[0]),

II.. abbreviated new int[] {

intParse(splitUpText[1]), II.. abbreviated intParse(splitUpText[6]), intParse(splitUpText[7])}

_ticketsAdd(ticket)

}

return &quot"

}

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

ПОВТОРНЕ ВИКОРИСТАННЯ КОДА

Запозичений код демонструє повторне використання коду методом копованія і вставки Майте на увазі, що код, який раніше використовувався для оброткі довічних обєктів, тепер застосовується в зовсім іншому контексті

Скопійований і вставлений код використовує класи і функціональності з контетом іншого завдання Ми скопіювали і вставили функціональність для преобразова і створення екземпляра типу Ticket, але використовували сам тип Ticket

Коли ми працювали над кодом для типу Ticket, то ви, напевно, не припускали, що цей код буде використаний для іншої програми Насправді, я і сам не думав, що цей код стане в нагоді для вирішення іншої проблеми Але використання коду одного рішення в іншому рішенні – зовсім не рідкість і трапляється в багатьох моїх проектах

Але як же це працює Я пишу код, щоб він задовольняв двома критеріями: був мімальним для вирішення даної проблеми і досить загальним, щоб не препятсовать повторного застосування Таким чином, я не розробляється код спеціально з метою його використання в інших рішеннях і не думаю про конкретні додатках, в яких його ще можна буде застосувати Але я думаю про те, щоб розробити код таким чином, щоб він, можливо, міг бути повторно використаний

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

string[] splitUpText = 1ineOfTextSplit(new char[] {‘}) frequency[intParse(splitUpText[0])] ++ frequency[intParse(splitUpText[l])] ++

Чи є цей код мінімальним Ні Незважаючи на те, що кількість рядків коду мінімально, сам код таким не є Якби я захотів використовувати цей же код

для вирішення іншої проблеми частоти входження, що цілком можливо, мені потрібно було б знову копіювати і вставляти тому код не є мінімальним

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

Це відмінний приклад коду, який можна написати дуже швидко і ефективно іользовать, копіюючи і вставляючи всюди Він дозволяє програмісту бути продуівним і вирішити проблему дуже швидко, але він не піддається легкому розширенню або супроводу Уявіть, що після вставки цього коду в десять різних місць в ньому виявляється помилка Це означає, що потрібно знайти всі ці десять місць і перевірити, чи викликається там ця помилка

Використання мови LINQ

Щоб обчислити частоту входження певного номера, зовсім не обовязково використовувати мову LINQ Фактично, замість LINQ завжди можна використовувати код на мові С # Так навіщо ж тоді використовувати LINQ Потім, що він полегшує Напані запитів для складних пошуків, незалежних від джерела даних Для прерії розглянемо два варіанти коду для підрахунку частоти входження числа: пеий не підлягає повторному використанню, а другий можна повторно використовувати Перший варіант реалізує запит без використання LINQ, а втой – з використанням

Спочатку розглянемо перший варіант:

int FrequencyOfANumberNotReusable(int numberToSearch) { int runningTotal = 0

foreach (Ticket ticket in _tickets) {

if (ticketNumbers[0]  == numberToSearch ticketNumbers[1] == numberToSearch || ticketNumbers[2] == numberToSearch || ticketNumbers[3] == numberToSearch || ticketNumbers[4] == numberToSearch || ticketNumbers[5] == numberToSearch) {

runningTotal++

}

}

return runningTotal

}

Зверніть увагу на схожість цього коду з кодом, розглянутим у чолі 9 Прлема з цим кодом полягає в тому, що ми виконуємо обробку в циклі і раєм конкретну проблему Даний код не можна з легкістю пристосувати для вирішення іншої проблеми

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

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

where ticketNumbers[0] == numberToSearch

|| ticketNumbers[1] == numberToSearch

|| ticketNumbers[2] == numberToSearch

|| ticketNumbers[3] == numberToSearch

|| ticketNumbers[4] == numberToSearch

|| ticketNumbers[5] == numberToSearch

• select ticketNumbers return queryCount()

}

У виразі 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&ltint[]&gt FrequencyOfANumberNotReusable(int numberToSearch) { List&ltint[]&gt retval = new Listcint[]&gt()

foreach (Ticket ticket in _tickets) {

if (ticketNumbers[0] == numberToSearch || ticketNumbers[l] == numberToSearch || ticketNumbers[2] == numberToSearch || ticketNumbers[3] == numberToSearch || ticketNumbers[4] == numberToSearch || ticketNumbers[5] == numberToSearch) {

retvalAdd(ticketNumbers)

}

}

return retval

}

Як було сказано раніше, все, що можна робити на LINQ, можна робити і на С # Але код на С # не буде ні повторно використовуваним, ні мінімальним, в той час як код на LINQбудет таким Так як дані були відфільтровані, то немає нікіх причин, чому отриманий набір даних не можна використовувати для далейшей фільтрації, наприклад, для обчислення частоти двох номерів розіграшу У первинному коді, обробному елементи в циклі і збільшує пеменную runningTotal, для обчислення нової частоти потрібно було б прібеуть до методу копіювання і вставки коду

Подивимося, як можна обчислити частоту двох номерів кодом за допомогою LINQ:

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

from ticket in _tickets

where ticketNumbers[0] == nmriberlToSearch

|| ticketNumbers[1] == nmriberlToSearch

|| ticketNuiribers[2] == nmriberlToSearch

|| ticket-Numbers[3] =- nmriberlToSearch

|| ticketNumbers[4] == nmriberlToSearch

|| ticketNumbers[5] == numberlToSeaxch select ticket

where ticket2Numbers[0] == number2ToSearch

|| ticket2Numbers[1] == number2ToSearch

|| ticket2Numbers[2] == number2ToSearch

|| ticket2Numbers[3] == number2ToSearch

|| ticket2Numbers[4]  == number2ToSearch

|| ticket2Numbers[5]  == number2ToSearch select ticket2Numbers

return queryCount()

}

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

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

ПРИМІТКА

Міць LINQ полягає в його можливості секціонувати і фрагментувати дані у пошуках необхідної інформації Для коду LINQ потрібно більше ресурсів, ніж для еквівалентного коду на С # Але LINQ дає перевагу у вигляді повторно ісполуемого і легко супроводжуваного коду

У попередньому розділі ми використовували LINQ для вирішення проблеми обчислення частоти випадання лотерейних номерів таким чином, який сприяє пторному використанню коду Наприклад, якщо б ми хотіли виконати інші статистичні обчислення за даними номерів лотерейних квитків, то могли б фокусуватися на статистичній складовою вирішення, а не на Інфраструкту, для підтримки цих обчислень Все, що нам потрібно було б зробити в цьому випадку, – це написати додаткові оператори LINQ для розбиття вимагаючи чином існуючого списку номерів квитків Для цього треба було б додати тільки один виклик методу lExtendedProcessorFinalizeO Але дайте будемо вважати проблему вирішеною і подивимося, що ще можна робити з пощью LINQ

Джерело: Гросс К С # 2008: Пер з англ – СПб: БХВ-Петербург, 2009 – 576 е: ил – (Самовчитель)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*