공부하는 블로그

#1 - 잘 설계된 프로그램이 세상을 뒤흔든다 본문

design patterns

#1 - 잘 설계된 프로그램이 세상을 뒤흔든다

devtimothy 2019. 3. 13. 22:37

본 블로깅은 Head first OOAD: 세상을 설계하는 객체지향 방법론 (한빛미디어) 책을 Typescript 문법으로 전환하며 공부하는 글입니다.

글을 읽기 전에, 광고 배너 한번씩만 클릭 부탁드립니다. 블로그 운영에 큰 보탬이 됩니다 :)

왜 Head first OOAD를 택했나?

코드스피츠 맹대표님의 첫강을 들으면서 정말 망치로 얻어맞은 듯 한 충격과 공포를 받게 되었다. 내가 그동안 정말 객체지향은 1도 모르고 짜왔구나 하는 생각이 들게끔 적나라하게 객체 지향과 값 지향 프로그래밍에 대해 강의해주셨다. (참고: https://www.youtube.com/watch?v=_JGchAMbPGI)

그러던 와중에 책 몇권을 추천을 받았는데, Head first OOAD와 다른 책 하나를 추천 받았다. (하나는 이름이 기억 안난다..)

사실 Head first OOAD 책은 절판된 상태여서 포기하고 있었는데, 동생이 대학교 도서관에서 빌려와줘서 급히 읽기 시작했다.

왜 Typescript인가?

사실 책은 Java5로 되어있다. 첫 장 내용을 보다보면 enum 클래스가 새로 만들어졌던 시기임을 짐작할 수 있다. (우와)

언어도 많이 발전해서 그냥 편하게 java로 코딩을 해도 좋았겠지만, 평소 주 개발 언어가 JS이기도 하고, 객체지향 패러다임을 좀 더 잘 구현한 TS를 이용하여 객체지향 패러다임을 공부 해보고자 하였다.

코드 구현

아무래도 다른 언어로 구현하다보니 문법이 다른 경우가 좀 있었다. Typescript에서는 클래스를 return value로 받는데 오류가 나기도 했다. 예를 들자면 아래와 같다.

const Item = class {
private _a: string;
get a() {
  return this._a;
}
};

// Cannot find name 'Item'.ts(2304)
const myItem: Item = new Item();

그래서 아래와 같이 interface를 생성하여 문제를 해결했다. 사실 왜 이렇게 해야 하는지는 좀 더 알아봐야 할 것 같다. TS에 대한 개념이 부족하다.

interface IItem {
a: string;
}
const Item = class implements IItem {
private _a: string;
get a() {
  return this._a;
}
};

const myItem: IItem = new Item();

여튼 책을 참고하여 작성한 코드는 아래와 같다.

// Guitar.ts
export interface IGuitar {
serialNumber: string;
price: number;
builder: string;
model: string;
type: string;
backWood: string;
topWood: string;
}

export const Guitar = class implements IGuitar {
private _serialNumber: string;
private _price: number;
private _builder: string;
private _model: string;
private _type: string;
private _backWood: string;
private _topWood: string;

constructor(
  serialNumber: string,
  price: number,
  builder: string,
  model: string,
  type: string,
  backWood: string,
  topWood: string
) {
  this._serialNumber = serialNumber;
  this._price = price;
  this._builder = builder;
  this._model = model;
  this._type = type;
  this._backWood = backWood;
  this._topWood = topWood;
}

get serialNumber(): string {
  return this._serialNumber;
}

get price(): number {
  return this._price;
}

set price(val: number) {
  this._price = val;
}

get builder(): string {
  return this._builder;
}

get model(): string {
  return this._model;
}

get type(): string {
  return this._type;
}

get backWood(): string {
  return this._backWood;
}
get topWood(): string {
  return this._topWood;
}
};

// Inventory.ts
import { IGuitar, Guitar } from "./Guitar";

export interface IInventory {
addGuitar(
  serialNumber: string,
  price: number,
  builder: string,
  model: string,
  type: string,
  backWood: string,
  topWood: string
): void;
getGuitar(serialNumber: string): IGuitar;
search(searchGuitar: IGuitar): IGuitar;
}

export const Inventory = class implements IInventory {
private _guitars: IGuitar[];
constructor() {
  // this._guitars = new Array<IGuitar>();
  this._guitars = [];
}
addGuitar(
  serialNumber: string,
  price: number,
  builder: string,
  model: string,
  type: string,
  backWood: string,
  topWood: string
): void {
  const guitar: IGuitar = new Guitar(
    serialNumber,
    price,
    builder,
    model,
    type,
    backWood,
    topWood
  );
  this._guitars.push(guitar);
}
getGuitar(serialNumber: string): IGuitar {
  return this._guitars.find(
    (item: IGuitar): boolean => item.serialNumber == serialNumber
  );
}
search(searchGuitar: IGuitar): IGuitar {
  const {
    builder: tBuilder,
    model: tModel,
    type: tType,
    backWood: tBackWood,
    topWood: tTopWood
  } = searchGuitar;
  return this._guitars.find(
    (item: IGuitar): boolean => {
      const { builder, model, type, backWood, topWood } = item;
      return (
        builder &&
        builder == tBuilder &&
        (model && model == tModel) &&
        (type && type == tType) &&
        (backWood && backWood == tBackWood) &&
        (topWood && topWood == tTopWood)
      );
    }
  );
}
};

// FindGuitarTester.ts
import { Inventory, IInventory } from "./Inventory";
import { Guitar, IGuitar } from "./Guitar";
const FindGuitarTester = class {
private initInventory(inventory: IInventory) {
  inventory.addGuitar(
    "",
    0,
    "fender",
    "Stratocastor",
    "electric",
    "Alder",
    "Alder"
  );
  inventory.addGuitar("a", 1000, "b", "c", "d", "e", "f");
  inventory.addGuitar("a1", 1000, "bsdf", "c", "d", "e", "f");
  inventory.addGuitar("a2", 1000, "bsdf", "c", "d", "e", "f");
  inventory.addGuitar("a3", 1000, "bsdf", "c", "d", "e", "f");
  inventory.addGuitar("a4", 1000, "bsdf", "c", "d", "e", "f");
}
public main(): void {
  const inventory: IInventory = new Inventory();
  this.initInventory(inventory);

  const whatErinLikes: IGuitar = new Guitar(
    "",
    0,
    "fender",
    "Stratocastor",
    "electric",
    "Alder",
    "Alder"
  );
  const guitar: IGuitar = inventory.search(whatErinLikes);
  if (guitar) {
    const { builder, model, type, backWood, topWood, price } = guitar;
    console.log(`Erin, you might like this ${builder} ${model} ${type} guitar:
        ${backWood} back and sides,
        ${topWood} top.
        You can have it for only ${price}!`);
  } else {
    console.log("Sorry, Erin, we have noting for you.");
  }
}
};

new FindGuitarTester().main();

검색해보면 사실 Erin이 찾는 기타가 나와야 하는데 나오질 않는다. 책은 설계가 💩같아서 그렇다며 우리에게 문제제기를 한다.

어떻게 다시 설계할 것인가?

나의 생각

  • Guitar와 Inventory가 너무 의존적이다. Guitar 클래스의 뭐를 하나 고치면 Inventory도 덩달아 고쳐야 하는 상황이 나올 수 있다.

책의 생각

  • 책은 한두가지만 생각하라고 하지 않는 듯 하다. 😒

  • 많은 문자열 타입 → 상수나 객체로 대체하라

  • search 메소드는 리스트를 반환해야한다.

  • Guitar 클래스와 Inventory 클래스는 서로 의존적이다.

오예. 하나는 맞았다. 😅

위대한 소프트웨어

사실 명칭이 되게 투머치다... 좋은 소프트웨어 정도로 번역했다면 좋았을텐데. 책에서 말하는 바는 다음과 같다.

  • 고객 중심: 맹대표님이 강의 때 자주 이야기하시는... 고객은 늘 우리를 엿먹이기 위해 노력하는 9살 초딩과 같다고 생각하라는 말씀이 떠오른다. 고객이 이상하게 SW를 사용해도 멈추거나 예상치 못한 결과를 초래하지 않는 SW.

  • 객체 지향: 중복 코드가 적다. 각 객체가 자신의 행동을 통제한다. 설계가 견고하고 유연해서 확장이 쉽다.

  • 설계 중시: 디자인패턴, 객체간 약결합, OCP, 코드의 재사용성

나의 생각

그간 나는 좋은 소프트웨어란 어떤 것일까? 라는 고민을 해보진 않았던 것 같다. 너무 기능 구현에만 치중해있고 데이터만 중요시해왔던 것을 깨닫게 된다. 사실 유지보수하기 쉽게 짜기 위해 나름 노력을 하긴 하지만 기능이 확장됨에 따라 예상치 못한 일들이 계속 벌어지는 것을 보게 되었다.

코드 구현 자체는 사실 DRY 원칙 같은 것들 줏어들어서 중복 코드를 많이 만들지 않으려고 나름 노력을 하긴 했었다.

책 says...

  1. 고객이 원하는 기능을 하도록 하라

  2. 객체지향 기본 원리를 이용해 소프트웨어를 유연하게 하라

  3. 유지보수와 재사용이 쉬운 디자인을 위해 노력하라

이제 다음 포스팅에서는 위의 세 단계를 하나하나 클리어해나가며 코딩을 하도록 하겠다.

Comments