Преобразование в производный тип

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

Я разбираю текстовые файлы, которые содержат несколько логов (по одному логу в строке). Формат примерно следующий:

Date Type Description
10/20 A LogTypeADescription
10/20 B LogTypeBDescription
10/20 C LogTypeCDescription

Здесь вы можете увидеть три «типа» журналов (A, B и C). В зависимости от того, какого типа журнал, я буду разбирать поле «Описание» по-разному.

Мой вопрос: как мне настроить структуру данных? Я хотел бы сделать что-то вроде этого:

class Log
{
  DateTime Date;
  String Type;
  String Description;

  public Log(String line)
  {
      Parse(line);
  }
}

class ALog : Log { }
class BLog : Log { }
class CLog : Log { }

Теперь каждый производный класс может иметь свои собственные уникальные свойства в зависимости от того, как анализируется поле «Описание», и они по-прежнему будут поддерживать три «основных» свойства (дата, тип и описание).

Пока все хорошо, за исключением того, что я не знаю, какой тип (производного) журнала мне нужен, пока не проанализирую строку из файла журнала. Конечно, я мог бы разобрать строку, а потом разобраться, но я действительно хочу, чтобы код разбора был в конструкторе "Журнал". Я хотел бы сделать что-то вроде этого:

void Parse(String line)
{
   String[] pieces = line.Split(' ');
   this.Date = DateTime.Parse(pieces[0]);
   this.Type = pieces[1];
   this.Description = pieces[2];

   if(this.Type == "A")
     this = new ALog();
   else if(this.Type == "B")
     this = new BLog();
   else if(this.Type == "C")
     this = new CLog();
}

Но, к сожалению, я не думаю, что это возможно. Я еще не пробовал, но я уверен, что делаю это:

Log l = new Log(line);
if(l.Type == "A") l = new ALog();

Будет либо незаконным, либо уничтожит весь синтаксический анализ, который я сделал, когда впервые создал «Журнал».

Какие-либо предложения?


person Eric    schedule 21.06.2011    source источник
comment
Возможно, вы можете сделать parse() статическим, как этот public static Log Parse(String line), и вернуть соответствующий экземпляр журнала на основе синтаксического анализа.   -  person Bala R    schedule 21.06.2011


Ответы (5)


Удалите конструктор и измените Parse на статический, возвращающий журнал.

static Log Parse(string line)
{
     string[] tokens  line.Split(' ');
     var log = null;
     if (tokens[1] == "A") log = new ALog();
     else if (tokens[1] == "B") log = new BLog();
     else log = new CLog();
     log.Date = tokens[0];
     log.Description = tokens[1];
     return log;
}
person Richard Schneider    schedule 21.06.2011

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

using System;

class Log
{
    DateTime Date;
    String Type;
    String Description;

    public Log(String line)
    {
        String[] pieces = line.Split(' ');
        this.Date = DateTime.Parse(pieces[0]);
        this.Type = pieces[1];
        LogParser parser = GetParser(this.Type);
        this.Description = parser.Parse(pieces[2]);
    }

    static LogParser GetParser(string type)
    {
        switch (type)
        {
            case "A":
                return new AParser();
            case "B":
                return new BParser();
            case "C":
                return new CParser();
            default:
                throw new NotSupportedException();
        }
    }
}

abstract class LogParser { public abstract string Parse(string line);}

class AParser : LogParser { public override string Parse(string line) { /* do parsing for A */ return string.Empty; } }
class BParser : LogParser { public override string Parse(string line) { /* do parsing for B */ return string.Empty; } }
class CParser : LogParser { public override string Parse(string line) { /* do parsing for C */ return string.Empty; } }
person joncham    schedule 21.06.2011

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

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

Конечно, при условии, что вы не хотите избегать размышлений.

person Brandon Moretz    schedule 21.06.2011

Еще одно решение. Положите свой ОО-молот и возьмите свой функциональный.

В C# есть словари и анонимные функции. Имейте словарь функций, которые знают, как взять Log и описание, и могут анализировать эту информацию и помещать ее в Log. Тогда вы просто parseDescription[logType](this, description).

Это означает, что вам нужен словарь с 3 функциями, а не с 3 новыми классами.

В этом примере разница не так велика. Но подумайте, есть ли в вашей записи журнала 2 разных поля, которые, возможно, потребуется проанализировать несколькими способами. Подход, основанный на классах, требует 9 новых классов, в то время как подход со словарем имеет 2 словаря с 3 функциями в каждом. Если бы их было 3, сравнение было бы 27 классов против 3 словарей с 3 функциями в каждом.

person btilly    schedule 21.06.2011
comment
Интересная концепция, хотя я не думаю, что мне удобно ее использовать. Тем не менее, определенно что-то, с чем я поиграю. Спасибо. - person Eric; 22.06.2011

Повторный взгляд на подходы joncham и btilly:

using System;

class Log
{
    DateTime Date;
    String Type;
    String Description;
    Dictionary<string,LogParser> logDictionary;

    static Log()
    {
        logDictionary = new Dictionary<string,LogParser>;

        logDictionary.Add("A",new AParser());
        logDictionary.Add("B",new BParser());
        logDictionary.Add("C",new CParser());
    }

    public Log(String line)
    {
        String[] pieces = line.Split(' ');    
        this.Date = DateTime.Parse(pieces[0]);    
        this.Type = pieces[1];
        LogParser parser = GetParser(this.Type);
        this.Description = parser.Parse(pieces[2]);
    }

    static LogParser GetParser(string type)
    {
        return logDictionary<string,LogParser>(type);
    }
}

abstract class LogParser { public abstract string Parse(string line);}

class AParser : LogParser { public override string Parse(string line) { /* do parsing for A */ return string.Empty; } }
class BParser : LogParser { public override string Parse(string line) { /* do parsing for B */ return string.Empty; } }
class CParser : LogParser { public override string Parse(string line) { /* do parsing for C */ return string.Empty; } }
person Peter K.    schedule 21.06.2011