객체간 서로 협업하려면 서로에 대해 지식이 있어야함. 이 지식은 의존성을 만듬
class Gear {
readonly cog: number;
readonly chainRing: number;
readonly rim: number;
readonly tire: number;
constructor(cog: number, chainRing: number, rim: number, tire: number) {
this.cog = cog;
this.chainRing = chainRing;
this.rim = rim;
this.tire = tire;
}
ratio() {
return this.chainRing / this.cog;
}
// 새로 추가된 함수
gear_inches() {
// 타이어는 바퀴테를 가싸고 있어서, 지름 계산시 타이어 높이에 2를 곱함
return this.ratio * new Wheel(rim, tire).diameter();
}
}
class Wheel {
rim: number;
tire: number;
constructor(rim: number, tire: number) {
this.rim = rim;
this.tire = tire;
}
diameter() {
return this.rim * (this.tire * 2);
}
}
객체는 다음과 같은 내용을 알고 있을 때 의존성을 가짐
다른 클래스의 이름
자기 자신을 제외한 다른 객체에게 전송할 메시지의 이름
메시지가 필요로 하는 인자들
인자들을 전달하는 순서
위에서 나열한 의존성은 Wheel 을 변경시 어쩔 수 없이 Gear 도 수정해야 하는 상황을 만듬 협업 과정에서 의존성이 생기는 것은 어쩔 수 없지만, 불필요한 의존성은 Gear 클래스의 수정을 강제함
이런 의존성은 Gear 를 Wheel 에 결합(couple) 시킴.
둘 이상의 객체가 강력하게 coupling 되어있을 때
클래스의 이름을 통해 다른 클래스를 참조하는 방식은 코드의 결합도를 높임 아래 예제는 gear_inches 메서드는 Wheel 클래스를 명시적으로 참조
class Gear {
readonly cog: number;
readonly chainRing: number;
readonly rim: number;
readonly tire: number;
constructor(cog: number, chainRing: number, rim: number, tire: number) {
this.cog = cog;
this.chainRing = chainRing;
this.rim = rim;
this.tire = tire;
}
gear_inches() {
return this.ratio * new Wheel(rim, tire).diameter();
}
}
Wheel 클래스를 직접 참조하면
중요한 것은 객체의 클래스가 무엇인지
가 아닌, 우리가 전송하는 메시지가 무엇인지
가 중요
Gear 에게는 diameter 메서드에 반응할 수 있는 객체가 필요
Gear 는 diameter 를 알고 있는 객체만 있으면됨
class Gear {
readonly cog: number;
readonly chainRing: number;
readonly wheel: Wheel;
constructor(cog: number, chainRing: number, wheel: Wheel) {
this.cog = cog;
this.chainRing = chainRing;
this.wheel = wheel;
}
// 새로 추가된 함수
gear_inches() {
// 타이어는 바퀴테를 가싸고 있어서, 지름 계산시 타이어 높이에 2를 곱함
return this.ratio * this.wheel.diameter();
}
}
Gear.new(52, 11, Wheel.new(26, 1.6)).gear_inches();
위 코드에서
의존성 주입
후 장점은
불필요한 의존성을 모두 제거하면 좋지만 현실적으로 불가능한 경우 있음
의존성 제거 어려운 경우, 의존성을 클래스 안에서 격리시켜야함
의존성은
Gear 에 Wheel 을 주입할 수 없다면 새로운 Wheel 인스턴스를 만드는 과정을 Gear 클래스 내부에 격리시켜야 함
이는
다음 두 개의 예시를 보자.
새로운 Wheel 인스턴스 생성하는 과정을 Gear 의 gear_inches 메서드에서 initialize 메서드로 이동
class Gear {
readonly cog: number;
readonly chainRing: number;
readonly wheel: Wheel;
constructor(cog: number, chainRing: number, wheel: Wheel) {
this.cog = cog;
this.chainRing = chainRing;
this.wheel = wheel;
}
// 새로 추가된 함수
gear_inches() {
// 타이어는 바퀴테를 가싸고 있어서, 지름 계산시 타이어 높이에 2를 곱함
return this.ratio * this.wheel.diameter();
}
}
이를 보완한 거는
명시적으로 정의된 wheel 메서드를 통해 Wheel 인스턴스 생성하기
class Gear {
readonly cog: number;
readonly chainRing: number;
readonly wheel: Wheel;
constructor(cog: number, chainRing: number, wheel: Wheel) {
this.cog = cog;
this.chainRing = chainRing;
this.wheel = wheel;
}
// 새로 추가된 함수
gear_inches() {
// 타이어는 바퀴테를 가싸고 있어서, 지름 계산시 타이어 높이에 2를 곱함
return this.ratio * this.wheel.diameter();
}
wheel() {
return this.wheel || Wheel.new(this.rim, this.tire);
}
}
위 두 예시에서도 Gear 는 너무 많은 것을 알고 있음
여전히 rim 과 tire 를 초기화 인자로 넘겨야 하고
Gear 를 위해 Wheel 인스턴스 생성해야 함
그래도 나아진 점은
외부 클래스 이름 참조하는 지점을 격리시켰으니 외부로 전송되는 메시지
를 보자.
외부로 전송되는 메시지: 나 자신이 아닌 객체에게 보내는 메시지
gear_inches() {
// 무시무시한 수학 공식 몇 줄
foo = this.ratio * this.wheel.diameter();
// 무시무시한 수학 공식 몇 줄
}
wheel.diameter 가 복잡한 메서드에 파묻힐 경우
외부에 대한 의존성을 걷어내고, 의존성을 클래스 내부의 메서드 속에 캡슐화 시켜서 gear_inches 메서드를 수정할 일을 줄이자.
gear_inches() {
// 무시무시한 수학 공식 몇 줄
foo = this.ratio * this.diameter();
// 무시무시한 수학 공식 몇 줄
}
// 클래스 내부에 메서드 속에 캡슐화
diameter() {
return wheel.diameter();
}
Wheel 이 diameter 메서드의 이름과 시그너처 바꿔도
정리하자면
클래스가 내부에서 변하기 쉬운 메시지를 참조하고 있을 때 이 기술 유용하게 사용
인자를 해시로 바꾸기. javascript 로 하면 object 로 전송하기
이점
순서에 대한 의존성 제거
우리가 메서드 수정할 수 없는 경우도 있음. 프레임워크를 사용할 경우
상황을 우리가 통제할 수 없지만, wrapper 메서드를 통해 프레임워크 메서드를 감싸서 코드를 DRY 하게 만들기 가능
// Gear가 외부 프레임워크의 한 부분일 때
// Gear/index.js
class Gear {
readonly cog: number;
readonly chainRing: number;
readonly wheel: Wheel;
constructor(cog: number, chainRing: number, wheel: Wheel) {
this.cog = cog;
this.chainRing = chainRing;
this.wheel = wheel;
}
}
// GearWrapper.js
// 외부 인터페이스를 감싸는 모듈을 만들어 변화를 받아들이자.
import Gear from 'Gear';
export gear (arg) {
Gear.new(arg.cog, arg.chainRing, arg.wheel)
}
// myCode.js
import * as GearWrapper from './GearWrapper';
GearWrapper.gear({
chainring: 15,
cog: 11,
wheel: new Wheel(26, 1.6).gear_inches()
})
위 코드에서 GearWrapper 의 두 가지를 기억하자
래퍼 모듈
오로지 다른 클래스의 인스턴스를 생성하기 위해 존재
class Gear {
readonly cog: number;
readonly chainRing: number;
constructor(cog: number, chainRing: number) {
this.cog = cog;
this.chainRing = chainRing;
}
ratio() {
return this.chainRing / this.cog;
}
gear_inches(diameter) {
return this.ratio * diameter;
}
}
class Wheel {
rim: number;
tire: number;
gear: Gear;
constructor(rim: number, tire: number, gear: Gear) {
this.rim = rim;
this.tire = tire;
this.gear = gear;
}
diameter() {
return this.rim * (this.tire * 2);
}
gear_inches() {
gear.gear_inches(diameter);
}
}
Wheel.new(26, 1.6, 62, 11).gear_inches();
위에서는 Wheel 이 Gear 나 ratio 에 의존.
클래스가 사람이라고 생각해보자. 자기 자신보다 덜 변하는 사람에게 의존해야 함
위 간단한 문장은 단순한 진실 세 가지를 기반으로 다듬을 수 있음
다른 클래스와 비교하여 얼마나 변경되지 않는지를 기준으로 순위 매기기
추상적 은 구체적인 것으로부터 분리된
것.
예륻 들어 알아보자.
Gear 가 Wheel, Wheel.new, Wheel.new(rim, tire)에 의존적일 때
구체적인 코드
에 의존적Wheel 을 Gear 에 주입하는 (의존성 주입) 방식으로 수정하면서
추상적인 것
에 의존diameter 메시지에 반응하는 어떤 객체를 필요로 한다
라는 추상적인 사실
에 의존2 번에서 interface
를 정의했다고 볼 수 있음
추상화의 훌륭한 점은
의존성 주입은
의존성 격리는
이 때, 추상화
를 사용하는 것은
평온한 유지보수는 자기 자신보다 덜 변하는 것에 의존하는 클래스들로 덮여있음.
의존성 있다는 거 알기 어떤 객체가 다음 내용을 알고 있을 때 의존성 발생
결합(coupling) 약하게 하기
주입할 수 없다면 의존성 격리시키기
인자 순서에 대한 의존성 제거
메서드를 직접 수정 불가능한 경우(프레임워크 이용 등)
의존성 방향 결정하기 기준은
구체적인 것과 추상적인 것 인지하기(추상적인 것에 의존하기)