"How do we actually design classes? How do we model real-world data into classes?"
These principles can also be used outside of OOP, but they are especially relevant in this context.
Ignoring or hiding details that don't matter, allowing us to get an overview perspective of the thing we're implementing, instead of messing with details that don't really matter to our implementation.
Do we really need to know all the things inside of iPhone? NO! We can use iPhone even we don't know at all behind the scenes.
Keeping properties and methods private inside the class, so they are not accessible from outside the class. Some methods can be exposed as a public interface(API).
Why do we need encapsulation?
Prevents external code from accidentally manipulating internal properties/state.
Allows to change internal implementation without the risk of breaking external code.
Making all properties and methods of a certain class available to a child class, forming a hierarchical relationship between classes. This allows us to reuse common logic and to model real-world relationships.
The goal of this is to reuse logic that is common to both of the classes.
A child classes can overwrite a method it inherited from a parent class (it's more complex than that, but enough for our purpose.)
JavaScript also has an OOP paradigm which objects are instantiated from a class, which functions like a blueprint.
"How do we actually create prototypes? And how do we link objects to prototypes? How can we create new objects, without having classes?
/***********************************************************
Inheritance Between "Classes": Constructor Functions
************************************************************/
// Real class do not exist in JS
// How do we set up the prototype chain between constructor functions
const Person = function (firstName, birthYear) {
this.firstName = firstName;
this.birthYear = birthYear;
};
Person.prototype.calcAge = function() {
console.log(2021 - this.birthYear);
};
// Parent Constructor와 같은 인자를 가지지만 추가할 수도 있다
// ParentConstructor.call(this, arguments...)로 부모의 프로퍼티를 가져온다
const Student = function (firstName, birthYear, course) {
// new Operator를 사용하지 않으면 Regular Function Call이 됨
Person.call(this, firstName, birthYear);
this.course = course;
};
// Linking prototype
// Create connection manually using Object.create()
Student.prototype = Object.create(Person.prototype);
// Student.prototype에는 기본적으로 빈 객체가 있으며
// 이곳에 Person.prototype을 할당해 주는 과정이 prototype chain을 만든다
// 이게 안되는 이유?
// 우리는 Student가 prototype chain으로서 Person에 종속, 프로퍼티를 상속하길 원하지
// Parent prototype 자체를 원하는 것은 아니기 때문이다.
// Student.prototype = Person.prototype;
Student.prototype.introduce = function() {
console.log(`My name is ${this.firstName} and I study ${this.course}`);
};
const wonkook = new Student('Wonkook', 2020, 'Computer Science');
console.log(wonkook);
wonkook.introduce();
// Student prootype의 constructor가 Person으로 되어있다
// 이것을 고쳐야 할 때가 있다
console.dir(Student.prototype.constructor);
// Inspection - prototype chain이 잘 셋업됨
console.log(wonkook instanceof Student);
// Expected Output: true
console.log(wonkook instanceof Person);
// Expected Output: true
console.log(wonkook instanceof Object);
// Expected Output: true
Student.prototype.constructor = Student;
// 바뀌었다
console.dir(Student.prototype.constructor);
/***********************************************************
217. Inheritance Between "Classes": ES6 Classes
************************************************************/
class PersonCl {
constructor(fullName, birthYear) {
this.fullName = fullName;
this.birthYear = birthYear;
}
// Instance methods
calcAge() {
console.log(2021 - this.birthYear);
}
get age() {
return 2021 - this.birthYear;
}
set fullName(name) {
if (name.includes(' ')) this._fullName = name
else console.log(`${name} is not a full name!`);
}
get fullName() {
console.log(this.fullName);
}
// Static method
static hey() {
console.log(`Hey there 👋🏻`);
}
};
// extends + super(...arguments)
class StudentCl extends PersonCl {
constructor(fullName, birthYear, course) {
// super는 항상 제일 먼저 호출되어야 한다!
super(fullName, birthYear);
this.course = course;
}
introduce() {
console.log(`My name is ${this._fullName} and I study ${this.course}`);
}
};
const wonkookCl = new StudentCl('Wonkook Lee', 1999, 'Computer Science');
console.log(wonkookCl);
// Expected Output: StudentCl {_fullName: "Wonkook Lee", birthYear: 1999, course: "Computer Science"}
wonkookCl.introduce();
// Expected Output: "My name is Wonkook Lee and I study Computer Science"
// Overwrite parents's (inheritanced) method
StudentCl.prototype.calcAge = function () {
console.log(`I'm ${2021 - this.birthYear} years old, but as a student I feel more like ${2031 - this.birthYear}`);
}
wonkookCl.calcAge();
// Expected Output: "I'm 22 years old, but as a student I feel more like 32"