공부하는 블로그

#3- 객체지향의 기본 원리를 이용해서 소프트웨어를 유연하게 하라 본문

design patterns

#3- 객체지향의 기본 원리를 이용해서 소프트웨어를 유연하게 하라

devtimothy 2019. 3. 18. 16:21

Head first OOAD를 타입스크립트로 읽기 #3

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

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

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

문제점

Inventory 클래스의 search 메소드를 살펴보자. 일단 고객은 기타의 일반 정보에 대해 제공한다. 고객은 가격, 일련번호는 입력하지 않는다.

search 메소드 분석

  1. 고객이 원하는 기타 사양을 전달

  2. 재고 목록 검색

  3. 고객이 원하는 사양과 비교

  4. 목록 표시

우리가 만들고자 하는 프로그램의 설계가 기능과 잘 어울리는지 확인하려면 해결하고자 하는 문제를 서술해본다.

객체 설계

잘 설계된 객체는 자신의 일에만 관심이 있고 자신의 본래 목적에 충실하다. 우리가 작성한 재고 검색 도구는 본래 목적 외에 다른 일에 사용되고 있다.

  1. 객체는 자신의 이름이 나타내는 일을 해야 한다 : 비행기는 이륙, 착륙 등을 하지만 비행기표를 받지는 않는다. 비행기 표 받는 일은 다른 객체가 해야할 일이다.

  2. 각 객체는 하나의 개념을 나타내어야 한다. : 객체가 두개 이상의 임무를 맡지 않도록 한다. 꽥꽥이 오리, 플라스틱 장난감 오리, 오리 같이 행동하는 사람 등의 개념을 나타냄은 피하라.

  3. 사용되지 않는 속성이 결정적 증거 : 객체에 값이 없거나 null이 있다면 객체가 하나 이상의 일을 하고 있을 가능성이 있다. 어떤 속성에 값이 없는 경우가 많다면 한번쯤은 생각해보는 것이 좋을듯 하다.

캡슐화

캡슐화를 통해서 우리는 프로그램을 논리적 그룹으로 나눌 수 있다. 기타 사양을 나타내는 객체를 만들도록 한다. 캡슐화는 변수를 private로 선언해서 오용하지 못하게 하는 것만을 의미하지 않는다. 프로그램을 논리적 그룹으로 나누어 분리시키는 일을 한다.

클래스 내에 데이터를 프로그램의 행위와 분리하듯이 기타의 일반 속성을 기타 객체로부터 분리시킬 수 있다.

search() 메소드에서 기타 사양을 전달하려면 객체가 필요하다. Guitar 객체의 속성을 저장할 때도 기타 사양 객체를 사용하는 이유는, 사양에 현(string) 수 를 추가한다고 한다면, 기타 사양 클래스와 기타 클래스에 속성과 메소드를 추가해야 할 것이다.

캡슐화는 프로그램 일부 정보를 다른 부분으로부터 보호하는 것이다. (ATM기에서 입금시에 어떤 처리가 이루어지는지 입금자가 알 필요가 없는 것처럼.) 클래스에서 행위를 캡슐화 하는 경우, 클래스가 변하지 않고도 행위를 변경할 수 있다. 저장되는 속성이 바뀌어도 속성들이 기타 클래스로부터 분리되어 캡슐화 되어 있기 때문에, 기타 클래스는 변경할 필요가 없다.

코드 수정

// Guitar.ts
import { GuitarSpec } from "./GuitarSpec";

export type Guitar = InstanceType<typeof Guitar>;
export const Guitar = class {
 private _serialNumber: string;
 private _price: number;
 private _spec: GuitarSpec

 constructor(
   serialNumber: string,
   price: number,
   spec: GuitarSpec
) {
   this._serialNumber = serialNumber;
   this._price = price;
   this._spec = spec;
}

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

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

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

 get spec(): GuitarSpec {
   return this._spec
}
};

// GuitarSpec.ts
import { Builder } from "./types/Builder";
import { Type } from "./types/Type";
import { Wood } from "./types/Wood";
export type GuitarSpec = InstanceType<typeof GuitarSpec>;
export const GuitarSpec = class {
   private _builder: Builder;
   private _model: string;
   private _type: Type;
   private _backWood: Wood;
   private _topWood: Wood;

   constructor(
       builder: Builder,
       model: string,
       type: Type,
       backWood: Wood,
       topWood: Wood
  ) {
       this._builder = builder;
       this._model = model;
       this._type = type;
       this._backWood = backWood;
       this._topWood = topWood;
  }

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

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

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

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

// Inventory.ts
import { Guitar } from "./Guitar";
import { Type } from "./types/Type";
import { Wood } from "./types/Wood";
import { Builder } from "./types/Builder";
import { GuitarSpec } from "./GuitarSpec";
export type Inventory = InstanceType<typeof Inventory>;
export const Inventory = class {
 private _guitars: Guitar[];
 constructor() {
   this._guitars = new Array<Guitar>();
   // this._guitars = [];
}
 addGuitar(
   serialNumber: string,
   price: number,
   builder: Builder,
   model: string,
   type: Type,
   backWood: Wood,
   topWood: Wood
): void {
   const spec: GuitarSpec = new GuitarSpec(builder, model, type, backWood, topWood)
   const guitar: Guitar = new Guitar(serialNumber, price, spec);
   this._guitars.push(guitar);
}
 getGuitar(serialNumber: string): Guitar {
   return this._guitars.filter(
    (item: Guitar): boolean => item.serialNumber == serialNumber
  )[0];
}
 search(searchSpec: GuitarSpec): Guitar[] {
   const {
     builder: tBuilder,
     model: tModel,
     type: tType,
     backWood: tBackWood,
     topWood: tTopWood
  } = searchSpec;
   return this._guitars.filter(
    (item: Guitar): boolean => {
       const { builder, model, type, backWood, topWood } = item.spec;
       return (
         builder &&
         builder == tBuilder &&
        (model && model.toLowerCase() == tModel.toLowerCase()) &&
        (type && type == tType) &&
        (backWood && backWood == tBackWood) &&
        (topWood && topWood == tTopWood)
      );
    }
  );
}
};

// FindGuitarTester.ts
import { Inventory } from "./Inventory";
import { Guitar } from "./Guitar";
import { Builder } from "./types/Builder";
import { Type } from "./types/Type";
import { Wood } from "./types/Wood";
import { GuitarSpec } from "./GuitarSpec";
const FindGuitarTester = class {
 public main(): void {
   const inventory: Inventory = new Inventory();
   this.initInventory(inventory);

   const whatErinLikes: GuitarSpec = new GuitarSpec(Builder.FENDER, "Stratocastor", Type.ELECTRIC, Wood.ALDER, Wood.ALDER);
   const guitars: Guitar[] = inventory.search(whatErinLikes);
   if (guitars.length > 0) {
     guitars.forEach((guitar: Guitar) => {
       const { builder, model, type, backWood, topWood } = guitar.spec;
       console.log(`Erin, you might like this ${builder} ${model} ${type} guitar:
               ${backWood} back and sides,
               ${topWood} top.
               You can have it for only ${guitar.price}!`);
    });
  } else {
     console.log("Sorry, Erin, we have noting for you.");
  }
}
 private initInventory(inventory: Inventory) {
   inventory.addGuitar("V95693", 1499.95, Builder.FENDER, "Stratocastor", Type.ELECTRIC, Wood.ALDER, Wood.ALDER);
   inventory.addGuitar("V9512", 1599.95, Builder.FENDER, "Stratocastor", Type.ELECTRIC, Wood.ALDER, Wood.ALDER);
   inventory.addGuitar("a", 1000, Builder.COLLINGS, "c", Type.ELECTRIC, Wood.ALDER, Wood.BRAZILIAN_ROSEWOOD);
   inventory.addGuitar("a1", 1000, Builder.FENDER, "c", Type.ACOUSTIC, Wood.ALDER, Wood.BRAZILIAN_ROSEWOOD);
   inventory.addGuitar("a2", 1000, Builder.GIBSON, "c", Type.ACOUSTIC, Wood.ALDER, Wood.BRAZILIAN_ROSEWOOD);
   inventory.addGuitar("a3", 1000, Builder.MARTIN, "c", Type.ACOUSTIC, Wood.ALDER, Wood.BRAZILIAN_ROSEWOOD);
   inventory.addGuitar("a4", 1000, Builder.OLSON, "c", Type.ELECTRIC, Wood.ALDER, Wood.BRAZILIAN_ROSEWOOD);
}
};

new FindGuitarTester().main();

용어 정리

  1. 유연성: 계속 고치지 않고서 소프트웨어가 변하고 성장 가능하게, 견고하게 만듦.

  2. 캡슐화: 변화하는 부분을 변화하지 않는 부분으로부터 분리할 때 사용.

  3. 기능: 기능 없이 고객 만족시킬 수 없다.

  4. 디자인 패턴: 재사용과 관련됨. 이미 해결한 문제를 우리가 다시 해결하지 않도록 한다.

캡슐화는 어디서부터 할 수 있는지가 중요하다. 캡슐화가 내 코드의 유연성에 어떤 영향이 있는가? 중복코드가 많거나 상속 구조가 혼란스럽다면 수정하기가 힘들어진다. 캡슐화 같은 좋은 클래스 설계를 통해서 수정이 더 쉽고 유연해진다.

다음 단계에서는 유지보수가 쉬운 디자인을 고민해보도록 한다.

Comments