class Person {
private bathroom: Bathroom;
constructor() {
this.bathroom = new Bathroom();
}
wakeUp() {
console.log('아침에 일어난다.');
}
bathroomActivity() {
this.bathroom.goToBathroom();
this.bathroom.washFace();
this.bathroom.brushTeeth();
this.bathroom.washHair();
this.bathroom.dryOff();
this.bathroom.leaveBathroom();
}
}
class Bathroom {
goToBathroom() {
console.log('화장실로 간다.');
}
washFace() {
console.log('얼굴을 씻는다.');
}
brushTeeth() {
console.log('양치를 한다.');
}
washHair() {
console.log('머리를 감는다.');
}
dryOff() {
console.log('물기를 닦는다.');
}
leaveBathroom() {
console.log('화장실을 나온다.');
}
}
class Morning {
private person: Person;
constructor() {
this.person = new Person();
}
morningActivity(): void {
this.person.wakeUp();
this.person.bathroomActivity();
}
}
const morning = new Morning();
morning.morningActivity();
[객체지향] SOLID 예제(1) - 단일 책임의 원칙(SRP)
위의 코드는 각 클래스가 하나의 책임(Person은 사람의 행위, Bathroom은 화장실에서의 행위, Morning은 아침의 행위)을 담당하고 있으므로 SRP를 만족한다고 볼 수 있다.
하지만, 누군가는 아침에 반드시 입욕을 한다면 어떻게 될까?
우선 입욕은 화장실에서의 행위이므로 Bathroom 클래스에 입욕과 관련된 메서드를 추가해야 할 것이다.
그에 따라 Person 클래스에도 변화가 생기게 될 것이다.
이러한 문제를 해결할 수 있는 방법이 의존성 역전의 원칙이다.
의존성 역전의 원칙(DIP)
의존성 역전의 원칙은 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 되며, 둘 모두 추상화에 의존해야 한다는 것이다.
Person은 사람이 아침에 하는 행위라는 높은 수준의 개념을 구현하고 Bathroom은 특정 장소(화장실)에서 하는 행위라는 더 구체적인 수준의 매커니즘을 담당한다.
하지만, 위의 코드에서는 상위 수준의 클래스인 Person이 하위 수준의 클래스인 Bathroom에 의존한다.
이에 대한 문제점으로는 Bathroom의 변경에 의해 상위 수준 클래스인 Person이 영향을 받는다는 것이다.
또한, 여러 명의 사람이 있다고 가정할 때 Person이 재사용되면서 Person이 의존하는 Bathroom도 함께 재사용해야 한다는 문제점이 발생한다.
이를 해결하기 위해 상위 수준의 클래스와 하위 수준의 클래스 모두가 추상화에 의존하도록 수정해야 하며, 이를 통해 하위 수준 클래스의 변경으로 인해 상위 수준의 클래스가 영향을 받는 것을 방지할 수 있다.
기존의 코드를 개선해보자.
class Person {
private bathroom: Bathroom;
constructor(bathroom: Bathroom) {
this.bathroom = bathroom;
}
wakeUp() {
console.log('아침에 일어난다.');
}
bathroomActivity() {
this.bathroom.goToBathroom();
this.bathroom.washFace();
this.bathroom.brushTeeth();
this.bathroom.washHair();
this.bathroom.dryOff();
this.bathroom.leaveBathroom();
}
}
class Bathroom {
goToBathroom() {
console.log('화장실로 간다.');
}
washFace() {
console.log('얼굴을 씻는다.');
}
brushTeeth() {
console.log('양치를 한다.');
}
washHair() {
console.log('머리를 감는다.');
}
dryOff() {
console.log('물기를 닦는다.');
}
leaveBathroom() {
console.log('화장실을 나온다.');
}
}
class Morning {
private person: Person;
constructor(person: Person) {
this.person = person;
}
morningActivity(): void {
this.person.wakeUp();
this.person.bathroomActivity();
}
}
const morning = new Morning(new Person(new Bathroom()));
morning.morningActivity();
기존의 코드와 달리 생성자에서 new 키워드를 사용해 객체를 생성하는 대신 외부에서 인스턴스를 생성해 생성자에 주입하고 있다.
이러한 방법은 "의존성 주입"이라 하며 DIP를 달성할 수 있는 방법 중 하나이다.
이렇게 함으로써 Person 클래스는 Bathroom 타입을 만족하는 어떠한 것이 오더라도 수정 없이 정해진 작업을 수행할 수 있을 것이다.
Person 객체의 생성자에서 new 키워드를 이용해 Bathroom의 인스턴스를 생성한다면 Person을 재사용하는 과정에서 반드시 Bathroom도 재사용해야 한다.
하지만, 위와 같이 의존성 주입을 사용한다면 의존성을 외부에서 주입받기 때문에 Bathroom 인스턴스 이외의 인스턴스를 주입해도 상관없다. (단, 주입되는 인스턴스의 타입은 Bathroom 타입이어야 한다)
위의 개선 코드에서 "추상화"에 대한 개념을 아직 적용하지 않았다.
(물론, 추상화에 대해 어떻게 생각하느냐에 따라 하위 수준 클래스를 상위 수준 클래스에서 분리하고 이를 외부에서 주입 받도록 하는 것을 추상화로 볼 수도 있을 것이다 - 구체적인 Bathroom 대신, Bathroom 타입을 갖기만 한다면 아무 인스턴스나 가능하기 때문)
단지 인스턴스의 생성 위치를 변경했으며 이를 통해 결합도를 줄일 수 있는 방법에 대해 서술했다.
DIP의 핵심은 추상화이며 추상화는 이후 알아볼 내용인 OCP(개방-폐쇄의 원칙)과도 연관이 있기 때문에 OCP를 다루며 알아보도록 하자.
[객체지향] SOLID 예제(3) - 개방-폐쇄의 원칙(OCP)
[참고자료]
위키북스, 오브젝트, 2019
'개발 > 개념' 카테고리의 다른 글
[객체지향] SOLID 예제(4) - 리스코프 치환의 원칙(LSP) (0) | 2023.09.29 |
---|---|
[객체지향] SOLID 예제(3) - 개방-폐쇄의 원칙(OCP) (0) | 2023.09.26 |
[객체지향] SOLID 예제(1) - 단일 책임의 원칙(SRP) (0) | 2023.09.25 |
[CS: 운영체제] 컴파일러 (0) | 2023.07.15 |
[CS: 운영체제] 프로세스 메모리 구조 (0) | 2023.07.15 |