Страницы

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

четверг, 30 мая 2019 г.

В чем преимущества выделения контроллера в 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(). Не лучше ли это делать в модели?


Ответ

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

Поиск всех значений в TreeMap c “неполным” ключом

Доброго времени суток. Нужен совет, как найти все значения в TreeMap с "неполным" ключом. Допустим имеются какие то значения с ключами "Book","Boom","Trap"... При ключе "Boo" должен выводить значения у которых ключ начинается на этот кусок.
Пробовал использовать методы ceilingKey(), floorKey(), higherKey(), lowerKey(), но столкнулся с проблемой, выводит лишь одно значение. Попробовал удалять элементы после получения, тогда он выводит весь TreeMap пока не опустеет.
Может есть какой то более подходящий подход для этой задачи ?


Ответ

На англоязычном SO есть несколько таких вопросов с ответом
Идея такая: используем метод subMap, первым параметром передаем наш префикс (или как вы говорите "неполный ключ"), вторым параметром передаем этот же неполный ключ, только с измененным последним символом, который на единицу больше предыдущего (либо можно просто в конец "неполного ключа" дописать Character.MAX_VALUE).

Узнать имя класса во время исполнения программы

У меня есть функция (используется для записи логов), которая вызывается в разных классах. Не хочется каждый раз переписывать код в ней для каждого нового класса(они будут и добавляться и убираться со временем, ну или вообще функция будет использоваться в дальнейшем в других проектах).
Мне хотелось бы узнавать имя класса, из которого она вызывается (для задания имени файлу и прочих плюшек) не передавая лишних параметров в функцию.
Это можно реализовать? Можно и boost`ом.


Ответ

Функция typeid(object) возвращает объект типа type_info с информацией о типе объекта, для которого она вызывается (собственно, о классе). У этого объекта(type_info) есть метод name(), который и возвращает имя класса. Для использования нужно подключить
Так что в итоге для получения имени класса нужно использовать
typeid(*this).name()
Или, если функция не является методом класса, передать в typeid сам объект или разыменованный указатель.

Как найти суммы последовательных узлов в бинарном дереве?

Дано: бинарное дерево (алгоритм дерева написан вручную). Число S. Нужно найти последовательность узлов (только с вверху вниз или наоборот) в бинарном дереве, сумма которых равна S
Например: есть бинарное дерево, а число S = 9.
Решение: 3+6, 4+5, 9.
п.с. желательно, что бы это был отдельный класс, который имел доступ к классу binTree
#pragma once class binTree { protected: struct Node { int Value; Node * pLeft; Node * pRight; Node * pParent; Node(int x) :Value(x), pLeft(NULL), pRight(NULL), pParent(NULL) {} }; Node * m_pRoot; void InoderTreeWalk(Node * x); Node * TreeSuccessor(Node *x); Node * TreeMin(Node * x); public: binTree(); ~binTree(); virtual void TreeInsert(int k); Node * TreeSearch(Node * X, int k); void ShowTree(); int Root(); };


Ответ

Задача тянет на олимпиадную, пишу идею.
Решение будет сделано с помощью динамики по дереву.
Для каждого узла в дереве заведём список значений. Инициализация - в листах {0, Value}. Пересчёт снизу вверху. Значение для узла пересчитывается примерно так (текущая вершина U):
for (auto left : U->left->list) U->list.add(left + U->Value); for (auto right: U->left->right) U->list.add(right+ U->Value); U->add(U->Value); U->list->unique();
Я специально пишу псевдокодом, т.к. там может быть любая структура от специфики задачи (list vector set bitset) и т.д.
Для каждой вершины, где в списке есть S будет последовательность ответ, которая заканчивается в ней. Её можно восстановить примерно так. Последовательность однозначно задаётся начальным и конченым элементом. Я верну только начальные.
list fun(Tree *U, int S){ S -= U->Value;
list tmp; if (S == 0) tmp.add(U);
if (U->left->list.contains(S)) tmp.add(fun(U->left,S); if (U->rigth->list.contains(S)) tmp.add(fun(U->rigth,S); return tmp; }
Сложно что-то порядка O(N^2 log N). Оптимизировать можно, но если выводить все последовательности то их может быть примерно столько же. Дальнейшие реализации/оптимизации вам виднее.

Entity Framework не удаляется связанная по внешнему ключу запись

Пытаюсь удалить элемент из коллекции UsersVaults объекта типа User, но получаю Exception (текст ошибки и код ниже). Подскажите, где я повернул не туда?
Метод изменения объекта:
[HttpPost] public ActionResult Edit(int id, FormCollection form) { try { using (var db = new AccessControlSystemDatabaseModel()) { var user = db.Users.Find(id); var tempData = form.AllKeys.ToDictionary(key => key, key => form[key]);
user.RoleID = Convert.ToInt32(tempData["RoleID"]); user.Username = tempData["Username"].ToString(); user.Password = tempData["Password"].ToString(); user.Email = tempData["Email"].ToString();
if (!tempData["GrantAccess"].ToString().Equals("false") && user.UsersVaults.Where( d => d.VaultID == AuthenticationController.CurrentUser.UsersVaults.ToList()[0].VaultID) .ToList() .Count == 0) { user.UsersVaults.Add(new UsersVault { UserID = id, VaultID = AuthenticationController.CurrentUser.UsersVaults.ToList()[0].VaultID, }); } else { var tt = user.UsersVaults.Where( d => d.VaultID == AuthenticationController.CurrentUser.UsersVaults.ToList()[0].VaultID).ToList()[0];
user.UsersVaults.Remove(tt); }
db.Entry(user).State = EntityState.Modified; db.SaveChanges();
return RedirectToAction("Index"); } } catch(Exception e) { return RedirectToAction("Index"); } }
Модель:
public partial class AccessControlSystemDatabaseModel : DbContext { public AccessControlSystemDatabaseModel() : base("name=AccessControlSystemDatabaseModel") { }
public virtual DbSet Roles { get; set; } public virtual DbSet Users { get; set; } public virtual DbSet UsersVaults { get; set; } public virtual DbSet Vaults { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity() .HasMany(e => e.UsersVaults) .WithRequired(e => e.User) .WillCascadeOnDelete(true);
modelBuilder.Entity() .HasMany(e => e.UsersVaults) .WithRequired(e => e.Vault) .WillCascadeOnDelete(true); } }
public partial class Role { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] public Role() { Users = new HashSet(); }
public int ID { get; set; }
[Required] [StringLength(255)] public string RoleName { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public virtual ICollection Users { get; set; } }
public partial class User { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] public User() { UsersVaults = new HashSet(); }
public int ID { get; set; }
[Required] [StringLength(255)] public string Username { get; set; }
[Required] [StringLength(255)] public string Password { get; set; }
public int? RoleID { get; set; }
[StringLength(255)] public string Email { get; set; }
public virtual Role Role { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public virtual ICollection UsersVaults { get; set; } }
public partial class UsersVault { public int ID { get; set; }
public int UserID { get; set; }
public int VaultID { get; set; }
public virtual User User { get; set; }
public virtual Vault Vault { get; set; } }
public partial class Vault { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] public Vault() { UsersVaults = new HashSet(); }
public int ID { get; set; }
[Required] [StringLength(255)] public string VaultName { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public virtual ICollection UsersVaults { get; set; } }

Текст исключения:
"Операция завершилась с ошибкой. Не удалось изменить связь, поскольку один или несколько свойств внешнего ключа не допускают значения NULL. При изменении связи соответствующему свойству внешнего ключа присваивается значение NULL. Если внешний ключ не поддерживает значений NULL, должна быть определена новая связь, свойству внешнего ключа должно быть присвоено другое значение, отличное от NULL, либо необходимо удалить несвязанный объект."


Ответ

В EF, к сожалению, операции Add и Remove для навигационных коллекций не всегда симметричны - первая операция устанавливает связь между сущностями и при необходимости добавляет их в базу, вторая же только разрывает связь. Но разорвать связь между UsersVault и User невозможно, отсюда и ошибка.
Поэтому надо вместо удаления объекта из коллекции явно удалить его из базы:
var tt = user.UsersVaults.Single(...); db.Entry(tt).State = EntityState.Deleted;
или
db.UsersVaults.Remove(tt);
Для сохранения симметричности операций, можно при добавлении UsersVault точно так же сразу добавлять его в базу:
db.UsersVaults.Add(new UsersVault { UserID = id, VaultID = ..., })
Кстати, в вашем текущем коде есть дублирование - UserID присваивать не обязательно если вы добавляете объект через навигационную коллекцию - EF проставит его сама.

В качестве альтернативного решения могу предложить вообще удалить сущность UsersVault - она не нужна! EF умеет самостоятельно отображать связи "многие-ко-многим" на промежуточные таблицы, просто создайте mtm-связь между Users и Vaults

Как работать с TCP в Swift?

У меня есть TCP сервер, который слушает входящие команды и общается с БД. Как мне слать к нему запросы из ios приложения? Работаю со swift 2
Другими словами, нужно реализовать следующее: Пользователь вводит логин и пароль, пытается войти в свой аккаунт. В это время посылается запрос на сервер - "Проверь, есть ли такой юзер в БД". Он это делает, и шлет ответ обратно на ios приложение.
Никак не могу понять, как мне такое реализовать. Насколько я понимаю, мне нужно работать с потоками, но ничего путного про это на ios я не нашел.


Ответ

Если я Вас правильно понял, то потоки Вам вовсе не нужны. Тут надо использовать блоки. Скажем Вы отправляете запрос и включаете индикатор загрузки, а когда приходит ответ Вы прекращаете загрузку и в зависимости от результата с сервера используете success или же failure блок. Советую использовать AFNetworking
Что-то на подобии
let manager = AFHTTPRequestOperationManager() manager.GET( "http://myServerUrl.com", parameters: ["email":"myemail@gmail.com", "password":"1234Password"], success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in //TODO - make login action }, failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in // TODO - show error message } )

Как добавить в приложение уже заполненную базу?

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


Ответ

Схематично:
В assets кладете Вашу БД Создаете БД в приложении (стандартно SQLiteOpenHelper) Копируете при первом запуске БД из assets в БД приложения. Работаете с заполненной БД. После очистки данных приложения пользователем повторить п.3