Open junedev opened 2 years ago
(Maybe related: https://javascript.info/private-protected-properties-methods)
Since this is a concept exercise, we can add some tests to remove some of this behaviour, if you'd like. Technically we could also start pushing the #private
syntax (even though I hate it) given its current stage in the TC39 process.
I read a lot of negative posts about the real private properties lately so I don't feel like pushing them either. Also I prefer to stick to stage 4 as much as possible in the concepts.
I would love to fix the exercise but I am not sure how to best approach this. I don't think it would be good to just make the tests enforce the use of the methods instead of direct assignment if there is no actual difference. It makes it very hard to argue the case in mentoring. I would prefer if it would be clear from the type of the problem that the method needs to be used, that some property needs to be "private", etc.
I am happy to make bigger changes to the tasks and story if someone has an idea how to construct the exercise in a way that a desired solution comes more naturally.
Here the minimum I would like to get out of the exercise:
The current instructions can be found here: https://exercism.org/tracks/javascript/exercises/windowing-system
Can anyone help with this? I have never worked in a real OOP style code base so it is hard for me to construct a sensible example here.
Ok. So. I solved the exercise @junedev , without looking at the exemplar, but purely reading the introduction and then following the instructions. This is what I came up with: https://exercism.org/tracks/javascript/exercises/windowing-system/solutions/SleeplessByte.
It is pretty close to the exemplar, so I think that if you know what you're doing, it's not a convoluted exercise at all. I do think the weakness in the exercises are as follows:
Position
and Size
to exist, because you'll always need to violate the Law of Demeter at the moment to get the properties you need. In my opinion, I would expect x
, y
, width
and height
to be either properties
or methods
of a ProgramWindow
, despite them perhaps being backed by a Position
or Size
class. I have worked with some game engines that definitely do it like this, it's just that I would likely not write it like this myself.Math.clamp
is not the fault of the exercise. I would recommend that we try to solve the issues at hand, but don't explicitly try to solve this. It does detract from the exercise, so perhaps we can drop it.Position
and Size
is the same, but you are effectively asked to do the same thing twice, with the only difference being a limit of 1 instead of 0.resize
/ move
) between the ProgramWindow
and the "helper" classes is annoying and makes the helpers pretty much "why are they there", because they don't do much.changeWindow
(see my solution and try to figure out why I do what I do).I have a few thoughts right now, but I will likely have more later this week. Most importantly, whatever we do to resolve your list of issues, I think we should not introduce inheritance, because we do not need it to solve these issues.
To show the power of the prototype we can introduce a story element that's something like:
You're building a modding system for the windows. The modding system will, when
activated, change _all_ existing windows and _future_ windows by adding
sparkles ✨. Implement a function `activateSparkles` that:
- Defines a property on all program windows called `sparkling` and set it to true.
- Defines a method on all program windows called `safeForWork()` which turns off
the sparkles and resizes the window to full screen.
This will force the student to use ProgramWindow.prototype
:
export function activateSparkles() {
ProgramWindow.prototype.sparkles = true
ProgramWindow.prototype.safeForWork = function safeForWork() {
this.sparkles = false
this.move(new Position())
this.resize(this.screenSize)
}
}
We would test that a window created in the test is also affected when calling activateSparkles
.
// Initially windows are unmodded
const testWindow = new ProgramWindow()
expect(testWindow.sparkles).toBeUndefined()
// When modded, all windows, including already existing ones become sparkling
activateSparkles()
expect(testWindow.sparkles).toBeTrue()
// Open a brand new window
const brandNewWindow = new ProgramWindow()
brandNewWindow.move(200, 200)
brandNewWindow.resize(300, 300)
expect(brandNewWindow.sparkles).toBeTrue()
// Make it safe for work
brandNewWindow.safeForWork()
expect(brandNewWindow .sparkles).toBeFalse()
expect(brandNewWindow.size.width).toBe(800)
expect(brandNewWindow.size.height).toBe(600)
// The old window is still sparkling
expect(testWindow.sparkles).toBeTrue()
Note that I kept the exercise intact, just to show how it would be done in this version of the exercise.
We can further show the power of instance vs prototype separation by adding "bugfixes" to the story.
There is a window that misbehaves. When it is shown full screen, it actually only
fills half the screen, because of a drawing mistake.
Write a function `patchSize(buggedWindow)` that will always double the width
of the `buggedWindow`, without affecting any other windows.
Depending on how we change the exercise, the student will need to overwrite the function on the instance / overwrite the property on the instance.
(A follow up excercise that deals with getters/setters etc. could then guard against this with Object.defineProperty and making a property non-configurable, or stuff like that. Cool shit).
These are my thoughts thus far. Sidenote: if we want people to not use properties, then we should have side-effects that are repeated, so you'll want to use the method.
After seeing a couple of solutions to "Windowing System" during mentoring, I think the exercise could need some changes to better achieve it's learning objectives.
Position
andSize
, the constructor and the method have the same implementation. I saw a solution that used the method in the constructor which is not a very typical example.Some ideas how to improve the exercise:
Position
andSize
could change the values by some amount instead of just setting a value. That would make them distinct from the constructor. (Probably only needed if we stick with "public properties".)