I am at currently navigating my way around OOP architecture and looking for best practices rather than code that just works. To highlight my latest conondrum I have written a short example of a problem I run into regularly below. In this example app users can start a learning journey of a programming subject they want to learn and plot their progress throughout. In reality I would need a backend for this type of app as the data will need to be saved, but for learning purposes I have written a short piece of code as if I am making it solely on the frontend.
A user can start a new learning journey by typing in the name of a journey and selecting the subject e.g. react, java, npm etc. This journey is then stored in an array of objects. Inside the app we can do a lot of things. e.g find all the React journeys by filtering, sort the journeys in various ways e.g. date started, add to an existing journey with text, highlight important words in the journey using the selection API in the browser.
I have written some code below which is a small snippet of what the app can do:
class Journey {
constructor(name, subject, dateStarted = new Date().toLocaleString()) {
this.name = name;
this.subject = subject;
this.dateStarted = dateStarted;
}
}
class JourneyCollection {
constructor(journeyArr = []) {
this.journeysArr = journeyArr;
}
addJourney(name, subject) {
this.journeysArr.push(new Journey(name, subject));
}
sortJourneys() { // as the app grows this will receive an argument to establish how we will sort e.g. by date, alphabetic, or number of updates to journey
this.journeysArr.sort((a, b) => {
return new Date(b.dateStarted) - new Date(a.dateStarted);
});
}
getJourneysBySubject(subject) {
const journeys = this.journeysArr.filter((jrny) => {
return jrny.subject === subject;
});
return journeys;
}
}
const journeys = new JourneyCollection();
document.querySelector("#add").addEventListener("click", () => {
journeys.addJourney(
document.querySelector("#name").value,
document.querySelector("#subject").value
);
});
document.addEventListener("keypress", (e) => { // if we hit enter the array gets sorted
if (e.keyCode === 13) {
journeys.sortJourneys();
}
});
document.querySelector("#findSubject").addEventListener("click", () => {
const returned = journeys.getJourneysBySubject(
document.querySelector("#find").value
);
});
Now the problem I am starting to have and will continue to have is that I am well on my way to having a god type class which controls the whole application because each method relies on the array which holds the all the Journeys.
Instead I was thinking of doing something where I can pass the array in each time as an argument and make several classes depending on what I want each part of the app to do. Taking just the part where I wan to add a journey, I thought of something like this:
const arrayOfJourneys = [];
class Journey {
constructor(name, subject, dateStarted = new Date().toLocaleString()) {
this.name = name;
this.subject = subject;
this.dateStarted = dateStarted;
}
}
class AddJourney {
constructor(arr, name, subject) {
this.arr = arr;
this.journey = new Journey(name, subject);
}
addJourney() {
this.arr.push(this.journey);
}
}
document.querySelector("#add").addEventListener("click", () => {
const add = new AddJourney(
arrayOfJourneys,
document.querySelector("#name").value,
document.querySelector("#subject").value
);
add.addJourney();
});
The pros and cons I can see so far
code1 Class will become too big and a lot of the Classes don't actually interact with each other or relate to each other, they all just need the journeys array inside the object created through the Class. I have read about God classes controlling apps and am not keen on this style.
code2 looks similar to an anti pattern I have read about called the Anemic Domain pattern where our classes just contain data. (in this case the array being the Data) although its not in a class it could be and I could call a method which returns it each time. Although reading into it I dont think my code2 above quite fits the Anemic criteria but it has frightened me. I like how I can change the state of this Array inside any other Class If I pass it in.
Code 2 whilst I think is better , my classes will all be verbs such as AddJourney or AddToExistingJourney or sortJourney. I have also read Classes should be Nouns and its behaviours should be the Verbs. I am not sure how important this is.
Perhaps there is another way that I am missing?
Thanks
classes any more - write a plainfunction. This would become functional programming or procedural programming though, not object-oriented programming.getFilteredJourneysmethod that takes the filter predicate as an argument. Whether this is useful or not depends on how related/unrelated those predicates are, where they are used respectively, and how often each is used.