Ответы пользователя по тегу Проектирование программного обеспечения
  • Проектирование и архитектура приложений?

    @MarkusD
    все время мелю чепуху :)
    Самое первое и самое главное: клиентский код должен оставаться чистым, состоять как можно больше из кода реализации алгоритма и как можно меньше из кода обслуживающего эту реализацию.
    Клиентский код ничего не должен знать о том, как ты хочешь, например, оповещать пользователя об исключительных ситуациях. Ему просто незачем это знать, ему важно просто работать.

    Поэтому, from classes import MyTelegramm - думаю, не стоит. Мне там и try-catch тоже не нравится. Пусть пользователь блоки try-catch пишет только для себя и только тогда, когда ему это надо. Когда он точно может словить исключение и выкрутиться из ситуации. Про такие исключения тебе, по-хорошему, и знать-то не стоит, т.к. они лишь меняют ветвь исполнения, но не обрывают его.
    Клиентский код стоит выполнять в контролируемой среде, внутри специального защитного блока try-catch, в котором ты уже будешь ловить все доступные тебе исключения и производить их диспетчеризацию.

    Скажем, doit все действия задачи выполняет внутри своего защитного контура. При этом, таких защитных контуров у doit несколько, включая и самый базовый, отвечающий за оповещение о критических сбоях работы. Что мне в doit малость не нравится, так это что обработка исключения делается по месту вместо того, чтобы организовать некоторый диспетчер, в котором это исключение уже и обрабатывать.

    Свой защитный контур я бы связал с диспетчером (Dispatcher) исключений. Если контуров делать несколько, то и диспетчеров я тоже сделал бы несколько, разместив все диспетчеры в локаторе (Service locator).

    Диспетчер начал бы свою работу с того, что завернул бы исключение в конверт (Envelope, [2], [3]). Конверт просто легче читать, чем исключение непредсказуемого типа. Заворачивание я бы сделал с помощью абстрактной фабрики (Abstract factory) конвертов, в которой исключение каждого типа и от каждого источника было бы завернуто в правильный конверт согласно конфигурации фабрики.

    Получив конверт, диспетчер посетил(Visitor) бы с этим конвертом почтовый ящик каждого адресата. Почтовый ящик адресата можно снабдить стратегией (Strategy) приема конвертов, согласно которой почтовый ящик или перепишет конверт к себе, или оставит без внимания, в зависимости от написанных на конверте данных. Управлять почтовыми ящиками может локальный реестр (Registry) диспетчера, в котором так же может быть реализована и функция обхода (Visit) для посещения почтовых ящиков.

    Почтовый ящик обслуживает адресата. Адресат может подписаться (Observer) на поступление письма, а может приходить к ящику за новыми письмами по расписанию и доставать только первые несколько писем, а не все. Как выгребать почтовый ящик - вопрос конфигурации адресата.
    Когда адресат забирает письма, он раздает их в конечные точки, в роли которых уже и будут выступать все эти MyTelegramm или MyLog, но строго сконфигурированные и принимающие уже только копию письма.

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

    Ну и, конечно же, любую часть всего этого безобразия можно отбросить и написать проще. :)
    На начальных этапах можно заглушить стратегии и свести конфигураторы к виду линейной функции.
    MVP тут, на мой взгляд, будет выглядеть как цельная система с гарантированной доставкой конверта с любым исключением во все созданные конечные точки, проводя их от защитного контура, через диспетчер и до адресата через почтовый ящик.
    Ответ написан
  • Как реализовать фабричный метод без switch?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Фабричный метод - это т.н. виртуальный конструктор. Внутри функции не должно быть конструкций выбора. Сам фабричный метод передается в другое место для порождения объектов некоторого класса с определенным интерфейсом.

    Конструкции выбора обычно присутствуют внутри абстрактных фабрик. В фабрику приходит некий идентификатор типа и (если идентификатор определен) фабрика создает объект некоторого класса с определенным интерфейсом.

    Абстрактную фабрику можно реализовать на основе контейнера фабричных методов. Шаблоны C++ и стандарты C++11/14 нам в этом только помогут. Самый простой код такой фабрики может выглядеть вот так:
    Пример фабрики
    template< typename TInterface, typename... TArguments >
    class AbstractFactory final
    {
    public:
    	// Produce the implementation, but return the pointer to interface.
    	inline std::shared_ptr<TInterface> Produce( const std::string& implementation_name, TArguments... arguments )
    	{
    		auto found_function = m_factory_functions.find( implementation_name );
    		return ( found_function == m_factory_functions.end() )? std::shared_ptr<TInterface>{} : found_function->second( std::forward<TArguments>( arguments )... );
    	};
    	
    	// Define the implementation.
    	template< typename TImplementation >
    	inline const bool DefineImplementation()
    	{
    		return DefineImplementation<TImplementation>( TImplementation::ClassName() );
    	};
    	
    	// Define the implementation.
    	template< typename TImplementation >
    	inline const bool DefineImplementation( const std::string& implementation_name )
    	{
    		// Abort the incorrect registration.
    		static_assert( std::is_base_of<TInterface, TImplementation>::value, "Implementation may only be derived from interface of Factory." );
    		
    		auto found_function = m_factory_functions.find( implementation_name );
    		if( found_function == m_factory_functions.end() )
    		{
    			m_factory_functions[ implementation_name ] = &AbstractFactory<TInterface, TArguments...>::template ConstructImplementation<TImplementation>;
    			return true;
    		};
    		
    		return false;
    	};
    	
    	// Check the implementation name is already defined.
    	inline const bool IsImplementationDefined( const std::string& implementation_name ) const
    	{
    		return m_factory_functions.find( implementation_name ) != m_factory_functions.end();
    	};
    	
    private:
    	// The factory function just produce implementation.
    	template< typename TImplementation >
    	static std::shared_ptr<TInterface> ConstructImplementation( TArguments... arguments )
    	{
    		return std::static_pointer_cast<TInterface>(
    			std::make_shared<TImplementation>( std::forward<TArguments>( arguments )... )
    		);
    	};
    
    private:
    	// Factory function produces the implementations of TInterface.
    	using FactoryFunction	= std::shared_ptr<TInterface> (*)( TArguments... arguments );
    	
    	std::unordered_map<std::string, FactoryFunction>	m_factory_functions;
    };


    Работает она примерно так:
    cpp.sh/93obm
    Ответ написан
    Комментировать
  • Разделение системы на модули?

    @MarkusD
    все время мелю чепуху :)
    Ты все правильно думаешь. Базовое разделение на модули выглядит рационально.
    А дальше давай думать логически.

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

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

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

    @MarkusD
    все время мелю чепуху :)
    Лично мне было бы интересно послушать подробности. В данной трактовке вопрос очень сложен из-за своей расплывчатости. :)
    Конкретнее! Нужна иллюстрация хода мыслей и критерии, по которым у тебя создается ощущение усложнения задачи.
    Так же очень важно понять, сам ты замечаешь усложнение или же тебе говорят об этом.
    Александр Синицын , дополни свой вопрос.

    Нередко в неадекватном усложнении задачи обвиняют именно те "соратники", кому попросту непонятна ни сама задача, ни ход твоих мыслей. Как правило это результат эффекта Даннинга-Крюгера.
    Ответ написан
    2 комментария
  • Как лучше организовать многопоточную архитектуру?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Смотри, прямо готового монолитного шаблона пожалуй нет (точнее, я со своего опыта такой не нахожу).
    По описанию видно что можно использовать Thread pool + Proactor + Actor model.
    Вместо потока на девайс будет пул потоков + управляющий поток, т.е. семейства потоков только два. Каждый девайс - актор. Действия актора выполняются атомарно в необходимом потоке (главный/сервисный) благодаря планированию нужного обработчика для нужного потока.

    Вот, подкину литературку:
    www.gamedev.ru/code/articles/concurrency_models
    https://habrahabr.ru/users/eao197/topics/ - SObjectizer является довольно интересной библиотекой.

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