레이블이 oop인 게시물을 표시합니다. 모든 게시물 표시
레이블이 oop인 게시물을 표시합니다. 모든 게시물 표시

2014년 4월 22일 화요일

UML 클래스 다이어그램


여러 책이나 자료들을 접하다 보면 클래스 다이어그램의 표기가 조금씩 틀려서 종종 헷갈리는 경우가 있어 정리한다. 이 블로그의 포스팅에 등장하는 클래스 다이어그램은 이 포스팅에 정리된 내용을 따른다.

Generalization

클래스의 상속관계를 의미한다.

그림 1 - Generalization

Realization

인터페이스의 구현을 의미한다.

그림 2 - Realization

Aggregation

다른 클래스를 참조하는 경우인데, 그 참조가 멤버변수로 유지되는 경우을 의미한다.

그림 3 - Aggregation

Composition

다른 클래스를 참조하는 경우인데, 그 참조가 멤버변수로 유지된다는 점은 동일하다. Java에서는 이 다이어그램을 사용하기가 애매한 경우가 많다. C++와 같이 프로그래머가 메모리를 직접 관리해야 하는 경우는 많이 사용된다.

  • 그림 4 에서 Building 은 Address 을 소유하고 있다. 이때 Building 이 소멸될 때 Address 도 소멸해야 하는 관계이다.

    그림 4 - Composition
     
  • 그림 5 는 오브젝트 다이어그램(클래스 다이어그램이 아님)을 보여준다. Building1 과 Building2 는 동일한 Address1 을 소유할 수 없다. 만약 Building1 객체(인스턴스)가 복사되어야 하는 경우 Address1 도 얕은 복사(Shallow Copy)가 아닌 깊은 복사(Deep Copy)가 되어야 한다.
     
그림 5 - Illegal composition

Association

클래스 A가 클래스 B을 사용한다. 이때 클래스 B의 참조는 매개변수을 통해서 넘겨받거나 생성할 수 있다. 단, B에 대한 참조는 멤버변수가 아닌 지역적으로 유지된다.


그림 6 - Association

Inner Class

내부 클래스를 의미한다.

그림 7 - Inner Class


참고자료

  • UML for Java Programmers(Robert C. Martin)

2014년 4월 20일 일요일

의존관계 주입

객체와 객체사이에 의존성이 있다는 것은 어떤 의미인가?

그림 1 은  A가 B에 의존하고 있음을 나타낸다. 의존하고 있다는 것은 무슨 의미인가? A가 B을 사용하고 있다는 것이다. 그래서 B의 변화가  A에 전달이 된다.  A는 B의 클라이언트라고 할 수 있다. 예를 들어 A에서 B에 정의된 메서드를 호출하는 것이다. B에 정의된 메서드가 변경이 되면 당연히 A 에서의 호출부분도 변경이 되어야 한다. 반대로 B는 A에 의존하고 있지 않다. A의 변경은 B에 전달되지 않는다.



그림 1 - 의존관계

의존관계 주입(Dependency Injection)

의존관계 주입은 구체적인 의존 오브젝트와 그것을 사용하는 클라이언트 오브젝트를 런타임시에 연결해주는 작업을 말한다.

  • 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야 한다.
  • 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 3의 존재가 결정한다.
  • 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로서 만들어진다.

의존관계 주입의 핵심은 설계 시점에는 알지 못했던 두 오브젝트의 관계를 맺도록 도와주는 제3의 존재가 있다는 것이다. 즉 클라이언트는 자신이 사용할 의존 오브젝트를 직접 생성하지 않고 이는 외부의 제 3의 존재가 결정하게 된다.

그림 2을 보자. Cafe의 성격에 따라 Coffee 만을 파는 일반적인 카페일지도 전통찻집일수도 있다. 그래서  Cafe 라는 클래스는 자신이 주로 파는 음료는 만드는 BeverageMaker 라는 의존 오브젝트를 사용한다. Cafe 라는 클래스만 봐서는 beverageMaker 에 어떤 구체 클래스가 할당이 될지 알 수 없다. 그리고 이것은 Cafe 라는 클래스가 직접 자신이 사용할 BeverateMaker을 선택하지 않고 외부의 제 3의 존재가 결정하게 된다.


그림 2 - Cafe 와 BeverageMaker



여기서는 CafeFactory 라는 클래스가 그 역활을 담당하게 된다. CafeFactory 의 구현은 아래와 같다.



그림 3 - CafeFactory



package iamhereweare.oop.di;

public class CafeFactory {
 
 public Cafe cafe() {
  Cafe cafe = new Cafe();
  cafe.setBeverageMaker(beverageMaker());
  return cafe; 
 }

 private BeverageMaker beverageMaker() {
  return new CoffeeBeverageMaker();
 }
}

코드  CafeFactory 구현

CafeFactory의 beverageMaker() 의 구현에 따라 Cafe에서 사용할 BeverageMaker가 결정이 되게 된다. (Cafe 클래스가 직접 결정하지 않는다.) 이런 것을 제어의 역전이라고 한다.

제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않느다. 당연히 생성하지도 않는다. 또 자신도 어떻게 만들어지고 어디서 사용되는지를 알 수 없다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다.

제어의 역전에 대해서는 별도의 포스팅이 있으니 그것을 참고하자. CafeFactory가 하는 일을 일반화해서 만든 것이 스프링의 애플리케이션 컨텍스트, 빈 팩토리, IoC 컨테이너 등이다. 모두 외부에서 오브젝트 사이의 런타임 관계를 맺어주는 책임을 지닌 제 3의 존재라고 볼 수 있다.

참고자료

  • 토비의 스프링 3.1(이일민)
                                                           

2014년 4월 19일 토요일

객체 지향 설계 원칙 SRP

단일 책임 원칙(Single Responsibility Principle)


하나의 모듈(클래스)은 한가지 책임을 져야 한다. 모듈은 단 한 가지의 변경 이유만을 가져야 한다.

그렇다면 책임이란 무엇인가? 책임을 변경을 위한 이유라고 생각해보자. 모듈 변경을 위한 한 가지 이상의 이유를 생각할 수 있다면, 그 모듈은 한 가지 이상의 책임을 가지고 있는 것으로 생각할 수 있다. 객체 지향의 원칙중에 SRP는 은근히 지켜기 어렵다. 

간단한 예를 들어 살펴보자. 특정 파일에 담겨 있는 내용을 읽어 테이블에 표시를 하는 기능을 개발한다고 생각하자. Displayer  라는 클래스를 만들었고, 이 클래스는 두 가지 메서드를 포함하고 있다.

그림 1 - 두가지 책임을 가지고 있는 Displayer 클래스

  • readContent : 파일로 부터 내용을 읽어온다.
  • displayContent : 화면에(GUI) 표시한다.

이후 기능의 호응이 좋아서 새로운 요구사항이 생겼다.
  • 파일뿐만 아니라 데이터베이스에서 내용을 읽어서 표시해달라는 것이다.
    readContent 메서드의 변경이 필요하다.
  • PC 화면뿐만 아니라 모바일 기기에서도 내용을 보기를 원한다.
    dispalyContent 메서드의 변경이 필요하다.

Displayer 클래스는 내용을 읽어들이는 것과 화면에 표시하는 두 가지 책임을 가지고 있다. 이 책임에 따라 변경이 일어날 가능성은 두 가지가 되는 것이다. 이것을 아래와 같이 바꾸어 보자.

그림 2 - 책임(관심)에 따른 클래스의 분리

  • Reader 클래스 : 특정 데이터 소스로부터 내용을 읽어온다.
  • Displayer 클래스 : 화면에 표시하는 로직만 담당한다. 


Displayer 클래스는 Reader 클래스를 사용한다.(또는 내용을 담고 있는 Content 클래스를 정의하고, 이것을 입력으로 받아서 화면에 표시하게 할 수도 있다.) SRP을 생각하면서 설계를 하다 보면 자연스럽게 객체 지향을 생각하게 된다. 지금의 설계에서는 하나의 클래스가 하나의 책임을 기지고 동작하게 된다. 나중에 변경이 생기게 되는 경우 그 변경에 따라 하나의 클래스만 수정을 하면 된다. 사실, 새로운 요구사항이 생겨 ConcreteDisplayer 에 기능을 추가하는 것보다는 전략패턴을 이용해서 아래와 같이 설계를 해서 사용할 전략(Reader의 구체화된 클래스) 을 선택하게 하는 것이 좋을 것이다. 요지는 모듈간의 의존성을 가능한한 느슨하게 만드는 것이다.


그림 3 - 전략패턴을 이용한 구현


이러한 설계를 확장하면, MVC 패턴과도 비슷하다는 것을 알 수 있다. 책임이나 관심사에 따라 적절하게 모듈(클래스)을 분리하는 것이 단일 책임 원칙의 핵심이다.


단일 책임 원칙의 장점

단일 책임 원칙을 지키려고 하다보면 인터페이스등을 도입하게 되며, 여러 디자인 패턴을 적용해서 설계를 우아하게 만들게 된다. 그러면서 자연스럽게 모듈간의 결합이 느슨해진다. 이것은 어떤 변경이 필요할 때 수정 대상을 명확하게 하는 결과를 가져온다.

참고자료

  • Agile Software Development,Principles, Patterns, and Practices (Robert C. Martin)
  • 토비의 스프링 3.1 (이일민)


2014년 4월 17일 목요일

객체 지향 설계 원칙 OCP

개방-폐쇄 원칙(Open-Closed Principle)


소프트웨어 개체(클래스, 모듈, 함수 등등) 은 확장에 대해 열려 있어야 하고, 변경에 대해서는 닫혀 있어야 한다.
  1. 확장에 대해 열려 있다.
    이것은 모듈의 동작을 확장할 수 있다는 것을 의미한다. 요구사항이 변경이 될 때, 이 변경에 맞게 새로운 동작이나 기능을 추가 할 수 있어야 한다. 즉 모듈이 하는 일이 변경이 가능한다.
  2. 변경에 대해 닫혀 있다.
    어떤 모듈의 동작이나 기능의 확장으로 인해서 그 모듈의 코드에 변경이 일어나는 것은 아니다.

참 아이러니하다. 어떻게 코드의 변경이 없이 모듈의 동작의 확장이 가능하단 말인가? 핵심은 객체지향에 있다. 잘 설계된 추상화된 모듈은 이것이 가능하다. 예를 들어, 화가가 자신이 선택한 재료로 그림을 그리듯이 이러한 기능이 담고 있는 애플리케이션을 (포토샵이나 페이터와 같은...) 만든다고 상상해보자. 먼저 화가라는 객체를 추상화가 필요하다. 그리고 이 객체의 주요 동작은 그리는 것이다. 아래 두 가지 방법의 설계를 살펴보자.

그림 1 - OCP가 고려되지 않은 설계

그림 1의 설계에서는 볼펜을 사용하는 화가, 연필을 사용하는 화가를 각각 클래스로 구현하였다. 클라이언트가 BallpenPainter 클래스를 사용하다가, 볼펜이 아닌 다른 느낌의 그리는 기능이 필요하게 되었다. 이럴 경우 클라이언트는 BallpenPainter 대신에 PencilPainter 클래스를 사용해야 하고, BallpenPainter 을 호출하는 코드의 변경이 필요하다. 만약 이 클래스가 상속이 되어 있거나 다른 곳에서 사용이 되고 있다면 연쇄적인 변경이 필요하다. 이러한 문제가 발생하는 근본적인 원인은 클라이언트가 BallpenPainter 나 PencilPainter 와 같은 구체화된 클래스에 의존하기 때문이다.


그림 2 - Client 가 사용하는 코드는 변하지 않는다.

그림 2의 설계에서는 클라이언트는 추상클래스에 대해서만 알고 있으며, 추상 클래스의 draw() 메서드만 호출한다. 그리는 동작의 변경은 이 추상클래스를 상속하는 구체클래스가 사용하는 전략에 따라 달라진다. 클라이언트 입장에서는 기능이 확장되어도 여전히 추상클래스의 draw() 메서드를 이용하는 것이고, 코드의 변경은 일어나지 않는다. 전략 패턴뿐만 아니라 템플릿 메서드 패턴이나 데코레이터 패턴 등의 여러 객체지향 디자인 패턴들을 활용하여 상황에 맞게 설계하면 된다.

 참고자료

  • Agile Software Development,Principles, Patterns, and Practices (Robert C. Martin)
  • Head First Design Patterns(에릭 프리먼, 엘리자베스 프리먼, 케이시 시에라 버트 베이츠)
  • GoF 의 디자인 패턴(에릭 감마, 리처드 헬름, 랄프 존슨, 존 블리시디스)
  • 토비의 스프링 3.1(이일민) 


2013년 11월 18일 월요일

제어의 역전 ( Inversion of Control, IoC )

객체에서 클라이언트란?

A 객체가 B 객체를 사용할 때, A는 B의 Client 이다.


제어의 역전

사용하고자 하는 오브젝트의 생성과 선택에 대해서 클라이언트가 능동적으로 직접 관여하는 게 전통적인 방식의 프로그래밍 모델이다. 제어의 역전은 클라이언트가 자신이 사용할 오브젝트를 스스로 생성하고 선택하지 않는다. 이 오브젝트의 생성과 제어의 흐름을 담당하는 대상에게 위임한다. 즉, 각각의 모듈들과 그들의 생성과 사용에 대한 제어를 담당하는 대상을 별도로 분리하는 것이 핵심인 프로그래밍 모델이다.

제어의 역전이 사용된 예는 다양하다. 가장 간단한 예로는 디자인 패턴에서 이야기하는 Template Method Pattern 이 될 수 있다. 상위 클래스에서는 제어의 흐름을 담당하고, 하위 클래스에서 실제적으로 구현과 확장을 함으로서 제어의 역전을 경험할 수 있다.

또 다른 예로는 특정 객체의 생성과 그 객체가 사용할 알고리즘(전략)을 결정하는 오브젝트 팩토리를 만드는 것이다. 생성이 되는 객체의 입장에서는 자신이 어떻게 생성이 되는지, 자신이 사용할 전략이 무엇이지 알지 못하고 알 필요도 없다. 이것에 대한 제어는 오브젝트 팩토리 객체가 가지는 것이다.

이 개념을 좀 더 확장해서 프레임워크와 애플리케이션 코드의(또는 비지니스 로직) 관점에서 생각해 볼 수 있다. 이일민님이 쓴 토비의 스프링3 책에서는 이 개념을 이렇게 설명하고 있다.
프레임워크도 제어의 역전 개념이 적용된 대표적인 기술이다. 프레임워크는 라이브러리의 다른 이름이 아니다. 프레임워크는 단지 미리 만들어둔 반제품이나, 확장해서 사용할 수 있도록 준비된 추상 라이브러리의 집합이 아니다. 프레임워크가 어떤 것인지 이해하려면 라이브러리와 프레임워크가 어떻게 다른지 알아야 한다. 라이브러리를 사용하는 애플리케이션 코드는 사용자의 흐름을 직접 제어한다. 단지 동작하는 도중에 필요한 기능이 있을 때, 라이브러리를 능동적으로 사용할 뿐이다. 반면에 프레임워크는 애플리케이션 코드가 프레임워크에 의해 사용된다. 보통 프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드가 사용된다. 보통 프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만드는 애플리케이션 코드를 사용하는 방식이다. 최근에는 툴깃, 엔진 라이브러리 등도 유행을 따라서 무작정 프레임워크라고 부르기도 하는데 이는 잘못된 것이다. 프레임워크에는 분명한 제어의 역전 개념이 적용되어 있어야 한다. 애플리케이션 코드는 프레임워크가 짜놓은 틀에서 수동적으로 동작해야 한다.
프레임워크 전체적인 틀위에 필요한 애플리케이션 코드들이 만들어지고, 이것들은 프레임워크에서 정의된 제어 흐름속에서 사용되는 것이다.

참고자료

  • 토비의 스프링, 
  • UML 실전에서는 이것만 쓴다(UML for Java Programmers), 
  • 소프트웨어 개발의 지혜(Agile Software Development : Principles, Patterns, and Practices)