Моя история вынужденного использования паттернов
Я считаю, что без ООП и паттернов жизни нет. Тем не менее до сих пор можно встретить множество холиваров различной величины о том насколько вредно или полезно ООП.
В этой небольшом топике я хочу бросить камень в огород противников ООП. Я понимаю, что противником ООП можно быть только в случае если подобная парадигма непонятна, а особенно непонятны причины её использования.
Все резоны в пользу ООП выглядят для начинающего программиста достаточно натянуто, а пробраться сквозь лес интерфейсов и абстрактных классов бывает непросто. Хуже всего то, что действительно объясняющих книг немного. И самая плохая книга которая нереально высоко задирает порог вхождения это самая полезная книга Банды Четырёх о паттернах. Она написана скучным, тоскливым языком с вычурными аккадемическими примерами.
Люди которые ратуют за красивые архитектуры, но при этом объясняют их необходимость на примерах с логерами, авторизациями, кэшем и щедро дают названия классам в виде 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*»
*заголовок для привлечения внимания
Найкращі коментарі пропустити