Hum… I’m not too sure. I think starting to think more in terms of values, and possibly starting to design more value objects (immutable objects) with value equality.
I feel one part of data-oriented to me actually relates a lot to Domain Driven Design.
If you model your domain as a mixture of Values with value semantics, and Identities over those.
Like Coordinate is a value object with fields X and Y. You make its equals be equal to other Coordinates of the same X and Y value. Then you make Player an Identity which has an ID and it has a Coordinate value. This would be a typical DDD setup.
Ok, this post will be a bit all over the place, since I’m not really sure and exploring the idea. I feel Java maybe just can’t really be used in a data-oriented way, unless you’d build so many other constructs and then use those.
Recapping here:
A standard Java design:
class Player {
private int X;
private int Y;
public Player (int X, int Y) {
this.X = X;
this.Y = Y;
}
public move(int x, int y) {
this.X = x;
this.Y = y;
}
public getX() { return this.X; }
public getY() { return this.Y; }
}
Notice that first of all I had to write so much code to get what basically is just: {:x x :y y}
in Clojure, and even then I actually don’t have the same thing yet, lacking equality semantics, proper hash-codes, and not immutable.
Now in standard Java the Player identity isn’t modeled explicitly, right now it’s based on the instance of the player object you’d create:
Player playerOne = new Player(10, 20);
Now the identity is playerOne, but that’s just an alias to the real identity, which is actually the Object memory address.
Also, two players are equal if they are the same Object. And I can’t really extract the coordinates in any way, like there’s no structure for them, and in Java you can’t just create structures dynamically, so I’d need to explicitly create a Coordinate class for it.
Now in Clojure, you could do this, but you most likely wouldn’t:
(def player-one {:x 10 :y 20})
I mean, look, in one line of code I have all the previous code I wrote in Java 
But ok, in Clojure you’d model the identity explicitly instead:
(def players {:player-one {:x 10 :y 20}})
Which is interesting, because even your list of players is now runtime data. To get all players you just do (keys players)
. Now depending on the use case, you could arrive to this as well in Java:
Map<String, Player> players = Map.of("playerOne", new Player(10, 20));
Don’t even remember if that’s a valid way to construct a map but oh well. It’s not as intuitive to get here from Java, and why is the player identity a string? And also, you now can’t mix more things in this map, so in Clojure you’d eventually get:
(def game-state (atom {:players {:player-one {:x 10 :y 20}}}))
And I mean wow already we accomplished so much domain modeling in a single line of code. There could be ways to do this in Java, but most likely at best you’d keep each of the keys in your game-state as top level variables with their identity an implicit variable reference, no real key associated to them.
Ok let me go back to Java. In DDD you’d make a change as so:
class Coordinates {
private int X;
private int Y;
public getX() { return this.X; }
public getY() { return this.Y; }
public Coordinates (int X, int Y) {
this.X = X;
this.Y = Y;
}
@override
public boolean equals(Coordinate other) {
return this.X == other.getX() && this.Y == other.getY();
}
}
class Player {
private Coordinates coordinates;
public Player(Coordinates c) {
this.coordinates = c;
}
public move(int x, int y) {
this.coordinates = new Coordinates(x, y);
}
}
Where now we’ve introduced a value object, and thus the concept of value/identity is forming more clearly. The Player as the identity for a set of immutable values, thus the Player is a representation of changing values over time that can be refered to by name (identified).
In Clojure, we already had that, because everything was already just values to start with. The map {:x 10 :y 20}
is already a Coordinates value object by itself, no special care needed, and the Player was a key/value pair of the identity over that map:
{:player-one {:x 10 :y 20}}
This is the same as the DDD version in Java.
So that’s already getting us a bit more data-oriented-ish, but we don’t meet all the requirements you’ve defined for data-oriented.
Now, in true DDD, we’d also be forced to be explicit about the identity, because generally it believes you want to have a database, or a way to add/remove/update your domains identities.
So you’d change things like:
class Player {
private String id;
private Coordinates coordinates;
public Player(String id, Coordinates c) {
this.id = id;
this.coordinates = c;
}
public move(int x, int y) {
this.coordinates = new Coordinates(x, y);
}
public String getId() { return this.id; }
}
Now in Clojure your player is already like that:
{:player-one {:x 10 :y 20}}
That’s the same thing, you want the id? (first (keys player))
that’s it. But you can refine it a little if you prefer and there’s a few different ways depending what’s most convenient like:
{:id :player-one
:coordinates {:x 10 :y 20}}
Is a simple change and maybe you prefer the explicit keys for the various piece of information inside the Player, so that’s all you need to do to get the same exact thing as Java.
Ok, one last thing, you could take the concept of identity/values to the extreme in Java, because you still have a lot of mutation in theory. If your Player had another value object, like say inventory
, well in theory you could have an inconsistent state because you could say have code that says move player to item and pick it up, which should result in your player being moved to that coordinate and also have the item in the inventory. But in Java, it would be easy to have a bug where somehow the player doesn’t end up with the updated coordinates but does end up with the item. Because nothing on the Object protects the invariant between mutable changes made to both inventory and coordinates, even though each one contains an immutable value, the player data is not a value object yet. So here’s the full version you would really want:
class PlayerData {
private Coordinates coordinates;
private List<Item> inventory;
public PlayerData(Coordinates c, Inventory i) {
this.coordinates = c;
this.inventory = i;
}
public move(int x, int y) {
return new PlayerData(new Coordinates(x, y), this.inventory);
}
public add-item(Item item) {
return new PlayerData (this.c, this.inventory.clone().add(item));
}
}
class Player {
private String id;
private PlayerData playerData;
public Player(String id, PlayerData playerData) {
this.id = id;
this.playerData = playerData;
}
public update(PlayerData newPlayerData) {
this.playerData = newPlayerData;
}
public String getId() { return this.id; }
public String getPlayerData() { return this.playerData; }
}
And now you can use it like:
playerOne.update(playerOne.getPlayerData.move(30, 50).add-item(item));
Seems we’re slowly getting closer to data-oriented programming here. Let’s see in Clojure:
(def player-one
{:id :player-one
:coordinates {:x 10 :y 20}
:inventory [:guitar]})
(-> player-one
(assoc :coordinates {:x 30 :y 50})
(update :inventory conj item))
Ya so the Java and Clojure are both starting to look a lot alike. In Clojure we don’t need the PlayerData abstraction, because the assoc and update to player are immutable already, and protect our invariants. Also, we can easily get the playerData out if we wanted without needing it be explicitly defined:
;; This gives us the PlayerData
(dissoc player-one :id)
Alright, I feel we’re getting close. To make it fully data-oriented now I think we need to make it more functional, and split data and methods into seperate things. So finally I present data-oriented Java:
class PlayerData {
private Coordinates coordinates;
private List<Item> inventory;
public PlayerData(Coordinates c, Inventory i) {
this.coordinates = c;
this.inventory = i;
}
public Coordinates getCoordinates() {
return this.coordinates;
}
public Inventory getInventory () {
return this.inventory.clone();
}
public static move(PlayerData pd, int x, int y) {
return new PlayerData(new Coordinates(x, y), pd.getInventory);
}
public static add-item(PlayerData pd, Item item) {
return new PlayerData(pd.getCoordinates, pd.getInventory().add(item));
}
}
class Player {
private String id;
private PlayerData playerData;
public Player(String id, PlayerData playerData) {
this.id = id;
this.playerData = playerData;
}
public String getId() { return this.id; }
public String getPlayerData() { return this.playerData; }
public static update(Player p, PlayerData pd) {
return new Player(p.getId(), pd);
}
}
- Separate code from data
Yes, our code is static (happens to be grouped on the same “namespace” (ie class in Java), but we could move them outside somewhere else if we wanted because they don’t belong to the object.
- Model entities with generic data structures
Not quite, which is why we need to implement a move and an add-item ourselves and can’t just reuse existing data-structure functions to do so. But in Java this is probably better in my opinion. At least we’ve reduced our data to dumb classes that only have data, and a clear distinction between identity/value.
- Data is immutable
Somewhat, we had to do a lot to protect data from being changed after it is set, such as cloning the list, and making the field private, and not having any setters.
Now, would this style in Java be a good idea? Honestly I don’t know, I never tried it on a code base. I don’t know how important a style is relative to the ergonomics of a language, this style doesn’t feel as ergonomic in Java as in Clojure, so I’m not sure how it would fair against other styles used in Java.
P.S.: Wrote this all on my phone, so there might be some mistake in my code.