×

Паттерн Loyalty

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Задача: Разрешить изменять поля класса только одному другому классу

upd Задача: Инкапсулировать алгоритм изменения сущностного агрегата в отдельном классе. Под агрегатом я понимаю некую сущность, которая может быть выражена набором логически связанных классов. Например, мы хотим отменить заявки на тендер — этот кейс должен быть завязан на рассылку остальным участникам тенедера уведомлений. Отмена заявок без уведомления или рассылка уведомлений без отмены заявок является нарушением бизнес логики. То есть у нас должны быть классы Заявки, Уведомитель и Биржа. В Бирже должен быть инкапуслирован алгоритм который успешно изменит поля класса Заявки и инициирует отправку уведомлений, а потом в лог ещё запишет. Нигде кроме как в классе Биржа не должно быть возможностей для изменения полей класса Заявки или вызов метода Уведомителья.

Прошу сообщество покрэшить «мой» паттерн Верность, построенный на базе паттерна Стратегия. А заодно подсказать — может быть уже есть готовый паттерн решающий эту задачу.

Предметная область: Маршрутный Лист. Изменение адресов маршрутного листа должен производить только Диспетчер.

UML картинка

class Program
    {
        static void Main(string[] args)
        {
            TrafficController disp = new TrafficController();
            BusRunninSchedule bus = new BusRunninSchedule(disp);
            
            List<Address> listone = new List<Address>() { new Address() { Point = "Kharkov" }, new Address() { Point = "Moskow" } };
            bus.DisplayInfo();
            disp.SetShedule(bus);
            disp.ChangeShedule(listone);
            bus.DisplayInfo();
            List<Address> listtwo = new List<Address>() { new Address() { Point = "Berlin" }, new Address() { Point = "NY" } };
            disp.ChangeShedule(listtwo);
            bus.DisplayInfo();
            List<Address> listthree = new List<Address>() { new Address() { Point = "Tokio" }, new Address() { Point = "London" } };
            //Можно обойти интерфейсное сокрытие, но реализация всё равно будет в TrafficController
            (bus as ILoyalty).performChangeAddress(listthree);
            bus.DisplayInfo();

        }
    }

    public class Address
    {
        public string Point { get; set; }
    }

    public abstract class RunningSchedule : ILoyalty
    {
        private ITrafficController dispetcher;
        private List<Address> listAddress;

        public RunningSchedule(ITrafficController _dispetcher)
        {
            dispetcher = _dispetcher;
            listAddress = new List<Address>
        }

        public void DisplayInfo()
        {
            foreach (Address cur in listAddress)
            {
                Console.WriteLine(cur.Point);
            }
            Console.ReadLine();
        }

        void ILoyalty.performChangeAddress(List<Address> newAddress)
        {
            dispetcher.ChangeAddress(this.listAddress, newAddress);
        }
    }

    public class BusRunninSchedule : RunningSchedule
    {
        public BusRunninSchedule(ITrafficController _disp)
            : base(_disp)
        {
        }
    }

    public class TrafficController : ITrafficController
    {
        private ILoyalty rs;

        public void SetShedule(ILoyalty _rs)
        {
            rs = _rs;
        }

        public void ChangeAddress(List<Address> existAddress, List<Address> addreses)
        {
            existAddress.AddRange(addreses);
        }

        public void ChangeShedule(List<Address> newAddress)
        {
            rs.performChangeAddress(newAddress);
        }

    }

    public interface ITrafficController
    {
        void ChangeAddress(List<Address> existAddress, List<Address> address);
    }

    public interface ILoyalty
    {
        void performChangeAddress(List<Address> newAddress);
    }

У меня создалось впечатление дублирования сокрытия. Возможно, стоило остановится на Стратегии, но тогда синтаксически вызов изменения в Расписании происходил бы в этом же классе.

И я не смог скрыть метод ChangeAddress в TrafficController

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Коментар порушує правила спільноти і видалений модераторами.

Коментар порушує правила спільноти і видалений модераторами.

Можно сделать по-разному:
1) использовать RTTI (Run-time Type Info),

#include <typeinfo>

class ILoyalty {
public:
virutal bool DoAllow(const std::string& typename) const = 0;
};

class Schedule : public ILoyalty {
public:
bool DoAllow(const std::string& typename) const {return typename == “TrafficController”;}

bool ChagneAddres(const ITrafficController& disp)
{
if(DoAllow(typeid(disp).name())
{
//modify whatever you need.
}
}
};

2) если это невозможно, релизовать свой механизм для получения идентификаторов классов

class IClass {
public:
virtual unsigned int ClassID() const = 0;
}

class ILoyalty : public IClass {
public:
...
virutal bool DoAllow(IClass* aclass) const = 0;
};

class ITrafficController : public IClass {}

class TrafficController : public ITrafficController{
public:
unsigned int ClassID() const {return kTrafficControllerClassID;}
}

class Schedule : public ILoyalty {
public:
bool DoAllow(IClass* aclass) const {return aclass->ClassID() == kTrafficControllerClassID;}

Это же индиский подход, не? Он работает, но не гибкий.

МаршрутныйЛист.ДобавитьАдрес(object caller, Address address)
{
if (!(caller is Диспетчер))
throw new Exception("Идите на");
}

какая задача, такой и подход , по крайней мере это решение.
выходит что «friend» сильно банально, а проверка типов сильно не гибко :)

Я уже понял свою ошибку в постановке задачи. Завтра допишу пояснения.

По названию темы я думал, что очередной вброс про лояльность 23-летних синьоров, а тут такая скука.
Меняю два паттерна Loyalty на один паттерн Royalty.

Задача: Разрешить изменять поля класса только одному другому классу

Правильно поставленный вопрос это половина решения. Из этой формулировки и «простыни» кода я лично ничего не понял. Что конкретно ты хочешь сделать, какую задачу все-таки нужно в итоге решить?

Предоставить право на изменение полей класса только одному, другому, конкретному классу. Эксклюзивный доступ к полям класса. Построить отношение 1 к 1 между сервисом и объектом-значением.

Не знаю как ещё сказать.

Ты описываешь решение задачи, а не задачу. «Предоставить право....» отвечает на вопрос ЧТО или КАК, а я хочу описание которое отвечает на вопрос ЗАЧЕМ.

Для того что бы избежать дублирования кода в различных местах программы... и дальше по тексту.

Не читал, но nested class c приватными полями?

Тоже не читал. Но кажется понял как надо было объяснят задачу.

Я понял задачу. Пока не читал код. Я имел ввиду что к приватным полям вложенного класса у класса есть доступ. У остальных классов его нет. Может это и есть решение задачи?

Я действительно не совсем точно её объяснил. Для примитивного решения вполне достаточно внутренних классов и других проявлений индийского кода, которые вполне работоспособны.

Я хотел инкапсулировать бизнес-логику изменения не класса, а агрегата — целостной сущности, которая может состоять из набора классов, которые могут быть никак не связаны между собой.

Скорее всего я действительно попытался написать архитектурный паттерн. Ещё подумаю и если сил хватит изложу более доступно идею.

Чи не схоже Ваше рішення на Команду: TrafficController — invoker, BusRunninSchedule — конкретна команда, List<adresses> — reciever? Тільки у Вашому випадку команда знає про інвоукера.

Да, что-то есть похожее. Надо подробнее разобрать Команду.

выскажу свои 5 копеек.
принятие решения о том, является ли МаршрутныйЛист редактируемым или нет — не есть зона ответственности МаршрутногоЛиста. Поэтому не обойтись без некоторой прослойки, которая будет принимать это решение на основе контекста.
Это может быть либо уровень сохранения значения — например, разрешение/запрещения записи в бд в зависимости от контекста. еще более наглядный пример — логгер. туда срут все подряд, но в файл отправляется только то, что законфигурено как loggable.
Альтернативное решение — это использовать две реализации одного интерфейса (TrackingList): TrackingListEditable и TrackingListReadOnly. Oбе имплементации по определению, будут реализовывать одинаковый набор методов с той лишь разницей, что методы updateXX() в ReadOnly версии не будут делать ничего (или бросать эксепшн). Итого, требуется некая фабрика/хэлпер, которая в зависимости от контекста будет возвращать объект в Editable или ReadOnly версиях. Типа вот так
TrackingList tl = TrackingListHelper.getTrackingList(context);
ps. с точки зрения жавера

Буду думать. А чем является context? Объект который хочет что-то записать? И надо же как-то тогда запретить создавать TrackingList через new.

контекстом может быть любой объект, на основе которого можно принять решение о том, возвращать Editable или ReadOnly. в качестве контекста может выступить даже класс вызывающего объекта, например (для Java)
TrackingList tl = TrackingListHelper.getTrackingList(this.class);
а запрет на создание объекта теоретически можно решить зоной видимости конструктора. с другой стороны это уже смахивает на защиту от программиста ))
upd. TrackingList — это интерфейс, у него не может быть конструкторов.

Собственно говоря, получаем Одиночка + Фабрика. Соглашусь, классически, это решение лучше. Правда чуть более сложное.

Правда всё-равно приходится писать Лог.Залогируйся(). Мне хотелось чтобы было однозначное Логгер.Залогируй(Лог) или Логгер.ЗалогируйЛогВБазу(Лог)

упд Наверное, сейчас должны прийти функциональщики.

Согласен с коллегами внизу, что вы создаёте проблему там, где её нет. Но если очень хочется, то можно обойтись вообще без паттернов. Скажем класс А должен дать доступ к своим методам классу В. Помещаем классы в одну сборку и делаем все необходимые В методы А protected internal.

хм.
Коллеги внизу не видят проблемы. Её действительно нет вначале. Потом она появляется и называется дублирование кода. После этого некоторые рефакторят до Стратегии, а некоторые нанимают джунов для поддержания. (Придумал мем — Надо больше джунов!)

Вариант со сборкой перемещает проблему в сборку. Но вариант, хотя ещё больше завязанный на .net Кроме этого у нашего класса-агрегата может быть множество бизнес-кейсов и по каждой разумно было бы назначить только свой ответственный класс

Вариант со сборкой перемещает проблему в сборку. Но вариант, хотя ещё больше завязанный на .net
Паттерны, ООП, всё это служит для уменьшения сложности. Если их применение увеличивает сложность — значит вы что то делаете не так.

Не завязанный. internal protected в .net — синоним protected в Java. А в C++ есть модификатор friend.

Паттерны, ООП, всё это служит для уменьшения сложности. Если их применение увеличивает сложность — значит вы что то делаете не так.

Сложность слишком сложное академически понятие чтобы обсуждать его здесь. Скорее стоит думать в терминах гибкость-строгость-бардак.

Вам жизненно необходимо прочитать «Complete Code» Макконела.

Мне она показалась слишком прямолинейной. Я пока читаю лагерь сторонников XP Эванса, Мартина, Бека

«Все нормально, Борис.
-За собаку не переживай.
-Я не переживаю.
Чем могу помочь, Борис?
У меня для тебя есть работа.
У меня уже есть работа.
Пятьдесят тонн за полдня работы.
Продолжай.
Я хочу, чтобы ты выставил одного букмекера.»

Допустим у вас есть кейс: Диспетчер может менять Маршрут.
Означает ли он что надо написать систему ролей и прав доступа и сделать доступ на страницу изменения Маршрута только для роли Диспетчер? А если завтра надо будет предоставить API для изменения маршрута?

Я не говорю что моя задача возникает постоянно и её решение я придумал замечательно, но говорить что такой задачи нет — это заниматься словоблудием. По моим подсчётам около 10000 страниц в книгах посвещены решению этой или похожих задач.

Уже писали — сделайте два интерфейса для доступа к маршруту. Диспетчер будет инкапсулировать изменяемый, остальные — неизменяемый. Как альтернативу, можно использовать proxy.

Нарисуйте UML и посмотрите на сложность. Получится комбинированный архитектурный паттерн.

Прокси Декоратор Адаптер — они не изолируют синтаксис вызова.

Вы фреймвёрк пишите? Если нет, то достаточно «не допускать». Но два интерфейса таки изолируют.

Ну какой фреймворк? Как раз всё просто. Кто ремонтирует Трубу? — Сантехник! А Столяр? -Нет! А если сильно захочет? -Нет! А если дирктор захочет чтобы Столяр ремонтировал Трубу? — Пусть сделает специальную Трубу и специального Столяра, а так Нет!

В том то и дело, что нет. ООП является моделью неких естественных объектов и их отношений. Так вот, столяр МОЖЕТ ремонтировать трубу. Потому что интерфейс трубы открыт для всех, как для столяра, так и для сантехника. Это естественно, а потому прекрасно переносится на ООП. А если нужно, что бы столяр не мог ремонтировать трубу — придумайте естественный механизм и перенесите его в программу. Скажем на трубе расположен распознователь личности, который пропускает только сантехника, и больше никого (передача this как параметра). Или к трубе приставлен сторож, который пропускает только сантехника (прокси). Или все детали трубы замаскированны, и нужен специальный прибор, что бы их увидеть (разные интерфейсы).

Не может. У него нет инструментов, читай алгоритмов для трубы. А те которые есть не подходят и могут испортить трубу.

Хорошо — Кто может ремонтировать кнопки в лифте? — Только тот у кого есть специальный ключ. — А пользоваться кнопками? — Все у кого есть пальцы.

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

Главный противник всегда внутри. Лао Дзы 8 в.д.н.э.*

Именно это и реализует мой паттерн — не только определяет наличие ключа но и заранее определяет алгоритм работы. Одного ключа не достаточно.

Я не спорю, я пытаюсь разобраться.

* цитата, автор и дата вымышленные

TrafficController disp = new TrafficController();
Это же моветон в дотнете. Не проще же ?
var disp = new TrafficController();

Шутка юмора?
Так то
TrafficController disp = FactoryDispetcher.CreateNewTerminator1000("Vasya");

upd Согласен, при однозначных и длинных, сложночитаемых типах var может убрать шум из текста. Не задумывался об этом.

Шутка юмора?
Нет, не шутка. По крайней мере Решейпер это дело правит. Хотя я тоже часто пишу тип вместо var.

У меня нет решарпера. А в чём профит в строго типизированном языке использовать var там где это не нужно?
У меня наверное пробел, но я его использую только когда косячу осознанно.
Ну и вот ещё blogs.msdn.com/...-on-fields.aspx — не всё правда понял, но возможно решарпер ругается на это или что то подобное. Но вроде бы var придумали для анонимных типов которые могут быть возвращены из linq (?)

Какой смысл писать var вместо типа? Просто экономить... э... ресурс клавиатуры, наверное.

Решейпер ругается на тип вместо вара, если тип однозначен.

Какой смысл писать var вместо типа? Просто экономить... э... ресурс клавиатуры, наверное.
Ну да. Писать IEnumerable<person>, а именно такого рода типы возвращает ЛИНК, когда есть var — не круто. А так — полноценного вывода типа в Шарпе пока нет, так что можно не заморачиваться.

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

из линка может вернуться вот такое new { Message = «Hello, DOU» } без всякого person.

Не понятно в чём притензии решарпера? Странно это...

Тем более я привёл пример с фабрикой выше — тогда вообще не понятно из текста кто там вернётся, хотя тип будет однозначным.
Ну значит вы не пишите самодокументирующийся код. Зря.
из линка может вернуться вот такое new { Message = «Hello, DOU» } без всякого person.
Это анонимный тип и там без вар нельзя.
Ну значит вы не пишите самодокументирующийся код. Зря.
+1

Ну и закругляясь на эту тему «var vs type» — всё-таки конкретное указание типа говорит о том, что ты понимаешь кого ты ждёшь от программы. var съест всё-что угодно и может служит потенциальным источником сложно уловимых багов. Если new то я ещё соглашусь, но фабрики фабрик фабрик зовут астронавтов архитектуры и там уж точно с var накосячишь.

Статья старая. Сейчас решарпер предлагает использовать var только когда тип однозначно определен — конструктор или обобщение. В остальных слцчаях — явное указание типа.

var, емнип, рекомендуется в случае инициализации переменной при её объявлении; TType foo = TType.Create(); — считается, что здесь очевидно, какого типа значение получит foo со старта, так что var foo = TType.Create() не ухудшает читабельности

Да, уже разобрался. Но var, еимнип, самим MS рекамендуется только для анонимного типа. Но многие не согласны и я пожалуй с ними соглашусь. Хотя привык к типу слева.

Это же моветон в дотнете.
Ну ты смотри! Кто-то повелсо. :)

Задайте себе простой вопрос — А зачем? Или грубо говоря, та кому ты нужен со своим расписанием. Я к чему это говорю, закрывать нужно только детали реализации, закрывать интерфейс только чтобы не дай бог его кто-то другой не вызвал, имхо, паранойя и бред.

Вы противоречите самому себе. Или

та кому ты нужен со своим расписанием.
или
закрывать нужно только детали реализации,

Мне хотелось получить правильную точку начала управления. Как в жизни. Диспетчер меняет Маршрут, а не Маршрут себя меняет или Буфетчица меняет Маршурт. Мне кажется, что ситуация когда все кто хочет может менять маршрут, тоже далека от идеальной.

Честно говоря не вижу тут большой паранои. В реальной жизни можно было конечно сделать индийскую проверку на вызывающий класс в методе Маршрутного Листа и этим ограничится. Но это не конкретная задача, это именно Паттерн. Но спасибо за мнение.

Нет, не противоречу. Читайте что такое инкапсуляция и зачем она нужна.

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

Инкапсуляция это возможность в ООП парадигме. Она никаким образом не защищает от спагетти кода (в данном случае от дублирования в разных местах программы). Когда спагетти становится невыносимым применяют паттерны (или не применяют), которые в большинстве своём инкапсулируют поведение в отдельных классах читай выкручивают код чтобы его не дай бог не в том месте не заюзали.

Такая у меня в голове картина мира сейчас.

Собственно я и хотел сразу запретить дублирование кода при работе с классом. Не потом путём рефакторинга, а сразу.

Предтавьте, что речь идёт об очень сложных правилах и пусть будут замешаны деньги — например Одобрение кредита для клиента. Оставите вы открытой возможность поставить true в любом месте программы?

а не Маршрут себя меняет
А что будет когда, захотят добавить «интеллекта» в систему и маршрут сможет себя менять? — Окуительный бонус на преодоление тех ограничений которые вы внесли в систему.
это именно Паттерн.
Паттерны полезны тогда когда применяешь их не задумываясь. Когда вы начинаете думать __какой шаблон применить__, вы попросту усложняете систему и делаете ее менее понятной.

Тот рефакторинг который описан в книгах обычно идёт по другому пути — из классов изымают возможности и инкапсулируют их в новых классах, путём добавления абстракций и построения заборов для «интеллекта».

Возможно существует и обратное направление, но я пока до туда не дочитал.

Почитайте, что такое авторизация и зачем она нужна. И как реализовывается, да.

Вы пытаетесь ограничить вызов методов класса. Это — авторизация. Не паттерн, а принцип. Ну почитайте хоть википедию, не позорьтесь.

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

<joke>
гы гы разве что как вариант — авторизация программистов для написания кода для отдельных классов. тоже хорошая мысль</joke>

То есть даже что такое авторизация и чем она отличается от аутентификации вы не знаете? Это печально.

Почему вы говорите про авторизацию? Может вы перепутали топик?

Давненько меня так толсто не травили.

Вспомнилась по этому поводу такая вещь как AspectJ.

офтоп к редакции.
Этот текст был направлен в рубрику Креш-тест после dou.ua/...ic/6790/#280586.
Не в форум. Если он не соответствует требованиям рубрики Креш-тест то хотелось бы получать краткие объяснения почему. Я всё-таки трудился, картинку рисовал и т д.

зы Хорошо, пусть висит здесь.

Тут вашему материалу достаточно хорошо.

Вот. Так же гораздо понятнее. Спасибо!

Loyalty
Общепринятое название — friend.

Друзей может быть много. Я подразумевал преданность как связь один к одному для каждого сервиса.

Сорри — только сейчас заметил, что это все сразу было на дотнете. А мне показалось, что это Java и вопрос сводится к тому, как лучше сделать friend на Java.

расскажите или покажите где, что там за friend в Java

Воспользуйтесь Interface segregation principle. Пусть лист реализует 2 интерфейса — List и EditableList. Диспечер пользуется EditableList, а все остальные List.

Насколько я понимаю я им и воспользовался. List<> это встроеннный дженерик (обобщённый класс) в .net

Названия интерфейсов понятное дело для примера. В вашем случае могут быть Schedule/EditableSchedule

Для одного сервиса, например, только для изменения списка Точек Маршрута, вариант наследования допустим, но в книжках пишут что наследование от класса это плохо. Простое же наследование от интерфейса обходится вызовом

(bus as ILoyalty).performChangeAddress(listthree);
в любом классе. В данном случае, потом, обязательно появятся требования по назначению Водителя на Маршрут, по установке Тарифов и т д. Мне хотелось бы что-бы этим могли заниматься только те классы которые имеют на это право. Хорошо ли будет плодить наследников на каждый сервис? Да и как потом вспоминать кого вызывать в программе?
(bus as ILoyalty).performChangeAddress
как по мне, это в любом случае плохая практика, и так писать не стоит. Все-таки интерфейс определяет роль.
Если вам передают read-only интерфейс — так и работайте с ним.

Назначение водителя/тарифа — через EditableSchedle

У меня постоянно возникало ощущении паранои при написании этого.
Видимо, нужен компромис между метазнаниями о программе и проектными ограничениями.

Коментар порушує правила спільноти і видалений модераторами.

Підписатись на коментарі