Нужна помощь в подборе подходящего шаблона проектирования

  • Автор темы Ash81
  • Дата начала
Статус
Закрыто для дальнейших ответов.
A

Ash81

Господа, прошу прощения за довольно ламерский вопрос, однако своим умом додуматься у меня не получилось, а время в большом дефиците =(

Суть вот в чем. Есть некий клас ответственный за разбор(парсинг) входящей строки. Предполагается что формат строки время от времени меняется, мы этот процесс не контролируем, по этому нужен некий "запас прочности" на случай изменений в формате. Предполагается сделать это посредством набора масок. Например из строки требуется считать дату. Маски будут соответственно чем-то вроде "??/??/????", "??/??/??", "??-??-??". Так же для времени, и прочего, что нам может понадобиться. Если к "слову"(элементу строки) не подходит ни одна маска - отбрасываем.

Проблемы на первый взгляд никакой, пиши по методу для каждой маски, вот и всё. Но, для того чтобы быть уверенными, что из строки считана вся полезная нам информация, нужно применить к ней все маски по очереди. И так во всех классах, которые производят парсинг строк (речь вообще идет об анализе разнородных логов, формат которых сильно плавает);

Если потребуется добавить новую маску, придется проделать много ненужной работы...

Очевидно есть какое-то более красивое решение. Что-нибудь наподобие: объявить класс

public abstract AbstractMask{
...
string mask;
virtual DateTime Parse(string frase);
}

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

Буду очень благодарен за помощь!
 
K

Kuljok

Ну......., я бы наверно сделал так.

Сначала определил некий абстрактный парсер строки, который на вход будет получать фразу, а на выходе Object. Ну что-то вроде такого:

Код:
...
public interface IParser
{
object Parse(string frase);
bool IsAccept(string frase);
}

...
public class Parser
{
private IParser[] parsers = new IParser[1] {new FirstParser(), new SecondParser()};

// тут собсна понятно, просто бегаем по этому массиву и делаем IsAccept, если да, 
// то Parse и возвращаем результат
}
...

Обработать этот результат можно как-то так..

Код:
switch (obj.GetType().Name)
{
case "DateTime":
DoSomething(obj);
break;
...
default:
throw new AttributeException("Такого типа я не ожидал");
}

Хотя возможно так делать и не стоит, полагаться на имя возвращаемого класса может быть мало, можно просто каждый IParser еще и снабжать атрибутами и смотреть по ним.


При таком подходе, для того чтобы добавить еще какой-то формат, достаточно написать еще одну реализацию IParser и не забыть ее добавить в массив parsers класса Parser.

А можно внутри Parser сделать ArrayList и добавить метод RegisterParser(IParser parser). И там еще что-нить. Тогда этой цепочкой парсеров можно управлять динамически. ....

Такая вот мысля....
 
K

karlito

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

Для: Ash81
Если твой парсер является ядром системы и ты видишь, что на него необходимо потратить время, то это может быть и резонно наворачивать громоздкие решения. Либо тебе на работе просто нечем бользе занять. :(
Eсли у тебя простое задание, которое нужно быстро сделать, то не задумывайся. И делай как тебе кажется правильней и проще.

Обычно хорошо написанные решения неплохо адаптированы к изменениям, но они ресурсоёмки.

Будь проще.
 
A

Ash81

Ну......., я бы наверно сделал так.

А что, вроде неплохая идея. Наверное не самое элегантное решение, но рабочее. Уж точно гораздо лучше чем решать в лоб =)
Я пока еще не обмозговал предложенный Вами вариант как следует, но как только выдастся свободная минутка, обязательно его обдумаю. Спасибо Вам большое!


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

А работы у меня, к сожалению, хватает. И приходится еще находить время на диплом =) (задача оттуда) Писать же в "процедурном" стиле я умею, но это мне совсем не интересно. По этому, вот... решил немножко помучать сетевую общественность, чтобы сделать красиво. Не прагматизма ради, а для души =)
 
K

Kuljok

Да я собственно сам еще учусь и думал над этим тоже для души. Но честно думал. :lol: Поэтому если появится какое-то более интересное решение, то поделитесь плз. :)
 
E

Electro

ПРИВЕТ
Если я пр.понял иречь идет о синтаксическом разборе
входного предложения то лучше всего его разбирать
с помощью рекурсивной процедуры(функц.)
Способ наз. рекурсивный спуск,применяется во
всех компиляторах.
Смысл в том что функция вызывает саму себя до
тех пор пока не достигнет определенного условия,
а затем начнет возвращать результат.
 
K

Kuljok

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

PS: Да и смысл тут в том, чтобы просто ивзлечь информацию, а не сделать анализ или получить какой-то результат.
 
E

Electro

namespace TestWin
{
public class VeryComplex
{
char delimiter;
int dateOne;
int dateTwo;
int dateThree;
public VeryComplex()
{
this.delimiter = '/';
this.dateOne = 0;
this.dateTwo = 0;
this.dateThree = 0;
}
public char Delimiter
{
get
{
return this.delimiter;
}
set
{
this.delimiter = value;
}
}
public bool ScanStr(string inStr)
{
if (this.recFunk(inStr) > 0)
return true;
else
return false;
}
int recFunk(string str)
{
int oldLen = str.Length;
if (this.dateOne == 0)
{
try
{
dateOne = Convert.ToInt32(str.Substring(str.IndexOf(delimiter) - 4, 4));
}
catch
{
}
finally
{
try
{
dateOne = Convert.ToInt32(str.Substring(str.IndexOf(delimiter) - 2, 2));
}
catch
{
}
}
str = str.Substring(str.IndexOf(delimiter) + 1);
if (str.Length == oldLen)
return 0;
else
return recFunk(str);
}
else
{
if (this.dateTwo == 0)
{
try
{
dateTwo = Convert.ToInt32(str.Substring(str.IndexOf(delimiter) - 4, 4));
}
catch
{
}
finally
{
try
{
dateTwo = Convert.ToInt32(str.Substring(str.IndexOf(delimiter) - 2, 2));
}
catch
{
}
}
str = str.Substring(str.IndexOf(delimiter) + 1);
if (str.Length == oldLen)
return 0;
else
return recFunk(str);
}
else
{
if (this.dateThree == 0)
{
try
{
dateThree = Convert.ToInt32(str.Substring(0 , 4));
}
catch
{
}
finally
{
try
{
dateThree = Convert.ToInt32(str.Substring(0, 2));
}
catch
{
}
}
if (dateThree == 0)
return 0;
else
return 1;
}
}
}
return 0;
}
}
}
Может быть не оптимально,зато работает.
ивзлечь информацию-это тоже результат.
 
K

Kuljok

Хм... Может быть я не совсем верно понял вашу идею.....
Но что решает данный код в свете поставленной задачи?


Я вижу класс с одним единственным доступным методом ScanStr......, который возвращает boolean. Даже если предположить, что это для разбора даты, с некоторыми изменениями, то мне кажется так проще:

Код:
string[] formats = new string[2] {@"dd\/MM\/yyyy", @"dd\/MM\/yy"};
DateTime dateTime = DateTime.ParseExact("18/10/06", formats , null, DateTimeStyles.None);
 
E

Electro

доступно еще св-во: Delimiter
Если в ScanStr указать произв. строку
в котороя будет содержать в произв.
месте "??/??/????"или "??/??/??"или"??-??-??".
то int dateOne;int dateTwo;int dateThree; заполняются.
Меняй Delimiter и жди true ;
Только
public char Delimiter
{
get
{
return this.delimiter;
}
set
{
this.dateOne = 0; //обнулить (день)
this.dateTwo = 0; //обнулить (месяц)
this.dateThree = 0; //обнулить (год )
this.delimiter = value;
}
}
Тогда в программе
private void button1_Click(object sender, EventArgs e)
{
VeryComplex dataObj = new VeryComplex();
dataObj.Delimiter='-'; //по умолчанию '/'
bool res = dataObj.ScanStr(textBox1.Text);
if(res )
{
/*создавай дату из
int dateOne;
int dateTwo;
int dateThree;*/

}
else
{
// меняй Delimiter и опять ScanStr
}
}
 
E

Electro

Уважаемый Kuljok прочитайте внимательно вопрос от Ash81 и просмотрите
строки:
"Меня зовут Вася ,а родился я1990/01/30,а помру кода незнаю",
"А меня зовут Федя и здох я22-02-2006 болше ничего не помню"
Как вы считаете потянет это на "входящей строки" ?
Думаю что Ash81 и сам бы догадался напечатать DateTime и нажать F1,
что бы просмотреть все конструкторы и проинициализировать свою переменную.
 
A

Ash81

...
Думаю что Ash81 и сам бы догадался напечатать DateTime и нажать F1,
что бы просмотреть все конструкторы и проинициализировать свою переменную.

Все равно это реализация конкретного класса, заточенного под разбор конкретного шаблона. Применение delimeter кардинально суть дела не меняет. Дата ведь может прийти и в формате 26-авг-2006.

Но допустим я пойду по пути предложенном вами. Что мне делать если кроме дат придется парсить из строки денежные суммы, или номера кредитных карт? Получится что для каждого случая придется писать отдельный клас. При этом их функционал (с логической точки зрения) будет пересекаться. Не надо быть докой в объектном проектировании, чтобы понять - это не правильно. Надо думать конкретные реализации парсеров должны быть потомками какого-то абстрактного класса. Возможно было бы полезно вынести часть функций в управляющий класс, вроде того, который предложил использовать Kuljok...

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

Electro

А черт, я всетаки оказался прав, только не знаю радоваться или нет.
Оказывается тут еще и номера карточек,счета и т.д.
Уважаемый Ash81 когда вы пишите свой код используя и классы и функции и
атрибуты и т.д. и т.п. ведь компилятор обрабатывает их, получает результат.(каково ему?)
А ведь принцип работы компилятора такой же как у ф-ии VeryComplex::int recFunk(string str)
Я просто обенул эту ф-цию в класс хотя это не обязательно. Используй внутри ее
DateTime.ParseExact ... и не бойся 26-авг-2006. Добавить в этот класс члены
для кредитных карт,денежные суммы немного усовеш-й recFunk и все она возмет за один проход.
Только так можно получить "запас прочности" .
А плодить классы с прицелом на полиморфизм для "входящих строк" ну не знаю...
 
Статус
Закрыто для дальнейших ответов.
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!