Моя история вынужденного использования паттернов
Я считаю, что без ООП и паттернов жизни нет. Тем не менее до сих пор можно встретить множество холиваров различной величины о том насколько вредно или полезно ООП.
В этой небольшом топике я хочу бросить камень в огород противников ООП. Я понимаю, что противником ООП можно быть только в случае если подобная парадигма непонятна, а особенно непонятны причины её использования.
Все резоны в пользу ООП выглядят для начинающего программиста достаточно натянуто, а пробраться сквозь лес интерфейсов и абстрактных классов бывает непросто. Хуже всего то, что действительно объясняющих книг немного. И самая плохая книга которая нереально высоко задирает порог вхождения это самая полезная книга Банды Четырёх о паттернах. Она написана скучным, тоскливым языком с вычурными аккадемическими примерами.
Люди которые ратуют за красивые архитектуры, но при этом объясняют их необходимость на примерах с логерами, авторизациями, кэшем и щедро дают названия классам в виде Foo и Boo делают очень вредное дело. Хотя я и понимаю, что эти люди являются обычно локомативом всего доброго и светлого в программировании. Это очень обидный парадокс.
Хорошо что есть книги такие как Фримен Э. (2 штуки) Паттерны Проектирования. Она послужила и служит истиным озарением для меня при изучении и самое главное практическом использовании паттернов. Я читал книги и посложнее, но не читал полезнее.
Итак, моя история вынужденного использования паттернов.
Изначально, Заказчик сформулировал одну из подзадач примерно так «Мы отправляем документы, написанные на языке клиента, в разные страны. В этих странах свои языки на которых документы могут быть приняты. Необходимо учитывать стоимость перевода и то какой из переводчиков что переводит»
Нет вопросов подумал я и написал первый раз модуль «Переводы» не мудрствуя лукаво и без всяких паттернов
Прошло некоторое время и Заказчик принёс из реального мира следующую новость «Неплохо было бы учитывать тот факт, что брать деньги за один и тот же перевод, который направляется в разные страны — это нехорошо и сильно бросается в глаза»
Я подумал, что это логично и сделал первую прямолинейную оптимизацию модуля «Переводы» по дублированию.
Вскоре Заказчик обнаружил расхождения нашего решения и реальной ситуации. Оказывается в реальном мире переводчик может переводит не напрямую с языка клиента, а перевести вначале на наиболее распространённый язык, например, английский, а потом уже добить всякие экзотические варианты. Это сильно уменьшает общую цену заказа.
Я логично согласился и написал, как мне казалось, максимально сложный алгоритм двойного перевода с оптимизацией по цене.
Если вы думаете, что на этом реальный мир оставил нас в покое, то вы наверное совсем недавно в програмирование.
С небольшим перерывом добавились следующие реальные факты: Документы состоят из частей и некоторые части почти полностью повторяют другие части; У клиента могут быть документы на двух и более языках, один из которых надо «вычитать»; Существуют особые условия в некоторых случаях, например, в некоторых странах документы должны быть переведены на все доступные языки, а в некоторых достаточно одного; Клиент может подождать одного перевода от более дешёвого переводчика и отдать готовый перевод вместе со своими языками... и это ещё не всё, просто я немного устал перечислять.
Если бы вы увидели мой модуль «Переводы» к этому моменту, то основной аргумент противников ООП «работает же без всякого ООП» сильно бы пошатнулся. Алгоритм стал настолько запутанным, что я сам уже с трудом разбирался в нём. И не помогали даже десятки листиков с блок-схемами.
И если вы начинающий программист, как и я, то просто поверьте мне, что мой код был к этому моменту очень похож на рукотворный Ад, хотя и работал.
И в этот момент произошло чудо озарения.
Я долго был не уверен, что инкапсуляция алгоритма допустима при написании алгоритма. Хотя это же очевидно, даже слова похожие, но я не мог поверить, что отдав объектам на откуп их ответственность можно заменить строгую и однозначную логику моих бесконечных foreach и if else. Но я решился и теперь у меня над головой нимб с background image как заставка в Матрице.
И кстати, я давным давно думал, что делиться своими примерами с кем то бесплатно это дикость, а открытый код в конце концов погубит индустрию — это тоже оказалось неправдой. Делиться это здорово. Вот я и делюсь, несколько упростив код.
Для решения моей самой общей задачи я использовал патерны Стратегия и Наблюдатель (у семьи Фриманов они первые в книге, но это совпадение)
Для начала реализуем интерфейс и инкапсулируем стратегии для различных условий перевода в разных странах
public interface ITranslateBehavior
{
bool IsTranslate(StepTranslate stepTranslate, List<languagecomplite> languageList);
}
Вот так выглядит реализаця базового требования «Один из доступных языков
public class BaseTransalte : ITranslateBehavior
{
public bool IsTranslate(StepTranslate stepTranslate, List<languagecomplite> languageList)
{
foreach (LanguageComplite curr in languageList)
{
if (curr.IdLanguage == stepTranslate.IdToLanguage)
{
curr.Iscomplite = true;
curr.Step = stepTranslate;
stepTranslate.Rating++;
return true;
}
}
return false;
}
}
а вот так вариант «Все из доступных»
public class SpecificTransalteRequirement : ITranslateBehavior
{
public bool IsTranslate(StepTranslate stepTranslate, List<languagecomplite> languageList)
{
foreach (LanguageComplite curr in languageList)
{
if (curr.IdLanguage == stepTranslate.IdToLanguage)
{
curr.Iscomplite = true;
curr.Step = stepTranslate;
stepTranslate.Rating++;
}
}
return (!languageList.Where(x => x.Iscomplite == false).Any());
}
}
Вариантов выполнения перевода в зависимости от условий может быть сколько угодно. И теперь для их реализации не потребуется переписывать весь алгоритм. Алгоритма вообще почти не осталось. Нужно будет просто написать новый небольшой класс с новыми требованиями.
StepTranslate — это определённый этап перевода, а List<languagecomplite> — это набор языков с результатами применения этого шага перевода.
public class StepTranslate
{
public int IdFromLanguage { get; set; }
public int IdToLanguage { get; set; }
public decimal Price { get; set; }
public int Step { get; set; }
public bool BeUsed { get; set; }
public int Rating { get; set; }
}
public class LanguageComplite
{
public int IdLanguage { get; set; }
public bool Iscomplite { get; set; }
public StepTranslate Step { get; set; }
}
Теперь со стратегией перевода можно определятся просто добавляя различные новые классы самых неожиданных желаний реального мира.
Для оповещения частей документа о том что наш переводчик перевёл какую то пару языков будем использовать паттерн Наблюдатель.
Определяем интерфейс для переводчика
public interface ITranslator
{
void RegisterParagraph(Paragraf p);
void RemoveParagraf(Paragraf p);
void TranslationParagrafs();
}
Paragraf — Это наша часть документа, которая реализуется вот так
public class Paragraf
{
public int IdCountry { get; set; }
public int IdTypepart { get; set; }
public ITranslateBehavior translateBehavior;
public List<languagecomplite> Languages { get; set; }
public bool transaltedValue = false;
public Translator translator;
public Paragraf(Translator translator)
{
this.translator = translator;
//Это подписка на переводы
this.translator.RegisterParagraph(this);
}
public bool PerformIsTranslate(StepTranslate stepTransalte)
{
//Это реализация паттерна Стратегия
return translateBehavior.IsTranslate(stepTransalte, Languages);
}
}
В моём виртуальном реальном мире Paragraf объявлен как abstract для того чтобы если в мир переводов каким нибудь образом проникнут другие зависимости поведения я мог бы на них отреагировать. Здесь для простоты я реализую его непосредственно.
И собственно сам переводчик
public class Translator : ITranslator
{
private List<paragraf> listParagraf;
private List<steptranslate> stepTranslate;
public Translator(List<steptranslate> stepTranslate)
{
listParagraf = new List<paragraf>();
this.stepTranslate = stepTranslate;
}
public void RegisterParagraph(Paragraf p)
{
listParagraf.Add(p);
}
public void RemoveParagraf(Paragraf p)
{
listParagraf.Remove(p);
}
public void TranslationParagrafs()
{
foreach (StepTranslate currentStep in this.stepTranslate.OrderBy(x => x.Step).ThenBy(y => y.Price))
{
foreach (Paragraf currentParagraf in this.listParagraf.Where(x => !x.transaltedValue))
{
currentParagraf.transaltedValue = currentParagraf.PerformIsTranslate(currentStep);
}
}
}
}
Картинки никак чтоли?
Я несколько упростил код для демонстрации. Убрал оптимизацию по цене и абстракции, и в реальном приложении всё немного сложнее, но в целом достигнутый результат меня радует — код стал понятен любому постороннему человеку, а самое главное он стал понятен мне, и он стал примерно в 50 раз короче.
Возможно прямой алгоритм, без объектов, работал бы быстрее, но возможность потенциальной ошибки в нём, которую практически стало невозможно найти, сводила на нет всё небольшое преимущество в скорости.
Резюме — есть ситуации когда для написания корректно работающих и легко поддерживаемых программ ООП является единственным выходом. И это почти все ситуации в реальном программировании приложений для реального мира.
Спасибо за внмание.
зы Формализую условное ТЗ
Необходимо составить алгоритм двухступенчатого перевода частей документа на языки (языки стран), допустимые в заданном списке стран с учётом минимальной стоимости всего пакета переводов.
Первоначально документ и все его части могут быть предоставлены на нескольких языках — языках клиента.
Условия при которых считается что документ может быть отправлен в страну могут быть вида
— В стране есть несколько языков и достаточно одного из них;
— В стране несколько языков и все они обязательные;
— Для некоторых частей документа некоторое подмножество языков страны обязательное;
— Для некоторых частей документа достаточным является хотя бы один из языков из подмножества языков страны;
Условия могут менятся. Например, могут появится условия типа
— Для некоторых частей документа достаточным является первод не менее чем на два языка из специального подмножества языков страны
зы Первоначальный заголовок вызвавший неоднозначную реакцию «История о том как я просил 1200, а меня взяли на 2700*»
*заголовок для привлечения внимания
Найкращі коментарі пропустити