Skip to main content

6장 객체와 자료 구조

written by
gyuseok-dev
gyuseok-dev 🏆Back End Engineer

자료추상화#

감추고 드러내라. 뭐래.. -장규석-

왜 구현을 감춰야되냐?#

  • 충동이든 변덕이든, 변수 타입이나 구현을 맘대로 바꾸고 싶어서다.
// 구체적인 클래스class Point {    x: number    y: number}
// 추상적인 클래스interface Point {    getX()    getY()    setCartesian(x: number, y: number)}
/* 구체적인 클래스는 각 인스턴스를 전부 따로 다뤄야한다. 추상적인 클래스는 x, y 인스턴스를 읽어올때는 따로 읽어오지만, 설정할 때는 두값을 한꺼번에 설정해야한다.*/

구현을 감추려면 추상화가 필요하다.#

  • bad
    interface Vehicle {    getFuelTankCapacityInGallons()    getGallonsOfGasoline()}// 이러면 객체에는 가솔린 차량 밖에 쓰지 못한다. 전기는? 수소는? 디젤은? 응???
  • good
    interface Vehicle {    getPercentFuelRemaining()}// 추상적으로 표현하면 가솔린이든 디젤이든 동일한 인터페이스로 표현할 수 있다.
  • 아무 생각 없이 조회/설정 함수를 추가하는 방법이 가장 나쁘다.

자료/객체 비대칭#

절차지향 코드#

  • 새 함수를 추가하는 것이 쉽다.
  • 새 클래스를 추가하기 위해서는 모든 함수를 수정해야한다.
interface Point = {      x: number      y: number}
class Square {    topLeft: Point    side: number}
class Rectangle {    topLeft: Point    height: number    width: number}
class Circle {    center: Point    radius: number}
class Geometry {    const PI = 3.14
    area(shape: object) {        if (shape instanceof Square) {            return shape.side * shape.side        }        else if (shape instanceof Rectangle) {            return shape.height * shape.width        }        else if (shape instanceof Circle) {            return this.PI * shape.radius * shape.radius        }    } }
// usages = new Square(...)g = new Geometry(...)return g.area(s)
// 여기에 다른 도형이 추가되면 Geometry의 모든 함수를 수정해야한다.

객체지향 코드#

  • 새 클래스를 추가하는 것이 쉽다.
  • 새 함수를 추가하기 위해서는 모든 클래스를 고쳐야한다.
interface Point {    x: number    y: number}
interface Shape {    area(): number}
class Square implements Shape {    topLeft: Point    side: number
    area() {        return this.side * this.side    }
}
class Rectangle implements Shape {    topLeft: Point    height: number    width: number
    area() {        return this.height * this.width    }}
class Rectangle implements Shape {    topLeft: Point    height: number    width: number
    area() {        return this.height * this.width    }}
class Circle implements Shape {    center: Point    redius: number    const PI = 3.14
    area() {        return this.PI * this.redius * this.redius    }}// 여기에 새로운 함수를 추가하려면 모든 클래스를 고쳐야한다!
  • 모든 것이 객체라는 생각은 미신이다.
  • 상황 맞게 절차지향 코드와 객체지향 코드를 적절히 사용해야 한다.

디미터 법칙#

  • 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙
  • 클래스의 메서드가 반환하는 객체의 메서드는 호출하면 안된다!
    • bad
      const outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath()// 위와 같은 코드를 기차충돌(train wreck)이라 부른다.// ctxt에서 너무 많은 자료를 공개해버린다.
    • good
      const opts = ctxt.getOptions()const scratchDir = opts.getScratchDir()const outputDir = scratchDir.getAbsolutePath() 

자료 전달 객체(DTO)#

  • 외부 애플리케이션(데이터베이스, 외부 API 등)에서 가공되지 않은 정보를 내부 코드에서 사용할 객체로 변환하는 단계에서 가장 처음으로 사용하는 구조이다.
    class UserDto {    name: string    email: string    password: string}

빈(bean) 구조 (bad)#

  • 그저 비공개(private) 변수에 조회/설정 함수로 조작한다.
  • 일종의 사이비 캡슐화로 별다른 이익을 주진 않는다.
class Address {    private street: string    private city: string    private state: string    private zip: string
    constructor(street: string, city: string, state: string, zip: string) {        this.street = street        this.city = city        this.state = state        this.zip = zip    };    getStreet() {        return this.street    }    getCity() {        return this.city    }    getState() {        return this.state    }    getZip() {        return this.zip    }}

활성 레코드#

  • DTO의 특수한 형태
  • save나 find 같은 추가적인 함수를 제공
  • 비즈니스 규칙 메서드를 추가하면 안된다.
  • MVC 패턴

결론#

객체는 동작을 공개하고 자료를 숨긴다.

  • 새로운 자료 타입(컴포넌트)를 추가하는 유연성이 필요하면, 객체지향(프론트)
  • 새로운 동작(메소드)을 추가 유연성이 필요하면, 자료구조 + 절차지향(백엔드)
  • 우수한 개발자은 편견없이 이 사실을 이해해 직면한 문제에 최적인 해결책을 선택한다.