I'm playing arround with JavaFX and various scenes that are loaded by FXML. So I got the idea to write a manager that handles scene switching.
So far everything works, but I'm unsure if this is a good implementation.
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class SceneManager {
private static final String[] fxmlFiles = {"../gui/MainWindow.fxml", "../gui/NewGameWindow.fxml"};
private static SceneManager instance = null;
private static Stage rootStage = null;
private FXMLLoader[] loadedFxml;
private Pane[] loadedPanes;
private Scene[] scenes;
public enum States {
MAIN_MENU, NEW_GAME;
}
private SceneManager() {
try {
this.loadedFxml = new FXMLLoader[States.values().length];
this.loadedPanes = new Pane[States.values().length];
this.scenes = new Scene[States.values().length];
for(int i = 0; i < fxmlFiles.length; i++) {
loadedFxml[i] = new FXMLLoader(getClass().getResource(fxmlFiles[i]));
loadedPanes[i] = loadedFxml[i].load();
scenes[i] = new Scene(loadedPanes[i]);
}
rootStage.setScene(scenes[0]);
rootStage.setResizable(false);
rootStage.show();
} catch(IOException e) {
e.printStackTrace();
}
}
public static SceneManager getInstance() {
if(instance == null) {
instance = new SceneManager();
}
return instance;
}
public static void setUp(Stage stage) {
SceneManager.rootStage = stage;
}
public void switchScene(States state) {
rootStage.setScene(scenes[state.ordinal()]);
}
}
So what I plan to do is, load the FXML via the loader, assign it to a pane, create all scenes.
Then I set a scene as its starting scene and do the rest via the getInstance().switchScene() method in the controller.
It works well but I'm unsure if this is a good approach.
The implementation is imho pretty bad for several reasons:
The singleton pattern is not properly implemented
The singleton pattern is used to access an instance containing the relevant data/functionality via a static method, but this instance should contain all the relevant data as instance fields.
rootStage and setUp are static though.
You store data in fields that is never needed again
You never read from loadedFxml or loadedPanes outside of the loop. You could instead create local variables in the loop body.
Everything is loaded at once
For several small scenes this may not make much difference, but as you add more and more scenes this will increase startup time. Consider lazily loading the scenes.
Data for scenes is kept in different data structures
Not much of an issue, but it makes the class a bit harder to maintain. The enum stores one part of the data used to create/access the scenes fxmlFiles contains the other half. Every time you add/remove a scene you need to update both parts of your code. In cases like this it would be preferable to store the url data in the enum itself:
public enum States {
MAIN_MENU("../gui/MainWindow.fxml"), NEW_GAME("../gui/NewGameWindow.fxml");
private final url;
States(String url) {
this.url = url;
}
}
for(States state : States.values()) {
FXMLLoader loader = new FXMLLoader(getClass().getResource(state.url));
...
}
Note that you use .. in your urls here which ceases to work, if you package your program as a jar.
But using a enum in the first place is a questionable decision. This way you won't be able to add/remove scenes without recompiling.
Using static doesn't seem necessary at all
It's good to avoid the use of static at all if possible. (see Why are static variables considered evil?).
If my assumption that you only use the SceneManager class from scenes loaded by it (and for displaying the initial scene) is correct, it's not hard to pass the SceneManager instance to the controllers of the scenes to avoid the need of using SceneManager.getInstance in those classes (see Passing Parameters JavaFX FXML):
superclass for controllers
public class BaseController {
protected SceneManager sceneManager;
void setSceneManager(SceneManager sceneManager) { // if SceneManager and BaseController are in different packages, change visibility
this.sceneManager = sceneManager;
}
}
FXMLLoader loader = ...
Pane pane = loader.load();
BaseController controller = loader.getController();
controller.setSceneManager(this);
Using the url as identifiers for the scenes for simplicity, you could improve the implementation:
public class SceneManager {
private final Stage rootStage;
public SceneManager(Stage rootStage) {
if (rootStage == null) {
throw new IllegalArgumentException();
}
this.rootStage = rootStage;
}
private final Map<String, Scene> scenes = new HashMap<>();
public void switchScene(String url) {
Scene scene = scenes.computeIfAbsent(url, u -> {
FXMLLoader loader = new FXMLLoader(getClass().getResource(u));
try {
Pane p = loader.load();
BaseController controller = loader.getController();
controller.setSceneManager(this);
return new Scene(p);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
});
rootStage.setScene(scene);
}
}
This allows you to
create different managers for different stages
load scenes when they are needed first
dynamically add more scenes
prevents state where switchScene is called but the stage is null
simplifies access to the SceneManager in controller classes to sceneManager.switchScene
SceneManager is possibly available for garbage collection before the program completes since there is no static reference to it.
Related
I'm new to the GUI world/OO design pattern and I want to use MVC pattern for my GUI application, I have read a little tutorial about MVC pattern, the Model will contain the data, the View will contain the visual element and the Controller will tie between the View and the Model.
I have a View that contains a ListView node, and the ListView will be filled with names, from a Person Class (Model). But I'm a little confused about one thing.
What I want to know is if loading the data from a file is the responsibility of the Controller or the Model?? And the ObservableList of the names: should it be stored in the Controller or the Model?
There are many different variations of this pattern. In particular, "MVC" in the context of a web application is interpreted somewhat differently to "MVC" in the context of a thick client (e.g. desktop) application (because a web application has to sit atop the request-response cycle). This is just one approach to implementing MVC in the context of a thick client application, using JavaFX.
Your Person class is not really the model, unless you have a very simple application: this is typically what we call a domain object, and the model will contain references to it, along with other data. In a narrow context, such as when you are just thinking about the ListView, you can think of the Person as your data model (it models the data in each element of the ListView), but in the wider context of the application, there is more data and state to consider.
If you are displaying a ListView<Person> the data you need, as a minimum, is an ObservableList<Person>. You might also want a property such as currentPerson, that might represent the selected item in the list.
If the only view you have is the ListView, then creating a separate class to store this would be overkill, but any real application will usually end up with multiple views. At this point, having the data shared in a model becomes a very useful way for different controllers to communicate with each other.
So, for example, you might have something like this:
public class DataModel {
private final ObservableList<Person> personList = FXCollections.observableArrayList();
private final ObjectProperty<Person> currentPerson = new SimpleObjectPropery<>(null);
public ObjectProperty<Person> currentPersonProperty() {
return currentPerson ;
}
public final Person getCurrentPerson() {
return currentPerson().get();
}
public final void setCurrentPerson(Person person) {
currentPerson().set(person);
}
public ObservableList<Person> getPersonList() {
return personList ;
}
}
Now you might have a controller for the ListView display that looks like this:
public class ListController {
#FXML
private ListView<Person> listView ;
private DataModel model ;
public void initModel(DataModel model) {
// ensure model is only set once:
if (this.model != null) {
throw new IllegalStateException("Model can only be initialized once");
}
this.model = model ;
listView.setItems(model.getPersonList());
listView.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) ->
model.setCurrentPerson(newSelection));
model.currentPersonProperty().addListener((obs, oldPerson, newPerson) -> {
if (newPerson == null) {
listView.getSelectionModel().clearSelection();
} else {
listView.getSelectionModel().select(newPerson);
}
});
}
}
This controller essentially just binds the data displayed in the list to the data in the model, and ensures the model's currentPerson is always the selected item in the list view.
Now you might have another view, say an editor, with three text fields for the firstName, lastName, and email properties of a person. It's controller might look like:
public class EditorController {
#FXML
private TextField firstNameField ;
#FXML
private TextField lastNameField ;
#FXML
private TextField emailField ;
private DataModel model ;
public void initModel(DataModel model) {
if (this.model != null) {
throw new IllegalStateException("Model can only be initialized once");
}
this.model = model ;
model.currentPersonProperty().addListener((obs, oldPerson, newPerson) -> {
if (oldPerson != null) {
firstNameField.textProperty().unbindBidirectional(oldPerson.firstNameProperty());
lastNameField.textProperty().unbindBidirectional(oldPerson.lastNameProperty());
emailField.textProperty().unbindBidirectional(oldPerson.emailProperty());
}
if (newPerson == null) {
firstNameField.setText("");
lastNameField.setText("");
emailField.setText("");
} else {
firstNameField.textProperty().bindBidirectional(newPerson.firstNameProperty());
lastNameField.textProperty().bindBidirectional(newPerson.lastNameProperty());
emailField.textProperty().bindBidirectional(newPerson.emailProperty());
}
});
}
}
Now if you set things up so both these controllers are sharing the same model, the editor will edit the currently selected item in the list.
Loading and saving data should be done via the model. Sometimes you will even factor this out into a separate class to which the model has a reference (allowing you to easily switch between a file-based data loader and a database data loader, or an implementation that accesses a web service, for example). In the simple case you might do
public class DataModel {
// other code as before...
public void loadData(File file) throws IOException {
// load data from file and store in personList...
}
public void saveData(File file) throws IOException {
// save contents of personList to file ...
}
}
Then you might have a controller that provides access to this functionality:
public class MenuController {
private DataModel model ;
#FXML
private MenuBar menuBar ;
public void initModel(DataModel model) {
if (this.model != null) {
throw new IllegalStateException("Model can only be initialized once");
}
this.model = model ;
}
#FXML
public void load() {
FileChooser chooser = new FileChooser();
File file = chooser.showOpenDialog(menuBar.getScene().getWindow());
if (file != null) {
try {
model.loadData(file);
} catch (IOException exc) {
// handle exception...
}
}
}
#FXML
public void save() {
// similar to load...
}
}
Now you can easily assemble an application:
public class ContactApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
FXMLLoader listLoader = new FXMLLoader(getClass().getResource("list.fxml"));
root.setCenter(listLoader.load());
ListController listController = listLoader.getController();
FXMLLoader editorLoader = new FXMLLoader(getClass().getResource("editor.fxml"));
root.setRight(editorLoader.load());
EditorController editorController = editorLoader.getController();
FXMLLoader menuLoader = new FXMLLoader(getClass().getResource("menu.fxml"));
root.setTop(menuLoader.load());
MenuController menuController = menuLoader.getController();
DataModel model = new DataModel();
listController.initModel(model);
editorController.initModel(model);
menuController.initModel(model);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
}
As I said, there are many variations of this pattern (and this is probably more a model-view-presenter, or "passive view" variation), but that's one approach (one I basically favor). It's a bit more natural to provide the model to the controllers via their constructor, but then it's a lot harder to define the controller class with a fx:controller attribute. This pattern also lends itself strongly to dependency injection frameworks.
Update: full code for this example is here.
If you are interested in a tutorial on MVC in JavaFX, see:
The Eden Coding tutorial: How to apply MVC in JavaFX
What i want to know is that if loading the data from a file is the responsibility of the Controller Or the model?
For me the model is only responsible of bringing the required data structures that represent the business logic of the application.
The action of loading that data from any source should be done by the Controller Layer. You could also use the repository pattern, which can help you in abstracting from the type of source when you are acessing the data from the view. With this implemented you should not care if the Repository implementation is loading the data from file, SQL, NoSQL, Web service ...
And the ObservableList of the names will be stored in the controller or the model?
For me the ObservableList is part of the View. It is the kind of data structure you can bind to JavaFX controls. So for example a ObservableList<String> could be populated with Strings from the model but the ObservableList reference should be an attribute of some View´s class.
In JavaFX it's very pleasant to bind JavaFX controls with Observable Properties backed by domain objects from the model.
You could also have a look to viewmodel concept. For me a JavaFX bean backed by a POJO could be considered as a view-model, you could see it as a model object ready to be presented in the view. So for example if your view needs to show some total value calculated from 2 model attributes, this total value could be an attribute of the view-model. This attribute would not be persisted and it would be calculated any time you show the view.
I am working on a new project that will need to show a separate stage on the secondary monitor. This will be a non-interactive stage (only used to display nodes). I will follow this approach to handle that part.
However, I also want to have a duplicate copy of that stage visible within a pane in my main app. It would need to update itself at the same time the stage does.
Where would I start learning how to implement this? Does Java provide a built-in API to display realtime screenshots of a stage, by chance?
I am no expert but I think there are two ways to do it.
Method 1
Wrap everything of that stage into a main FXML/controller file/class (as a View). Then you need to load that FXML file twice, once in the new stage, the other in a dedicated space you have prepared in your main stage.
The reference of both controller instances should ideally be held at the same object, either in your application class or the class hosting your main stage.
From there, you can either bind values and let the binding API do the work for you.
Example:
Main View:
public class MainView {
private Model model = new Model();
private Pane space; // Dedicated space
public void spawnView() {
FXMLLoader spawnViewLoader = new FXMLLoader(getClass().getResource("View.fxml"));
Parent spawnView = spawnViewLoader.load(); // Need to catch IOException
ViewController spawnController = spawnViewLoader.getController();
spawnController.setup(model);
new Stage(new Scene(view));
FXMLLoader dupViewLoader = new FXMLLoader(getClass().getResource("View.fxml"));
Parent dupView = dupViewLoader.load(); // Need to catch IOException
ViewController dupController = dupViewLoader.getController();
dupController.setup(model);
space.getChildren().setAll(dupView);
}
}
Model:
public class Model {
private final StringProperty title = new SimpleStringProperty();
private final StringProperty titleProperty() { return title; }
private final String getTitle() { return title.get(); }
private final void setTitle(final String title) { this.title.set(title); }
}
View controller:
public class ViewController {
#FXML private Label label;
private Model model;
public void setup(Model model) {
if (model == null)
throw new NullPointerException();
this.model = model;
label.textProperty().bind(model.titleProperty());
}
// Other stuff
}
Be careful of the binding though - I'm quite sure whatever I wrote is going to cause memory leak; both Views will not clean up as long you're holding a reference of model. You can always do it without binding, but it's going to be more tedious to update values.
Method 2
This method is more complex and is likely to be several frames slower. I'm not going to post sample codes as this is not that straight-forward.
You need to have a reference of the Scene of that stage, and use an AnimationTimer to call the snapshot method of the scene object.
Then you need to use an ImageView in your main stage to display the snapshots returned.
Seriously, I think this method would cause the duplicate View to be several frames slower than the original's.
one question that always pops up when my java project gets bigger is if there is an easy way of making references to a specific object thats cannot be referenced by super or getParent(). The following graph should illustrates my current problem:
For every FileListItem I want to instatiate a new FileListItemController which needs methods from ProtocolController. Is there a way of referencing the ProtocolController object instatiated by main in the FileListItems other than passing it on through mainWindow, FileListContentPanel, FileListItems?
First thanks to all your answers.
I am using the model, view, control pattern for my project.
The singleton pattern sounds interesting but my feeling is that it does not solve my issue. Here is some code to illustrate my problem:
public class Main {
public static void main(String [ ] args) {
ProtocolController pc = new ProtocolController();
mainWindow mw = new mainWindow();
}
}
public class ProtocolController {
File protocol;
public ProtocolController(File protocol){
this.protocol = protocol;
}
public void writeSomethingToProtocolFile(String something){
// write something to th protcol file specified by the object
}
}
public class mainWindow {
public mainWindow(){
FileListContentPanel flcp = new FileListContentPanel();
}
}
public class FileListContentPanel {
public FileListContentPanel(){
int numListItems = 10;
for (int i = 0; i < 10; i++) {
FileListItem fli = new FileListItem();
FileListItemController flic = new FileListItemController(fli);
}
}
}
public class FileListItemController {
public FileListItemController(FileListItem fli){
}
public void addSomethingToProtocol(String something){
// at this point I want to use a method from the ProtocolController class instantiated by the main method
}
}
There are different approaches for this.
For instance, if you only need one ProtocolController instance, you could use the singleton pattern. Each FileListItemController is then able to retrieve the same ProtocolController object.
class ProtocolController {
private static instance;
private ProtocolController() { }
public static ProtocolController getInstance() {
if (instance == null) {
instance = new ProtocolController();
}
return instance;
}
}
You can then get ProtocolController.getInstance() from within FileListItemController and then call the desired method.
If you have only one instance of FileListItemController, you can use Singleton pattern with "Lazy instantiation".
And ProtocolController instantiates FileListItemController like this :
FileListItemController fileListItemControllerInstance = FileListItemController.getInstance();
EDIT
If each FileListItem references one FileListItemController, you can use a Factory (Abstract Factory Pattern)
In this case, your ProtocolController can instiate a FileListItemController factory. You can pass ProtocolController reference to the factory if needed.
Then FileListItem can use this factory to create new FileListItemController instances.
This looks like a use case for Model-View-Controller design pattern.
Your FileListItem objects are model (You'll need a new class to hold them). Both Controller and View (FileListControlPanel) get a reference to Model. Controller then uses methods on model to change its contents, and View subscribes to model's events to show the changes.
https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
Window doesn't need to know about the protocol. In fact, Panel doesn't need to know about the protocol either.
Use dependency injection.
Panel
Since Panel creates the controllers, Panel will need a reference to Protocol. So you'll need to inject the protocol into the panel.
class Panel {
public Panel(Protocol protocol) {
for(...) {
Item item = new Item();
Controller controller = new Controller(item, protocol);
}
}
}
I don't recommend populating the list in the constructor like this, but I kept it so the code is familiar.
Window
To prevent Window knowing about Protocol, you should inject the panel.
class Window {
public Window(Panel panel) {
// ...
}
}
Update main
With these new changes, you'd be interfacing with your types as follows:
Protocol protocol = new Protocol();
Panel panel = new Panel(protocol);
Window window = new Window(panel);
Now Window doesn't need to depend on Protocol.
Introduce a factory
I mentioned how Panel doesn't need to depend on Protocol either.
It's the Controller that uses the protocol; the panel only references the protocol so it can pass it to newly created controllers. The protocol is needed for the creation of controllers.
You could introduce a factory to handle the creation of controllers. The factory would reference the protocol:
class ControllerFactory {
private Protocol protocol;
public ControllerFactory(Protocol protocol) {
this.protocol = protocol;
}
public Controller newController(Item item) {
return new Controller(item, protocol);
}
}
Panel would now depend on the factory instead of the protocol:
class Panel {
public Panel(ControllerFactory factory) {
factory = factory;
for(...) {
Item item = new Item();
Controller controller = factory.newController(item);
}
}
}
Your new usage would be as follows:
Protocol protocol = new Protocol();
ControllerFactory factory = new ControllerFactory(protocol);
Panel panel = new Panel(factory);
Window window = new Window(panel);
Neither Window or Panel know about the protocol.
JavaFX itself has some means of DI to allow binding between XML-described UIs and controllers:
<Pane fx:controller="foo.bar.MyController">
<children>
<Label fx:id="myLabel" furtherAttribute="..." />
</children>
</Pane>
The Java-side looks like this:
public class MyController implements Initializable {
#FXML private Label myLabel;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// FXML-fields have been injected at this point of time:
myLabel.setText("Hello world!");
}
}
For this to work, I can not just create an instance of MyController. Instead I have to ask JavaFX to do stuff for me:
FXMLLoader loader = new FXMLLoader(MyApp.class.getResource("/fxml/myFxmlFile.fxml"), rb);
loader.load();
MyController ctrl = (MyController) loader.getController();
So far, so good
However, if I want to use Dagger 2 to inject some non-FXML-dependencies into the constructor of this controller class, I have a problem, as I have no control over the instantiation process, if I use JavaFX.
public class MyController implements Initializable {
#FXML private Label myLabel;
/*
How do I make this work?
private final SomeService myService;
#Inject
public MyController(SomeService myService) {
this.myService = myService;
}
*/
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// FXML-fields have been injected at this point of time:
myLabel.setText("Hello world!");
}
}
There is one API that looks promising: loader.setControllerFactory(...); Maybe this is a good point to start with. But I do not have enough experience with these libraries to know how to approach this problem.
A custom ControllerFactory would need to construct Controllers of certain types only known at runtime. This could look like the following:
T t = clazz.newInstance();
injector.inject(t);
return t;
This is perfectly ok for most other DI libraries like Guice, as they just have to look up dependencies for the type of t in their dependency graph.
Dagger 2 resolves dependencies during compile time. Its biggest features is at the same time its biggest problem: If a type is only known at runtime the compiler can not distinguish invocations of inject(t). It could be inject(Foo foo) or inject(Bar bar).
(Also this wouldn't work with final fields, as newInstance() invokes the default-constructor).
Ok no generic types. Lets look at a second approach: Get the controller instance from Dagger first and pass it to the FXMLLoader afterwards.
I used the CoffeeShop example from Dagger and modified it to construct JavaFX controllers:
#Singleton
#Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
Provider<CoffeeMakerController> coffeeMakerController();
}
If I get a CoffeeMakerController, all its fields are already injected, so I can easily use it in setController(...):
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
CoffeeMakerController ctrl = coffeeShop.coffeeMakerController().get();
/* ... */
FXMLLoader loader = new FXMLLoader(fxmlUrl, rb);
loader.setController(ctrl);
Parent root = loader.load();
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.show();
My FXML file must not contain a fx:controller attribute, as the loader would try to construct a controller, which of course stands in conflict with our Dagger-provided one.
The full example is available on GitHub
Thanks to Map multibinding mechanism hint from #Sebastian_S I've managed to make automatic controller binding using Map<Class<?>, Provider<Object>> that maps each controller to its class.
In Module collect all controllers into Map named "Controllers" with corresponding Class keys
#Module
public class MyModule {
// ********************** CONTROLLERS **********************
#Provides
#IntoMap
#Named("Controllers")
#ClassKey(FirstController.class)
static Object provideFirstController(DepA depA, DepB depB) {
return new FirstController(depA, depB);
}
#Provides
#IntoMap
#Named("Controllers")
#ClassKey(SecondController.class)
static Object provideSecondController(DepA depA, DepC depC) {
return new SecondController(depA, depC);
}
}
Then in Component, we can get an instance of this Map using its name. The value type of this map should be Provider<Object> because we want to get a new instance of a controller each time FXMLLoader needs it.
#Singleton
#Component(modules = MyModule.class)
public interface MyDiContainer {
// ********************** CONTROLLERS **********************
#Named("Controllers")
Map<Class<?>, Provider<Object>> getControllers();
}
And finally, in your FXML loading code, you should set new ControllerFactory
MyDiContainer myDiContainer = DaggerMyDiContainer.create()
Map<Class<?>, Provider<Object>> controllers = myDiContainer.getControllers();
FXMLLoader loader = new FXMLLoader();
loader.setControllerFactory(type -> controllers.get(type).get());
Alternatively you can do something like:
...
loader.setControllerFactory(new Callback<Class<?>, Object>() {
#Override
public Object call(Class<?> type) {
switch (type.getSimpleName()) {
case "LoginController":
return loginController;
case "MainController":
return mainController;
default:
return null;
}
}
});
...
As #Sebastian_S noted, a reflection-based controller factory is not possible. However calling setController is not the only way, I actually like this setControllerFactory approach better because it doesn't break the tooling (e.g. IntelliJ's XML inspections) but having to explicitly list out all the classes is definitely a drawback.
This is solved probably long ago for many people. I did not like the solution described here in as it relies on class names or reflection over clean design. I wrote a bit different one that looks more maintainable to my eyes.
The gist of it is to use Dagger to create the Scene that is injected into the Stage. Here is my Application class
CameraRemote context;
public static void main(String[] args) {
launch(args);
}
public SimpleUI() {
context = DaggerCameraRemote.builder().build();
}
#Override
public void start(Stage stage) throws IOException {
stage.setTitle("Remote Control");
stage.setScene(context.mainFrame());
stage.show();
}
I have in my Dagger 2 module the logic for loading fxml and customization of the controller i.e. injecting the SsdpClient
#Provides
public static Scene provideMainScene(SsdpClient ssdpClient) {
try {
FXMLLoader loader = new FXMLLoader(CameraModule.class.getResource("/MainFrame.fxml"));
Parent root;
root = loader.load();
MainController controller = (MainController) loader.getController();
controller.setClient(ssdpClient);
return new Scene(root, 800, 450);
} catch (IOException e) {
throw new RuntimeException("Cannot load MainFrame.fxml", e);
}
}
I can split further the creation of Parent instance. It is not used anywhere else and I compromised.
Overview
In my (Android) Java game, I have a class called resources. As the name suggests, this class holds the resources for the game. All of my OpenGL objects (Sprites) are created here
It's looks something like the following (obviously, this is a simplified version compared to that which appears in the real project):
public class Resources {
Hero hero;
Enemy enemy;
MenuButtons mainMenuButtons;
Background background;
Scene mainMenu;
public void createObjects(){
hero = new Hero();
enemy = new Enemy();
mainMenuButtons = new MenuButtons();
background = new Background();
mainMenu = new Scene(this);
}
}
So, within my mainMenu scene, I need access my objects, so we may see something like this:
public class mainMenu implements Scene {
Resources resources;
public mainMenu(Resources resources){
this.resources = resources;
}
#Override
public void render(){
resources.background.draw();
resources.hero.draw();
resources.enemy.draw();
mainMenuButtons.draw();
}
#Override
public void updateLogic(){
resources.hero.move();
resources.enemy.move();
resources.mainMenubuttons.animate();
}
}
Now, the above method is just one way to get access to the objects in resources and their methods. But does this actually break the Law of Demeter? If not, why not? If so, what is the best way to get access to these objects in a way that does not violate the LOD?
Accessors?
One option (which I've ruled out TBH - see below) is placing accessor methods into my resources class. So that I could do something like:
resources.drawBackround();
I have a lot of objects and I need an accessor for each method/variable of each object. Not really practical, it seems like I'm writing a ton of extra code and most importantly, it makes the resources class ridiculously long as it becomes filled with these accessors. Therefore, I'm not going down this road.
Passing in objects into the scene's constructor
Of course, I can also do something like this:
hero = new Hero();
enemy = new Enemy();
mainMenuButtons = new MenuButtons();
background = new Background();
mainMenu = new Scene(hero, enemy, mainMenuButtons, background);
So I can simply do this:
background.draw(); //etc....
This is workable for simple scene's (such as menu systems that don't require a lot of objects) but for the main game, it could quickly become a mess as I'd have to pass references to some 30+ objects into the constructor which doesn't really sound quite right......
So I would really appreciate if someone could point out the best way to proceed and why.
So I would really appreciate if someone could point out the best way to proceed and why.
The best way, in my opinion, is to keep the Resources class, make all objects private to not break the law and write accessors (but not for every object like you already ruled out).
I have a lot of objects and I need an accessor for each method/variable of each object. Not really practical, it seems like I'm writing a ton of extra code and most importantly, it makes the resources class ridiculously long as it becomes filled with these accessors. Therefore, I'm not going down this road.
I assume many objects are of the same class. So you do not have to make an accessor for every object what would really blow up the class.
I a game you normally have a hero, one or more enemies and many sprites.
public class Resources {
private Hero hero;
private Enemy enemy;
private MenuButtons mainMenuButtons;
private Background background;
private Scene mainMenu;
public void createObjects(){
hero = new Hero();
enemy = new Enemy();
mainMenuButtons = new MenuButtons();
background = new Background();
mainMenu = new Scene(this);
}
public Hero getBackground() {
return background;
}
public Hero getHero() {
return hero;
}
public List<Enemy> getEnemies() {
ArrayList<Enemy> list = new ArrayList<Enemy>();
list.add(enemy);
list.add(next_enemy);
return list;
}
public List<Sprite> getSprites() {
ArrayList<Sprite> list = new ArrayList<Sprite>();
list.addAll(enemy.getActiveSprites());
return list;
}
}
Instead of getHero() and getEnemy() you could also make a getActor() method if Hero and Enemy are derived from the same class.
The getSprites() method is just an example how it could look like.
If that solution is not going to work for you, I have another suggestion.
Make the Resources class do some work.
public class ResourceManager {
private Hero hero;
private Enemy enemy;
private MenuButtons mainMenuButtons;
private Background background;
private Scene mainMenu;
public void createObjects(){
hero = new Hero();
enemy = new Enemy();
mainMenuButtons = new MenuButtons();
background = new Background();
mainMenu = new Scene(this);
}
public void render(Scene scene) {
this.background.draw();
if (scene != mainMenu) {
this.hero.draw();
this.enemy.draw();
}
this.mainMenuButtons.draw();
}
public void updateLogic(Scene scene){
this.hero.move();
this.enemy.move();
this.mainMenubuttons.animate();
}
}
The mainMenu then calls logic methods directly in the RescourceManager class.
public class mainMenu implements Scene {
ResourceManager resourceManager;
public mainMenu(ResourceManager resourceManager){
this.resourceManager = resourceManager;
}
#Override
public void render(){
resourceManager.render(this);
}
#Override
public void updateLogic(){
resourceManager.updateLogic(this);
}
}
I hope my suggestions helped you a bit figure out how to continue with your project.
You could use dependency injection and eliminate your Resources class. Then you can call functions on your fields and wouldn't be in violation of the Law of Demeter.
Here is an example using constructor injection:
public class MainMenu implements Scene {
Background background;
Hero hero;
Enemy enemy;
MenuButtons buttons
public mainMenu(Background background, Hero hero, Enemy enemy, MenuButtons buttons){
this.background = background;
this.hero = hero;
this.enemy = enemy;
this.buttons = buttons;
}
#Override
public void render(){
this.background.draw();
this.hero.draw();
this.enemy.draw();
this.mainMenuButtons.draw();
}
#Override
public void updateLogic(){
this.hero.move();
this.enemy.move();
this.mainMenubuttons.animate();
}
}
With dependency injection, you pass instances into constructors and functions instead of "newing" them inside your class. You need to manage your instances somewhere though, and there are plenty of libraries that will do that for you. Dagger is a popular one for Android: http://square.github.io/dagger/
The idea of passing a list isn't a bad first step, but it's not sufficient. Game developers have a (somewhat controversial) concept of a structure called a "scene graph" that helps them keep track of their resources (among other things). https://en.wikipedia.org/?title=Scene_graph
It's a pretty complicated concept, but you're going to need to learn about it sooner or later. There's a lot of good advice on gamedev.stackexchange.com, so I'd suggest you take a peek over there.
Here's a nice YouTube video tutorial on the subject. https://www.youtube.com/watch?v=ktz9AlMSEoA
You could create an Drawer class that handles the drawing of all the objects. Your scene objects simply need to feed the Drawer the objects that I assume are Drawable.
public class Drawer {
public void drawObjects(Drawable... objects) {
for(Drawable drawable : objects) {
drawable.draw();
}
}
}
This is then used by Scene to draw those objects.
public class mainMenu implements Scene {
Resources resources;
Drawer drawer;
...
public void render() {
drawer.drawObjects(resources.background,
resources.hero,
resources.enemy,
resources.mainMenuButtons);
}
...
}
A similar strategy, using an Updater, can applied for the other methods. If your updateLogic() method makes as simple of calls as it looks, you can definitely do the same thing, by making all those objects inherit from an Updateable interface.
public interface Updateable {
void update();
}
Hero's and Enemy's update() methods could simply call their move() methods, while MenuButtons's update() could delegate to animate(), etc.
Obviously, if you like, you can use some sort of collection instead of varargs for the parameter of Drawer's drawObjects(). I just like the nice fluency made possible by the varargs, since you don't have to create the collection.
For other tips for keeping code in line with the Law of Demeter, check out this article: Law of Demeter and How to Work With It
I like the concept of a ResourceManager. But a ResourceManager should be responsilbe for loading Resources, caching and freeing them. Rendering is definitly a Method of a Render Object.
So the Scence - render Method could delegate the rendering to it after instantiating a Renderer and feed it with Drawables as the Renderer does not render Resources but renderable objects.
Say:
class MainMenu implements Scene {
Renderer sceneRenderer = new Renderer();
AnimatedRenderer animatedRenderer = new AnimatedRenderer();
ResourceManager resourceManager = ResourceManager.getInstance();
List<Resource> resources;
List<Drawable> renderedObjects;
GameObjectController gameObjectController;
void initializeScene() {
resources = resourceManager.getResources();
renderedObjects = resourcesAsRenderables();
sceneRenderer.setDrawables(renderedObjects);
}
List<Drawable> resourcesAsRenderables() {
// if resources are not directly renderable, do decoration etc
// and return a List of Drawable
}
#Override
public void render(){
sceneRenderer.render();
}
#Override
public void updateLogic(){
moveGameObjects();
doAnimations();
}
protected void moveGameObjects() {
gameObjectController.moveAllObjects(this, resources);
}
protected void doAnimations() {
animatedRenderer.render(resources);
}
class ResourceManager {
private static ResourceManager instance = null;
List<Resource> resources;
public ResourceManager getInstance() {
if(instance == null) {
instance = new ResourceManager();
instance.loadResources();
}
return instance;
}
private void loadResources() {
resources = new LinkedList<Resource>();
resources.add(new Hero());
....
}
public List<Resource> getResources() {
return resources;
}
}
This clearly separates the logic and responsibilities for the tasks carried out during the scene lifecycle. A resource manager which is responsible for retrieving resources and as they may take long loading times does things like caching or freeing in low memory situations hiding the details from the client. A renderer which is responsible for displaying the objects and a controller which is responsible for moving the objects. The controller itself may implement handlers for keyboard events but that is not something which must be transparent to the scene. The renderer may swap backgrounds in or out or scale or set lighting effects but the scene only calls its render method. The animated renderer is responsible for starting , rendering and stopping animations.
Change this:
public void render(){
resources.background.draw();
resources.hero.draw();
resources.enemy.draw();
mainMenuButtons.draw();
}
#Override
public void updateLogic(){
resources.hero.move();
resources.enemy.move();
resources.mainMenubuttons.animate();
}
With this:
public void render(){
resources.render();
}
#Override
public void updateLogic(){
resources.update();
}
ResourceManager don't have to know what's inside Resources. It may be one enemy or ten, it doesn't care to ResourceManager.
And so in 'Resource' you can do:
public void update(){
hero.update();// Cause hero may, or may not move, he makes the choice
enemy.update();//...
mainMenubuttons.update();//.
}
public void render(){
...
}
More than this! you could change the "Resource" implementation with an interface and you will be programming for interfaces and not for implementations, which is cool! This way you can have a 'Resources' for in-game and another one for menus that will be used in same way: Only changing, at runtime, the concrete Resources you will be in a menu or in game!
Anyway, not always is needed to fill Demeter.
As can be seen your Resources dont need to be recreated, instead they do use some resources that cant be reloaded (probably images).
You should share the images object within a Resource class, and create your objects within a Scene class, on the constructor of the entities you can get the shared resource that is pre-loaded.