Страницы

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

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

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

Реализация синглтона с многократным освобождением ресурсов

#c_sharp #шаблоны_проектирования

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


Ответы

Ответ 1



А в чём проблема? Ну окружите весь доступ мьютексом. Поскольку вам нужна логика реинициализации, простое решение с Lazy-свойствами не проходит. public sealed class Singleton { // классическая реализация из статьи Jon Skeet'а // http://csharpindepth.com/articles/general/singleton.aspx private static readonly Lazy lazy = new Lazy(() => new Singleton()); public static Singleton Instance { get { return lazy.Value; } } private Singleton() { } /////////////////// ленивые свойства /////////////////// object accessMutex = new object(); string lazyProperty; public string LazyProperty { get { lock (accessMutex) return lazyProperty ?? (lazyProperty = CreateLazyProperty()); } } public void Reset() { lock (accessMutex) { lazyProperty = null; // обнулите остальные свойства, освободите IDisposable-ресурсы } } string CreateLazyProperty() { ... } }

Подмена базового класса в сложившейся архитектуре классов

#c_sharp #шаблоны_проектирования

                    
Имеется базовый класс (предположим, Window), у которого огромное число классов-потомков
(BlackWindow, TransperentWindow, ...).

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

Проблему можно проиллюстрировать следующим кодом:

class Window
{
    public virtual Rectangle ClientArea()
    {
        return new Rectangle(0, 0, 100, 50);
    }
}

class BlackWindow : Window
{
    public BlackWindow(Window window) { ... }
}

class BorderedWindow : Window
{
    public overide Rectangle ClientArea()
    {
        return new Rectangle(5, 5, 110, 60);
    }
}

Window window = new Window();
BorderedWindow borderedWindow = new BorderedWindow();
new BlackWindow(window).ClientArea();                // Rectangle(0, 0, 100, 50), ОК
new BlackWindow(borderedWindow).ClientArea();        // Rectangle(0, 0, 100, 50), FAIL!


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

Как можно исправить ситуацию?
    


Ответы

Ответ 1



Почему бы тогда не «Стратегия»? Вместо довольно неудобного наследования переходите на прогрессивную композицию. Пусть любой из классов получает ClientStrategy как часть конструктора. interface IClientAreaStrategy { Rectangle ClientArea { get; } } class DefaultClientAreaStrategy : IClientAreaStrategy { Rectangle clientArea = new Rectangle(0, 0, 100, 50); public Rectangle ClientArea { get { return clientArea; } } } class BorderedClientAreaStrategy : IClientAreaStrategy { Rectangle clientArea = new Rectangle(5, 5, 110, 60); public Rectangle ClientArea { get { return clientArea; } } } class Window { protected readonly IClientAreaStrategy clientAreaStrategy; public Rectangle ClientArea { get { return clientAreaStrategy.ClientArea; } } public Window(IClientAreaStrategy clientAreaStrategy) { this.clientAreaStrategy = clientAreaStrategy; } } class BlackWindow : Window { public BlackWindow(Window window) : base(window.ClientStrategy) { // ... } } (Кстати, у нужно ли наследование для BlackWindow? Возможно, вам снова нужна композиция?) Для конкретно этого случая вам не нужен целый интерфейс IClientAreaStrategy, а просто Func или даже просто Rectangle. Но в более сложном случае понадобится интерфейс.

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

В чем преимущества выделения контроллера в mvc?

#java #mvc #шаблоны_проектирования


У меня сразу несколько вопросов по mvc. Спрашивать буду на примере игрушечной программки,
которую специально для этого написал:

public class View implements Observer {

private Model model;
private IController controller;

private JLabel firstNumber = new JLabel();
private JLabel secondNumber = new JLabel();
private JLabel resultLabel = new JLabel();

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            Model model = new Model();
            IController controller = new Controller();
            View view = new View();
            view.setModel(model);
            view.setController(controller);
            view.createAndShowGUI();
        }
    });
}

public void setModel(Model model) {
    this.model = model;
    model.addObserver(this);
}

public void setController(IController controller) {
    this.controller = controller;
}

public void createAndShowGUI() {
    JFrame frame = new JFrame("mvc train");
    frame.setSize(400, 400);
    frame.getContentPane().add(createMainPanel());
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
}

private Component createMainPanel() {
    JPanel mainPanel = new JPanel();
    mainPanel.add(firstNumber);
    mainPanel.add(secondNumber);
    mainPanel.add(resultLabel);
    JButton inc1 = new JButton("increment first");
    inc1.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            controller.execute("inc1", model);
        }
    });
    JButton dec1 = new JButton("decrement first");
    dec1.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            controller.execute("dec1", model);
        }
    });
    mainPanel.add(inc1);
    mainPanel.add(dec1);
    JButton inc2 = new JButton("increment second");
    inc2.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            controller.execute("inc2", model);
        }
    });
    JButton dec2 = new JButton("decrement second");
    dec2.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            controller.execute("dec2", model);
        }
    });
    mainPanel.add(inc2);
    mainPanel.add(dec2);
    return mainPanel;
}

@Override
public void update(Observable arg0, Object arg1) {
    firstNumber.setText("" + model.getA());
    secondNumber.setText("" + model.getB());
    resultLabel.setText("" + (model.getA() + model.getB()));
}

}


Это класс представления. Получается такая картинка:


Кнопка increment first увеличивает на единицу первое число, increment second - второе,
decrement first уменьшает первое число на единицу, decrement secomd - второе. В общем,
после двух нажатий на increment first и одного нажатия на increment second получаем
такую картинку:



Два числа хранятся в модели. Вот она:

public class Model extends Observable {

private int a;
private int b;

public void incrementA() {
    a++;
    setChanged();
    notifyObservers();
}

public void incrementB() {
    b++;
    setChanged();
    notifyObservers();
}

public void decrementA() {
    a--;
    setChanged();
    notifyObservers();
}

public void decrementB() {
    b--;
    setChanged();
    notifyObservers();
}

public int getA() {
    return a;
}

public int getB() {
    return b;
}

@Override
public void addObserver(Observer observer) {
    super.addObserver(observer);
    setChanged();
    notifyObservers();
}

}


Мой контроллер:

public class Controller implements IController {

@Override
public void execute(String action, Model model) {
    switch (action) {
    case "inc1":
        model.incrementA();
        break;
    case "inc2":
        model.incrementB();
        break;
    case "dec1":
        model.decrementA();
        break;
    case "dec2":
        model.decrementB();
    }
}

}


Теперь вопросы.
Самый главный вопрос собственно в названии.


Чем нам помог контроллер? Код из веток case оператора switch в
контроллере мог бы с таким же успехом находится в слушателях. На
каждую ветку по слушателю. В итоге слушателей все равно приходится
создавать, что бы вызвать метод контроллера. А потом в этом методе в
операторе switch прописывать тот же самой код, что мог бы быть в
слушателях. Может быть я вообще неправильно реализовал mvc?
Подозреваю что именно так. Причем именно в части контроллера. Потому
что по нему в сети мало информации. Если это так то прошу подсказать
как правильно. Слышал что можно создавать контроллеры для каждого
элемента (в данном случае для каждой кнопки). Но ситуация не сильно
поменяется.
В моем примере все методы, которые вызывается в ветках case не имеют параметров.
В реальных программах это скорее всего будет не так. Некоторые кнопки могли бы требовать
вызовов методов с параметрами, причем с разным количеством параметров. Что делать в
такой ситуации?
Где лучше вычислять самое правое число? В моем примере я делаю это в функции update().
Не лучше ли это делать в модели?

    


Ответы

Ответ 1



Контроллер помогает структурировать код. Весь код, реагирующий на пользовательский ввод, принадлежит контроллеру. Можно было бы всё слить в один класс, и делать всю логику в OnClick, и код получился бы наверное даже короче. Но вашей целью должен быть не самый короткий, а самый понятный и легко поддерживаемый код. Мне кажется, ваша диспетчеризация команд по строкам — неправильна. Команды должны быть не строками, а объектами. (У нас же ООП, в конце-концов, да?) И вместо длинного свитча у вас должен быть всего лишь вызов Invoke. [Но это практика из MVVM, общепринятого паттерна в WPF, а традиции в MVC могут быть и другими.] Вычисления принадлежат модели и только ей. Представление должно не выполнять работу модели (а сложение в реальной программе превратится в серьёзное, длинное вычисление), а лишь только отображать её.

воскресенье, 8 марта 2020 г.

Экспорт модели в .xlsx соблюдая SOLID

#c_sharp #ооп #шаблоны_проектирования #solid


В БД есть таблица результатов тестирования по русскому языку и математике (SubjectCode=2):

dbo.Result


Во второй таблице находятся просто сведения о школах:

dbo.School



Таких результатов бывает 40.000-70.000. Нужно на выходе получить для каждого тестируемого
вот такой отчет в pdf-файле:



Мое решение:


Создал Excel-шаблон;
Беру данные из базы и заношу их в этот xlsx-шаблон;
Сохраняю этот шаблон в pdf-файл;
И так для каждого ученика.




LearnerReport.cs

namespace so16092016.Models
{
    public class LearnerReport
    {
        public string SNS { get; set; } //Surname Name SecondName
        public string SchoolName { get; set; }
        public string ClassName { get; set; }
        public int TestResult5 { get; set; }
    }
}


Program.cs

using Excel = Microsoft.Office.Interop.Excel;

namespace so16092016
{
    class Program
    {
        static void Main(string[] args)
        {
            resultsEntities context = new resultsEntities();
            ResultsRepository resultsRepository = new ResultsRepository(context);
            var ma_results = resultsRepository.GetTList().Where(x => x.SubjectCode
== 2); //получить результаты по математике

            Excel.Application app = new Excel.Application();
            app.DisplayAlerts = false;
            Excel.Workbook book_template = app.Workbooks.Open(@"шаблон_отчета.xlsx");
            Excel._Worksheet sheet_template = book_template.Sheets["отчет"];

            foreach(var ob in ma_results)
            {
                //1. Создаем объкт LearnerReport из БД
                LearnerReport report = new LearnerReport
                {
                    SNS = $"{ob.surname} {ob.name} {ob.SecondName}",
                    SchoolName = ob.SchoolName,
                    ClassName = ob.ClassName,
                    TestResult5 = ob.TestResult5                     
                };

                //2. Экспорт объкта LearnerReport в шаблон xlsx
                sheet_template.Range["C4"].Value2 = report.SNS;
                sheet_template.Range["C5"].Value2 = report.SchoolName;
                sheet_template.Range["C6"].Value2 = report.ClassName;
                sheet_template.Range["C9"].Value2 = report.TestResult5;

                //3. Сохраняем полученный файл в .pdf на рабочем столе
                string file_name = $@"{Environment.GetFolderPath(Environment.SpecialFolder.Desktop)}\{report.SNS}.pdf";
                sheet_template.ExportAsFixedFormat(Excel.XlFixedFormatType.xlTypePDF,
file_name);
            }

            book_template.Close(0);
            book_template = null;
            app.Quit();
            app = null;
        }
    }
}


Необходимо: Приложение работает и дает необходимый результат. Но вы наверное видите,
что оно далеко от ООП (SOLID) и как результат очень трудно его "допиливать" и масштабировать.
Помогите правильно спроектировать данный механизм формирования подобных отчетов: 


логика экспорта модели в .xlsx должна быть у самой модели или
необходимо создать отдельный класс-менеджер для этого?
какие должны быть модели?
как правильно создавать модель-отчета на основе объектов БД? 
какой порождающий паттерн здесь больше подходит?

    


Ответы

Ответ 1



Конкретно в вашем случай нет необходимость, применять какие либо паттерны, т.к. у вас довольно простая программа и паттерны добавят лишь ненужную сложность (применять паттерны ради паттернов плохая практика). Паттерны сгодятся если вы пишите большие Enterprise приложения, где необходима гибкость и масштабируемость. Если я правильно понял, то вы хотите отрефакторить программу. 1) Из кода я не понял что вы используете для получения данных из БД, но посоветовал бы использовать какую нибудь ORM, например Entity Framework 6. Создать DbContext и использовать его в репозиториях ResultsRepository и SchoolRepository Все выборки засунуть в соответствующий репозиторий например class ResultsRepository { // возвращает все результаты public IEnumerable GetResults() // возвращает все результаты по заданому предмету. public IEnumerable GetResults(Subject subject) // возвращает все результаты для заданой школы. public IEnumerable GetResults(int idSchool) // возвращает все результаты для заданой школы по заданому предмету. public IEnumerable GetResults(int idSchool, Subject subject) } class SchoolRepository { // возвращает список всех школ. public IEnumerable GetSchools() } 2) Модель данных приблизительно выглядит так class Result { [Key] public Guid Id { get; set; } public string Surname { get; set; } public string Name { get; set; } public string SecondName { get; set; } [NotMapped] public string SNS => $"{Surname} {Name} {SecondName}"; public School School { get; set; } public string ClassName { get; set; } public Subject Subject { get; set; } public int TestResult5 { get; set; } } class School { [Key] public int Id { get; set; } public string Name { get; set; } } enum Subject { RussianLanguage = 1, Mathematics = 2 } 3) Можно создать отдельный класс для работы отчётами class LearnerReportManager { public Excel._Worksheet CreateReport(Result result) public SaveReport(Excel._Worksheet reportWorksheet, string fileName) } 4) Для формирования Excel отчёта луче используйте EPPlus или более крутой File Formats от Syncfusion (у них есть бесплатная версия). вы так не будите зависеть от установленного MS Office. 5) Если кол. формируемых отчетов за раз от 40 до 70 тыс., то было бы не плохо добавить многопоточное создание отчетов, это существенно уменьшит время создания отчётов.

вторник, 25 февраля 2020 г.

Приложения баз данных. Паттерны проектирования.

#c_sharp #шаблоны_проектирования #sql_server


Здравствуйте! Есть небольшая база данных, допустим, библиотеки. Какие паттерны проектирования
стоило бы использовать? Какие правила стоит учитывать при проектировании базы данных
и самого приложения? Стоит ли использовать паттерн MVP и какая выгода будет от этого?
Заранее спасибо.    


Ответы

Ответ 1



Как сказали в уточнениях, паттерн MVP действительно не имеет отношение к БД. Он скорее имеет отношение к интерфейсной части (client-side). Я бы сказал, что самое главное правило, которого стоит придерживаться при разработке приложений, которые обращаются к БД, это трехслойная архитектура: UI - Business Logic - DB. В самом тупом случае, слой DB у вас может представлять собой набор хелперов для работы с БД. В частности, отмечу некоторые важные моменты: все прямые обращения к БД должны быть только в слое DB, никаких прямых вызовов к БД, разбросанных по всему коду все обращения к слою DB должны исходить только из слоя BL, в UI не должно быть прямых вызовов кода из слоя DB В остальном, реально не имеет значения (особенно в небольшом проекте), как у вас будет организовано получение и сохранение данных: тупые датасеты, ORM или что-то еще. Существует несколько распространенных паттернов для работы с данными, однако их применение также не очень критично (и уж точно не стоит применять их ради самого факта применения, о чем говорили выше). Главное — обособить код для работы с базой, это уже избавит вас от множества проблем. Ниже список некоторых паттернов для работы с БД: http://design-pattern.ru/patterns/repository.html http://ru.wikipedia.org/wiki/Data_Access_Object http://design-pattern.ru/patterns/table-data-gateway.html http://design-pattern.ru/patterns/row-data-gateway.html http://design-pattern.ru/patterns/active-record.html -- тут я готов спорить, предпочитаю иметь тупые объекты (DTO, только данные) плюс классы, которые отвечают за загрузку/сохранение http://design-pattern.ru/patterns/data-mapper.html

понедельник, 24 февраля 2020 г.

Ревью Архитектуры приложения

#c_sharp #архитектура #шаблоны_проектирования #инспекция_кода


Есть решение разбитое на следующие слои: DAO, DAL, Services Gui где,


DAO - здесь хранятся классы описывающие доменнные модели;
DAL - Generic Repository и его реализация; 
Services - здесь у меня методы по типу следующего: IEnumerable GetEntities();
Gui - клиентская часть


предвижу сразу вопрос чем Services отличается от Repository, в репозитории у меня
базовые методы CRUD:

void Create(Entity entity);
void Update(Entity entity);
void Delete(Entity entity);
IQueryable Table {get;}


в Services же у меня строются более сложные запросы: соединения, группировка например:

var _orders = получаем данные;
var _histories = получаем данные;
var orders = _orders.Join(_histories, o=>o.Id, h=>h.OrderId,(o,h)=>new OrderView
                    {
                        //Формируем необходимое представление
                    })
                    .ToList();


ну и т.п.

зависимости между проектами следующие:


DAO подключен в качестве reference в DAL, Services, Gui
DAL подключен в качестве reference в Services, Gui
Services подключен в качестве reference в Gui


Хочу проект сделать так что бы дальнейшая поддержка причиняла как можно меньше проблем.

Правильно ли я поступил разбив проект на более мелкие части, или я сделал так зря
и необходимо слить все в один проект а разделение сделать на уровне директорий внутри
решения и namespace.

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


Ответы

Ответ 1



Есть самые разные способы проектирования приложения. Как я вижу вы пытаетесь использовать DDD. Этот паттрен достаточно универсален, но не является сильвербуллетом, а так же имеет очень много вариаций реализации, например CQRS+ES или DDD+Onion которые имеют свои назначения, а вместе с ним свои плюсы и минусы. По мимо DDD и его вариаций, существуют такие архитектурные паттерны как EDA, SOA, NLayer и другие. Что бы поддержка не причиняла больших проблем, нужно сначала выбрать правильную архитектуру, а это вопрос не только правильного выбора паттерна, но языка, стека технологий и многого другого. Сам же выбор зависит от решаемой задачи и различных условий.

Ответ 2



Был у меня опыт разработки именно при такой архитектуре, только за место Entity Framework использовался NHibernate. Ну и был еще один дополнительный слой абстракции для веб сервисов. То бишь было параллельно несколько веб сервисов, которые пользовались фунциями из слоя Services. В целом код получался весьма читабельным и понятным. Но вместе с этим при реализации функции в GUI приходилось протягивать эту функцию через все слои это со временем начинало бесить. Получалось как-то слишком много лишнего кода который приходилось писать лишь из-за всех этих слоёв. Дебаг так же происходил чуть медленнее. Были некоторые трудности с определением того куда стоит засунуть функцию, в DAL или Services. В целом считаю данную архитектуру весьма адекватной для решения многих задач. Она предлагает высокую степень гибкости и читабельности, но может стать очень громоздкой. Что бы избавиться от громоздкозти этого монолита мне кажется, можно было бы начать отщипывать от монолита микросервисы.

пятница, 14 февраля 2020 г.

Паттерны Poor Man's DI и Bastard Injection

#шаблоны_проектирования


Наткнулся в сети на комментарий, в котором упоминались паттерны Poor Man's DI и Bastard
Injection -- что это такое и в чём между ними разница?

(Любой язык программирования, не обязательно c#)
    


Ответы

Ответ 1



Poor Man's DI, также называемое Pure DI - это DI без специальной библиотеки-контейнера. В случае использования такого подхода все зависимости классов выносятся в параметры их конструкторов либо в свойства, причем все классы, конструкторы и свойства обязаны быть открытыми. Пример на C#: public class Baz { private readonly IBar bar; public Baz(IBar bar) { this.bar = bar; } } В Composition Root же пишется код, который собирает все эти классы вместе - то есть весь граф объектов собирается вручную. Код получается вроде вот такого: var foo = new Foo(); var bar = new Bar(foo); SomeFrameworkClass.SetControllerFactory(() => { // точка входа var baz = new Baz(bar); return new Controller(foo, baz); }); В чистом виде Pure DI используется редко, однако если ваши классы написаны с использованием Pure DI-подхода, то к проекту можно прикрутить любую библиотеку-контейнер, или же сменить ее в любой момент. Bastard Injection же - это полная противоположность прошлому подходу. В случае использования Bastard Injection каждый класс самостоятельно затягивает свои зависимости из контейнера используя его как Service Locator: public class Baz { private readonly IBar bar = App.container.Resolve(); } Главный недостаток такого подхода - зависимости оказываются раскиданы по коду. К примеру, без доступа к исходникам единственный способ определить какие зависимости нужны классу Baz - это вызвать его конструктор и посмотреть на отсутствие чего он ругнется. А уж опциональные зависимости без документации найти можно только декомпиляцией.

четверг, 13 февраля 2020 г.

Какой паттерн проектирования выбрать?

#проектирование #шаблоны_проектирования


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

Посоветуйте, какие паттерны следует использовать в слое бизнес-логики, чтобы затем
реализовать, скажем, сервисный слой. Какие есть варианты?

Поправьте меня, если я чего-то недопонимаю, или вопрос задан некорректно. Спасибо.
    


Ответы

Ответ 1



В сервисном слое (слой бизнес-логики) можно использовать многие паттерны, как ОО паттерны, так и другие паттерны более узкого или широкого профиля. Все зависит от того, какие у вас задачи в этом слое встречаются. Невозможно дать общий готовый список шаблонов, вот, мол, используй вот это и будет тебе счастье. Не-воз-мож-но. И не нужно. Забудьте на время о паттернах. Они не самоцель. Сперва делайте так, как вам кажется правильным. И в какой-то момент некоторые задачи покажутся знакомыми и уже их при желании можно решить, применив тот или иной паттерн. Еще лучше, забудьте о паттернах на годик. И вернитесь к ним после того, как получите чуть больше опыта. Тогда, я уверяю вас, вы совершенно по-другому на них взглянете, и более того, даже поймете, что использовали некоторые из них, не осознавая этого. Проанализировав свой опыт, поймете, где могли бы применить тот или иной шаблон. Еще раз повторюсь, вы неправильно понимаете для чего нужны паттерны, если отталкиваетесь от них. Шаблоны -- не самоцель, а лишь средство. Это как спрашивать "я хочу построить дом, какой кирпич мне взять?". Вы не с того начинаете, строитель!

Ответ 2



Если в бизнес-логике находится неизменяемая часть программы, а реализация сервисов может меняться, то в такой ситуации сервисы надо определить как интерфейсы, и реализовать их в классах. При этом в бизнес-логику передается только интерфейс сервиса, а сами классы (т.е. реализация интерфейса) должна быть недоступной из бизнес-логики. Это позволяет менять реализацию сервисов, не меняя бизнес-логику. Например, использовали сервис логирования, который выводил данные в файл, а позже решили выводить данные в облачный сервис. Для этого надо заменить только реализацию сервиса, а бизнес-логика остается без изменений. Такой паттерн называется "Cтратегия" -- это изолирование изменяемой части посредством интерфейса. Для передачи интерфейсов сервисов в бизнес-логику используется инъекция зависимостей (Dependency Injection, DI). Для внедрения зависимостей используются разные способы и целые фреймворки, например, MEF.

Как перевести понятия MVP/MVC в термины WinForms?

#c_sharp #winforms #шаблоны_проектирования


В описании паттерна MVP (Model-View-Presenter) сказано следующее:


Model (Модель) — предоставляет данные для пользовательского интерфейса.   
View (Представление) — реализует отображение данных (Модели) и маршрутизацию пользовательских
команд или событий Presenter-у.   
Presenter — управляет Моделью и Представлением. Например извлекает данные из Модели
и форматирует их для отображения в Представлении.   


Что в WinForms приложениях соответствует View и Presenter?
    


Ответы

Ответ 1



Стандартная лапша - в code-behind форм в обработчиках событий производится запросы к модели, какая-то работа и визуализация результата. В mvp модель остается моделью, то есть это набор бизнес-логики, а код в форме делится на View и Presenter View - это формы, code-behind этих форм, где производится работа по визуальной составляющей, а также туда же сводятся подписки на события контролов Presenter - туда вынесен рабочий код, который не являются частью визуализации. View содержит ссылку на presenter и вызывает его методы. А presenter содержит ссылку на View через интерфейс То есть View в обработчиках событий контролов делегирует presenter-у "сделай это", а presenter содержит управляющий код, который опросит модель, получит результат и потом дернет View и скажет ему "отрисуй такое то" Таким образом визуализирующий и управляющий код разделяются. По просьбе @Stack привожу пример. Псевдошарпокод. Имеем простую модель (она же бизнес логика). Просто читает файл планет class PlanetReader{ public IList ReadPlanetsFromFile(){ return File.ReadLines(...) } } Далее интерфейс представления public interface IMainView{ void ShowPlanets(IList planets); } ну и презентер public class Presenter{ private IMainView _view; public Presenter(IMainView view){ _view=view; } public void LoadPlanets(){ var planets=new PlanetReader().ReadPlanetsFromFile(); _view.ShowPlanets(planets); } } и представление winforms public partial class MainForm : Form, IMainView{ private Presenter _presenter; public MainForm(){ InitializeComponent(); button.Click+=OnButtonClick; _presenter=new Presenter(this); } public void OnButtonClick(...){ _presenter.LoadPlanets(); } public void ShowPlanets(IList planets){ TextBox.Text=string.Join("\n", planets); } } для WPF код в общем то идентичен Winforms кроме класса родителя и сигнатур обработчиков событий (хотя лучше все таки MVVM) public partial class MainWindow : Window, IMainView для консоли class ConsoleApp : IMainView { private Presenter _presenter; public ConsoleApp(){ _presenter=new Presenter(this); } public void Run(){ Console.WriteLine("press any key to load planets'); Console.ReadKey(); _presenter.LoadPlanets(); } public void ShowPlanets(IList planets){ Console.WritrLine(string.Join("\n", planets)); } } за IMainView может быть скрыто что-угодно - от простой формы winforms до самописной системы отрисовки на удаленной машине. Плюшки: "Логика интерфейса" по факту делится на "презентационную логику" и собственно "логику визуализации". В "презентационную логику" входит решение, что делать по нажатию на кнопку и что делать с результатом - по сигналу от UI работает с моделью и просит потом "логику визуализации" отобразить результат. В "логику визуализации" входит отображение того, что просят - контролы, события, установки полей у контролов, кобминирование контролов и так далее. Презентер не имеет прямой ссылки на представление, поэтому детали отображения на UI инкапсулированы (опять же чистота кода, вся сложность спрятана) и возможность заменять представление на другое. Презентер не имеет прямой ссылки на представление, а значит вместо представлениям можно подсунуть заглушку и протестировать презентер. Тестировать же логику интерфейса без такого разделения крайне сложно (нужно тыкать мышью руками или автоматикой и считывать как то результаты с экрана)

Ответ 2



Решил добавить еще один ответ, потому что размер комментариев ограничен, а у @Stack все перемешалось в голове и мой пост отвечает не на вопрос "как", а на "где и почему?" Цель MVP не сделать возможность подмены UI (для этого просто нужно менять UI), а разделить ответственности и обеспечить тестируемость. Допустим нам нужно написать приложение под разные абстрактные платформы. у нас разные UI на разных платформах. варианты: Делаем проект-ядро, где будет логика приложения и создаем по проекту UI на каждую платформу и при компиляции оно разберется Делаем UI на кроссплатформенных контролах прячем за фасадом (паттерн такой) любые реализации UI приводя их к обобщенному виду фасада. Банально делается класс/интерфейс за которым прячется реальный UI. но это не MVP, а просто сокрытие сложности и зависимостей Для подобной же цели служит паттерн "мост". Отличие от варианта с "фасадом", что, вместо сокрытия реализации за фасадом, более ярко выражена связующая шина между приложением и графическим приложением и независимость реализации обоих частей А где же MVP? Когда задача подмены UI решена, то нужно реализовать эти самые UI. Допустим одна из наших UI на WinForms. В силу особенностей Visual Studio мы получаем на выходе в Code-Behind основной формы кучу методов (евент-хендлеры и кучу приватных методов реализующих логику интерфейса). Мало того, что это лапша кода, так еще все это невозможно тестировать - для тестирования нужно тыкать на кнопки и отслеживать изменение GUI, что сложно. Возникает желание навести порядок. Это можно сделать 101 способом (MVC/MVP/MVVM/MVVMC/свой вариант), просто некоторые в конкретном случае будут менее удобны чем другие, а ведь еще хочется не терять визуальный дизайнер студии. И паттерн MVP хорошо подходит для UI, где нет нормальных биндингов, потому что прост и прячет кучу визуализационного кода за интерфейсом. Это и Winforms, и я вот под андроид тоже использую MVP. вообще то это очевидное решение - разделить godobject на классы и удалить зависимости от реализаций, спрятав за интерфейсами. Типичные варианты решений и обозвали паттернами Разделив визуализацию и логику мы получаем возможность тестировать ту логику, что вошла в презентер (а туда входит вся управляющая логика взаимодействия с моделью и управления визуализацией). Тестировать просто, ведь презентер просто класс с зависимостями. Если же еще хочется тестировать кнопочки, то winium в руки, но тестировать UI всегда сложно "как в вашем примере тестировать Presenter. из-за new в методе LoadPlanets() { var planets=new PlanetReader()...} его невозможно тестировать отдельно от конкретной модели" @Stack Для этого используется то, что называется "принцип инверсии зависимостей". Еще одно очевидное решение, имеющее свое название Делаем интерфейс IPlanetReader и выносим его в конструктор. Или же используем ServiceLocator. Или же IoC-контейнеры. Или любой другой вариант. MVP всего лишь вариант разделения на ответственности "где за что отвечает", а реализация ответственностей уже не входит в него и там полная свобода действий тестирование UI это проверка того как смотрятся шрифты -это вообще тестируется только глазами. мы же говорим про автоматическое тестирование. Тестирование через эмуляцию нажатий кнопок и считыванием информации с контролов. недостатки: сложность считывания даннных (мало ли какие контролы), сложность изоляции (для проверки, что пользователь существует нам нужно подсунуь базу где пользователь есть), скорость работы (из-за того, что полностью работают все слои приложения). Не выявляет функциональные ошибки, когда делает не то, но показывает правильный результат. При тестировании презентера можно изолировать и подсунуть любые данные, проверить что идет правильный порядок вызовов модели. Пишется намного проще, есть изоляция и высокая скорость работы. В идеале должно быть больше различных тестов, но выбор необходимых тестов определяется возможностями (тесты бизнесу не нужны. они нужны программисту и за них платить не любят). Лично мне куда проще тестировать презентер (он выявляет функциональные ошибки и за них мне по шапке дают), а кривизна дизайна не фатальна (вся логика в презентере, в представлении логики практически нет и сломать его сложнее, а вреда от этого минимум).

Ответ 3



Давайте представим, что надо создать справочник планет Солнечной системы в виде Console Application. Список планет выводится в консоль. Для выбора планеты надо набрать ее порядковый номер и нажать Enter. В соответствии с описанием паттерна в своей программе надо определить три отдельных класса. При этом Presenter не должен знать как работает View, который по команде Presenter'a, выводит список планет в консоль, а введенный порядковый номер планеты возвращает Presenter'у. Считается, что такое разделение и сокрытие деталей реализации позволяет сделать части системы заменяемыми. Но это не так. Представьте, что прошло время, и надо в справочник планет добавить миллионы астероидов, сделать 3D визуализацию движения объектов и реализовать сенсорное управление. Понятно, что если Model можно дополнить, то Presenter и View придется создавать с нуля, т.к. UI-логика совершенно отличается от той, что была в консольном приложении. При этом логика в Model (бизнес-логика) никак не меняется и не зависит от UI-логики. Паттерн MVP является производным от MVC (Model-View-Controller), который был представлен в 1979 г. и назывался Thing-Model-View-Editor. На сайте автора, профессора Реенскауг Трюгве, видно на графике, что Controller и несколько View объединяются в Tool. Графический интерфейс в Windows построен на основе оконной подсистемы - Windows Manager, GDI и DWM. Есть десятки системных функций WinAPI, которые позволяют управлять окнами, перехватывать события движения мыши и т.д. В WinForms базовым классом для создания графического интерфейса является Control. Если посмотреть на определение MVP, то получается, что раз Control позволяет отображать данные и получать события мыши и т.д., то Control - это и есть View. Но это не так. Дело в том, что Control является оберткой для оконной подсистемы Windows, взаимодействие с которой происходит через вызовы функций Win API (их можно увидеть в исходниках Control). Движение мыши, нажатия клавиш и другие системные события попадают в Message Queue (это очередь системных событий, которые транслируются в вызовы соответствующих методов Control; системные события можно перехватывать, если переопределить метод WndProc или PreProcessMessage). Получается, что View - это оконная подсистема Windows, а Presenter - это Control. MVP : WinForms приложение ---------------------------------------------- Мodel : Объекты для работы с данными ---------------------------------------------- View : Оконная подсистема Windows ---------------------------------------------- Presenter : Производные от Control Пример на C# с цитатами из определения паттерна MVP. interface IModel { int Data { get; set; } } class Presenter : TextBox { IModel Model; public Presenter(IModel m) { this.Model = m; var d = m.Data; // извлекаем данные из модели var s = d.ToString(); // форматируем данные для отображения this.Text = s; // передаем в Представление (оконная подсистема Windows). } // маршрутизация пользовательских команд protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); if (this.Text.Contains("1")) { // логика var d = int.Parse(this.Text); // форматируем данные this.Model.Data = d; // управляем моделью } } }

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

Паттерн “Посетитель”. Java

#java #шаблоны_проектирования


В первый раз реализую, и что-то не заработало.

public interface FieldVisitor {
    Object visit(Field field);
}


А это методы из реализации:

private FieldVisitor factory = new FieldVisitor() {
    public Object visit(PresetField presetField) {
        ....
    }

    public Object visit(StringField stringField) {
        ....
    }

    @Override
    public Object visit(Field field) {
        System.out.println(field.getClass());
        ....
    }
};


Вызываю так:

public Node getFieldFor(Field field) {
    return (Node) factory.visit(field);
}


Видно что приходят объекты разных классов:

class com.dma.params.model.field.PresetField
class com.dma.params.model.field.PresetField
class com.dma.params.model.field.NumberField
class com.dma.params.model.field.NumberField
class com.dma.params.model.field.StringField
class com.dma.params.model.field.BooleanField


Однако попадаю как видно только в метод для суперкласса. В общем, как я понял, какой
тип указателя, такая функция вызывается. Я могу как то изменить это поведение? Чтобы
вызывалась функция для фактического типа или я где-то тупанул с паттерном?
    


Ответы

Ответ 1



Потому что всё совершенно не так. Во-первых интерфейс визитёра должен объявлять методы для всех классов иерархии public interface FieldVisitor { Object visit(PresetField field); Object visit(StringField field); ... } Во-вторых родоначальник иерархии объявляет абстрактный метод accept, в котором принимается визитёр class Field { ... public abstract void accept(FieldVisitor visitor); ... } В-третьих конкретные такие потомки реализуют/переопределяют метод-акцептор таким образом, чтобы вызывался метод визитёра для нужного (своего) класса class StringField { ... @Override public abstract void accept(FieldVisitor visitor) { visitor.visit(this); // вызов visit(StringField field) } ... } Наконец визитёр может посетить иерархию, предварительно реализовав интерфейс визитёра вызовом акцептора ... field.accept(new FieldVisitor() { @Override Object visit(PresetField field) { ... } @Override Object visit(StringField field) { ... } }); ... Благодаря полиморфизму вызывается accept конкретного класса, который в свою очередь вызывает "свой" перегруженный метод визитёра.

воскресенье, 26 января 2020 г.

Паттерн ООП, ограничивающий количество экземпляров класса по аттрибуту

#ооп #многопоточность #шаблоны_проектирования


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


Ответы

Ответ 1



В общем случае наиболее подходит под вашу задачу паттерн Factory. В частном случае, когда разрешен всего 1 объект это классический Singleton. Единственное, я бы не стал возлагать на паттерн задачу безопасности ибо что Singleton, что Factory ломаются на раз-два. Update Применительно к Java такой способ ведь действительно существует и часто используется в реальной жизни - например кэширование пула коннектов (например JDBC). Как известно, коннект ресурс достаточно дорогой и ценный и если по ходу пьесы коннект берется во многих местах имеет смысл организовать кэширование коннектов с ограничением количества оных. Вполне аналогично также и ставится ограничение на количество окон в системе. Да в том же Android'е надысь писал такую штуку. Ставится корневой класс - прародителей всех Activity и вперед создавать списочек, контроль все как надо. Так что надо попроще и без кошачьих завихрений, а то совсем ужо замутили головы boost commiter, перегрузка операторов... P.S. Ну вы млин даёте :)

Ответ 2



Эта задачу (если решать ее в общем виде) очень сложно решить хорошо в языках с Garbage Collector'ами в силу недетерминированности работы последних. Под хорошим решением подразумевается что-то типа специальной фабрики ObjectFactoryWithAttributeChecking, в которой хранятся слабые ссылки на объекты выбранных типов, и при попытке создания нового объекта она проверяет, жив ли прошлый / прошлые или нет. Понятно, что, в силу недетерминированности GC очень легко получить ситуацию, когда на объект уже никто не ссылается, но он еще не заколлекчен (и в этот момент фабрика ObjectFactoryWithAttributeChecking будет вести себя некорректно). Плохой подход - вводить такую же фабрику с явными методами Create и Dispose - решает задачу, но сводит на нет преимущества автоматической сборки мусора и крайне неустойчив. Если calling site в случае такого подхода забудет вызвать Dispose, то ваша фабрика моментально оказывается в неконсистентном состоянии. Если задача поставлена из соображений отладки, то самый верный путь решения заключается в инжектировании своего кода в аллокатор на низком уровне. .NET, например, допускает такие штуки для решения задач профайлинга, но, в случае выбора такого подхода каждый кейс нужно рассматривать индивидуально. В случае языков без сборки мусора (типа C++) вроде как можно реализовать решение на уровне собственного аллокатора, но сделать это правильно для всех кейсов крайне нетривиально. Такая задача должна быть по зубам разработчиками уровня boost commiter, да и то, наверняка, не всем. Небольшой Update: Сейчас еще раз перечитал вопрос и, в принципе (если вас устроит такой подход), вы можете просто сделать фабрику ленивых синглтонов - т.е фабрику с методом getObjectWithSomeConcreteAttribute, которая будет создавать объект с таким атрибутом только один раз по его первому запросу. Создание объектов в обход фабрики в таком случае, разумеется, надо запретить. Другое дело, что это не слишком интересно, если сравнивать с задачей, решения для которой я предлагаю выше :)

Ответ 3



Есть такой вариант решения "проблемы", недостатки такого подхода очевидны, хотя вариант имеет право на жизнь (видел в нескольких enterprise продуктах): public enum TestEnum { INTEGER { @Override public void doAction(Object arg) { if (arg instanceof Integer) { System.out.println("int - " + arg); } } }, STRING { @Override public void doAction(Object arg) { if (arg instanceof String) { System.out.println("str - " + arg); } } }, BOOLEAN { @Override public void doAction(Object arg) { if (arg instanceof Boolean) { System.out.println("bool - " + arg); } } }; public abstract void doAction(Object arg); public static void applyAction(Object arg) { System.out.println("Handling: '" + arg + "'"); for (TestEnum e : values()) { e.doAction(arg); } } public static void main(String args[]) { List objects = new LinkedList(); objects.add("ab"); objects.add(Integer.valueOf(1)); objects.add("cd"); objects.add(Integer.valueOf(2)); objects.add(Boolean.valueOf(true)); objects.add(Integer.valueOf(3)); objects.add("ef"); for (Object object : objects) { TestEnum.applyAction(object); } } }

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

Принцип открытости/закрытости при динамическом определении типа файла

#c_sharp #шаблоны_проектирования #проектирование #инспекция_кода #solid


Есть код приложения в котором необходимо динамически определить тип файла (HTML или
TXT) по содержимому и в зависимости от полученного типа файла вызвать соответствующий
алгоритм обработки.

interface IFileType
{
    void Process();
}
class HtmlFile : IFileType
{
    public void Process()
    {
        Console.WriteLine("HTML");
    }
}
class TxtFile : IFileType
{
    public void Process()
    {
        Console.WriteLine("TXT");
    }
}
class FileTypeHandler
{
    public static IFileType Define(string fileContent)
    {
        var file = fileContent.IndexOf("


Ответы

Ответ 1



Я вижу следующие проблемы в вашем коде. Метод FileTypeHandler содержит логику определения, для всех форматов. На данном этапе это не страшно, т.к. их не много, но если их количество будет увеличиваться, метод станет запутанный и тяжел в расширении. Рекомендую, создать сущности, для каждого формата, и в них реализовывать определение. Это позволит избежать запутанности и облегчит добавление новых. Приведу код, как я бы в данной ситуации реализовал. В нем есть небольшие упрощения. Написал, на java, но думаю вы разберетесь. enum FormatTypes { TXT("txt", new TxtDetector()), HTML("html", new HtmlDetector()); public final String name; private final Detector detector; FormatTypes(String name, Detector detector) { this.name = name; this.detector = detector; } } interface Detector { boolean isCorrectType(String fileName, String content); } class TxtDetector implements Detector { } class HtmlDetector implements Detector { } class DetectorHandler { public static String getType(String fileName) { String content = //чтение содержимого for (FormatTypes types : FormatTypes.values()) if (types.detector.isCorrectType(fileName, content)) //просто возвращает название формата, //можно при необходимости в FormatTypes положить какую то логику return types.name; throw new IllegalArgumentException("type is not supported"); } }

Потокобезопасная реализация одиночки

#python #шаблоны_проектирования #singleton #шаблон_одиночка


Здравствуйте! Подскажите, пожалуйста относительно моей реализации одиночки. Реализую
как рекомендуют pep-0318:
Итого, получается код, вида:

def singleton(cls):
    instance = {}

    def get_instance():
        if cls not in instance:
            instance[cls] = cls()
        return instance[cls]
    return get_instance()

@singleton
class MyClass:
      ******


Я никак не могу понять, почему работает мой код. Я экспериментировал с 10 потоками,
которые вызывают один и тот же метод этого класса, метод отрабатывает по времени долго
(4-5 сек. реального времени), но блокировок вызвать не получилось. Данная реализация
является потокобезопасной? Если да, то почему?
    


Ответы

Ответ 1



В CPython (стандартный интерпретатор Python) используется GIL. Из-за него потоки в Python работают не параллельно (за исключением потоков ввода вывода), а по очереди (кооперативно). Когда один поток запускается GIL блокирует все остальные. Более подробно о GIL wiki, habr Дополнение: Сама реализация сингл тона (без доп. блокировок в методах) не является потоко-безопасной Прим: @singleton class MyClass: def __init__(self, a): self.a = a def sum(self, arr): for val in arr: self.a += val def mrange( limit ): for i in range( limit ): yield i if i % 100 == 0: time.sleep(0.000001) if __name__ == '__main__': obj = MyClass(0) start = time.time() threads_num = 2 range_lenth = 100000 threads = [] for i in range( threads_num ): data = range( range_lenth ) #data = mrange( range_lenth ) t = threading.Thread(target=obj.sum, args=(data,)) t.start() threads.append(t) for thr in threads: thr.join() print(obj.a) print('Программа отработала за:',time.time() - start) При использовании стандартного range программа может выдавать разный результат, т.к. прерывание иногда может происходить внутри операции += % python threads.py 9999900000 Программа отработала за: 0.024893522262573242 % python threads.py 8426007253 Программа отработала за: 0.02556920051574707 % python threads.py 6761264079 Программа отработала за: 0.02519679069519043 Но если по какой-либо причине, внутри метода синглтона, будет происходить прерывание чаще чем требует GIL то результат будет стабилен и верен. (Если раскомментировать data = mrange). % python threads.py 9999900000 Программа отработала за: 0.10795187950134277 % python threads.py 9999900000 Программа отработала за: 0.08879899978637695 % python threads.py 9999900000 Программа отработала за: 0.10483145713806152

Ответ 2



Если я правильно вопрос понял import threading, time def singleton(cls): instance = None def inner(*args, **kwargs): nonlocal instance if instance is None: instance = cls(*args, **kwargs) return instance return inner @singleton class MyClass: def __init__(self, a, b): self.a = a self.b = b def add(self): self.a += 1 self.b += 1 if __name__ == '__main__': obj = MyClass(1, 2) obj1 = MyClass(3, 4) print(obj == obj1) start = time.time() threads = [] for i in range(10): t = threading.Thread(target=obj.add) t.start() threads.append(t) for thr in threads: thr.join() print(obj.a, obj.b) print('Программа отработала за:',time.time() - start) # 0.002 сек.

Стоит ли применять паттерн итератор

#java #шаблоны_проектирования #итераторы


Доброго времени суток.

Вопрос следующий: имеется двумерный массив объектов. Несколько модулей в программе
регулярно запрашивают какую-то часть этого массива и последовательно перебирают её
элементы. Для некоторых модулей порядок обхода не важен, но для некоторых это критично.
Так например один модуль запрашивает "прямоугольный" кусок этого двумерного массива
и ему нужно дважды пробежаться по каждой строке начиная с верхней. Так вот, уместно
ли применять паттерн итератор в случае, если порядок обхода для его клиентов критически
важен и каждому клиенту может понадобиться установить свой порядок обхода, при этом
придется реализовывать несколько итераторов с разным интерфейсом? И если нет, подскажите
пожалуйста, стоит ли тогда передовать клиентам непосредственно сам двумерный массив
с учетом, что может понадобиться изменить способ хранения данных? Какое архитектурное
решение лучше подойдет в данном случаи?
    


Ответы

Ответ 1



Общие рекомендации Стоит сделать либо один класс с несколькими методами, предоставляющими выборки, либо несколько классов, по одному на вид выборки, и раздать их потребителям. Каждый метод выборки должен возвращать специализированный итератор. Но интерфейсы у итераторов будут одинаковыми: это обычный Java-интерфейс Iterator. По поводу выборки прямоугольной области и двойных итераций по строкам: Стоит сделать так: метод выборки возвращает итератор по объектам типа Row (добавьте какой-либо свой префикс), реализующий интерфейс Iterable. Каждый такой объект имеет ссылку на строку данных и поля first и length, и из него можно получить итератор по такому "слайсу". Таким образом можно итерироваться и по строкам и по элементам: Iterable rows = someQuerier.getRect(left, top, width, height); for (RectRow row : rows) { // многократный проход по одной и той же строке for (int rowPassCount = 0; rowPassCount < maxRowPassCount; rowPassCount += 1) { for (YourItem item : row) { // Ваши действия с элементом } } }

Как разрешать зависимости не подходящих друг другу классов в PHP (ООП)?

#php #ооп #шаблоны_проектирования


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


Получает товары из прайс-листа (артикул, цена), который может быть в разных форматах
Обрабатывает их
Сохраняет в базу.


Вопрос в правильной организации кода с точки зрения ООП.

Начнём создавать классы:

Readers/XmlReader.php
Readers/XlsReader.php
Readers/ReaderInterface.php

Handlers/DefaultHandler.php
Handlers/CustomHandler.php
Handlers/HandlerInterface.php

Savers/DefaultSaver.php
Savers/SaverInterface.php

Service.php - класс, в который внедряем зависимости
Item.php - сущности, которые будет создавать ридер


Начинаем эту красоту использовать и всё отлично работает:

$service = new Service(
    new XlsReader($path),
    new DefaultHandler(),
    new DefaultSaver()
);
$service->run();


Но потом вдруг оказывается, что нужно создать новый ридер CSVReader, который будет
получать из файла ещё какие-то свойства помимо стандартных, например категорию. И в
новом хендлере NewHandler мы должны будем не только артикул и цену обработать, но и
третий параметр.

Если так сделать, получится не универсальная система - нельзя просто взять и заменить
один Reader на другой (например XlsReader + NewHandler), поскольку это приведёт к ошибке.
Надо проверять, что они друг другу подходят. Теряется смысл интерфейсов.

Можно условиться, что у Item всегда есть 3 свойства, просто категория может быть
пустой. Но тогда получается, что мы нагружаем класс Item лишним функционалом, который
ему в большинстве случаев будет не нужен. Тоже плохо.

Как правильно поступать в данном случае с точки зрения ООП?

UPD1: уточню, что в базу всегда сохраняются только article и price. А другие поля
(как category в моём примере) нужны только для Handler'ов, чтобы вели всякие расчёты
и меняли цену Item'а перед сохранением.

UPD2: может быть создать фабрику для этого?

public static function make(string $type)
{
    if($type === 'xls')
        return new Service(new XmlReader(), new DefaultHandler(), new DefaultSaver());
    elseif($type === 'csv')
        return new Service(new CsvReader(), new CsvHandler(), new DefaultSaver());
}


И вызывать так:

$service = ServiceFactory::make('xls');

    


Ответы

Ответ 1



Здесь проблема не в объектах, а в технических требованиях. Если речь идёт о фиксированном наборе атрибутов, проще всего увековечить его в коде в виде так называемого Объекта переноса данных, на английском Data Transfer Object, DTO. Это паттерн проектирования, в котором только поля примитивного типа и нет поведения. В вашей программе ему соответствует класс Item. Но если набор атрибутов может быть изменён, это надо отразить в явном виде. Если речь идёт о редких изменениях, то можно остановиться на ручной правке кода. Как гарантировать, что при добавлении нового поля вы добавите код в все классы чтения и классы обработки? Например, с помощью модульных тестов. Придётся опираться на механизм рефлексии: перебрать все поля класса Item, и убедиться, что все из них имеют значения. Если вы добавите новое поле, которому не будет присвоено значение, оно должно быть равным null, и в этом случае тест должен проваливаться. Если же речь идёт о том, чтобы пользователь мог добавлять и убирать атрибуты по своему желанию, то эта задача гораздо более сложная. Её тоже делают, но надо иметь в виду, что хорошего решения на классических реляционных базах она не имеет. Посмотрите в сторону паттерна Сущность-Атрибут-Значение (Entity-Attribute-Value). Вот, например, есть статья на хабре на эту тему. Другим решением в современных СУБД являются XML или JSON поля. Это решение мне кажется более простым, чем EAV. В этом случае класс для чтения должен возвращать схему (описание столбцов) и сами значения, а класс для обработки должен уметь сохранять нефиксированный набор столбцов. В сложных случаях вам может также понадобиться конвертер (или описание способов конвертации их исходной схемы в целевую). Очень надеюсь, что ничего подобного вашим заказчикам не нужно. Наконец, ещё один способ, когда набор полей может быть изменён, но это может случиться только один раз. Заказчик говорит вам, какие атрибуты применяются в его задачах, а вы быстро адаптируете свой код. Тут хорошо подойдут скрипты кодо-генерации. Тогда у вас может быть один JSON с описанием схемы, по которой будет идти генерация, и скрипт, который сгенерирует Item, классы чтения и классы обработки по вашим образцам.

Индикатор работы процесса

#архитектура #java #javafx #шаблоны_проектирования


Как правильно организовать индикацию состояния выполнения задачи, с минимальным взаимодействием
с интерфейсом пользователя? Например, программа выполняет долгий расчёт(возможно в
отдельном потоке), а пользователю отображается прогресс бар в GUI. Думал создать какой-нибудь
промежуточный объект, который будет передаваться задаче.А этот, назовём его ProgressMonitor
будет посылать сообщение интерфейсу о том, что процесс идёт. В общем, выполняемая задача
не должна знать о GUI. Желательно, чтобы и GUI знал поменьше о задаче, в идеале нужно
лишь выполнить функцию StartTask, а всю работу будет выполнять задача.
Может кто-нибудь знает другое решение? Есть ли паттерн проектирования на этот случай?
На всякий случай дополню, что пишу на Java. В качестве библиотеки для GUI использую
JavaFX.    


Ответы

Ответ 1



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

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

Как работать с формами которые содержат большое количество данных

#java #sql #jpa #шаблоны_проектирования


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

Задача:
Есть объект который по мимо прочего содержит таблицу с данными. Данных в таблице
на столько много что их нельзя все поднять.


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


Вопрос очень общий. Меня интересует подход. В частности как такое реализовать с реляционной
базой данных, а ещё точнее с JPA 2.1

Что я уже пробовал:


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


Поделитесь своими знаниями и опытом по такой проблеме. Может быть есть паттерн или
какое нибудь общепринятое решение?
    


Ответы

Ответ 1



Описанная вами проблема решается "паттерном" Диалоги (Conversations). Пример “долгоиграющего” диалога: Открывается первый экран диалога. Данные, показываемые пользователю, подгружаются в отдельной сессии Session и БД-транзакции. Пользователь может модифицировать любые поля диалога. После пяти минут редактирования, пользователь использует UI элемент для сохранения. Изменения отразились в БД. Пользователь также ожидает эксклюзивного доступа к данным на время сессии редактирования. Хотя в описанном примере есть несколько случаев доступа к БД, с точки зрения пользователя, данная серия шагов представляет одну единицу совершенной работы (Unit of Work). Есть множество путей реализации этого в приложении. Первый (наивный) метод заключается в удержании открытыми сессий Session и транзакции на время редактирования пользователя, с использованием механизмов синхронизации БД для обеспечения эксклюзивного доступа пользователя к редактируемым данным, и предотвращению обращения к ним со стороны других пользователей, гарантируя изоляцию и атомарность. Это анти-паттерн, так как лишняя синхронизация является узким местом при проблемах производительности, встающих в высоконагруженных приложениях. Другой метод - использование ряда транзакций БД для реализации диалога с БД. В данном случае, обеспечение изоляции бизнес-процессов ложится на плечи приложения. Один диалог обычно покрывает несколько транзакций. Множественные доступы к БД могут быть атомарными, если только одна транзакция (обычно последняя) осуществляет запись в БД. Все другие только читают данные. Типичный путь реализации – через создание wizard-style диалога, покрывающего несколько шагов цикла запрос/ответ. Hibernate включает в себя некоторые возможности, позволяющие реализовать подобный функционал: Автоматическое версионирование: Hibernate может осуществлять за вас concurrency-контроль. Он может автоматически обнаружить, осуществлялись ли сторонние обновления данных за время ожидания пользователя. Отсоединенные (Detached) объекты: если вы предпочтете использовать шаблон сессия-на-запрос, все загруженные экземпляры будут отсоединены за время ожидания пользователя. Hibernate позволяет вам обратно подсоединить объекты и сохранить модификации. Данный паттерн называется сессия-на-запрос-с-отсоединенными объектами. Автоматическое версионирование используется для изоляции параллельно выполняющихся запросов. Расширенная сессия: сессия Session может быть отсоединена от нижележащего JDBC соединения после того, как БД транзакция будет закоммичена, и переподсоединена, когда возникнет новый клиентский запрос. Этот паттерн называется сессия-на-диалог, делающий повторное соединение (reattachment) объектов ненужным. Автоматическое версионирование используется для изоляции параллельных модификаций, при этом сессия не может быть сброшена (flushed) автоматически, только явно. Т. е. для реализации диалогов в Hibernate можно использовать паттерны Сессия-на-запрос-с-отсоединенным-объектами и сессия-на-диалог. Для описания паттерна Диалог я использую Hibernate, т. к. в нём точно есть встроенная поддержка паттера и он описан в официальной документации. Про поддержку паттерна в JPA мне говорить сложно, но быстрое и поверхностное гугление показывает, что в JPA паттерн реализовать можно. Источники: Документация разработчика Hibernate – Глава II. Транзакции и контроль многопоточности Hibernate ORM User Guide Implementing conversations В последнем источнике есть примеры различных реализаций паттерна Диалог.

воскресенье, 12 января 2020 г.

Возможно ли создать интерфейс для создания экземпляра одного из следующих классов?

#c_sharp #проектирование #шаблоны_проектирования


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

public class ProductA 
{
   public string FieldFirst { get; set; }
   public string FieldSecond { get; set; }
   public string FieldThird { get; set; }
   public string FieldFourth { get; set; }
}

public class ProductB 
{
   public string FieldFirst { get; set; }
   public string FieldSecond { get; set; }
   public string FieldThird { get; set; }
   public List FieldFourth { get; set; }
}

public class ProductC 
{
   public string FieldFirst { get; set; }
   public string FieldSecond { get; set; }
   public List FieldThird { get; set; }
}

public class ProductD 
{
   public string FieldFirst { get; set; }
   public List FieldSecond  { get; set; }
}


Собственно, хотел узнать, возможно ли сделать общий интерфейс для выбора класса и
создания объекта. Нельзя ли найти какой-нибудь единый  интерфейс для них, и использовать,
например  Абстрактную Фабрику. Или это нельзя сделать в данном случае. Заранее огромное
спасибо! 
    


Ответы

Ответ 1



Простое решение Если типы аргументов известны на стадии компиляции и наборы типов параметров уникальны, то можно сделать банальную фабрику: public static class ProductFactory { public static ProductA Create (string fieldFirst, string fieldSecond, string fieldThird, string fieldFourth) { return new ProductA(fieldFirst, fieldSecond, fieldThird, fieldFourth); } public static ProductB Create (string fieldFirst, string fieldSecond, string fieldThird, List fieldFourth) { return new ProductB(fieldFirst, fieldSecond, fieldThird, fieldFourth); } public static ProductC Create (string fieldFirst, string fieldSecond, List fieldThird) { return new ProductC(fieldFirst, fieldSecond, fieldThird); } public static ProductD Create (string fieldFirst, List fieldSecond) { return new ProductD(fieldFirst, fieldSecond); } } Тогда создание объектов будет выглядеть следующим образом: ProductA productA = ProductFactory.Create("1", "2", "3", "4"); ProductD productD = ProductFactory.Create("1", new List()); Здесь я предположил следующие классы продуктов: public class ProductA { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public string FieldThird { get; set; } public string FieldFourth { get; set; } public ProductA (string fieldFirst, string fieldSecond, string fieldThird, string fieldFourth) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; FieldFourth = fieldFourth; } } public class ProductB { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public string FieldThird { get; set; } public List FieldFourth { get; set; } public ProductB (string fieldFirst, string fieldSecond, string fieldThird, List fieldFourth) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; FieldFourth = fieldFourth; } } public class ProductC { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public List FieldThird { get; set; } public ProductC (string fieldFirst, string fieldSecond, List fieldThird) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; } } public class ProductD { public string FieldFirst { get; set; } public List FieldSecond { get; set; } public ProductD (string fieldFirst, List fieldSecond) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; } } Сложное решение Если на стадии компиляции ничего не известно, а аргументы собираются вручную во время выполнения программы, то можно воспользоваться Autofac, например. Вот пример использования, когда выбор делается на основе имён и типов параметров, и имена параметров не совпадают с именами аргументов конструктора: var builder = new ContainerBuilder(); builder .Register((c, p) => { switch (p.Count()) { case 4: if (p.OfType().Any(a => a.Name == "str4")) return new ProductA( p.Named("str1"), p.Named("str2"), p.Named("str3"), p.Named("str4")); else return new ProductB( p.Named("str1"), p.Named("str2"), p.Named("str3"), p.Named>("ints")); case 3: return new ProductC( p.Named("str1"), p.Named("str2"), p.Named>("doubles")); case 2: return new ProductD( p.Named("str1"), p.Named>("floats")); } throw new DependencyResolutionException("Could not resolve product."); }) .As(); IContainer container = builder.Build(); var productA = container.Resolve( new NamedParameter("str1", "1"), new NamedParameter("str2", "2"), new NamedParameter("str3", "3"), new NamedParameter("str4", "4")); var productD = container.Resolve( new NamedParameter("str1", "1"), new NamedParameter("floats", new List())); Предполагается, что продукты реализованы следующим образом: public interface IProduct {} public class ProductA : IProduct { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public string FieldThird { get; set; } public string FieldFourth { get; set; } public ProductA (string fieldFirst, string fieldSecond, string fieldThird, string fieldFourth) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; FieldFourth = fieldFourth; } } public class ProductB : IProduct { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public string FieldThird { get; set; } public List FieldFourth { get; set; } public ProductB (string fieldFirst, string fieldSecond, string fieldThird, List fieldFourth) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; FieldFourth = fieldFourth; } } public class ProductC : IProduct { public string FieldFirst { get; set; } public string FieldSecond { get; set; } public List FieldThird { get; set; } public ProductC (string fieldFirst, string fieldSecond, List fieldThird) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; FieldThird = fieldThird; } } public class ProductD : IProduct { public string FieldFirst { get; set; } public List FieldSecond { get; set; } public ProductD (string fieldFirst, List fieldSecond) { FieldFirst = fieldFirst; FieldSecond = fieldSecond; } }

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

Что использовать абстрактный класс или интерфейс в фабричном методе?

#c_sharp #ооп #шаблоны_проектирования


Полностью осознаю, что это очередной вопрос из  серии "Разница между абстрактным
классом и интерфейсом". И должен сказать, что для меня эти две концепции ясны или возможно
нет раз задаю этот вопрос :)

Наверное каждый второй, если не первый из девелоперов использует порождающий паттерн
"Фабричный метод" и меня интересует вопрос, так какую абстракцию лучше использовать
при реализация этого паттерна: абстрактный класс или интерфейс?

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

Вот пример из википедии, который рассказывает паттерн на 

Абстрактных классах

namespace CSharpConsoleApp
{
    abstract class AbstractProduct
    {
        public abstract string GetType();
    }

    class ConcreteProductA : AbstractProduct
    {
        public override string GetType() { return "ConcreteProductA"; }
    }

    class ConcreteProductB : AbstractProduct
    {
        public override string GetType() { return "ConcreteProductB"; }
    }

    abstract class AbstractCreator
    {
        public abstract AbstractProduct FactoryMethod();
    }

    class ConcreteCreatorA : AbstractCreator
    {
        public override AbstractProduct FactoryMethod() { return new ConcreteProductA(); }
    }

    class ConcreteCreatorB : AbstractCreator
    {
        public override AbstractProduct FactoryMethod() { return new ConcreteProductB(); }
    }
    class Program
    {
        static void Main(string[] args)
        {
            AbstractCreator[] creators = { new ConcreteCreatorA(), new ConcreteCreatorB() };
            foreach (AbstractCreator creator in creators)
            {
                AbstractProduct product = creator.FactoryMethod();
                Console.WriteLine("Created {0}", product.GetType());
            }

            Console.Read();
        }
    }
}


а это переделанный мной вариант на

Интерфейсах

namespace CSharpConsoleApp
{
    interface IProduct
    {
        string GetType();
    }

    class ProductA : IProduct
    {
        public string GetType() { return "ProductA"; }
    }

    class ProductB : IProduct
    {
        public string GetType() { return "ProductB"; }
    }
    interface ICreatorProduct
    {
        IProduct FactoryMethod();
    }

    class CreatorProductA : ICreatorProduct
    {
        public IProduct FactoryMethod() { return new ProductA(); }
    }

    class CreatorProductB : ICreatorProduct
    {
        public IProduct FactoryMethod() { return new ProductB(); }
    }

    class Program
    {
        static void Main(string[] args)
        {           
            ICreatorProduct[] productCreators = { new CreatorProductA(), new CreatorProductB() };
            foreach (ICreatorProduct creatorProduct in productCreators)
            {
                IProduct product = creatorProduct.FactoryMethod();
                Console.WriteLine("Created {0}", product.GetType());
            }

            Console.Read();
        }
    }
}




Так какова лучшая практика использования данного паттерна в плане абстракций?
    


Ответы

Ответ 1



Смысл абстрактного класса — совместное использование кода классами-потомками. Абстрактный класс нужен для того, чтобы было легко создавать немного отличающиеся классы, общую часть которых вы выносите в реализованные методы абстрактного класса. В вашем случае абстрактный класс не нужен. В вашем коде смысл AbstractProduct состоит лишь в том, чтобы в дочернем классе затребовать наличия метода string GetType(). Точно так же смысл AbstractCreator лишь в том, чтобы в дочернем классе затребовать наличия метода AbstractProduct FactoryMethod(). Это прекрасно ложится на предназначение интерфейсов. Вывод: интерфейсы в данном коде намного более естественны. Предположу, что код, который использует абстрактные классы, написан для языка C++, в котором интерфейсов просто нет.

Фабрика синглтон

#шаблоны_проектирования


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


Ответы

Ответ 1



Одиночка (англ. Singleton) — порождающий шаблон проектирования, гарантирующий, что в однопроцессном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру. Источник. Чисто из определения - нет, не нужно использовать синглтон. Паттер "синглтон" предполагает, что будет существовать единственный экземпляр данного класса. Но задайте себе вопрос: а нужен ли вам этот экземпляр? Имеется класс - простая фабрика, в котором реализован статический метод, который генерирует инстанс одного объекта на основе принятого в параметрах другого объекта. Предполагая, что этот статический метод является в классе единственным, то нет необходимости вообще иметь возможность порождать объекты данного класса. Вместо этого можно обойтись простой функцией-фабрикой. class Fabric { static generate(template) { // ... } } Fabric.generate(instance); // думаю, можно безболезненно превратить в это: function generate(template) { // тот же код, что и в Fabric.generate(); } generate(instance); Более того, статический метод по определению не может быть вызван на экземпляре класса, поэтому нет абсолютно никакой необходимости в наличии даже единственного экземпляра этого класса, ведь фабричный метод можно вызвать на самом классе (более того, только на нем, поскольку он является статическим). Поэтому на вашем месте я бы однозначно не стал реализовывать синглтон, а более того, рассмотрел бы возможность избавления от класса вообще.

Ответ 2



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