воскресенье, 8 марта 2015 г.

Полиморфизм?


Вот в таком классе мне недавно пришлось исправлять ошибку:

public abstract class UpdaterBase {
protected abstract IMyControl CreateMyControl();
protected abstract IMySettingsStore CreateMySettingsStore(IMyControl control);
public void Update(MyTarget target) {
if(target != null && !MySettingsHelper.HasSettings(target)) {
IMyControl control = CreateMyControl();
control.Fields["Priority"].Area = MyArea.ColumnArea;
control.Fields["Subject"].Area = MyArea.DataArea;
control.Fields["AssignedTo.DisplayName"].Area = MyArea.RowArea;
MySettingsHelper.SaveSettings(CreateMySettingsStore(control), target);
}
}
}

На первый взгляд этот код выглядит нормально: базовая логика для использования и перекрывания и все такое про ООП и полиморфизм. И даже наследники есть:

    public class MyUpdaterWin : UpdaterBase {
        protected override IMyControl CreateMyControl() {
            return new MyControlWin();
        }
        protected override IMySettingsStore CreateMySettingsStore(IMyControl control) {
            return new MySettingsStoreWin(((MyControlWin)control).WinGrid);
        }
    }

    public class MyUpdaterWeb : UpdaterBase {
        protected override IMyControl CreateMyControl() {
            return new MyControlWeb();
        }
        protected override IMySettingsStore CreateMySettingsStore(IMyControl control) {
            return new MySettingsStoreWeb(((MyControlWeb)control).WebGrid);
        }
    }

Тоже вроде нормально выглядит в лучших традициях полиморфизма, хотя вот эти преобразования "((MyControlWeb)control).WebGrid" выглядят не очень хорошо. И я решил посмотреть на реальное использование этих классов:

    new MyUpdaterWin().Update(myTarget);

и

    new MyUpdaterWeb().Update(myTarget);

Это уже совсем не похоже на необходимость применения полиморфизма.
Такая логика гораздо легче кодируется на прямом алгоритме без ветвлений в виртуальных и перекрытых методах:

    if(myTarget != null && !MySettingsHelper.HasSettings(myTarget )) {
        MyControlWin control = new MyControlWin();
        control.Fields["Priority"].Area = MyArea.ColumnArea;
        control.Fields["Subject"].Area = MyArea.DataArea;
        control.Fields["AssignedTo.DisplayName"].Area = MyArea.RowArea;
        MySettingsHelper.SaveSettings(new MySettingsStoreWin(control.WinGrid), target);
    }

В таком варианте кода гораздо проще понять "а что же тут собственно делается-то?" без прыжков "базовый класс->наследник->базовый класс->другой наследник->опять базовый класс->а что я вообще начал смотреть?"
Так как вызовов у меня было два:

    new MyUpdaterWeb().Update(myTarget);
    new MyUpdaterWin().Update(myTarget);

то под них я сделал метод:

    public static class UpdaterBase {
        public static void Update(MyTarget target, IMyControl control, ISettingsStore settingsStore) {
            control.Fields["Priority"].Area = MyArea.ColumnArea;
            control.Fields["Subject"].Area = MyArea.DataArea;
            control.Fields["AssignedTo.DisplayName"].Area = MyArea.RowArea;
            MySettingsHelper.SaveSettings(settingsStore, target);
}

и вызвал его с двумя наборами параметров:

    MyControlWin control = new MyControlWin();
    UpdaterBase.Update(myTarget, control, new MySettingsStore(control.WinGrid));

и

    MyControlWeb control = new MyControlWeb();
    UpdaterBase.Update(myTarget, control, new MySettingsStore(control.WebGrid));

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