Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Oop exercise #416

Merged
merged 8 commits into from
Sep 26, 2022
Merged

Oop exercise #416

merged 8 commits into from
Sep 26, 2022

Conversation

OlexiyDobroskok
Copy link
Contributor

OOP exercise

Demo |
Code base

The code is submitted in a dedicated feature branch.

Only code files are submitted.

Please, review.

@github-actions
Copy link

github-actions bot commented Sep 3, 2022

Hey!

Congratulations on your PR! 😎😎😎

Let's do some self-checks to fix most common issues and to make some improvements to the code before reviewers put their hands on the code.

Go through the requirements/most common mistakes listed/linked below and fix the code as appropriate.

If you have any questions to requirements/common mistakes feel free asking them here or in Students' chat.

When you genuinely believe you are done put a comment stating that you have completed self-checks and fixed code accordingly.

Also, be aware, that if you would silently ignore this recommendation, a mentor can think that you are still working on fixes. And your PR will not be reviewed. 😒

A Tiny JS World -- OOP exercise check list

Relates to A Tiny JS World OOP exercise.

Check-list - definition of done

Minimal requirements to meet:

  • Implement a base class to inherit from
  • Employ default parameters
  • Each species is represented with its own class
  • No need to specify species at instantiation
  • Classes for species that do not have hands by natural design, do not consequently have hands or any equivalent property and do not inherit such properties from any base classes
  • All inhabitants are stored in a container (array or object)
  • JS native features are intensively employed (const, let, Array.map|join|forEach|..., etc)
  • Object methods like keys, values, entries as well as for...in shouldn't be used as these do not guarantee any particular order of keys/values
  • Properties used to form an object presentation string must be explicitly listed/specified; Use Array.map and Array.join to build a presentation string from the list.
  • A parent class shouldn't know anything about its children's props. Child classes must have good reasons to assign or refer to any parent's props. There are no such reasons in this task. Method overloading is to the rescue when building an inhabitant's presentation string.
  • OOP, SOLID and DRY principles are intensively employed

Optional level up (not required to implement):

  • Friends list is a list of objects refs rather than names (strings)
  • Cat-woman class is built employing composition rather than inheritance only

Bonus:

  • toString magic method; when implemented print(inhabitant) does the job as .toString is called implicitly
  • this.constructor.name; when used for output then no need to store species property

Helpful resources:

Universal recommendations:

  • Give variables and functions meaningful names. Avoid generic names like item, element, key, object, array or their variations. Exception: helper functions that are specifically and intentionally designed to be multipurpose.
  • Function names should start with a verb as they denote actions; variables are normally nouns; boolean variables/functions start with is, does, has etc; variable containing multiple entities and functions returning lists contain entity name in plural form.
  • Have consistent code style and formatting. Employ Prettier to do all dirty work for you.
  • Use common sense or seek for an advice whenever requirements look ambiguous or unclear.

Also take a note of the requirements above and follow them in all your future projects.

By the way, you may proceed to the next task before this one is reviewed and merged.

Sincerely yours,
Submissions Kottachecker 😺

@OlexiyDobroskok
Copy link
Contributor Author

Made a self-test and looked at the task from a new perspective)

@OleksiyRudenko OleksiyRudenko added the self-check-done Student confirmed that self-checks against requirements/common-mistakes are done label Sep 20, 2022
@OleksiyRudenko OleksiyRudenko self-assigned this Sep 20, 2022
Copy link
Member

@OleksiyRudenko OleksiyRudenko left a comment

Choose a reason for hiding this comment

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

@OlexiyDobroskok a really good job!
A few improvements and we are done.

Comment on lines 56 to 59
super({ species: "human", name, gender, saying, friends });
this.hands = hands ?? humanDefaultProperties.hands;
this.legs = legs ?? humanDefaultProperties.legs;
}
Copy link
Member

Choose a reason for hiding this comment

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

Why not just to set default value the same way it is done for species?
This is not a rhetoric question.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I’ve overdone the default values a little bit. =))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines 35 to 37
addFriend({ name }) {
this.friends += name + "; ";
}
Copy link
Member

Choose a reason for hiding this comment

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

From the requirements:

Friends list is a list of objects refs rather than names (strings)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

class CatWoman extends Woman {
constructor({ name, saying = "", friends, legs, hands }) {
super({ name, saying, friends, legs, hands });
this.saying = Cat.meow + " " + saying;
Copy link
Member

Choose a reason for hiding this comment

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

Use static property is a good start in the right direction.
But here is the problem: if we change cat's defaut meow (which is allowed by Cat class API), cat-women's saying will be out of sync. Can you devise a way to bind their saying more tightly? You are only one step away from the solution.

Copy link
Contributor Author

@OlexiyDobroskok OlexiyDobroskok Sep 21, 2022

Choose a reason for hiding this comment

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

@OleksiyRudenko The easiest way I see is to assign a default value to the static property of the cat:

Cat.meow = "¡miau miau";
this.saying = Cat.meow;

but then all cats lose their identity and it makes me sad =)))
Is this the right way?

Copy link
Member

Choose a reason for hiding this comment

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

As long as property holds a primitive value the tie is weak, i.e. simply copied once. Strings, numbers and booleans are primitives. We need something that is passed by reference. These are JS objects (arrays and functions are also objects). Class methods can also be static. There are also very specific methods - getters and setters.
Given the above find a solution that will help to both meet requirements and minimize impact on code to keep it readable (i.e. we do not want dogs and humans to have added complexity just becase we need to resolve a matter between cats and cat-women).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@OleksiyRudenko Tried to bind Woman and cat by static method. Looks strange)

Copy link
Member

Choose a reason for hiding this comment

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

It does. Truth is that hybrids do not fit OOP hierarchical nature really well.
Composition is a better pattern in similar cases.
The purpose of this part of task was exactly to see the weirdness of how it has to be implemented in OOP paradigm.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@OleksiyRudenko Good task. At a glance nothing difficult, but on the small details forces to revise a lot of materials). Do I still have to make some adjustments to the task?

Comment on lines 1 to 16
const inhabitantDefaultProperties = {
species: "No species",
name: "No name",
gender: "No gender",
saying: "Hello",
friends: "",
};

const humanDefaultProperties = {
hands: 2,
legs: 2,
};

const petsDefaultProperties = {
paws: 4,
};
Copy link
Member

Choose a reason for hiding this comment

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

It would be a better approach to have defaults within class definitions. Not necessarily in form of explicit const definitions. KISS

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

this.gender = gender ?? inhabitantDefaultProperties.gender;
this.saying = saying ?? inhabitantDefaultProperties.saying;
this.friends = friends ?? inhabitantDefaultProperties.friends;
Inhabitant.worldPopulation.push(this);
Copy link
Member

Choose a reason for hiding this comment

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

Not a good idea to populate world from within inhabitants.
There is a Factory pattern in SW design, but this is not the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not a good idea to populate world from within inhabitants. There is a Factory pattern in SW design, but this is not the case.

Can I create a separate function that will create and push objects into an array?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah. Creating World class would be an overkill at this stage of project development.
But please avoid .push. Array methods provide more semantic ways to solve this.
And you will probably want to help inhabitants make friends after they are born. At least exisiting inhabitants cannot make friends with those who are not born yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I abandoned this idea because it was originally intended to simplify the creation of the world, but Later there are problems with withdrawal of friends.What method can you recommend to replace the push?It seemed to me very convenient to work initially with an empty array. (

Copy link
Member

Choose a reason for hiding this comment

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

I reviewed this fragment. I agree that .push is a way to go.
The only thing remains: it is a world that consists of Inhabitants rather than world is a part of any Inhabitants.
Sure, syntax allows us to do this and make a static prop looks attractive. But having world as a part of inhabitant is counter-intuitive.
World can be a collection of inhabitants. Simplest collection is JS array. If we wanted some world-wide operations we would have multiple functions for those operations and it would make sense to scope them under class World.

Changed the default parameter settings;
Removed the static method of creating world in a inhabitant;
Copy link
Member

@OleksiyRudenko OleksiyRudenko left a comment

Choose a reason for hiding this comment

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

@OlexiyDobroskok nearly there.


introduceYourSelf() {
if (this.friends.length === 0) {
this.friends = "Looking for friends!";
Copy link
Member

Choose a reason for hiding this comment

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

What will happen if we .addFriend after first call of .introduceYourSelf?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@

What will happen if we .addFriend after first call of .introduceYourSelf?

@OleksiyRudenko Instead of an empty array, I get a string...) Done)

Comment on lines 23 to 27
return `<strong>${this.saying}! My name is ${this.name}. Species: ${this.species}. Gender: ${this.gender}. Friends: ${this.friends} </strong>`;
}
const friendsName = this.friends.map((friend) => friend.name).join(", ");
return `<strong>${this.saying}! My name is ${this.name}. Species: ${this.species}. Gender: ${this.gender}. Friends: ${friendsName}. </strong>`;
}
Copy link
Member

Choose a reason for hiding this comment

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

This fragment can be made DRY.
Optimize this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This fragment can be made DRY. Optimize this method.

done

Fixed bug with adding friends.
Copy link
Member

@OleksiyRudenko OleksiyRudenko left a comment

Choose a reason for hiding this comment

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

@OlexiyDobroskok well done!

Comment on lines +29 to +34
const template = `<strong>${this.saying}! My name is ${this.name}. Species: ${this.species}. Gender: ${this.gender}.</strong>`;
if (this.friends.length !== 0) {
const friendsName = this.friends.map((friend) => friend.name).join(", ");
return template + `<strong>Friends: ${friendsName}.</strong>`;
}
return template + `<strong>Friends: Looking for friends!</strong>`;
Copy link
Member

Choose a reason for hiding this comment

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

You have something for any situation about friends (0 or non-zero number of friends).
const friendsName = this.friends.length ? this.friends.map((friend) => friend.name).join(", "); : "Looking for friends"
Now you can have a single return statement. Altogether 2 lines of code for this method.
Feel free searching for ternary operators and/or seek for explanation in Students' chat.

Ternaries are helpful, you will use them often. Worth mastering them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@OleksiyRudenko Thanks. Looks cool) Need to look at ternary operators.

@OleksiyRudenko OleksiyRudenko merged commit 1aa498a into kottans:main Sep 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
self-check-done Student confirmed that self-checks against requirements/common-mistakes are done task-TJSW-OOP
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants