All streams
Search
Write a publication
Pull to refresh
56
0

Пользователь

Send message
Если внутри конструктора будет эксепшн, объект сразу попадет в очередь GC, где и будет радостно собран вместе со всеми его зависимостями (включая вызов финализатора, если тотопределен)

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

Вызов GetSomeData до инициализации объекта невозможен

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

Меня бы устроила примерно следующая реализация.

    internal class UserDataProvider : IExposable, IDisposable
    {
        public Guid UserId { get; set; }
        public IDictionary<string, object> SessionData { get; private set; }

        private SqlConnection _conn;

        public bool HasConnection { get { return _conn != null; } }

        public UserDataProvider()
        {
            Expose();
        }

        ~UserDataProvider()
        {
            Dispose();
        }

        public void Expose()
        {
            if (HasConnection) return;
            _conn = new SqlConnection();
            _conn.Open();
        }

        public void Dispose()
        {
            if (!HasConnection) return;
            _conn.Dispose();
            _conn = null;
        }

        public X GetSomeData()
        {
            if (!HasConnection) Expose();

            try
            {
                var cmd = _conn.CreateCommand();
                //...
            }
            catch (TimeoutException exception)
            {
                _conn = null;
                return GetSomeData();
            }
        }
    }


Всё гарантировано вызывается, покрывается ваш случай и даже более. Плюс объект становится возобновляемым. Какие минусы, на ваш взгляд, у такой реализации и чем она хуже классической?
В крайнем случае попробуйте использовать только один метод Expose, например, во вью-моделях, как описано в статье. Он помогает очень красиво организовать код и реализовать циклические инжекции.
Почему нет разделения ответственности? Всё так же, как и у вас, единственное отличие в том, что вместо new Provider(ConnectionString) будет _provider.Expose(). Если вам привычнее классический вариант, то используйте его, или назовите методы по-другому, суть паттерна останется прежней.

Мне нравится связка Expose/Dispose, непривычно, но ничего сверхсложного в ней тоже нет.
Или тривиально решается при помощи Expose/Dispose с тем же разделением ответственности, но только провайдер не создаётся заново каждый раз, а выходит из режима ожидания. Просто альтернативные способы со своими плюсами и минусами. И каждый имеет право на жизнь.
Пусть это будет провайдер данных для пользователя. Внутри он хранит информацию о сессии пользователя и кэш-данных. В Exposed состоянии (активная работа пользователя) держит соединение к БД, а в Disposed (простой или малая активность) ожидает запросов или использует только кэш.

Банально, ведь так работает телевизор или компьютер. Для быстрого включения, сохранения состояния (открытые приложения) и экономии ресурсов используется спящий режим. Почему не перенести такую аналогию в программирование, ведь это тоже своего рода паттерн проектирования бытовой и вычислительной техники…
В любом случае хочу поблагодарить вас за дискуссию! Благодаря ей удалось более строго и чётко сформулировать мысли, изложенные в статье.
Пускай есть объект, который достаточно дорог при создании и занимает ресурсы которые нужно переодически освобождать, а также важно его внутреннее состояние. То есть вариант с UOW и пулом не подходит, поскольку внутреннее состояние должно оставаться прежним. Совместное же использование Expose/Dispose решает эту проблему.

Если вы обнаружили что-то новое, используйте это новое, а не трогайте уже существующее. Вы правда не понимаете смысла понятия «паттерн»/«шаблон проектирования»?

Глупо соревноваться в том, кто лучше или хуже понимает понятие «паттерн». Просто мы с вами вкладываем в него несколько отличающийся смысл. Часто новое строится на основе уже существующего, для меня это как раз тот случай.
Соглашусь с вашими рассуждениями. Вероятно, подобрал не самый удачный пример для иллюстрации паттерна. Взгляните на вышестоящий комментарий habrahabr.ru/post/256629/#comment_8395113, чтобы лучше уловить суть того, что мне хотелось сказать.
Мой ответ, наверняка, вас не устроит — в этом есть красота :)
По крайней мере, вижу её там очень чётко.

Чем вас не устраивает object pool, в котором все уже реализовано?

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

Потому что его семантика уже определена.

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

В обычном понимании отказ, но если вдуматься, то не совсем так.Cсылка на объект вполне может оставаться и после вызова Dispose. Зачастую обращение к большинству свойств и методов вызовет исключение, если программист это предусмотрел, но экземпляр обычно запросто можно использовать в качестве ключа, вызывать ToString, Equals… Так почему бы не расширить понимание паттерна Disposable? Пусть метод приводит объект в дежурное состояние, когда он занимает меньше ресурсов, в спящий режим! Но тогда должен существовать и метод выводящий из этого состояния — Expose. Всё очень закономерно и логично. То есть мы получили некоторое обобщение паттерна Disposable, а ваш сценарий с отказом от объекта — это лишь его частный случай…
И вас вот вообще не смущает, что DbContext — это UOW, и его нельзя разделять между разными клиентами?

В статье упоминается про это, но задача стоит в том, чтобы показать альтернативу UOW.

На основании чего вы думаете, что создание и уничтожение DbContext — такая дорогая операция?

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

Это означает, что не «существует паттерн Exposable», а «я нашел что-то похожее на паттерн». Так вот, то, что вы пытаетесь выше показать для DbContext, называется object pooling, и реализуется не так. А то, что вы показываете во вью-моделях — это обычный инициализационный метод. И это два разных паттерна для решения разных задач.

Даже если классический object pooling выглядит по-другому, то это не означает, что альтернативных реализаций быть не может. Если Expose — обычный инициализационный метод, то Dispose — деинициализационный — всё просто :)

Но стоит учитывать те преимущества, которые даёт использование этого инициализационного метода перед классическим вызовом конструктора.

Хочу лишь спросить, не замечаете ли вы своего рода гармонии и красоты в существовании Expose и Dispose, конструктора и финализатора? Это, конечно, странный вопрос, но лично меня в программировании цепляет такая стройность…
Да, спасибо! Вкралась опечатка. Уже исправил.
При необходимости цикличных ссылок двух классов друг на друга, проще поддерживать шаблон медиатор.

А если у вас циклические ссылки не двух, а трёх и более классов друг на друга?
Механизм Exposable позволяет красиво разрешить и такие ситуации.
Хорошо, всё те же условия — сервер с неравномерной загрузкой. Запросы могут идти как по одному, так и большими «пачками».

Решение:
Когда приходит запрос, проверяется по таймеру состояние объекта context и, если он не готов к работе, вызывается метод Expose, после чего включается таймер, например, на секунд 10. Если запросов больше не было, то спустя этот промежуток вызывается Dispose, иначе таймер продлевается. В результате соединение слишком долго не держится и не нужно постоянно создавать и удалять context. По-моему, по такой схеме вполне можно получить выигрыш в производительности. Не проверял это на практике, но исхожу из логических рассуждений.

А кто-нибудь, кроме вас, его признает?

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

Exposable и Disposable применимы как раздельно, так и совместно — зависит от ситуации. Внимания заслуживает тот факт, что оба паттерна обратно симметричны относительно жизненного цикла объекта.
Самое очевидное, что можно сделать для синхронизации:

lock(context)
{
    context.Expose();
    persons = context.Persons.Where(p=>p.Age > minAge).ToList();
    context.Dispose();
}

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

Реальных измерений производительности в описанных сценариях у меня нет, тут могут оказывать влияние многие факторы, например, ресурсоёмкость объекта DbContext, наличие свободной оперативной памяти, количество и временное распределение запросов. Суть в том, чтобы показать, что существует такой паттерн Exposable обратный Disposable и их возможно применять как по раздельности, так и вместе.
Ради интереса замерил разницу в скороти чтения и записи на реализациях INotifyPropertyChanged с лямбда-выражениями и без них.

Получившиеся результаты:
По скороти доступа (get) классическая реализация выигрывает примерно в 100 раз
По записи (set) классическая лучше примерно в 20 раз

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

Разница в 100 и 20 раз выглядит внушительно, но на деле это оказываются наносекунды (скорость записи, более медленная операция, у меня получилась около 2нс для лямбд, но зависит от производительности устройства). Частота же обносвления экрана в 100 Гц эквивалентна10 мс, поэтому в реальных приложениях на FPS это оказывает ничтожное влияние, если вообще оказывает. Экономятся лишь такты процессора.

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

persons.OrderBy(p=>p.Age).Skip(100).Take(10)

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

Information

Rating
Does not participate
Registered
Activity