Страницы

Поиск по вопросам

Показаны сообщения с ярлыком mvp. Показать все сообщения
Показаны сообщения с ярлыком mvp. Показать все сообщения

понедельник, 30 марта 2020 г.

Материалы по Clean Architecture в Android [закрыт]

#android #архитектура #mvp #dagger #moxy


        
             
                
                    
                        
                            Закрыт. Данный вопрос необходимо конкретизировать. Ответы
на него в данный момент не принимаются.
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            Хотите улучшить этот вопрос? Переформулируйте вопрос,
чтобы он был сосредоточен только на одной проблеме, отредактировав его.
                        
                        Закрыт 2 года назад.
                                                                                
           
                
        
Имею 3 года практики в Android разработке и недавно начал интересоваться такими вещами,
как DI, Moxy, Cicerone и Clean Architecture. Здесь, на SO, я почти не вижу вопросов,
относящихся к этой теме. В телеграмме очень мало людей состоят в чатах по ним. Как
я понял, эти вещи изучаются в самом конце, на пути к "званию" Senior Developer'а и
сложнее них нет ничего. Год назад я не понимал суть Dagger'а и MVP. Сейчас я, кажется,
понимаю зачем это все нужно: так как разработка почти всегда командная, у всех участников
проекта должны быть определенные стандарты проектирования. Разработчики будут лучше
понимать друг друга. 

Все же полностью понять эти вещи я не могу, я думаю здесь есть те, кто тоже их не
понимал, но сейчас вполне их использует и счастлив. Хочу у них спросить какие книги
они читали и как трудно там, в клине? Из мной прочитанных и полностью изученных, являются
"Философия Java" и "Java 8 Полное руководство". Если Clean Architecture относится к
архитектуре кода, то мне нужно сначала прочитать, например, "Паттерны проектирования"
и "Эффективное программирование"? Какие книги я должен для начала прочитать?

Также хотел бы узнать какая сейчас средне ситуация с ними на рынке, они уже must
have или пока еще в будет плюсом?

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

Спасибо. Думаю, что книги, которые вы посоветуете (и Clean Architecture в общем)
будут интересны многим.
    


Ответы

Ответ 1



Роберт Мартин, человек описавший чистую архитектуру, написал книгу с соответствующим названием - "Чистая архитектура". Его фундаментальный труд в виде статьи лежит здесь. Разъяснения касательно разработки под android, и clean architecture в целом, расписаны в этом труде. Материалы по второй и третьей ссылке дадут вам надежную почву для старта.

среда, 5 февраля 2020 г.

Одному фрагменту один презентер?

#android #mvp


Есть активити у которого 3-4 фрагмента .Для каждого из низ надо создать View и Presenter?или
Несколько View можно привязать к одному презентеру?
    


Ответы

Ответ 1



Ответ может быть дан только тогда, когда вы сами архитектурно примите решение, я постараюсь помочь: Согласно MVP по структуре у вас у каждой view должен быть свой pesenter, логично. В нашем случае нужно просто решить что такое View, если к примеру у вас activity, то фрагменты в таком случае часто называют subView, и они будут являться дополнением основной View кое у вас Activity, тогда у них будет общий presenter. На самом деле в небольших проектах я бы делал именно так, будет меньше зависимостей, генерируемого кода, интерфейсов, и легче будет построить логику между фрагментами, хотя такую зависимость лучше избегать, но... Presenter этой view (activity) разрастеться, и потом будет тяжело расщеплять эти куски, также такая паутина будет тяжелее тестироваться, ну опять же если проект большой. Чтоб убедить, что такой подход довольно частый, гугл его предлагает во время аттестации и описывает в stable blue prints MVP. Другой очевидный вариант, что activity выступает в роли View, а каждый фрагмент это тоже View, что тоже вполне логично, тогда нужно будет делать для них отдельные presenter. Те будет View(Activity) -> Presenter, View(Fragment) -> Presenter. Плюсы будут в том что такие фрагменты будут полностью независимы, и у нас не будет такого God object в роли жирного Presenter с логикой всех фрагментов внутри. Такой код легче будет поддерживать и тестировать, фрагменты можно использовать правильно по их назначению в других местах приложениях, как угодно при этом придется поправить только поведение, те в Java эту роль берет интерфейс. Проблема есть только в начальной структуре, например в Dagger2 чтоб выстроить такой граф нужно время и умение. Те по сути вы жертвуете начальным временем, чтоб решить какое будет поведение и всё. Такой подход я считаю более правильным, но его почему-то реже видно в исходниках или примерах в сети.

Ответ 2



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

среда, 22 января 2020 г.

Android MVP валидация во view

#java #android #mvp


Скажите пожалуйста, допускается ли вариант валидации полей во view при использовании
MVP подхода?
Поясню на примере: Есть форма входа с полями для логина и пароля, нужно провалидировать
на заполенность полей и отобразить ошибку в случаи пустых полей. Эта логика должна
быть реализованная в presenter или view?
    


Ответы

Ответ 1



Логика валидации должна быть во Presentere, нужно передать данные в презентер, в презентере проверить данные и в зависимости от результата что-то делать (отобразить ошибку, либо продолжить авторизацию). View - отвечает только за отображение данных. Presenter - логика, взаимодействие View с Model

суббота, 11 января 2020 г.

Как общаться с PreferenceHelper минуя DataManager в MVP?

#android #mvp


Использую шаблон проектирования MVP. Для наглядности приложу картинку:



Очень часто нужно просто записать данные в PreferenceHelper из презентера, но нужно
это делать через DataManager. Приходиться дублировать методы из PreferenceHelper в
DataManager. Можно ли этого избежать? (Наследование явно не предлагать) Общаться на
прямую с PreferenceHelper думаю не правильно.

PS
Делать например метод:

fun getData(dataKey: String, datatype: T) : T {}


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


Ответы

Ответ 1



Можно, но не нужно. "Расставляя капканы" SharedPreferences, это источник данных как и БД. У вас на схеме всё верно. Если вы PreferencesHelper положите в Presenter(или View), data слой (MODEL) не узнает, вы срежете угол, а там капкан. "Почему это плохо? ведь я его вижу и не попался?" Если завтра вы из префов забираете данные, но захотели и кешировать или писать БД, или необходимо с какими-то данными выйти в сеть - мы нарушаем singleResponsibility и архитектура начнет понемногу шататься. Вам придется еще больше дублировать методы и думать почему в 2 (или более) местах имеем доступ к тому же самому классу, а так DataManager (Repository) разобрался бы какие данные отдавать, кешировать, преобразовать для presentation layer. В целом это довольно частый вопрос в Android архитектуре и permissions, bundle, uri, states, services и тд. Много проектов, где шли коротким путем как хотите вы, в живых осталоись немногие, ведь вы уже пошли дальше, а вот коллеги про тот капкан узнают уже поздно.)

пятница, 10 января 2020 г.

MVP, слой models

#java #android #mvp


Такой вопрос по поводу MVP, а именно хочу уточнить по поводу слоя models, если класс
является объектом структуры БД, и в нем никакой другой бизнес логики, можно ли его
считать model? 

Пример класса:

public class User extends RealmObject {

    @SerializedName("username")
    String username;
    @SerializedName("name")
    String name;
    @SerializedName("email")
    String email;
    @SerializedName("properties")
    private Properties properties;
    @SerializedName("password")
    String password;

    public User(String username, String name, String email, Properties properties) {
        this.username = username;
        this.name = name;
        this.email = email;
        this.properties = properties;
    }

    public User() {

    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }


    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Properties getProperties() {
        return properties;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public String toString() {
        return username + " (" + name + ")";
    }

}

    


Ответы

Ответ 1



Да, безусловно это модель данных. ORM (к которым относится и Realm) вообще специально для того и задумывалось, чтобы совместить возможности СУБД и CRUD-операции с удобным для ООП форматом хранения данных (объекты-модели)

четверг, 9 января 2020 г.

Нужно ли хранить презентер MVP?

#android #mvp


В разных описаниях архитектуры MVP встречал разный подход к хранению\уничтожению
presenter. Кто-то сериализует его, и затем в onCreate() восстанавливает. Кто-то никак
не сохраняет и каждый раз в  onCreate() создает новый. 

Вопросы:


Зачем кто-то хранит peresenter, в нем ведь никогда не будет закешированных данных.
Плюс, который я вижу, это не тратить каждый раз ресурсы на создание нового presenter
в onCreate(). Но вместо этого нам придется каждый раз сериализовывать и десераилизовывать
его. Оно того стоит?
Зачем некоторые перед созданием presenter проверяют поле mPresenter в нашей вьюхе
на null, ведь если вызывается onCreate(), то вьюхи либо не было никогда, либо она была
уничтожена вместе со всеми полями и
mPeresnter в любом случае будет null.
Зачем вызывается detachView() в onDestroy(), если после
уничтожения активити ссылка на presenter перестанет существовать
и GC скорее всего удалит его, вместе с ссылкой на view. Чтобы
это было не скорее всего,а наверняка?


Лучшая практика - это создавать новый presenter в onCreate() или восстанавливать
старый и атачить к нему новый экземпляр view?
    


Ответы

Ответ 1



Часто нужно чтобы презентер жил во время процесса смены конфигурации, смены ориентации экрана, например. В этом случае какой-то длительный сетевой запрос может быть начат при одной ориентации, а закончен быть при другой или в процессе смены ориентации. В таких случаях презентеры хранят не в активти/фрагментах, но в отдельном месте, кое не зависит от жизненного цикла. Например в синглтоне, коий содержит мапу с презентерами по их ID. При таком подходе презентер создаётся если его нет в мапе, уведомляетя о прикреплении вьюхи и об откреплении оной. Так он сможет обработать ситуацию прихода данных во время "мёртвой вью" не нарвавшись на NPE и сможет продолжать работу пока пересоздаётся активити. В любом случае подходов много на разные случаи. В каких-то случаях поворотов экрана не будет, в других же надо данные пришедшие во время поворота отобразить и используют ViewState для хранения состояния вьюхи, кое к ней применяют после её прикрепления к презентеру.

Ответ 2



На мой взгляд пересоздание самый простой сценарий. Умер фрагмент - убили презентер. Да, можно хранить в мап синглтона, а можно доверить управление жизненным циклом презентера dagger с его скоупами. Тогда останеться только аттачить и детачить созданные и убиваемые вью к презентерам. Хороший подход использовать вместо presenter новый компонент viewModel с его liveData. Можно очень просто сэттить закешированные данные если вью умерало и восстанавливалось. ViewModel не привязано к жизненному циклу вью. очень удобно. Для сэттинга закешированных данных классно использовать паттерн MVI. Подробно есть в блоге Тинькофф на хабре.

четверг, 2 января 2020 г.

Объясните паттерн mvp в android [дубликат]

#java #android #mvp


        
             
                
                    
                        
                            This question already has answers here:
                            
                        
                    
                
                        
                            Отличие MVP от MVC
                                
                                    (2 ответа)
                                
                        
                                Закрыт 3 года назад.
            
                    
Объясните пожалуйста паттерн mvp.

Mvc - понятно, есть модель, есть контроллер и есть вью (xml), а вот как в mvp это
все не пойму. 

Пожалуйста с примерами кода, а то чувствую, что пора писать чистый код, и нужно двигаться
вперед.
    


Ответы

Ответ 1



Главное отличие MVP от MVC: в MVP представление определяет презентер, а не наоборот. А в MVC контроллер контролирует ввод данных пользователем и использует модель и представление для реализации необходимой реакции. Также отличаются условия использования этих паттернов. MVC применяется там, где представление обновляется каждый раз по какому-либо событию, а MVP применяется, когда представление не нужно каждый раз пересоздавать. Ещё важное отличие MVP и MVC заключается в том, что обычно в MVP между представлением и презентером существует связь один к одному, с возможностью использования нескольких презентеров для сложных представлений. В то время как в MVC один контроллер могут использовать несколько представлений. И вот достаточно хороший пример кода.

среда, 1 января 2020 г.

MVP Pattern Android для больших приложений

#java #android #view #mvp


Пробую постигать MVP Pattern для Android приложений. Сама суть, вроде, ясна, но для
совсем небольших приложений, которые обычно и бывают в примерах. При создании большого
приложения возник ряд вопросов, а именно:


Например, в приложении 10 activity. Следует ли создавать 10 presenter и model? Видел
информацию, что 1 view = 1 presenter.
Следует ли создавать presenter, если в конкретной активити к нему обращаются лишь
однажды и он вызывает одноименный метод у view, где и реализован intent для перехода
к следующей activity? Или это можно делать напрямую из view?
Как правильно реализовать "отписку" presentera от view, дабы избежать утечек памяти?

    


Ответы

Ответ 1



1)Например, в приложении 10 activity. Следует ли создавать 10 presenter и model? Видел информацию, что 1 view = 1 presenter. Да, лучше придерживаться паттерна 1 презентер - 1 вью. Исключение, если какая то логика часто переиспользуется. Но если она переиспользуется, то высокий шанс, что и вьюха тоже, а значит можно выделить эту логику в отдельную связку вью-презентер. 2)Следует ли создавать presenter, если в конкретной активити к нему обращаются лишь однажды и он вызывает одноименный метод у view, где и реализован intent для перехода к следующей activity? Тут важно отличать события в презентере: 1 тип - Если событие такое, что данные должны сохраняться при пересоздании вью, то это нужно делать через презентер, чтобы не ходить за данными два раза. Например, вы нажали на кнопку, чтобы скачать список новостей. Он скачался, вы закешировали его в презентере(как вариант, сохранили в глобальную переменную в презентере) и отобразили. Пользователь повернул экран, данные из кеша подгрузились, вместо того, чтобы делать заново запрос. 2 тип - События навигации, отображение тоста и похожее - это события, которые должны произойти один раз. Вы не хотите, чтобы когда повернулся экран, появился тост с ошибкой еще раз. Теперь ответ. Если это событие первого типа, то очевидно что это надо делать через презентер. Это как раз один из кейсов для которого мы его и создаем. Если это событие второго типа, то решаете вы или договоренности в вашей команде. Главное, чтобы подход был консистентный. Если на одном экране для вашего кейса с интентом вы сделали презентер, а на другом экране не сделали, то это запутывает при чтении кода и лучше придерживаться одного стиля. 3)Как правильно реализовать "отписку" presentera от view, дабы избежать утечек памяти? В onDestroy в Activity вызываете метод презентера, в котором зануляете ссылку на вью. В onDestroyView в Fragment делаете тоже самое, если пользуетесь фрагментами

пятница, 27 декабря 2019 г.

Как правильно передавать данные из Model во View через Presenter при использовании EF, CodeFirst, Linq?

#c_sharp #winforms #entity_framework #mvp


Я пытаюсь спроектировать приложение реализующее паттерн MVP на WinForms.

При этом я использую EF+CodeFirst+Linq

На View есть DataGridView, который нужно заполнить данными. View вызывает метод Select()
класса Presenter, который в свою очередь вызывает метод Select() класса Model.

Как правильно предать полученные из БД данные обратно в Presenter, чтобы тот вставил
их во View? 

Через возврат значения не получается т.к. используется using. Отказываться от using?

Реализация Model.Select()

    internal void Select()
    {
        using (GoodsContext context = new GoodsContext())
        {
            var items = from Items in context.Goods
                        select Items;
        }
    }


UPD:

Уходить от Linq не вариант.
    


Ответы

Ответ 1



Model Entity: public class Customer { public string Name { get; set; } public string Address { get; set; } public string Phone { get; set; } public override bool Equals(object obj) { Customer other = obj as Customer; return Equals(other); } public override int GetHashCode() { return Name.GetHashCode() ^ Address.GetHashCode() ^ Phone.GetHashCode(); } public bool Equals(Customer other) { if (other == null) return false; return this.Name == other.Name && this.Address == other.Address && this.Phone == other.Phone; } } //На самом деле для объяснения не было необходимости расписывать поля и переопределять методы, но решил добавить, т.к. многие это не делают, и еще хуже не знают зачем Интерфейс репозитория: public interface ICustomerRepository { IEnumerable GetAllCustomers(); Customer GetCustomer(int id); ... } Реализация репозитория internal class CustomerRepository : ICustomerRepository { private readonly DbContext dbContext = new DbContext(); IEnumerable GetAllCustomers() { return dbContext.Set().ToList(); } Customer GetCustomer(int id) { return dbContext.Set().SingleOrDefault(e => e.Id == id) } ... } Presenter public class CustomerPresenter { private readonly ICustomerView _view; private readonly ICustomerRepository _repository; public CustomerPresenter(ICustomerView view, ICustomerRepository repository) { _view = view; view.Presenter = this; _repository = repository; UpdateCustomerListView(); } private void UpdateCustomerListView() { var customerNames = from customer in _repository.GetAllCustomers() select customer.Name; int selectedCustomer = _view.SelectedCustomer >= 0 ? _view.SelectedCustomer : 0; _view.CustomerList = customerNames.ToList(); _view.SelectedCustomer = selectedCustomer; } public void UpdateCustomerView(int p) { Customer customer = _repository.GetCustomer(p); _view.CustomerName = customer.Name; _view.Address = customer.Address; _view.Phone = customer.Phone; } ... } View интерфейс вьюхи public interface ICustomerView { IList CustomerList { get; set; } int SelectedCustomer { get; set; } string CustomerName { get; set; } string Address { get; set; } string Phone { get; set; } Presenter.CustomerPresenter Presenter { set; } } Реализация интерфейса вьюхи internal partial class CustomerForm : Form, ICustomerView { private bool _isEditMode = false; public CustomerForm() { InitializeComponent(); } public IList CustomerList { get { return (IList)this.customerListBox.DataSource; } set { this.customerListBox.DataSource = value; } } public int SelectedCustomer { get { return this.customerListBox.SelectedIndex; } set { this.customerListBox.SelectedIndex = value; } } public string Address { get { return this.addressTextBox.Text; } set { this.addressTextBox.Text = value; } } public string CustomerName { get { return this.nameTextBox.Text; } set { this.nameTextBox.Text = value; } } public string Phone { get { return this.phoneTextBox.Text; } set { this.phoneTextBox.Text = value; } } public Presenter.CustomerPresenter Presenter { private get; set; } private void customerListBox_SelectedIndexChanged(object sender, EventArgs e) { try { Presenter.UpdateCustomerView(customerListBox.SelectedIndex); } catch(Exception e) { //логирование ошибки } } } PS: В коде могут быть ошибки, т.к. часть кода писал прямо тут, но для объяснения концепции этого должно быть достаточно.

Android MVP взаимодействие presenter и adapter

#java #android #mvp


Подскажите пожалуйста, более правильный вариант:


Presenter и адаптер содержат ссылку на List. Presenter добавляет, удаляет, изменяет
items, сообщаяет item position во view, view просто обновляет адаптер. (notifySetDataChanges)
Только адптер содержит ссылку на List и сам добавляет, удаляет, изменяет items по
команде от view. Presenter передает во view полноценный объект и команду что нужно
сделать, добавить, удалить или изменить.


Или есть какие то другие классические варинты?
    


Ответы

Ответ 1



Самое важно в этом вопросе является : используется ли в вашем проекте Immutable objects. Если нет, то изменение объекта в Presenter списке будет отображено и в списке Adapter. Если да, то необходимо всегда полностью заменять объекты при их изменении. Второй вариант удобно использовать в паре с SortedList и DiffUtil. Эти классы упрощают работу со списком в RecyclerView. DiffUtil позволяет чётко определить изменения в списке, и уведомить именно об этих изменениях. На основе этого решения Presenter говорит View: deleteElement(Element e); insertNewElement(Element e); updateElement(Element e); insertNewElements(List elements) // Используй DiffUtil для этого случая Получается, что в Adapter и Presenter будут иметь разные ссылки на коллекции, но одни ссылки на объекты этих коллекция. Вся логика определения изменения исходного списка ложиться на Presenter. Он должен определить изменения и сообщить View об этом: deleteElement(int position); updateElement(int position); insertNewElements(List elements, int position); insertNewElement(Element e, int position);

воскресенье, 22 декабря 2019 г.

Интерфейсы в MVP

#java #android #архитектура #mvp


Это наверное тысячный вопрос по MVP. Перечитал очень много статей, пересмотрел многие
исходники из GitHub. Но мне до сих пор не понятны некоторые вещи. И исходя из одного
примера погрузился в MVP, уже не помню название туториала. Но не в этом суть. 

Вот пример где практикую с MVP. Структура такова:



Где IChatsInteractor.java:

public interface IChatsInteractor {
    void getChatsByCount(int count, IChatsCallback chatsCallback);

    interface IChatsCallback{
        void onSuccess(ChatsModel chatsModel);

        void onFailure(String errorMessage);
    }
}


А ChatsInteractorImpl реализовывает IChatsInteractor и подтягивает данные с сервера.
Полученные данные записывает в RealmDB и возвращает полученные данные в методе ichatsCallback.onSuccess,
ichatsCallback.onFailure если ошибка.

Работает все это корректно. Но проблема в том, что я даже понятия не имею что вообще
значит слово interactor (из урока начал использовать). Хочу переименовать, но никак
не пойму как, на какое название и т.д.

Вопросы:


Правильно ли то, что из за одного Activity (ChatsActivity) создал 5 классов: (ChatsView
- он нужен, понимаю, ChatsInteractorImpl - работает с сервером.) Остальные 3 класса
нельзя ли как то объединить или так правильно?
Как видите для интеракторов создал отдельный пакет. Можно ли его запихнуть в папку model?
Как правильно переименовать интеракторов на название основываясь на его функции?
Стоить ли вытаскивать функцию записи полученных данных в базу данных из метода getChatsByCount()
в классе ChatsInteractorImpl?
И вообще, правильно ли я структурировал (спроектировал) весь MVP, то есть классы, методы?

    


Ответы

Ответ 1



Очень спорный вопрос, нет, это не значит, что он плох, даже наоборот - откровенно всё пишите. По вопросом, мне кажется понимаете правильно, я просто немного попытаюсь помочь. Я люблю вопросы по архитектуре, потому что даже Google колбасит, а различий множество MVP, MVI, MVB, MVVM, MVVSP, VIPER... Не говоря, о том что популярность некоторых полностью или частично меняет структурные решения. Но самое интересное, что действительно каждому, есть место быть, если разработчик считает, что это действительно удобно, во всех направлениях. Если у вас всё хорошо помещалось в 1 activity, вы легко тестировали этот компонент и легко добавляли новые фичи или даже они не нужны в будущем, то возможно вы излишне использовали MVP. Да, именно так!, я видел множество мелких проектов, которые писали под разные решения, состоящие из несколько простых activity, и действительно тот же MVP там смотрится ужасно. Но! для больших проектов в activity могут использоваться и больше кол-во классов, десятки и сотни и так далее. Вот тогда это будет вашим спасением и во время добавления фичи, вы будете чувствовать себя комфортно, а сторонние разработчики намного быстрей смогут понять идею и приступить к помощи. Объединить можно...., например обернуть фасадом интерфейсы и это смотрится хорошо и редактирование удобное для MVP. Действительно интеракторы совмещают с моделью, но тогда модель быстро раздувается, и становится сложно тестируемой и заменяемой. Сейчас есть решения для таких ситуаций и это наверное отдельная тема. Dagger 2 к примеру с Rx, делает эту прослойку незаметной. Если и всё-таки решили так сделать, то модель должна предоставить необходимые данные а вот сама функция, как и всегда по codeStyle должна - быть говорящей. Нет, интеракторы являются посредниками между P и M , и это его задача осуществить нужный запрос от необходимых менеджеров, для получения или записи данных в модель и оповещать presenter о действиях, но это суть, на практике очень сильно бывает размыта. Возможно. По package и нескольким классам тяжело сказать. UPDATE: Сразу не увидел link на GitHub, что я могу добавить. Структуруа нормальная, хотя мне кажется MVP уже так мало кто использует, такой примитивный вариант, голый я бы сказал, хотя для понимания так и должно происходить. А вот используете, а точней не используете MVP, рассмотрим на примере LoginActivity. @Override protected void onStart() { super.onStart(); Realm realm = Realm.getDefaultInstance(); RealmResults accessDM = realm.where(AccessDataModel.class).findAll(); if (accessDM != null) { AccessDataModel accessDataModel = accessDM.get(0); final String string = accessDataModel.getAccessToken() + "\n" + accessDataModel.getUserId() + "\n" + accessDataModel.getExpiresIn(); Intent intent = new Intent(this, ChatsActivity.class); intent.putExtra("data", string).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); finish(); } realm.close(); } CodeStyle упустим. Самое плохое, что вы смешали бизнес логику с View, не смотря на то что вы построили MVP, вы продолжаете все делать в Activity. Давайте рассмотирм один вариант на вашем примере: LifeCycle метод onStart() очень показательный. #109 line number Кратко. Вы берете instance Realm, проверяете какие-то данные на доступ, если данные есть - формируете String и переходите в ChatActivity. Как примерно должно быть: Когда срабатывает onStart Activity() вы просто сообщаете презентору об этом. И ваш метод должен выглядеть примерно так: @Override protected void onStart() { super.onStart(); mPresenter.onActivityStarted(); } Замечу, что плохим тонном считается дублировать колбеки жизенного цикла компонентов 1 в 1: Плохо: @Override protected void onDestroy() { presenter.onDestroy(); super.onDestroy(); } они должны для презенторов нести логику, но также нужно сходу отличать название, ибо когда другой компонент будет вызвать аналогичный метод или Fragment, вам придется не только переименовать, но и переходить внутрь, чтоб понять, что куда. Более того заметьте что вы вызываете презентер перед супер методом, тоже сомнительно, ибо если есть базовые классы или измененные State, может полвиять на логику работу в презентере то можете словить Leak. в Presenter должно быть примерно так: @Override public void onActivityStarted() { boolean ifAccess = getInteractor.getAccess(); if (ifAccess) getMvpView().navigateToChatsActivity(getInteractor.getAccessToken); else getMvpView().showAccessDenied(); } во View если доступ есть: @Override public void navigateToChatsActivity(String accessToken) { Intent intent = new Intent(this, ChatsActivity.class); intent.putExtra("data", accessToken).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); finish(); } если нет: @Override public void showAccessDenied() { Toast.makeText(this, "AccessDenied", Toast.LENGTH_LONG).show(); } А интерактор разбирается с instance realm, и выполняент работу асинхронно. У меня в примере, более примитивно, чтоб вы уловили суть MVP, особенно если вы не работаете c Rx.

понедельник, 16 декабря 2019 г.

Помогите разобраться с отделением бизнес-логики от формы

#c_sharp #winforms #entity_framework #linq #mvp


Я только начинаю осваивать C#. Сейчас пытаюсь разобраться в аспектах проектирования
приложения для работы с базами данных. Практически каждый раз я слышу такую фразу "бизнес-логика
должна существовать отдельно от формы". Но я не совсем понимаю как этого добиться при
программировании WinForms?

Посоветуйте пожалуйста исчерпывающее руководство или литературу на этот счет.

UPD:

На данный момент удалось понять, что при использовании WinForms необходимо использовать
паттерн MVP. И единственный пример использования MVP для WinForm, по которому удалось
построить рабочее приложение, я смог найти вот в этом топике Как начать пользоваться
MVP + WinForms?. Следую изложенной в нём информации у меня получилось следующее приложение.

Можете оценить, насколько у меня получилась правильная реализация применения паттерна
MVP и отделения бизнес-логики от формы?

View

using System.Linq;

namespace EFCodeFirstMVP
{
    interface IView
    {
        void SetData(IQueryable items);
    }
}

using System;
using System.Linq;
using System.Windows.Forms;


namespace EFCodeFirstMVP
{
    public partial class Form1 : Form, IView
    {
        private readonly GoodsPresenter presenter;

        public Form1()
        {
            presenter = new GoodsPresenter(this, new GoodsModel());
            InitializeComponent();
        }

        public void SetData(IQueryable items)
        {
            dataGridView1.DataSource = items.ToList();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            presenter.LoadData();
        }
    }
}


Presenter

namespace EFCodeFirstMVP
{
    class GoodsPresenter
    {
        private readonly IView view;
        private readonly IModel model;

        public GoodsPresenter(IView view, IModel model)
        {
            this.view = view;
            this.model = model;
        }

        public void LoadData()
        {
            var data = model.LoadData();
            view.SetData(data);
        }
    }
}


Model

using System.Linq;

namespace EFCodeFirstMVP
{
    interface IModel
    {
        IQueryable LoadData();
    }
}

using System.Linq;

namespace EFCodeFirstMVP
{
    class GoodsModel : IModel
    {
        public IQueryable LoadData()
        {
            Context context = new Context();

            var items = from Items in context.Goods
                        select Items;

            return items;
        }
    }
}


Data

using System.Data.Entity;

namespace EFCodeFirstMVP
{
class Context: DbContext
    {
        public DbSet Goods { get; set; }
        public DbSet GoodsList { get; set; }

        public Context()
        {
            Database.SetInitializer(new CreateDatabaseIfNotExists());
        }
    }

public class Goods
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string Barcode { get; set; }
        public int Price{ get; set; }
    }
}

    


Ответы

Ответ 1



Окей, давайте попробуем поговорить об этом вне привязки к WinForms. Смотрите. У вас есть две различные вещи: внутреннее поведение программы, и то, как она показывает это пользователю. Представьте себе, чтобы вы пишете программное обеспечение радара. У вашей программы внутри есть список отслеживаемых самолётов. Вы принимаете информацию с датчиков, обсчитываете её, принимаете решение о том, возник новый самолёт, или ложная цель, или известный вам самолёт переместился. Всё это происходит внутри, и для этого взаимодействие с пользователем не так уж и обязательно. Это внутренняя часть программы, модель. Теперь, вам нужно донести эту информацию до оператора. В каком виде вы будете представлять информацию — в виде распечаток зелёного текста на чёрном фоне, или в виде трёхмерной голографической визуализации — не так уж важно, и модель по существу не зависит от этой части. Поэтому вы должны писать модель так, чтобы модель ничего не знала о представлении. Это не то, чтобы строго обязательно, но это позволяет разделить программу на независимые части, и даёт лёгкость работы с ними. Здесь ещё остаются открытыми вопросы о том, как передавать действия пользователя модели, но это отдельная тема. Посмотрим на более приземлённый пример: работа с базой данных. Точно так же у вас есть модель: база данных, и операции над ней, которые вы собираетесь делать. Это всё организуется в модуль, возможно, навешивается сверху синхронизация и асинхронность, на этом модель можно считать оконченной. Представление должно просто показывать пользователю часть модели принимать у пользователя команды, и доставлять их модели после обновления модели показывать обновлённую информацию Обычно выделяют ещё и промежуточный уровень — бизнес-логику, контроллер, view model, которые занимаются пинанием модели, с тем чтобы представление занималось только представлением.

Ответ 2



Когда говорят, что форма/UI/View отделена от логики/модели, то имеется ввиду, что UI определен в отдельном namespace и/или class. При этом View получает минимальное количество информации о модели. View подключается к стандартным интерфейсам модели и таким образом может отслеживать и выводить на экран изменения данных. Ниже пример, в котором модель отделена от View. По таймеру в модель добавляются данные, которые выводятся в View. class View { static public void Show(object model, string member) { // создаем UI var f = new Form(); var g = new DataGridView() { Parent = f, Dock = DockStyle.Fill, DataSource = model, DataMember = member }; f.ShowDialog(); } } class Model { public Model() { // создаем модель - набор данных и правила их обработки var d = new DataSet(); d.ReadXml(new StringReader("")); // создаем таймер, для изменения модели раз в секунду var timer = new Timer() { Interval = 1000 }; // обработчик событий таймера timer.Tick += (s, e) => { var t = d.Tables["row"]; // создаем новую строку var r = t.NewRow(); r[0] = DateTime.Now.Millisecond; // доавляем строку в DataSet. при этом UI обновится сам. t.Rows.Add(r); }; // запускаем таймер timer.Start(); this.DataMember = "row"; this.DataSource = d; } public readonly string DataMember; public readonly object DataSource; } [STAThread] static void Main() { var m = new Model(); // создаем UI и привязываем его к модели View.Show(m.DataSource, m.DataMember); }

Как правильно реализовывать модель MVP?

#c_sharp #winforms #mvp


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

View реализация

interface ICustomer
{
    string FirstName { get; set; }
    string LastName { get; set; }
    string SurName { get; set; }
    string Address { get; set; }
    string Code { get; set; }

    event Action Search;
    event Action Cancel;
}

public partial class Customer : Form, ICustomer
{
    public string FirstName
    {
        get { return lblFirstName.Text; }
        set { lblFirstName.Text = value; }
    }

    public string LastName
    {
        get { return lblLastName.Text; }
        set { lblLastName.Text = value; }
    }

    public string SurName
    {
        get { return lblSurname.Text; }
        set { lblSurname.Text = value; }
    }

    public string Address
    {
        get { return lblAddress.Text; }
        set { lblAddress.Text = value; }
    }

    public string Code
    {
        get { return txtCode.Text; }
        set { txtCode.Text = value; }
    }

    public event Action Search;
    public event Action Cancel;

    public Customer()
    {
        InitializeComponent();
    }

    private void btnSearch_Click(object sender, EventArgs e)
    {
        Search();
    }

    private void btnCancel_Click(object sender, EventArgs e)
    {
        Cancel();
    }
}


Presenter реализация

class CustomerPresenter 
{
    public ICustomer View;
    public ICustomerModel Model;

    public CustomerPresenter(ICustomer view, ICustomerModel model)
    {
        View = view;
        Model = model;

        View.Search += View_Search;
        View.Cancel += View_Cancel;
    }

    private void View_Search()
    {
        new Task(() => {
            View.FirstName = "Alex Krass";
        }).Start();
    }

    private void View_Cancel()
    {

    }
}


Модель и вызов

Модель у меня пока пустая, как я понимаю с ней проблем не должно быть, вызов всего
этого безобразия через Unity IoC.

class UnityIoC
{
    private static UnityContainer unityContainer;

    public static UnityContainer Instance 
    {
        get
        {
            if (unityContainer == null) CreateContainer();
            return unityContainer;
        }
    }

    private static void CreateContainer()
    {
        unityContainer = new UnityContainer();
        unityContainer.RegisterType();
        unityContainer.RegisterType();
    }
} 

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    ApplicationContext context = new ApplicationContext()
    {
        MainForm = UnityIoC.Instance.Resolve().View as Form
    };
    context.MainForm.Show();

    Application.Run(context);
}


Ну соответственно при нажатии на кнопку Search я не могу обновить UI, неужели придется
в Presenter пробрасывать сам TextBox в ICustomer и вызывать BeginInvoke? Или я просто
чего-то недопонимаю в реализации MVP?

UPD: 

Вопрос с обновлением UI без его блокирования вроде решился через использование таймера,
когда информацию надо забирать по мере выполнения Task и через TaskScheduler, когда
надо обновлять после выполнения Task.

**Вариант 1**

timer = new Timer() { Interval = 1000 };
timer.Tick += timer_Tick;
timer.Start();

private void timer_Tick(object sender, EventArgs e)
{
    UpdateView();
}

**Вариант 2**
task = new Task(new Action(UpdateModel));
task.ContinueWith(new Action(UpdateView), TaskScheduler.FromCurrentSynchronizationContext());
task.Start();


private void UpdateView(Task task = null)
{
    view.SomeVal = SomeVal;
    view.SomeValNext = SomeValNext;
}

    


Ответы

Ответ 1



Вопросы по интерпретации и реализации паттернов проектирования почти всегда холиварные, поэтому сразу предупреждаю, что все что написано ниже - это мое личное видение основанное на моих же логике, понимании и практике использования WinForms. Про еще один вариант интерпретации и реализации MVP можно почитать в статье на хабре По аналогии с биологией начнем с простейших одноклеточных (одно-оконных) приложений содержащих только простые контролы (кнопки, картинки, текстовые поля со вводом или без). 1. Одноклеточные Определимся с компонентами паттерна и тем, какие элементы приложения к ним относятся. M - Model - отдельный класс инкапсулирующий работу с данными. Все операции над данными производятся только в нем. Если рассматривать модель под микроскопом, то в ней можно рассмотреть много-поточную или асинхронную обработку тяжелых вычислений, обращения к сервисам и базам данных и прочие слои свойственные моделям. V - View - визуальное представление данных модели. Сюда можно отнести все контролы нашей, пока единственной, формы, которые собственно отображают нашу модель с выбранного ракурса. P - Presenter - не буду выдумывать специальный перевод, остановимся на длинном но более-менее точном определении - компонент, который отвечает за получение данных из модели и знает как и когда их нужно отображать, а также обрабатывает пользовательский ввод, дергает модель за соответствующие методы и обрабатывает события модели. В случае простейших, презентером будет выступать класс формы, в логике которого мы и организуем передачу данных из модели в контролы для представления данных и обработку событий контролов, для передачи в модель действий пользователя. 2. Многоклеточные Когда окон становится больше одного или появляются сложные контролы, предыдущая модель приложения все еще имеет право на существование, но работать с ней становится неудобно. Добавим класс-презентер, для которого в качестве представления будут выступать наши одноклеточные описанные выше. Его главная задача - передать частным перезентерам нужный кусочек модели для работы, агрегировать события от них и передавать эти события в нужном порядке и количестве в модель, а также маршрутизировать события модели к частным презентерам. Для частных презентеров - общий будет выступать в роли модели. Также в нем можно реализовать много-поточное/асинхронное обращение к модели, т.к. он связан только с моделью и подчиненными презентерами, а не элементами UI, за которые отвечают частные презентеры. Таким образом получается что наше приложение состоит из множества простых, относительно самостоятельных фрагментов, под руководством старшего презентера, обеспечивающего их согласованную работу. 3. Меняем паттерн Приложение растет и усложняется и в определенный момент даже многоклеточная модель становится неудобной. Тут самое время вспомнить, что WinForms поддерживает DataBinding. А в связи с этим можно немного поменять шаблон. В сети он часто упоминается как MVPVM. Тут появляется новый компонент - VM - view-model, и роль презентера немного меняется. VM - по сути, срез модели для отображения. В задачи презентера теперь не будет входить передача данных в подчиненные вьюхи, а только создание биндингов к нужным VM, привязка биндингов к вьюхам и обработка событий. И мы в принципе можем отказаться от общего презентера, т.к. при создании очередного контрола можно просто передавать в него нужную VM, а остальное он сделает сам. Правда в сложных случаях нам все еще будет нужен агрегатор для событий, особенно если события от разных контролов взаимосвязаны или конфиликтуют между собой. Эта модель позволяет наиболее гибкое расширение функционала и массштабирование приложения. 4. Заключение Несмотря на то, что я рассматривал каждую модель по отдельности, на самом деле они плавно вытекают одна из другой по мере усложнения приложения и на практике в чистом виде ни одна из рассмотренных моделей практически не встречается и даже простое с виду приложение может требовать сложных комбинированных решений и наоборот. Просто следуйте логике, здравому смыслу и принципу - "проще - лучше". Ну соответственно при нажатии на кнопку Search я не могу обновить UI, неужели придется в Presenter пробрасывать сам TextBox в ICustomer и вызывать BeginInvoke? Или я просто чего-то недопонимаю в реализации MVP? По предложенным мной вариантам, вы используете второй вариант, и единственно чего в нем не хватает - это события в CustomerPresenter о том что поиск завершен и можно забирать данные для отображения, на которое сможет подписаться форма. Или определить в ICustomer и реализовать в форме Customer метод DataUpdate (или переопределить метод Control.Update формы) и вызывать его из CustomerPresenterкогда поиск завершен и к результатам можно получить доступ из основного потока. краткий алгоритм: кнопкой (или другим действием) на форме Customer передаем запрос на поиск в CustomerPresenter. CustomerPresenter передает запрос в модель модель активирует поиск отдельным потоком. Компоненты основного потока с этого момента свободны для другой полезной работы. посик завершен, результаты загружены в модель. Модель активирует событие о том, что поиск завершен. CustomerPresenter получает событие о завершении поиска, получает результаты поиска из модели и: вызывает метод UpdateData (примерное название) у формы и передает результаты поиска в параметрах. активирует событие завершения длительной операции Customer, получает результаты поиска у CustomerPresenter, как именно зависит от предыдущего пункта, и отображает их. Простоя в работе интерфейса во время поиска нет. Можно и по имеющемуся сценарию: кнопкой (или другим действием) на форме Customer передаем запрос на поиск в CustomerPresenter. CustomerPresenter в отдельном потоке вызывает метод поиска в модели. Компоненты основного потока с этого момента свободны для другой полезной работы. После завершения в потоке поиска с помощью Invoke: вызываем метод UpdateData (примерное название) у формы и передает результаты поиска в параметрах. активируем событие завершения длительной операции Customer, получает результаты поиска у CustomerPresenter, как именно зависит от предыдущего пункта, и отображает их. Еще можно завернуть длинный поиск в асинхронный метод, и уже внутри ожидать завершения потока поиска без подвешивания интерфейса, но этот вариант я только в теории представляю, руками не делал.

вторник, 10 декабря 2019 г.

Как правильно оформить модель в MVP?

#java #android #mvp


В паттерне MVP за данные отвечает модель, то есть, в ней мы реализуем все, что связанно
с получением данных.

Если приложение работает с сервером, то в модели описаны все методы для работы с
сервером. 

Если модель работает с BD, то в модели описаны все методы для работы с ней.

Но если приложение использует и сервер и BD 

Если приложение в оффлайн, то используем то что есть в BD. Если онлайн, то делаем
обращение к серверу.

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

Как это правильно организовать?

1) динамически менять модели в зависимости от оффлайн или онлайн?

2) создать модель, в которой будут имплементированны методы и для работы с сервером,
и для BD?
    


Ответы

Ответ 1



Подходов здесь может быть несколько, и ещё несколько если использовать иные архитектурные паттерны. Я приму вашу вводную, как правило, буду отвечать отталкиваясь от MVP который вы частично описали, без CleanArchitecture Ваша задача больше похожу на организацию Local и Remote Storage, те организацию внутреннего(локального) и удаленного хранилища, а не проблема с архитектурой. Те это всё внутренняя организация Model(а она бывает очень разной). Первым делом я бы советовал организовать логику смены сети прямо в модели. Такой подход можете значительно увеличить скорость приложения, управляя удаленными и локальными источниками данных. Смотря на ваше описание проще сделать прямо наоборот те вы показываете всегда сохраненные(или закэшированные данные), из LocalStorage, если их нет(или другое условие), делаете запрос через RemoteStorage, сохраняете (своя логика) в LocalStorage и показываете их, тем самым вы добьетесь того что ваше приложение будет стабильно работать в любых условиях сети и самое главное оффлайн. Я накидал быстрый пример для вас, чтоб было наглядней, как бы сделал я: в Presenter: model .getDataList() .subscribe(this::showDataList); в Model: public Single> getDataList(){ if (localStorage.isFilled()) return localStorage .getDataList() .subscribeOn(schedulers.io()) .observeOn(schedulers.ui()); else return remoteStorage .getDataList() .flatMap(localStorage::save) .subscribeOn(schedulers.io()) .observeOn(schedulers.ui()); } Заметьте здесь даже нет проверки соединения, потому что это скорей всего будет другое состояние в RemoteStorage, если учитывать наши условия и вы захотите обойтись без состояний, ещё и данные будут динамически меняться, то обновляем данные примерно так: public Single> updateDataList(){ if (isInternetConnection) return remoteStorage .getDataList() .flatMap(localStorage::save) .subscribeOn(schedulers.io()) .observeOn(schedulers.ui()); else return localStorage .getDataList() .subscribeOn(schedulers.io()) .observeOn(schedulers.ui()); } в View: private void showDataList(ArrayList dataList){ adapter.setDataList(dataList); } Смотрите даже в таком примере с руки, получилось довольно компактно и понятно, если использовать дополнительный слой например DataMapper, тогда всё сведется в 1 структуру Rx используя flitre, flatmap будет ещё проще организовывать сложную логику, ведь там ещё напрашивается кэширование. PS: писал без IDE мог сделать ошибки:)

среда, 27 ноября 2019 г.

Как начать пользоваться MVP + WinForms?

#c_sharp #база_данных #winforms #mvp


Пишу приложение с использованием БД - Firebird. Компьютеры у людей не очень мощные
и WPF там тормозит. Поэтому необходимо на WinForms (прощай удобный MVVM). Узнал что
для удобной работы люди используют MVP. 

Есть какой-то вводный материал или статьи нормальные на эту тему, или может собственный
опыт у кого есть? Ибо самостоятельное выяснение нормальных результатов не дало.
    


Ответы

Ответ 1



Немного ссылок: Вводная от Википедии Model-View-Presenter и сопутствующие паттерны Особенности реализации MVP для Windows Forms (тут, на мой взгляд, немного наворочено, но для ознакомления тоже подойдет) Изложу также свой опыт. Для каждого экрана должно быть три модуля: модель, представление и презентер. Модель отвечает за работу с данными (загрузка/сохранение). Она является своеобразным фасадом к некоторому источнику данных или к слою доступа к данным. Ее задача -- загружать и сохранять данные согласно задачам конкретного экрана. Представление отвечает за пользовательский интерфейс. Это, по сути, и есть ваш экран. Представление вызывает методы презентера (например, в обработчиках событий), а также предоставляет методы для отображения данных (они вызываются презентером). Презентер отвечает за взаимодействие между представлением (которое умеет только показывать данные и реагировать на действия пользователя) и моделью (которая знает только про данные). Как правило, это включает в себя логику представления данных, валидацию и другие вещи, тесно связанные с интерфейсом. Направление ссылок получается следующим: представление <-> презентер -> модель. Важно запомнить, что эта тройка нужна для каждого экрана. Это не что-то единое для всего приложения. Иногда возможны исключения: в случае необходимости единого управления несколькими экранами может быть несколько представлений/моделей и всего один презентер. Например, на экране есть кнопка "Загрузить заказы". В обработчике кнопки вызывается соответствующий метод презентера -- LoadOrders(). Внутри метода презентера идет обращение к модели, внутри модели непосредственно загружаются данные. После того, как презентер получил от модели данные, он может как-то преобразовать их для показа или выполнить над ними какую-то логику. После этого данные либо возвращаются из метода, либо -- что более канонично -- вызывается метод представления SetOrders(), внутри которого данные уже непосредственно загружаются в какой-либо контрол. По коду получается следующая структура. Представление: interface IOrdersView { void SetOrders(Order[] orders); } class OrdersForm : Form, IOrdersView { private OrdersPresenter presenter; public void OrdersForm() { ... // презентер можно создавать в конструкторе, // а можно иметь отдельный метод инициализации экрана, // который будет вызываться сразу после создания формы, // создавать презентер и вызывать у него метод загрузки данных presenter = new OrdersPresenter(this, new OrdersModel()); } void btnLoadOrders_Click(...) { presenter.LoadOrders(); } void IOrdersView.SetOrders(Order[] orders) { // загружаем данные в контрол } } Презентер: class OrdersPresenter { private readonly IOrdersView view; private readonly IOrdersModel model; OrdersPresenter(IOrdersView view, IOrdersModel model) { this.view = view; this.model = model; } void LoadOrders() { var orders = model.LoadOrders(); view.SetOrders(orders); } } Модель: interface IOrdersModel { Order[] LoadOrders(); } class OrdersModel : IOrdersModel { Order[] IOrdersModel.LoadOrders() { // тут логика по загрузке } } Интерфейсы в принципе опциональны, но удобны для тестирования и моков. Вопросы из комментариев: А как мне допустим если в таблице (DataGridView) изменили запись, записать эти изменения в базу. Сразу причем. То есть как только закончили редактирование сразу в базу. Отслеживаете событие изменения ячейки/строки, вызываете презентер, передав ему измененную запись, дальше презентер при необходимости валидирует и передает запись на сохранение модели. А модель уже обращается к слою доступа к данным (DAL'у). В интерфейсе IOrdersView объявлен метод SetOrders(), который принимает значение типа Order[]. Но что это за тип? Где он описан? В данном случае это какой-то пользовательский тип. Важное тут -- что SetOrders() принимает некоторые данные, которые готовы для отображения и которые представление (в данном случае форма) знает, как отображать. Это может быть и DataTable, и массив строк. Что угодно. Во View Вы создаете экземпляр класса OrdersPresenter и в качестве параметра передаете экземпляр класса OrdersModel. Насколько это укладывается в концепцию? Разве View и Model не должны быть развязаны и ничего не знать друг о друге? В идеале -- да, представление и модель должны быть развязаны и не должны ничего знать друг о друге. Создание экземпляра модели внутри представление является некоторым упрощением, и в целом втискивается в шаблон, поскольку представление не использует модель явным образом. Если же оставаться пуристом, то есть следующие варианты: Создавать модель внутри презентера. Главный недостаток -- плохая тестируемость презентера, поскольку невозможно подменить модель своей реализацией (а в тестах на презентер она всегда подменяется). Обойти это можно имея в презентере два конструктора -- один создает модель по умолчанию, второй -- принимает модель извне. Хотя и тут найдутся пуристы, утверждающие, что иметь специальные члены, которые используются только в тестах, плохо. Поэтому я иду по простому пути и всегда создаю модель в представлении. Передавать в представление уже созданный презентер. Т.о. модель будет инициализирована по крайней мере вне текущего представления. Однако по большому счету это ничего не дает, т.к. текущее представление будет открываться из другого представления, и теперь уже другому представлению нужно будет что-то знать о модели. Правильно я понял, что если у меня в программе 100 таблиц и мне нужно 100 форм для работы с ними, то для каждой формы я должен содать свой интерфейс IOrdersModel и класс унаследованный от него, который может быть уже не Orders, а Person, например и свой Presenter? Интерфейс типа IOrdersView тоже для каждой формы создавать? В общем случае да, для каждой XXXForm у вам должны быть XXXView, XXXPresenter и XXXModel. Однако если форм действительно много и они очень однотипные, то, возможно, достаточно будет обобщенных IView, Presenter, IModel, где T -- конкретный тип редактируемой сущности.

Ответ 2



WPF там тормозит. Поэтому необходимо на WinForms (прощай удобный MVVM). Прощаться с MVVM не обязательно если реализация моделей в WPF-приложении не зависит от контролов, то модели можно перенести в WinForms. Т.к. в WinForms есть своя реализация bindings, но она немного менее удобная чем в WPF. Пример Model и View с bindings. Модель отделена от View, т.к. модель остается прежней, если поменять View. [STAThread] static void Main() { var m = new Model(); View.Show(m); } class View { static public void Show(object model) { var f = new Form(); var b = new RadioButton() { Parent = f, Dock = DockStyle.Fill }; // привязываем RadioButton.Checked к значению bool SomeValue b.DataBindings.Add("Checked", model, "SomeValue"); var t = new TextBox() { Parent = f, Dock = DockStyle.Top }; // привязываем Text к свойству int Number. привязка как TwoWay в WPF. var tb = t.DataBindings.Add("Text", model, "Number", true, DataSourceUpdateMode.OnPropertyChanged); // это как IValueConverter в WPF tb.Format += (s, e) => // транслируем данные из model.Number в TextBox.Text e.Value = e.Value + "!"; tb.Parse += (s, e) => { // транслируем TextBox.Text в model.Number. var m = Regex.Match(e.Value.ToString(), "\\d+"); e.Value = Convert.ChangeType(m.Success ? m.Value : "0", e.DesiredType); }; f.ShowDialog(); } } class Model : INotifyPropertyChanged { public Model() { // таймер - для изменения свойств модели. они будут выводиться в View. var t = new Timer() { Interval = 500 }; t.Tick += (s, e) => { this.SomeValue = !this.SomeValue; this.Changed("SomeValue"); }; t.Start(); } // событие необходимо для уведомлений о том, что изменились значения свойств public event PropertyChangedEventHandler PropertyChanged = delegate { }; private void Changed(string name) { this.PropertyChanged(this, new PropertyChangedEventArgs(name)); } // свойства public bool SomeValue { get; set; } public int Number { get { return _Number; } set { _Number = value; MessageBox.Show(value.ToString()); // тут это только для примера Changed("Number"); } } int _Number = 123; } После открытия формы RadioButton переключается каждый полсекунды. А если начать ввод значения в TextBox, то строка преобразуется в число и передается в Model.Number и откроется MessageBoх. Как видно, Model и View отделены друг от друга. И достаточно просто заменить View, не меняя при этом Model. Если Model определить в отдельной сборке, то ее можно использовать как в WinForms, так и в WPF приложениях.

вторник, 16 июля 2019 г.

Логика построения MVP приложения с несколькими View-Presenter

К сожалению, все примеры из сети, которые я нашел, содержат примитивную логику построения MVP. Все компоненты инициализируются и связываются в main-методе. А как быть если пар View-Presenter N-ое число? Например, это могут быть модальные окна, или вкладки. Кому, с точки зрения архитектуры, будет правильно делегировать логику создания и инциализации нового View и связи его с Presenter ? Фабрике ?
DI использовать не получается, поскольку возникает круговая зависимость. View нужна ссылка на Presenter, а Presenter нужна ссылка на View.
Буду признателен за пример, или ссылку на рабочее swing приложение, использующее MVP.


Ответ

Я нашел ответ свой вопрос. Логика инциализации может быть передана View или Presenter, в зависимости от выбранного паттерна: Supervising Controller или Passive View. Тут подробное описание. Всем спасибо.

понедельник, 10 июня 2019 г.

Как назначить TextWatcher на EditField при MVP

Есть MainActivity, есть Presenter, есть отдельный класс Util который наследуется от TextWatcher. Задача в том, чтобы назначить этот TextWatcher на editField.
Сейчас MainActivity
mainPresenterImpl = new MainPresenterImpl(); mainPresenterImpl.translationInputFieldListener(translatedTextInput);
Presenter
@Override public void translationInputFieldListener(EditText translatedTextInput) { Log.v("TAG", "In presenter"); translatedTextInput.addTextChangedListener(textWatcherUtil = new TextWatcherUtil()); }
Util
public class TextWatcherUtil implements TextWatcher{
@Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override public void afterTextChanged(Editable editable) { }
}
Задача, назначить в методе afterTextChanged. Там уже дальше будет выполнятся метод сохранения значения в SharedPreferences.
P.S. Если мой вопрос составлен не корректно, то если минусуете, пожалуйста отпишите в комментариях, что сделанно не верно, чтобы ошибок не повторялось. Спасибо


Ответ

textWatcher это из категории дата биндинга в сторону MVVM. В случае паттерна MVP TextWatcher'a держите во вьюхе и в его колбеках вызываете презентер. и вся логика уже в презентере. т.е. будет вот так:
public class TextWatcherUtil implements TextWatcher{
@Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { presenter.beforeTextChanged(...); }
@Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { presenter.onTextChanged(...); }
@Override public void afterTextChanged(Editable editable) { presenter.afterTextChanged(...); }
Т.е. в вашем конкретном случае, вы в коллбеке afterTextChanged() вызываете presenter.onTextChanged() и в этом методе презентер вызывает свои Private методы работы с префами
А если у вас идет работа с префами, что можно считать репозиторием, то можете прикрутить к MVP еще и Interactor если простым языком, это класс между презнтером и моделью, который преобразует данные (+логику работы с ними) из модели в презентер и обратно в необходимом виде

пятница, 7 июня 2019 г.

Материалы по Clean Architecture в Android [закрыт]

Имею 3 года практики в Android разработке и недавно начал интересоваться такими вещами, как DI, Moxy, Cicerone и Clean Architecture. Здесь, на SO, я почти не вижу вопросов, относящихся к этой теме. В телеграмме очень мало людей состоят в чатах по ним. Как я понял, эти вещи изучаются в самом конце, на пути к "званию" Senior Developer'а и сложнее них нет ничего. Год назад я не понимал суть Dagger'а и MVP. Сейчас я, кажется, понимаю зачем это все нужно: так как разработка почти всегда командная, у всех участников проекта должны быть определенные стандарты проектирования. Разработчики будут лучше понимать друг друга.
Все же полностью понять эти вещи я не могу, я думаю здесь есть те, кто тоже их не понимал, но сейчас вполне их использует и счастлив. Хочу у них спросить какие книги они читали и как трудно там, в клине? Из мной прочитанных и полностью изученных, являются "Философия Java" и "Java 8 Полное руководство". Если Clean Architecture относится к архитектуре кода, то мне нужно сначала прочитать, например, "Паттерны проектирования" и "Эффективное программирование"? Какие книги я должен для начала прочитать?
Также хотел бы узнать какая сейчас средне ситуация с ними на рынке, они уже must have или пока еще в будет плюсом?
Пожалуйста, не пишите "Не понимаешь - не используй", я действительно хочу понять это. Давайте те, кто в этом уже разбирается, оставит тут информацию о книгах и свои советы.
Спасибо. Думаю, что книги, которые вы посоветуете (и Clean Architecture в общем) будут интересны многим.


Ответ

Роберт Мартин, человек описавший чистую архитектуру, написал книгу с соответствующим названием - "Чистая архитектура". Его фундаментальный труд в виде статьи лежит здесь. Разъяснения касательно разработки под android, и clean architecture в целом, расписаны в этом труде. Материалы по второй и третьей ссылке дадут вам надежную почву для старта.

понедельник, 8 апреля 2019 г.

Одному фрагменту один презентер?

Есть активити у которого 3-4 фрагмента .Для каждого из низ надо создать View и Presenter?или Несколько View можно привязать к одному презентеру?


Ответ

Ответ может быть дан только тогда, когда вы сами архитектурно примите решение, я постараюсь помочь:
Согласно MVP по структуре у вас у каждой view должен быть свой pesenter, логично. В нашем случае нужно просто решить что такое View, если к примеру у вас activity, то фрагменты в таком случае часто называют subView, и они будут являться дополнением основной View кое у вас Activity, тогда у них будет общий presenter. На самом деле в небольших проектах я бы делал именно так, будет меньше зависимостей, генерируемого кода, интерфейсов, и легче будет построить логику между фрагментами, хотя такую зависимость лучше избегать, но... Presenter этой view (activity) разрастеться, и потом будет тяжело расщеплять эти куски, также такая паутина будет тяжелее тестироваться, ну опять же если проект большой. Чтоб убедить, что такой подход довольно частый, гугл его предлагает во время аттестации и описывает в stable blue prints MVP.
Другой очевидный вариант, что activity выступает в роли View, а каждый фрагмент это тоже View, что тоже вполне логично, тогда нужно будет делать для них отдельные presenter. Те будет View(Activity) -> Presenter, View(Fragment) -> Presenter. Плюсы будут в том что такие фрагменты будут полностью независимы, и у нас не будет такого God object в роли жирного Presenter с логикой всех фрагментов внутри. Такой код легче будет поддерживать и тестировать, фрагменты можно использовать правильно по их назначению в других местах приложениях, как угодно при этом придется поправить только поведение, те в Java эту роль берет интерфейс. Проблема есть только в начальной структуре, например в Dagger2 чтоб выстроить такой граф нужно время и умение. Те по сути вы жертвуете начальным временем, чтоб решить какое будет поведение и всё. Такой подход я считаю более правильным, но его почему-то реже видно в исходниках или примерах в сети.

пятница, 1 марта 2019 г.

Нужно ли хранить презентер MVP?

В разных описаниях архитектуры MVP встречал разный подход к хранению\уничтожению presenter. Кто-то сериализует его, и затем в onCreate() восстанавливает. Кто-то никак не сохраняет и каждый раз в onCreate() создает новый.
Вопросы:
Зачем кто-то хранит peresenter, в нем ведь никогда не будет закешированных данных. Плюс, который я вижу, это не тратить каждый раз ресурсы на создание нового presenter в onCreate(). Но вместо этого нам придется каждый раз сериализовывать и десераилизовывать его. Оно того стоит? Зачем некоторые перед созданием presenter проверяют поле mPresenter в нашей вьюхе на null, ведь если вызывается onCreate(), то вьюхи либо не было никогда, либо она была уничтожена вместе со всеми полями и mPeresnter в любом случае будет null Зачем вызывается detachView() в onDestroy(), если после уничтожения активити ссылка на presenter перестанет существовать и GC скорее всего удалит его, вместе с ссылкой на view. Чтобы это было не скорее всего,а наверняка?
Лучшая практика - это создавать новый presenter в onCreate() или восстанавливать старый и атачить к нему новый экземпляр view?


Ответ

Часто нужно чтобы презентер жил во время процесса смены конфигурации, смены ориентации экрана, например. В этом случае какой-то длительный сетевой запрос может быть начат при одной ориентации, а закончен быть при другой или в процессе смены ориентации. В таких случаях презентеры хранят не в активти/фрагментах, но в отдельном месте, кое не зависит от жизненного цикла. Например в синглтоне, коий содержит мапу с презентерами по их ID. При таком подходе презентер создаётся если его нет в мапе, уведомляетя о прикреплении вьюхи и об откреплении оной. Так он сможет обработать ситуацию прихода данных во время "мёртвой вью" не нарвавшись на NPE и сможет продолжать работу пока пересоздаётся активити.
В любом случае подходов много на разные случаи. В каких-то случаях поворотов экрана не будет, в других же надо данные пришедшие во время поворота отобразить и используют ViewState для хранения состояния вьюхи, кое к ней применяют после её прикрепления к презентеру.