С классами теперь все просто.
Receipt – класс рецептуры. Атрибуты над полями нужны для загрузки данных из БД, про это как-нибудь в другой раз.
public class Receipt : Entity
{
/// <summary>
/// Дата создания
/// </summary>
[FieldName("CreatedDate")]
public DateTime CreatedDate { get; set; }
/// <summary>
/// Название
/// </summary>
[FieldName("Title")]
public string Title { get; set; }
/// <summary>
/// Описание
/// </summary>
[FieldName("Description")]
public string Description { get; set; }
/// <summary>
/// XML описание команд
/// </summary>
[FieldName("Commands")]
public string Commands { get; set; }
}
Для команд я ввел перечисление, хранящее типы команд:
public enum ReceiptCommandType
{
[ViewableEnumMember("Загрузка компонента")]
Loading = 1,
}
Теперь надо разобраться с классом команды. Очевидно, что нужно сделать базовый класс для команд:
public abstract class BaseReceiptCommand
{
private ReceiptCommandType commandType;
private string commandTypeName;
public BaseReceiptCommand(ReceiptCommandType commandType, XElement element)
{
Init(commandType);
if (element != null)
LoadFromXML(element);
}
public BaseReceiptCommand(ReceiptCommandType commandType) :
this(commandType, null)
{
}
public abstract void LoadFromXML(XElement element);
private void Init(ReceiptCommandType commandType)
{
this.commandType = commandType;
this.commandTypeName = EnumHelper<ReceiptCommandType>.GetName(commandType);
}
public ReceiptCommandType CommandType
{
get
{
return commandType;
}
}
public string CommandTypeName
{
get
{
return commandTypeName;
}
}
public virtual XElement ToXml()
{
return new XElement("command", new XAttribute("type", CommandType));
}
public static ReceiptCommandType GetTypeFromXML(XElement element)
{
return (ReceiptCommandType)Enum.Parse(typeof(ReceiptCommandType), element.Attribute("type").Value);
}
public override string ToString()
{
return "Команда";
}
public abstract string ToFullString();
}
По идее, тип команды определяется самим классом команды и нужды в отдельном поле нет, но мне не хотелось завязывать имена классов на типы команд именно таким образом, поэтому я добавил поле типа в сам класс команды. Хотя, конечно, связать их нужно и я сделал это с помощью фабрики:
public static class ReceiptCommandFactory
{
public static BaseReceiptCommand GetReceiptCommand(ReceiptCommandType commandType)
{
return GetReceiptCommand(commandType, null);
}
public static BaseReceiptCommand GetReceiptCommand(ReceiptCommandType commandType, XElement element)
{
switch (commandType)
{
case ReceiptCommandType.Loading:
return new LoadReceiptCommand(element);
default:
throw new ApplicationException("ReceiptCommandFactory::GetReceiptCommand");
}
}
}
Конструкторов у класса команды получилось два – один создает пустую (новую) команду, второй – загружает ее из XML. Обратите внимание на абстрактный метод LoadFromXML. Он заставляет все классы, наследующие от базового, реализовывать собственный метод загрузки параметров команды (об этом я потом расскажу отдельно). Метод абстрактный, т.к. каждая команда сама решает, как ей загрузить себя из XML.
Кроме поля типа команды мне потребовалось поле имени (названия) этой команды. Это легко было сделать, получив атрибут соответствующей команды. Поменять тип команды на лету нельзя, поэтому вполне достаточно прочитать ее имя в конструкторе.
Ну и еще, при чтении XML мне потребовалось заранее знать тип команды, поэтому нужен был отдельный метод для чтения типа.
Вот как выглядит чтение и сохранение команд в XML:
public static class ReceiptXMLHelper
{
/// <summary>
/// Сохранение списка команд в XML
/// </summary>
public static XDocument ReceiptCommandListToXml(IList commandList)
{
XDocument document = new XDocument();
XElement root = new XElement("root");
document.Add(root);
foreach (BaseReceiptCommand command in commandList)
{
root.Add(command.ToXml());
}
return document;
}
/// <summary>
/// Получение списка команд
/// </summary>
public static List<BaseReceiptCommand> GetReceiptCommandList(Receipt receipt)
{
List<BaseReceiptCommand> result = new List<BaseReceiptCommand>();
if (receipt != null)
{
if (!string.IsNullOrEmpty(receipt.Commands))
{
XDocument document = XDocument.Parse(receipt.Commands);
return GetReceiptCommandListFromXml(document);
}
}
return result;
}
/// <summary>
/// Восстановление списка команд из XML
/// </summary>
public static List<BaseReceiptCommand> GetReceiptCommandListFromXml(XDocument document)
{
List<BaseReceiptCommand> result = new List<BaseReceiptCommand>();
foreach (var command in document.Root.Elements())
{
ReceiptCommandType type = BaseReceiptCommand.GetTypeFromXML(command);
result.Add(ReceiptCommandFactory.GetReceiptCommand(type, command));
}
return result;
}
}
Ничего сложного тут нет. При сохранении в XML мы бежим по списку команд и каждой команде “говорим” сохранить себя в XML. При чтении – обратная операция, только сначала нам нужно определиться с типом создаваемой команды, запросить этот тип у фабрики и затем передать команде XML, чтобы она прочитала из него свои параметры.
А вот как выглядит команда "загрузка компонента":
public class LoadReceiptCommand : BaseReceiptCommand
{
static CultureInfo cultureEnUS = new CultureInfo("en-US");
public LoadReceiptCommand() : base (ReceiptCommandType.Loading)
{
}
public LoadReceiptCommand(XElement element)
: base(ReceiptCommandType.Loading, element)
{
}
public Int64 ComponentId
{
get
{
if (componentRecord != null)
return componentRecord.Id;
else
return -1;
}
set
{
if ((componentRecord == null) || (componentRecord.Id != value))
{
componentRecord = MainBLL.Instance.GetComponent(value);
}
}
}
public double Weight
{
get; set;
}
private Entities.Component componentRecord = null;
public string ComponentName
{
get
{
if (componentRecord == null)
return "(не задан)";
return componentRecord.ComponentName;
}
}
public override void LoadFromXML(XElement element)
{
ComponentId = -1;
XAttribute componentAttr = element.Attribute("componentId");
if (componentAttr != null)
{
ComponentId = Int64.Parse(componentAttr.Value);
}
XAttribute weightAttr = element.Attribute("weight");
if (weightAttr != null)
{
Weight = double.Parse(weightAttr.Value, cultureEnUS);
}
}
public override XElement ToXml()
{
XElement result = base.ToXml();
result.Add(new XAttribute("componentId", ComponentId));
result.Add(new XAttribute("weight", Weight));
return result;
}
public override string ToString()
{
return "Загрузка компонента " + ComponentName;
}
public override string ToFullString()
{
return string.Format("Загрузка [{0}]: {1} кг", ComponentName, Weight);
}
}
Команда загрузки компонента имеет два дополнительных поля: ComponentId (Id загружаемого компонента), Weight (сколько нужно загружать). Причем, первое поле на самом деле загружает собственно запись компонента, полученную из таблицы компонентов. При отображении нам потребуется не Id, а поле ComponentName, возвращающее название компонента.
Сохранение и чтение из XML просто записывает и читает все нужные поля из XML.
В следующей части я расскажу, как просто и быстро сделать редактирование списка команд.
No comments:
Post a Comment