📍코드업는 프로그래밍 채널의 내용을 바탕으로 정리했습니다.📍
SRP(Single Responsibility Principle): 단일 책임 원칙
하나의 클래스는 하나의 책임만 가져야 한다. ↔ 클래스를 변경하는 이유는 오직 한 가지뿐이어야 한다.
예시)
고양이는 밥을먹고, 걷고, 말을하는 행동은 당연한 기능이지만 print와 log 같은 행동은 고양이의 기능이 아니다.이것은 단일 책임 원칙에 위배된다. 따라서 다른 방식으로 두 개의 기능을 구현해야 한다.
// before
class Cat {
constructor(private age: number, private name: string) {}
eat() {
console.log(`${this.name} 고양이가 밥을 먹습니다.`);
}
walk() {
console.log(`${this.name} 고양이가 걷습니다.`);
}
speak() {
console.log(`${this.name} 고양이가 짓습니다.`);
}
print() {
console.log(`${this.name} 고양이는 ${this.age}살 입니다.`);
}
log() {
console.log(`${this.name} 고양이는 ${this.age}살 입니다.`);
logger.log(date.now()) // 예시입니다.
}
}
// after
class Cat {
constructor(private age: number, private name: string) {}
eat() {
console.log(`${this.name} 고양이가 밥을 먹습니다.`);
}
walk() {
console.log(`${this.name} 고양이가 걷습니다.`);
}
speak() {
console.log(`${this.name} 고양이가 짓습니다.`);
}
status() {
return `${this.name} 고양이는 ${this.age}살 입니다.`
}
}
const kitty = new Cat();
console.log(kitty.status());
logger.log(kitty.status());
OCP(Open Closed Principle): 개방 폐쇄 원칙
확장에는 열려 있으나 변경에는 닫혀 있어야 한다. ↔ 높은 응집도와 낮은 결합도
예시)
동물을 나타내는 Animal 클래스가 있다고 가정한다.
각 동물들의 종류를 받고 해당 동물의 울음소리를 출력하는 hey 메서드가 있다.
개, 고양이 이외의 동물을 입력 받는 경우 해당 동물의 울음소리를 추가해주어야만 울음소리를 출력할 수 있다.
이는 확장에 닫혀있는 즉, OCP의 위배된다.
// before
class Animal {
constructor(private type: string) {}
hey(animal: Animal) {
if (animal.type === 'Dog') console.log('bark');
else if (animal.type === 'Cat') console.log('meow');
else throw new Error('정의하지 않은 동물입니다.');
}
}
const bingo = new Animal('Dog'); // bark
const kitty = new Animal('Cat'); // meow
const cow = new Animal('Cow'); // 정의하지 않은 동물입니다.
// after
const hey = (animal: Animal) => {
animal.speak();
};
abstract class Animal {
public abstract speak(): void;
}
class Dog extends Animal {
speak(): void {
console.log('bark');
}
}
class Cat extends Animal {
speak(): void {
console.log('meow');
}
}
const dog = new Dog();
const cat = new Cat();
hey(dog);
hey(cat);
// 개, 고양이 이외의 동물(양: sheep) 추가
class Sheep extends Animal {
speak(): void {
console.log('meh');
}
}
const sheep = new Sheep();
hey(sheep);
LSP(Liskov Substitution Principle): 리스코프 치환 원칙
객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야한다.
예시)
부모 클래스인 Cat을 상속받은 BlackCat 클래스가 있다고 가정한다.
고양이 인스턴스를 만든 후 해당 고양이를 서브 클래스인 검정 고양이로 치환을 해도 잘 작동해야한다.
다만, 뜬금없이 Fish로 치환하는 것은 LSP 원칙에 위배된다.
class Cat {
speak() {
console.log('meow');
}
}
class BlackCat extends Cat {
speak() {
console.log('black meow');
}
}
const speak = (cat: Cat) => {
cat.speak();
};
let cat = new Cat();
speak(cat); // meow
cat = new BlackCat();
cat.speak(); // black meow
// 고양이는 생선으로 치환할 수 없다. 그렇기에 LSP 원칙에 위배된다.
class Fish extends Cat {
speak() {
throw new Error('생선을 말을 할 수 없어요.');
}
}
cat = new Fish();
cat.speak(); // 생선은 말을 할 수 없어요.
ISP(Interface Segragation Principle): 인터페이스 분리 원칙
인터페이스가 클라이언트에서 필요하지 않은 메서드를 제공하지 않아야 한다.
예시) 수륙양용차: 물 위에서나 땅 위에서 모두 다닐 수 있게 만든 자동차
수륙양용차를 만들기 위해서는 물이나 땅에서 운전할 수 있는 기능이 있어야한다.
하지만 물에서만 다닐 수 있는 기능, 땅에서만 다닐 수 있는 기능을 구현하기 위해서 수륙양용차의 기능을 전부 집어넣으면 불필요한 메서드를 지닌 자동차가 만들어질것이다. 이것은 ISP 원칙에 위배된다.
// before
interface CarBoat {
drive: () => void;
turnLeft: () => void;
turnRight: () => void;
steer: () => void;
steerLeft: () => void;
steerRight: () => void;
}
class Genesis implements CarBoat {
drive() {
/* ... */
}
turnLeft() {
/* ... */
}
turnRight() {
/* ... */
}
/** 불필요한 메서드
steer: () => void;
steerLeft: () => void;
steerRight: () => void;
*/
}
class Boat implements CatBoat {
steer() {
/* ... */
}
steerLeft() {
/* ... */
}
steerRight() {
/* ... */
}
/** 불필요한 메서드
drive: () => void;
turnLeft: () => void;
turnRight: () => void;
*/
}
// after
interface Car {
drive: () => void;
turnLeft: () => void;
turnRight: () => void;
}
interface Boat {
steer: () => void;
steerLeft: () => void;
steerRight: () => void;
}
class Genesis implements Car {
drive() {
/* ... */
}
turnLeft() {
/* ... */
}
turnRight() {
/* ... */
}
}
class Boat implements Boat {
steer() {
/* ... */
}
steerLeft() {
/* ... */
}
steerRight() {
/* ... */
}
}
class CarBoat implements Car, Boat {
drive() {
/* ... */
}
turnLeft() {
/* ... */
}
turnRight() {
/* ... */
}
steer() {
/* ... */
}
steerLeft() {
/* ... */
}
steerRight() {
/* ... */
}
}
DIP(Dependency Inversion Principle): 의존관계 역전 원칙
추상화에 의존해야지, 구체화에 의존하면 안된다.
High Level 모듈에서 Low Level 모듈들에 의존하게 만드는 것이 아닌 중간에 추상화 모듈을 만들어 High Level 모듈과 Low Level 모듈이 의존할 수 있게 만들어준다.
예시)
High Level → Low Level
High Level → 추상화 모듈 ← Low Level
// High Level → Low Level
class Cat {
speak() {
console.log('meow');
}
}
class Dog {
speak() {
console.log('bark');
}
}
class Zoo {
constructor(private cat: Cat, private dog: Dog) {
this.cat = cat;
this.dog = dog;
}
}
// 양을 추가하려면 Zoo 클래스에 수동으로 추가 해야한다.
class Sheep {
speak() {
console.log('meh');
}
}
class Zoo {
constructor(private cat: Cat, private dog: Dog, private sheep: Sheep) {
this.cat = cat;
this.dog = dog;
this.sheep = sheep;
}
}
// High Level → 추상화 모듈 ← Low Level
class Animal {
speak() {}
}
class Cat extends Animal {
speak() {
console.log('meow');
}
}
class Dog extends Animal {
speak() {
console.log('bark');
}
}
class Zoo {
constructor(private animals: Animal[]) {}
addAnimal(animal: Animal) {
this.animals.push(animal);
}
}
const zoo = new Zoo();
zoo.addAnimal(new Dog());
zoo.addAnimal(new Cat());
// 동물(양: sheep) 추가
class Sheep extends Animal {
speak() {
console.log('meh');
}
}
zoo.addAnimal(new Sheep());
틀린 부분이 있거나 보충해야 할 내용이 있다면 댓글이나 DM(sungstonemin)으로 알려주시면 감사하겠습니다😄
좋은 글 감사합니다~