Skip to content

Start task2#2

Open
As-pasa wants to merge 34 commits intomasterfrom
startTask2
Open

Start task2#2
As-pasa wants to merge 34 commits intomasterfrom
startTask2

Conversation

@As-pasa
Copy link
Owner

@As-pasa As-pasa commented May 17, 2023

No description provided.

As-pasa added 27 commits April 17, 2023 16:25
…on from GameVisualizer to Drawable interface
…alls replaced by controller calls, model and controller creation moved to MainApplicationFrame contructor
…nerating it and rebuilding source from state

import java.io.IOException;

public class ConfigException extends IOException {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А зачем наследоваться именно от IOException?
Обычно в своем коде строятся собственные иерархии исключений, чтобы они не смешивались с другими предопределенными типами...

import java.io.ObjectInputStream;

public interface ConfigReader extends Closeable {
Object readObject() throws ConfigException, IOException, ClassNotFoundException;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь две проблемы:

  1. исключение IOException фактически маскирует ConfigException (как наследника)
  2. зачем вызывающему коду показывать ошибки обнаружения классов? следовало бы их обернуть в наш ConfigException.

public PredeterminedPathConfigReader(String path){
m_path=new File(path);
try{
m_data=new ObjectInputStream( new BufferedInputStream(new FileInputStream(m_path)));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А зачем открывать файловый поток в конструкторе? Почему бы не открывать и не закрывать его непосредственно в методе readObject? Логика оказалась бы проще... Да и тогда можно было бы не делать классы PredeterminedPathConfigReader и PredeterminedPathConfigWriter закрываемыми...
Та логика, которая сейчас реализована, была бы уместной, если бы работа с файлом состояла бы из серии операций (т.е. они были бы объединены общим состоянием - например, положением в файле). Но в реальности такой необходимости нет.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

m_data=new ObjectInputStream( new BufferedInputStream(new FileInputStream(m_path)));
}
catch (IOException e){
m_data=null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

public PredeterminedPathConfigReader(String path){
m_path=new File(path);
try{
m_data=new ObjectInputStream( new BufferedInputStream(new FileInputStream(m_path)));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

if(m_data==null){
return;
}
m_data.close();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Метод close должен обладать свойством идемпотентности (уж использую этот термин из программирования и математики). То есть многократный вызов метода должен быть эквивалентным однократному.
В данном случае это означает, что после успешного выполнения операции close (а может быть и после неуспешного тоже) надо выставить m_data в null, чтобы предотвратить попытки повторного закрытия потока.

import java.io.*;
import java.nio.file.Paths;

public class PredeterminedPathConfigWriter implements ConfigWriter {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь все те же замечания, что и для reader-а

m_stream.close();
}

public static void main(String[] args) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, можно делать такие стартовые методы для проверки кода. Но правильнее использовать JUnit для этих целей (ибо тесты же пишем).

String d ="fooo";

var p= new File(System.getProperty("user.home"),"config.conf");
System.out.println(p);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Когда код отлажен, вывод на консоль, по идее, надо убирать.

}
});
}
private static void initProgramState(){
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

RobotState robotContainer=m_robotModel.getState();
TargetState targetContainer=m_targetModel.getState();
LoggerSourceState logs=m_logs.getState();
writer.writeObject(windowContainer);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

И, кстати, если в процессе работы программы окно (например, окно с координатами) закрыть, то код сохранения не сработает...

import java.io.Serializable;

public class MainWindowStateContainer implements Serializable {
public InnerWindowStateContainer GameState;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не забываем, что в Java поля и переменные называются с маленькой буквы.

PositionShowState = positionShowState;
}

public InnerWindowStateContainer PositionShowState;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

}
}
public MainWindowStateContainer getFrameState(){
return new MainWindowStateContainer(m_game.getState(),m_logger.getState(),m_coordShow.getState());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А как теперь будет изменяться код по мере добавления новых окошек? Так и будет разрастаться набор параметров конструктора?

@@ -0,0 +1,4 @@
package gui.serial;

public interface MySerializable {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

pack();
}
public InnerWindowStateContainer getState(){
return new InnerWindowStateContainer(getX(),getY(),getSize().width,getSize().height);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно заметить, что эта конструкция встречается в коде несколько раз (в разных классах). То есть имеем дублирование кода. И тут напрашивается введение конструктора InnerWindowStateContainer от java.awt.Component. Собственно, и восстановлением мог бы, в принципе, заниматься InnerWindowStateContainer.


import java.io.File;

public class PredeterminedPathFileSupplier implements FileSupplier{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А зачем так усложнять код? Почему было просто не использовать File в качестве абстракции для представления целевого файла? :)
Я бы понял смысл этой абстракции, если бы предполагалась возможность изменять эти целевые файлы на ходу. Но тогда было бы правильнее просто ввести сущность для хранения конфигурации приложения, где были бы, в частности, нужные данные о файлах.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я как раз предполагал возможность использования окна выбора файлов(JFileChooser). Мне казалось что абстрагироваться от способа задачи файла хардкодом/выбором с помощью пользовательского интерфейса было бы неплохо. Но логика не была реализована, так что пока это действительно абстракция ради абстракции

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P.S. не до конца прочитал ваше предолжение о новой сущности+выяснил что логика работы с JFileChooser не является блокирующией и текущая система абстракций все равно не позволит без костылей добавить этот функционал

import java.io.Serializable;


public abstract class AbstractWindowConstructor implements WindowConstructor, Serializable {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

А еще здесь также появляется возможность для применения дженериков. Смысл в чем. Мы же знаем, что конкретная фабрика порождает окошки конкретного типа. Поэтому можно было зафиксировать эту связь:

public abstract class WindowFactory<T extends JInternalFrame>
{
    public T construct(ModelAndControllerLocator locator){ ... }
}

public class LogWindowConstructor extends AbstractWindowConstructor<LogWindow>
{
    @Override
    protected LogWindow windowFabricMethod(ModelAndControllerLocator locator) {
        return new LogWindow(locator.getLogSource());
    }
}

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

public AbstractWindowConstructor(int x, int y,int width,int height){
m_windowState=new InnerWindowStateContainer(x,y,width,height);
}
protected abstract JInternalFrame windowFabricMethod(ModelAndControllerLocator locator);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Снова про именование. Методы обычно называются глаголами. Классы - существительными. Соответственно, здесь более уместным было бы какое-то название, которое обозначало бы операцию построения окна (например, createWindow). А прямо сейчас windowFabricMethod - это существительное (оконный фабричный метод).


import javax.swing.*;

public interface WindowConstructor {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

}
}

private static void onExit() throws FileNotFoundException {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А зачем указывать, что выбрасывается FileNotFoundException, если оно на самом деле не выбрасывается (да и не должно ведь, по идее)?

writeProgramState(modelsInputStream, windowsInputStream);

} catch (IOException e) {
throw new RuntimeException(e);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@alklepin
Copy link

Мне понравилась реализация сохранения. Красиво получилось. За это +5 бонусных баллов.
Тем не менее в рамках задачи остаются доделки по джененрикам и обработке исключений (ну и мелкие замечания по именованию классов, наличию ненужных интерфейсов и т.п.). На этом можно получить еще 4 балла.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants