모든 클래스는 하나의 책임만을 가지며 그 책임을 완전히 캡슐화해야 함을 일컫는다.
- 캡슐화 : 객체의 속성(data fields)과 행위(Methods)를 하나로 묶, 실제 구현 내용 일부를 외부에 감추어 은닉하는 것
따라서, 클래스의 크기는 책임
에 의해 측정된다.
좋은 소프트웨어 설계는 높은 응집도와 낮은 결합도를 가져야한다.
즉, 클래스 안의 모든 필드는 각 메소드에 의해서 사용되어야 하고 (높은 응집도)
각 클래스들은 서로 연관되어 있지 않아서 상호 영향을 주지 않아야한다. (낮은 결합도)
높은 응집도를 가짐으로써, 하나의 기능에 변화가 생길 때 유지 보수에 용이하며, 마찬가지로 낮은 결합도를 가짐으로써 한 기능의 변화가 다른 기능에 영향을 미치지 않아서 이 역시 유지 보수에 용이하기 때문이다.
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)
이므로 상속을 받는다.
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) 관계
이므로 조합을 사용한다.
캡슐화를 깨뜨린다.
상위 크래스의 구현이 하위 클래스에 그대로 노출되기 때문에 캡슐화가 깨진다. 따라서 자식 클래스가 부모 클래스에 강하게 결합 / 의존하게 된다.
이로 인하여 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작이 달라질 수 있으며 상위 클래스에 변화가 있을 때 하위클래스에 예기치 못한 변화를 초래 할 수 있다.
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
클래스도 변경된다.
HAS-A
관계가 아닌 IS-A
관계 일 때메서드 체이닝이란?
메서드가 객체를 반환하면 반환 값 객체를 통해 또 다른 메서드를 호출 수 있는 패턴
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();
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();