polymorphism
const Worker = class {
run(){
console.log('working')
}
print(){
this.run() // 내적 일관성 ( internal identity )
}
}
const HardWorker = class extends Worker {
run() {
console.log('hardworking')
}
}
const worker = new HardWorker()
console.log(worker instanceof Worker) // 대체 가능성 ( substitution )
worker.print();
// 다형성 = 내적 일관성 + 대채 가능성
JavaScript
Object Essentials
const EssentialObject = class {
#name = ''; // hide state -> 필드를 숨김
#screen = null;
constructor(name){
this.#name = name;
}
camouflage(){
this.#screen = (Math.random() * 10).toString(16).replace(".", '')
}
get name(){ // encapsulation -> 메서드의 기능을 숨김
return this.#screen || this.#name;
}
}
// Encapulation of Functionality
// Maintenance of State
JavaScript
Isolation of Change
SOLID
SRP Single Responsibility 단일 책임 (하나의 수정 범위, 하나의 테스트 범위)
산탄총 수술 ( shotgun surgery )
OCP Open Closed 개방폐쇄
LSP Liskov Substitusion 업캐스팅 안전
Polymohpism ( 자식은 부모를 대체 가능하다. )
LSP 위배
LSP에 적합
ISP Interface Segregation 인터페이스 분리
LSP를 해결함
또 다른 예시
DIP Dependency Inversion 다운캐스팅금지
의존성은 부모쪽으로만 흘러야 한다.
Others...
1.
DI Dependency Injection 의존성주입
(IoC Inversion of Control 제어역전)
2.
DRY Don't Repeat Yourself 중복방지
3.
Hollywood Principle 의존성 부패방지
( 물어보지 말고 요청하라 )
번호 좀 줘 → X 번호 필드가 노출됨
연락 해 줄래? → O
4.
Law of demeter 최소 지식 [무시하면 열차전복 (train wreck)이 일어남]
classA.methodA의 최대지식한계
•
classA의 필드 객체
•
methodA가 생성한 객체
•
methodA의 인자로 넘어온 객체
객체 망
1.
메시지 - 의뢰할 내용
2.
오퍼레이션 - 메시지를 수신할 객체가 제공하는 서비스
3.
메소드 - 오퍼레이션이 연결될 실제 처리기
오퍼레이션은 인터페이스에 선언한 기능
메소드는 런타임에서 실제로 실행할 오퍼레이션 ( 동적 바인딩이라고 부름 )
Dependency
의존성 종류
객체의 생명 주기 전체에 걸친 의존성
•
상속 (extends) => 부모가 변하면 모든 자식이 변한다 => 상속은 크리티컬하다.
•
연관 (association) => 필드에 그 객체타입을 알고 있음
각 오퍼레이션 실행 시 임시적인 의존성
•
의존(dependency)
상속 -> 연관 ( 상속을 소유모델로 변경 )
연관 -> 의존 ( 연산을 통해 해결 => 함수로 이용하라 => 객체지향 깨지지만 여기로 가서 의존성을 약화 할 수도 있음 )
DI
어떠한 경우에도 다운캐스팅은 금지
폴리모피즘 (추상인터페이스) 사용
const Worker = class {
run(){
console.log('working')
}
print(){
this.run() // 내적 일관성 ( internal identity )
}
}
const HardWorker = class extends Worker {
run() {
console.log('hardworking')
}
}
const Manager = class{
#workers;
constructor(...workers) {
// OCP
// 의존성을 구상클래스 HardWorker가 아닌 추상클래스인 Worker가 가지고 있음
// 때문에 확장가능함
if(workers.every(w=>w instanceof Worker)) this.#workers = workers;
else throw "invalid workers";
}
doWork() {
this.#workers.forEach(w=>w.run())
}
};
const manager = new Manager(new Worker(), new HardWorker());
manager.doWork();
JavaScript
Inversion of Control
제어역전의 개념과 필요성
개념
1.
Control = flow control(흐름 제어)
2.
광의에서 흐름 제어 = 프로그램 실행 통제
3.
동기흐름제어, 비동기 흐름제어 등
⇒ 제어를 다른 곳에 위임한다.
문제점
1.
흐름 제어는 상태와 결합하여 진행됨
2.
상태 통제와 흐름제어 = 알고리즘
3.
변화에 취약하고 구현하기 어려움
대안
1.
제어를 추상화하고
2.
개별 제어의 차이점만 외부에서 주입받는다.
const Renderer = class {
#view = null; #base = null;
constructor(baseElement) {
this.#base = baseElement;
}
set view(v) {
if(v instanceof View) this.#view = v;
else throw `invalid view: ${v}`
}
render(data) { // View의 제어는 renderer가 한다.
const base = this.#base, view = this.#view
if(!base || !view) throw 'no base or view';
let target = base.firstElementChild;
do base.removeChild(target); while(target = target.nextElementSibling);
base.appendChild(view.getElement(data));
view.initAni();
view.startAni();
}
}
const View = class{
getElement(data) { throw 'override!' }
initAni(){throw 'override!' }
startAni() { throw 'override!' }
}
const renderer = new Renderer(document.body)
renderer.view = new class extends View{
#el;
getElement(data){
this.#el = document.createElement("div");
this.#el.innerHTML = `<h2>${data.title}</h2><p>${data.description}</p>`;
this.#el.style.cssText = "`width:100%; background:${data.background}`";
return this.#el
}
initAni(){
const style = this.#el.style;
style.marginLeft = "100%";
style.transition = "all 0.3s";
}
startAni(){
requestAnimationFrame(_=>this.#el.style.marginLeft = 0);
}
}
renderer.render({title:'title test', description:'contents....', background:'#ffffaa'})
JavaScript
제어역전 실제 구현
전략 패턴 & 템플릿 메소드 패턴 < 컴포지트 패턴 < 비지터 패턴
보다 넓은 범위의 제어 역전을 실현함
추상팩토리메서드 패턴
왼쪽 패턴은 이미 만들어진 객체의 행위를 제어역전에 참여시킬 수 있지만 참여할 객체 자체를 생성할 수 없음
참여할 객체를 상황에 맞게 생성하고 행위까지 위임하기 위해 추상팩토리 메소드를 사용함