Функциональность Diff/Merge для объектов (не файлов!)

У меня есть коллекция объектов одного типа, назовем ее DataItem. Пользователь может просматривать и редактировать эти элементы в редакторе. Также должна быть возможность сравнивать и объединять разные элементы, т. е. что-то вроде сравнения/слияния для DataItem экземпляров.

Функциональность DIFF должна сравнивать все (соответствующие) свойства/поля элементов и обнаруживать возможные различия. Затем функция MERGE должна иметь возможность объединять два экземпляра, применяя выбранные различия к одному из элементов.

Например (псевдообъекты):

DataItem1 {                  DataItem2 {
    Prop1 = 10                   Prop1 = 10
    Prop2 = 25                   Prop2 = 13
    Prop3 = 0                    Prop3 = 5
    Coll = { 7, 4, 8 }           Coll = { 7, 4, 8, 12 }
}                            }

Теперь пользователю должен быть предоставлен список различий (т. е. Prop2, Prop3 и Coll), и он должен иметь возможность выбрать, какие различия он хочет устранить, присвоив значение одного элемента другому. Он также должен иметь возможность выбирать, хочет ли он присвоить значение от DataItem1 до DataItem2 или наоборот.

Существуют ли общепринятые методы реализации этой функции?

Поскольку тот же редактор должен также предоставлять функции отмены/возврата (используя шаблон команды), я думал о повторном использовании реализаций ICommand, потому что оба сценария в основном обрабатывают назначения свойств, изменения коллекций и т. д. Моя идея состояла в том, чтобы создать Difference объектов с ICommand свойствами, которые можно использовать для выполнения операции слияния для этого конкретного Difference.

Кстати: языком программирования будет C# с .NET 3.5SP1/4.0. Однако я думаю, что это больше вопрос, независимый от языка. Приветствуются любые дизайнерские шаблоны/идеи/что угодно!


person gehho    schedule 26.03.2010    source источник


Ответы (1)


Это в значительной степени то, что я делаю. У меня есть класс "Diff" для объекта, который использует класс PropertyDiff для сравнения значений свойств с использованием Reflection. Это слишком много, чтобы вставить весь код в SO, но это должно дать вам представление. Коллекция неравных объектов PropertyDiff затем отображается для пользователя, который может выбрать, какие из них оставить, а какие отбросить. Мы используем NHibernate, поэтому объекты изменяются в памяти, а затем все изменения сохраняются в транзакции. У нас была более старая версия, в которой был создан набор команд SQL, и она тоже работала, но вы должны быть осторожны, чтобы команды выполнялись в том же порядке и в той же транзакции. Худшее, что может случиться, это если произойдет исключение и оба объекта будут FUBAR.

Класс PropertyDiff представляет собой сравнение одного и того же свойства двух объектов. Это работает только для простых свойств, и для обслуживания коллекций существует отдельный код.

public class PropertyDiff
{
    private bool _isEqual;

    public PropertyDiff(string propertyName, object xvalue, object yvalue)
    {
        PropertyName = propertyName;
        Xvalue = xvalue;
        Yvalue = yvalue;
        _isEqual = Xvalue == Yvalue;
    }

    public string PropertyName { get; private set; }
    public object Xvalue { get; private set; }
    public object Yvalue { get; private set; }
    public bool IsEqual
    {
        get { return _isEqual; }
    }

    internal static IList<PropertyDiff> GetPropertyDiffs(IEnumerable<string> properties, object x, object y)
    {
        if (x.GetType() != y.GetType())
        {
            throw new ArgumentException("Objects must be of same type");
        }

        var list = new List<PropertyDiff>();
        var t = x.GetType();

        foreach (string propertyName in properties)
        {
            PropertyInfo pi = t.GetProperty(propertyName);
            if (pi != null)
            {
                object xVal = pi.GetValue(x, null);
                object yVal = pi.GetValue(y, null);
                PropertyDiff propDiff = new PropertyDiff(propertyName, xVal, yVal);
                list.Add(propDiff);
            }
        }
        return list;
    }
}

И класс CompanyDiff:

    public class CompanyDiff
    {
        private List<string> _propertyNames;
        private IList<PropertyDiff> _propertyDiffs;

        public CompanyDiff(Company companyX, Company companyY)
        {
            this.CompanyX = companyX;
            this.CompanyY = companyY;

            // Build list of property names to be checked
            _propertyNames = new List<string>()
                             {
                                 "Type",
                                 "Name",
                                 "DBA"
                                 // etc.
                             };

            _propertyDiffs = PropertyDiff.GetPropertyDiffs(_propertyNames, this.CompanyX, this.CompanyY);
    }

    public Company CompanyX { get; private set; }
    public Company CompanyY { get; private set; }

    public IList<PropertyDiff> PropertyDiffs
    {
        get { return _propertyDiffs; }
    }
}
person Jamie Ide    schedule 26.03.2010
comment
Ого, я не ожидал полного исходного кода. :) Спасибо! Я также думал об использовании отражения. Однако я планировал не использовать его из-за проблем с производительностью. Вместо этого я хотел реализовать интерфейс IDiffable в каждом классе с помощью метода Compare(...). Какой опыт вы получили, используя свой подход, основанный на размышлениях (с точки зрения производительности)? Мне нужно будет сравнить наборы примерно из 2000 объектов с примерно 50 свойствами в каждом (не в одном классе, а рекурсивно). - person gehho; 26.03.2010
comment
Я не измерял производительность, потому что она была в порядке. Для нас это своего рода исключительный процесс (введена повторяющаяся компания и т. д.), поэтому производительность не является основной целью. Мой совет — использовать самый простой подход с использованием отражения и рефакторинга, если есть реальная проблема с производительностью, т. е. не оптимизируйте преждевременно. - person Jamie Ide; 26.03.2010
comment
Спасибо. Возможно, я также сначала попробую подход, основанный на отражении. Затем я посмотрю, соответствует ли он моим требованиям к производительности. - person gehho; 29.03.2010