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 #222

Merged
merged 5 commits into from
Aug 25, 2022
Merged

OOP exercise #222

merged 5 commits into from
Aug 25, 2022

Conversation

kotlyar-andrey
Copy link
Contributor

OOP exercise

Demo |
Code base

The code is submitted in a dedicated feature branch.

Only code files are submitted.

Please, review.

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.

@kotlyar-andrey very well done!
OOP is not really good for building hybrids. And this is one of the learnings from this exercise to take. The proper approach would be composition. You do not need to fix anything in these regards. Just reflect on possible flaws of OOP-like approach.

Still we need to add some consistency with regards to the business domain. Please check comments below.

}

class HomoSapiens extends Creature {
constructor(species, name, gender, phrase, legs, hands) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we really want users to specify species for class Homo Sapiens?

}
}

class Animal extends Creature {
Copy link
Member

Choose a reason for hiding this comment

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

Strictly speaking humans are animals as well. We need to resolve the matter in a consistent way.

@kotlyar-andrey
Copy link
Contributor Author

Thanks for the review!
I've made changes, please re-review.

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.

@kotlyar-andrey good fix. Yet we need to make this code compliant with OOP/SOLID principles.
You will find extra materials here helpful.
Feel free asking specific questions based on your learnings.

constructor(species, name, gender, phrase, legs, hands) {
super(species, name, gender, phrase);
this._legs = legs;
this._hands = hands;
Copy link
Member

Choose a reason for hiding this comment

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

Do all animals have hands?
Imagine we popultae the world with birds, fish, rhinos and deer.
OOP helps to model the real world quite accurately avoiding defining properties and behavior that the counterparts in the real world do not have.

@kotlyar-andrey
Copy link
Contributor Author

I think, I understand what you mean. Review, please)

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.

@kotlyar-andrey very good.
Although there are two flaws that will make the project harder to maintain and scale if we want to expand it.

display() {
const friends = this.getFriends();
const saying = this.getSaying();
print(
Copy link
Member

Choose a reason for hiding this comment

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

A class shouldn't know anything about external environment.
Using a side effect (print is a side effect) also makes the class less flexible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I can rename display to getInfo and return value

getInfo() {
  const friends = this.getFriends();
  const saying = this.getSaying();
  return this._properties
    .map(prop => this[prop])
    .join("; ")
    .concat(`; ${saying}; ${friends}`);
}

End then print inhabitants

inhabitants.forEach(creature => {
   print(creature.getInfo());
});

Copy link
Member

Choose a reason for hiding this comment

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

Making class method just return some data is definitely a good design decision.

Comment on lines 48 to 50
.map(prop => this[prop])
.join("; ")
.concat(`; ${saying}; ${friends}`)
Copy link
Member

@OleksiyRudenko OleksiyRudenko Aug 22, 2022

Choose a reason for hiding this comment

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

Not a really good approach to have a list of non-array props and handle array props in a totally different way. Besides, this method does two three things: converts array props to strings, builds object presentation string, prints the presentation string.

What if we have 20 more array props? What if we have props that keep objects?

We need a more consistent approach.

Suggest an approach before implementing changes in these regards.

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 really good approach to have a list of non-array props and handle array props in a totally different way. Besides, this method does two three things: converts array props to strings, builds object presentation string, prints the presentation string.

We can refactor this method to three another function

What if we have 20 more array props? What if we have props that keep objects?

Every class has addProperties method for adding new properties.
If we want to get another data in child classes, we can override method getAnotherData

getDataProperties() {
   return this._properties.map(prop => this[prop]).join("; ");
 }

 getAnotherData() {
   const friends = this.getFriends();
   const saying = this.getSaying();
   return `; ${friends}; ${saying}`;
 }

 getInfo() {
   return this.getDataProperties() + this.getAnotherData();
 }

This is all what I can offer

Copy link
Member

Choose a reason for hiding this comment

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

Look into method overloading and accessing inherited method version.

class A {
  method() { handle class A props }
}

class B extends A {
  method() { handle class B props and call `super.method()` if we need anything from the base class }
}

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.

@kotlyar-andrey great job done!
Takes notes of the comments below.

Comment on lines +61 to +66
class Animal extends Creature {
constructor(species, name, gender, phrase, legs) {
super(species, name, gender, phrase);
this._legs = legs;
this.addProperties("_legs");
}
Copy link
Member

Choose a reason for hiding this comment

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

If some class (e.g. Creature) is extended by one and only one class (e.g. Animal) this means they can be effectively merged. Yes, we may have legless creatures in future, but that's exactly the point when we would split a base class and/or add yet another base class on the top of hierarchy tree.

constructor(name, gender, phrase) {
super("human", name, gender, phrase, 2);
this._hands = 2;
this.addProperties("_hands");
Copy link
Member

Choose a reason for hiding this comment

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

We delegate child class property handling to a base class.
This is a legal approach. Although we should always consider first that every class handles its own and only its own properties. This way the handling logic will be encapsulated within the owning class and not spread across the hierarchy.

@OleksiyRudenko OleksiyRudenko merged commit db0c4a2 into kottans:main Aug 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants