[클린코드 TS] Class

moonee·2021년 8월 5일
0

클린코드

목록 보기
4/5

1. 단일 책임 원칙

모든 클래스는 하나의 책임만을 가지며 그 책임을 완전히 캡슐화해야 함을 일컫는다.
- 캡슐화 : 객체의 속성(data fields)과 행위(Methods)를 하나로 묶, 실제 구현 내용 일부를 외부에 감추어 은닉하는 것

따라서, 클래스의 크기는 책임에 의해 측정된다.



2. 높은 응집도와 낮은 결합도

  • 응집도 (Cohesion) : 클래스 멤버가 서로에게 연관되어 있는 정도
  • 결합도 (Coupling) : 서로 다른 클래스 간에 상호 의존하는 정도 혹은 연관 되어 있는 정도

좋은 소프트웨어 설계는 높은 응집도와 낮은 결합도를 가져야한다.
즉, 클래스 안의 모든 필드는 각 메소드에 의해서 사용되어야 하고 (높은 응집도) 각 클래스들은 서로 연관되어 있지 않아서 상호 영향을 주지 않아야한다. (낮은 결합도)

왜 ?

높은 응집도를 가짐으로써, 하나의 기능에 변화가 생길 때 유지 보수에 용이하며, 마찬가지로 낮은 결합도를 가짐으로써 한 기능의 변화가 다른 기능에 영향을 미치지 않아서 이 역시 유지 보수에 용이하기 때문이다.



3. 상속보다는 조합

  • 상속(Inheritance) : 기존 클래스에 기능을 추가하거나 재정의하여 새로운 클래스를 정의한다.
class Book {
  constructor(
    private readonly title: ,
    private readonly author: string) {
  }

  // ...
}

class Novel extends Book {
  constructor(
    title: string,
    author: string,
    private readonly genre: string) {
    super(title, author);
  }

  // ...
}

위의 예제에서 소설 (Novel) 은 책(Book)의 일종 (IS-A) 이므로 상속을 받는다.

  • 조합(Composition) : 기존 클래스가 새로운 클래스의 구성요소로서 사용된다. 즉, 기존 클래스를 확장하는 대신 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하게 하는 설계이다.
class Book {
  private bookInfo : BookInformation;
  
  constructor(
    private readonly title: string,
    private readonly author: string) {
  }
  
  setBookInfomation(createdAt:number, publisher:string){
    this.bookInfo = new BookInformation(createdAt, publisher);
    return this;
  }
  // ...
}

class BookInformation {
  constructor(
    public readonly createdAt:number,
    public readonly publisher:string) {
  }

  // ...
}

위 예제에서 책 정보(BookInformation)는 책(Book)의 일종이 아니고, 책(Book)이 책 정보(BookInformation)를 가지고 있는 (HAS-A) 관계이므로 조합을 사용한다.


상속의 장점

  1. 코드의 재사용을 통해서 중복을 줄인다.
  2. 확장성이 증가한다.
  3. 클래스 간 계층적 관계를 구성하여 다형성을 구현 할 수 있다.

상속의 단점

캡슐화를 깨뜨린다.
상위 크래스의 구현이 하위 클래스에 그대로 노출되기 때문에 캡슐화가 깨진다. 따라서 자식 클래스가 부모 클래스에 강하게 결합 / 의존하게 된다.
이로 인하여 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작이 달라질 수 있으며 상위 클래스에 변화가 있을 때 하위클래스에 예기치 못한 변화를 초래 할 수 있다.

IS-A 관계가 아닌 HAS-A 관계에 상속 사용 예제

class Book {
  constructor(
    private readonly title: string,
    private readonly author: string) {
  }
  // ...
}

class BookInformation extends Book{
  constructor(
    title: string,
    author: string,
    public readonly createdAt:number,
    public readonly publisher:string) {
    super(title,author);
  }

  // ...
}

위의 예제에서 BookInformation 클래스가 Book을 가지게 되면서 Book 클래스에 구현된 정보가 노출되는 문제를 가진다.
또한, BookInformation NOT IS A Book 인데도 불구하고 Book 클래스의 구현이 변경 될 경우 BookInformation 클래스도 변경된다.


조합의 장점

  1. 새로운 클래스는 기존 클래스의 내부 구현 방식의 영향에서 벗어나고, 기존 클래스에 변경이 있어도영향을 받지 않는다.
  2. 캡슐화를 깨뜨리지 않는다.
    • 왜 ? 기존 클래스를 상속받는게 아니라, 메서드를 호출하여 값을 사용하기 때문이다.


언제 상속을 써야할까?

  • HAS-A 관계가 아닌 IS-A 관계 일 때
  • 부모 클래스로부터 코드를 재사용 할 수 있을 때
    - ex) 사람(자식 클래스)은 모든 동물(부모 클래스) 처럼 움직일 수 있다.
  • 부모 클래스를 변경하여 파생된 클래스를 전체적으로 변경하려는 경우
    - ex) 모든 동물(부모 클래스)은 움직일 때 칼로리가 소비 된다. (변경사항)


4. 메서드 체이닝 사용하기

메서드 체이닝이란?
메서드가 객체를 반환하면 반환 값 객체를 통해 또 다른 메서드를 호출 수 있는 패턴

메서드 체이닝의 장점

  • 코드가 한 문장으로 작성되므로 가독성이 좋다.
  • 체이닝 패턴을 도입함으로써 함수(메서드)를 더 작은 단위로 분할하게 된다.

Bad

class Move {
  private name: string;
  private departure : string;
  private arrival : string;
  
  setName(name: string):void{
  	this.name=name;
  }

  setFrom(departure: string): void {
    this.departure = departure;
  }

  setTo(arrival:string): void {
    this.arrival=arrival;
  }

  print(): string {
    return `${name} is going from `${departure} to `${arrival}`;
  }
}

const tracer = new Move();
tracer.setName('jane doe');
tracer.setFrom('JFK');
tracer.setTo('HND');

const record = tracer.print();

Good

class Move {
  private name: string;
  private departure : string;
  private arrival : string;
  
  setName(name: string):this{
  	this.name=name;
    return this;
  }

  setFrom(departure: string): this {
    this.departure = departure;
    return this;
  }

  setTo(arrival:string): this {
    this.arrival=arrival;
    return this;
  }

  print(): string {
    return `${name} is going from `${departure} to `${arrival}`;
  }
}

const record = new Move()
.setName('jane doe')
.setFrom('JFK')
.setTo('HND')
.print();


참고

profile
기록

0개의 댓글