디자인 패턴의 중요성

프로그래밍 자주 발생하는 문제를 이미 누군가 좋은 해답으로 해결한

그것을 우리는 배워서 재사용하는 것이다.

디자인 패턴은 코드가 아닌 경험을 재사용하는 것이다.

 

 

디자인 원칙
애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다.(캡슐화)

달라지는 부분을 캡슐화하면 향후 다시 변경 시 나머지 안 달라지는 부분에 대한 영향도가 감소한다.

 

 

디자인 원칙
구현보다는 인터페이스에 맞춰서 프로그래밍한다.(다형성)

구현부가 존재하는 구체적인 클래스에 맞춰 프로그래밍하면 유연하지 못하다.

 

디자인 원칙
상속보다는 구성을 활용한다

자바는 단일 단일 상속만 지원한다. 따라서 더욱 적극적으로 구성을 활용하는 것이 좋다.

 

전략패턴(Strategy Pattern)

알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해준다.

알고리즘은 일련의 행동을 정의해둔 행동이다

 

public interface Movable {
	void move();
}

public class Fly implements Movable{

	@Override
	public void move() {
		System.out.println("날아갑니다...");
	}

}

public class Riding implements Movable{

	@Override
	public void move() {
		System.out.println("말타고 갑니다...");
	}

}
public class Walk implements Movable{

	@Override
	public void move() {
		System.out.println("걸어가기...");
	}

}

public interface Weapon {
	void attack();
}

public class BowWeapon implements Weapon{

	@Override
	public void attack() {
		System.out.println("화살 쏘기!");
	}
	
}
public class SpearWeapon implements Weapon{

	@Override
	public void attack() {
		System.out.println("찌르기!");
	}
	
}

public class SwordWeapon implements Weapon{

	@Override
	public void attack() {
		System.out.println("베기!");
	}
	
}



public abstract class Character {
	String type;
	//상속보단 일반적으로 구성과 위임이 더 유연하다(특히 자바는 단일 상속만 되므로)
	Movable movable;
	Weapon weapon;
	
	public void setMovable(Movable movable) {
		this.movable = movable;
	}
	public void setWeapon(Weapon weapon) {
		this.weapon = weapon;
	}
	
	public void attack() {
		weapon.attack();
	}
	
	public void move() {
		movable.move();
	}
}
public class Angel extends Character{
	
	public Angel() {
		super.type = "천사";
	}
	
	@Override
	public void attack() {
		System.out.print("신성한 ");
		super.attack();
	}
	
}
public class Devil extends Character{
	
	public Devil() {
		super.type = "악마";
	}
	
	@Override
	public void attack() {
		System.out.print("타락한 ");
		super.attack();
	}
}


public class Ex {
	public static void main(String[] args) {
		
		//인터페이스에 맞춰서 프로그래밍 한다.
		Character angel = new Angel();
		Character devil = new Devil();
		
		//전략 객체 
		//인터페이스에 맞춰서 프로그래밍 한다.
		Movable fly = new Fly();
		Movable riding = new Riding();
		Movable walk = new Walk();
		Weapon sword = new SwordWeapon();
		Weapon spear = new SpearWeapon();
		Weapon bow = new BowWeapon();
		
		//실세 런타임 환경이라면 버튼이던 이벤트로 설정자 주입으로 객체 행동을 마음것 바꿀 수 있다.
		angel.setMovable(fly);
		angel.setWeapon(sword);
		behavior(angel);
		angel.setMovable(riding);
		angel.setWeapon(spear);
		behavior(angel);
		angel.setMovable(walk);
		angel.setWeapon(bow);
		behavior(angel);
		
		//람다식을 사용하면 전략 객체를 일급함수처럼(변수처럼) 다룰 수 있다.
		devil.setMovable(()->System.out.println("뛰기!"));
		devil.setWeapon(()->System.out.println("침뱉기!"));
		behavior(devil);
	}
	
	static void behavior(Character character) {
		character.move();
		character.attack();
	}
	
}
날아갑니다...
신성한 베기!
말타고 갑니다...
신성한 찌르기!
걸어가기...
신성한 화살 쏘기!
뛰기!
타락한 침뱉기!

mark4.zip
0.00MB

 

 

패턴과 전문 용어

서로 알고 있는 패턴은 막강하다. 불필요한 설명이 필요없이 짧은 대화로 모든 의사소통된다.

 

예시, 우리는 여기 부분을 전략 패턴으로 설계했습니다. 

속뜻, 자주 변경되는 부분을 캡슐화하여 쉽게 확장하고 런타임 환경에서 객체 행동을 자유롭게 변경할 수 있게 설계했다.

 

 

 

 

핵심 정리

패턴의 밑바탕에는 객체지향 기초(추상화, 캡슐화, 다형성, 상속)가 있다.

프로그램 생애주기를 보면 개발보다 유지보수가 훨씬 더 많은 비중을 차지한다. 따라서 재사용성, 확장성, 관리 용이성을 갖추는 것이 중요하다.

패턴은 검증받은 객체지향 경험이다.

대부분 패턴과 원칙은 변경에 관련된 것이다.

변경 가능성 있는 부분은 캡슐화해야 한다.

 

 

 

 

 

 

 

 

 

메시지를 따라라

객체지향의 핵심, 메시지

협력을 위한 유일한 수단

클래스 상속관계를 핵심으로 생각하는 잘못된 관점이 존재한다. 하지만 객체지향은 '객체'지향이지 클래스 지향이 아니다. 그래서 클래스가 없는 객체지향 언어도 있다.

 

클래스는 동적인 객체들의 특성과 행위를 정적인 텍스트로 표현하기 위해 사용할 있는 추상화 도구일 뿐이다.

 

객체지향 설계는 메시지를 주고 받는 동적인 객체들 관점에서 설계

 

객체지향 설계의 중심에는 메시지가 위치

객체가 메시지를 선택하는 것이 아닌

메시지가 객체를 선택해야 한다.(적절한 객체에게 적절한 책임 분배)

 

 

책임-주도 설계 다시 살펴보기

책임을 완수하기 위해 협력하는 객체들을 이용해 시스템을 설계하는 방법을 책임-주도 설계라고 한다.

 

필요한 책임(행동) 먼저 정의한다.

그리고 책임을 수행할 객체를 선정한다.(메시지 수신은 객체에게 부여된 책임)

맡은 책임을 객체가 수행할 없다면 다른 객체에게 요청을 한다.(책임 연쇄)

이를 반복한다.

 

What/Who 사이클

어떤 행위가 필요한지를 먼저 결정한 행위를 수행할 객체를 결정하는 것을 What/Who 사이클이라 한다.

What(행위) Who(객체)

 

이과정에서 상태는 중요치 않다. , 어떤 객체가 특정 특성을 가졌다고 해서 반드시 그와 관련된 행위를 수행할 것이라고 가정하지 않는다.

 

협력이라는 문맥 안에서 객체의 책임을 결정하라.

 

시스템이 수행해야 하는 전체 행위는 협력하는 객체들의 책임으로 분배된다.

 

묻지 말고 시켜라

메시지를 먼저 결정하고 객체가 메시지를 따르게 하는 설계 방식은 객체가 외부에 제공하는 인터페이스가 독특한 스타일을 따르게 한다. 이를 Tell, Don't Ask 또는 데메테르 법칙이라 한다

 

메시지를 결정하는 시점에서 어떤 객체가 메시지를 수신할 것인지를 없다. 때문에 메시지 송신자는 메시지 수신할 객체의 내부 상태를 없다.

이는 메시지 수신자의 캡슐화를 의미한다.

이는 느슨한 결합을 의미한다.

 

묻지 말고(내부 구체적인 동작방식= how) 시켜라(요청= 메시지)

객체의 자율성 증가

 

어떻게 해야하는지 지시하지 말고 무엇을 해야하는 지를 요청하는 것은 인터페이스 크기 감소를 가져온다.

 

인터페이스 크기가 작다는 것은 해당 객체에게 외부에서 의존하는 부분이 적다는 것이다

메시지를 믿어라

메시지는 전송하는 객체 관점에서 수신하는 객체가 의도한 대로 처리만 있기만 하면된다. 구체적인 방법은 중요하지 않다. , 수신하는 객체가 변경되어 다른 방법을 취하더라도 처리만 되면 그만이다.

 

유연하고 재사용성이 높아진다.

 

 

객체 인터페이스

인터페이스

인터페이스란 어떤 사물이 마주치는 경계 지점에서 서로 상호작용할 있게 이어주는 방법이나 장치

 

인터페이스 가지 특징

첫째, 인터페이스의 사용법을 익히기만 하면 내부 구조나 동작 방식을 몰라도 쉽게 대상을 조작하거나 의사를 전달할 있다.

 

둘째, 인터페이스 자체는 변경하지 않고 단순히 내부 구성이나 작동 방식만을 변경하는 것은 인터페이스 사용자에게 어떤 영향도 미치지 않는다.

 

셋째, 대상이 변경되더라도 동인한 인터페이스를 제공하기만 하면 아무런 문제 없이 상호작용할 있다.

 

자동차를 운전할 패달, 핸들 정도만 조작할 알면 된다. 엔진이 어떻게 구성되어 있는지 알필요가 있는가?

자동차 수리로 내부 부품이 변경되어도 운전하는데 문제 없다.

또한 다른 자동차로 변경되어도 운전에는 무리가 없다.

 

인터페이스를 통한 상호작용이 중요하다.

 

메시지가 인터페이스를 결정한다.

객체 협력을 위한 유일한 방법은 메시지

객체가 수신할 있는 메시지 목록이 인터페이스 모양이 된다.

 

 

공용 인터페이스

지금까지는 전부 공용 인터페이스를 기본으로 말했다.

외부에 공개된 인터페이스를 공용 인터페이스라 한다.

사적인 인터페이스도 존재한다. 다만 사적인 인터페이스는 객체 자신과의 메시지 상호작용이다.

 

 

책임, 메시지, 그리고 인터페이스

객체의 책임이 자율적이어야 한다.

객체가 수행할 구체적인 행동에 대한 자율성

 

메시지로 구성된 공용 인터페이스는 객체의 외부와 내부를 명확하게 분리한다.

 

 

인터페이스와 구현의 분리

객체 관점에서 생각하는 방법

와이스펠드의 객체지향적인 사고 방식 이해를 위한 가지 원칙

추상적인 인터페이스

객체의 구현에 대한 자율성 보장

최소 인터페이스

외부에 노출될 메서드를 최소한

의존성 저하로 결합도와 재사용성 혜택

노출 최소화로 캡슐화

인터페이스와 구현 간에 차이가 있다는 점을 인식

 

구현

객체지향에서 내부 구조와 작동 방식을 가리키는 용어

객체를 구성하지만 공용 인터페이스에 포함되지 않는 모든

인터페이스에 정의된 선언부를 제외한 모든

메서드 구현부, 변수

 

 

인터페이스와 구현의 분리 원칙

separation of interface and implementation

객체 외부에 노출되는 인터페이스와 객체의 내부에 숨겨지는 구현을 명확하게 분리

 

개의 분리된 요소로 분할해 설계하는 (외부 공개 인터페이스와 내부에 감춰지는 구현)

 

느슨한 결합을 보장

인터페이스와 구현을 분리한다는 것은 변경될 만한 부분을 객체의 내부에 꽁꽁 숨겨 놓는다는 것을 의미 (캡슐화)

따라서 변경에 유리

 

캡슐화

객체의 자율성을 보존하기 위해 구현을 외부로부터 감추는

객체는 상태와 행위를 캡슐화함으로써 협력속에서 자율적인 존재가 있다.

캡슐화를 정보 은닉이라고 부르기도 한다.

 

 

 

상태와 행위의 캡슐화

객체 = 상태 + 행위

객체는 자신의 상태를 스스로 관리하며 상태를 변경하고 외부에 응답할 있는 행동을 내부에 함께 보관한다.

중에서 외부에서 반드시 접근해야만 하는 행위만 골라 공용 인터페이스를 통해 노출

 

전통적인 개발에서는 데이터와 프로세스를 엄격히 분리

객체지향에서는 데이터와 프로세스를 객체라는 하나의 안으로 함께 묶어 객체의 자율성을 보장

 

사적인 비밀의 캡슐화

캡슐화를 통해 변경이 빈번하게 일어나는 불안전한 비밀을 안정적인 인터페이스 뒤로 숨긴다.

 

공용 인터페이스를 수정하지 않는 자신과 협력하는 외부 객체에 영향을 미치지 않고 내부의 구현을 자유롭게 수정할 있다.

이것은 인터페이스와 구현의 분리원칙과 연결된다.

구현 변경에 의한 파급효과를 최대한 억눌른다.

 

객체를 자율적인 존재로 만든다 = 내부와 외부를 엄격히 분리한다.

 

 

 

책임의 자율성이 협력의 품질을 결정한다

첫쨰, 자율적인 책임은 협력을 단순하게 만든다.

구체적인 구현을 감춤으로써 핵심만 간략히 있다.

 

둘째, 자율적인 책임은 모자 장수의 외부와 내부를 명확하게 분리한다.

책임만 보장하면 된다. 책임을 어떻게 수행할지는 자유이다.

사적인 부분이 캡슐화되기 때문에 인터페이스와 구현이 분리된다.

 

셋째, 책임이 자율적일 경우 책임을 수행하는 내부적인 방법을 변경하더라도 외부에 영향을 미치지 않는다.

요청자는 메시지 수신자에 내부를 수도 수도 없다.

이는 변경되어도 요청자는 변경됐다는 사실조차 수가 없다.

결국 메서드의 결합도가 객체의 결합도에 영향을 미친다.

 

넷째, 자율적인 책임은 협력의 대상을 다양하게 선택할 있는 유연성을 제공한다.

책임이 자율적일수록 협력이 유연해지고 다양한 문맥에서 재활용될 있다.

 

다섯째, 객체가 수행하는 책임들이 자율적일수록 객체의 역할을 이해하기 쉬워진다.

 

 

책임이 자율적일수록 적절하게 '추상화'되며, '응집도' 높아지고, '결합도' 낮아지며, '캡슐화' 증진되고, '인터페이스와 구현이 명확히 분리'되며, 설계의 '유연성' '재사용성' 향상된다.

 

 

개념의 가지 관점

일반적으로 객체의 분류 장치로서 개념을 이야기할 때는 아래의 가지 관점을 함께 언급한다[Martin 1998, Larman 2004]

  • 심볼 : 개념을 가리키는 간략한 이름이나 명칭
  • 내연 : 개념의 완전한 정의를 나타내며 내연의 의미를 이용해 객체가 속하는지 여부를 확인할 수 있다.
  • 외연 : 개념에 속하는 모든 객체의 집합

심볼은 예시에서 '트럼트' '토끼'

내연 예시 몸이 납작하고 손과 발이 네모난 모서리에 달려 있다는 트럼프 설명이 예시다.

외연 예시 실제 개념에 일치해 개념에 속한 객체들

 

개념이 심볼, 내연, 외연으로 구성돼 있다는 사실보다는 개념을 이용해 객체를 분류할 있다는 사실이 중요하다.

객체지향 패러다임이 복잡성을 극복하는 사용하는 가장 기본적인 인지 수단이기 때문이다.

 

객체지향의 세계에서 Class 사용한다는 사실을 감안하면

분류(Classification)라는 개념이 얼마나 중요한지 실감할 있다.

 

 

 

객체를 분류하기 위한

분류란 객체에 특정한 개념을 적용하는 작업이다. 객체에 특정한 개념을 적용하기로 결심했을 우리는 객체를 특정한 집합의 멤버로 분류하고 있는 것이다.

 

분류는 객체지향의 가장 중요한 개념 하나다.

분류가 적절치 않다면 애플리케이션 유지보수가 어렵고 변화에 쉽게 대처하지 못한다.

 

 

 

분류는 추상화를 위한 도구다

추상화 가지 차원

번째, 구체적인 사물 간의 공통점을 취하고 차이점을 버리는 일반화를 통한 단순화

번째, 중요한 부분을 강조하기 위해 불필요한 세부 사항을 제거해 단순화

 

개념을 통한 객체 분류는 추상화의 가지 차원을 모두 사용한다.

 

개념은 객체들의 복잡성을 극복하기 위한 추상화 도구다.

추상화를 사용함으로써 우리는 극도로 복잡한 세상을 그나마 제어 가능한 수준으로 단순화할 있는 것이다.

 

 

 

타입

타입은 개념이다

개념을 대체할 있는 용어를 수학에서 차용, 그것은 바로 타입(Type)

 

타입은 개념과 동일하다. 따라서 타입이란 우리가 인식하고 있는 다양한 사물이나 객체에 적용할 있는 아이디어나 관념을 의미한다. 어떤 객체에 타입을 적용할 있을 객체를 타입의 인스턴스라고 한다. 타입의 인스턴스는 타입을 구성하는 외연인 객체 집합의 일원이 된다.

 

 

데이터 타입

실제로 컴퓨터는 01로만 이루어져 있다. 비트배열이 숫자인지 문자인지 구분이 안간다.

그래서 메모리 안에 데이터에 특정한 의미를 부여하기 시작했다. 그것이 타입 시스템(Type System)이다.

타입 시스템의 목적은 메모리 안의 모든 데이터가 비트열로 보임으로써 야기되는 혼란을 방지하는 것이다.

 

타입에 관련된 가지 중요한 사실

첫째, 타입은 데이터가 어떻게 사용되느냐에 관한 것이다.

숫자형 데이터는 연산을 있다.

둘째, 타입에 속한 데이터를 메모리에 어떻게 표현하는지는 외부로부터 철저하게 감춰진다.

데이터 타입의 표현은 연산 작업을 수행하기에 가장 효과적인 형태가 선택되며, 개발자는 해당 데이터 타입의 표현 방식을 몰라도 데이터를 사용하는 지장이 없다.

단지 데이터 타입을 사용하기 위해 연산자만 알고 있으면 된다.

숫자형 데이터에 산술 연산자를 알고 있다면 메모리 내부에 숫자가 어떤 방식으로 저장되는지를 모르더라도 숫자형 데이터를 사용할 있다.

 

책에서는 프로그래밍 언어 관점에서 데이터 타입을 다음과 같이 정의한다.

데이터 타입은 메모리 안에 저장된 데이터의 종류를 분류하는 사용하는 메모리 집합에 관한 메타데이터. 데이터에 대한 분류는 암시적으로 어떤 종류의 연산이 해당 데이터에 대해 수행될 있는지를 결정한다.

 

객체와 타입

객체지향 프로그램에서 객체를 일종의 데이터처럼 사용한다.

객체를 타입에 따라 분류하고 타입에 이름을 붙이는 것은 결국 프로그램에서 사용할 새로운 데이터 타입을 선언하는 것과 같다.

 

객체는 행위에 따라 변할 있는 상태를 가지고 있다는 사실을 기억하라

 

그렇다면 객체는 데이터인가? 그렇지 않다.

다시 한번 강조하지만 객체에서 중요한 것은 객체의 행동이다.

상태는 행동의 결과로 초래된 부수효과를 쉽게 표현하기 위해 도입한 추상적인 개념일 뿐이다.

객체가 협력을 위해 어떤 책임을 지녀야 하는지 결정하는 것이 객체지향 설계의 핵심이다.

협력은 객체간 행동으로만 이루어진다.

 

데이터 타입 가지 조언을 객체의 타입에 적용

첫째, 어떤 객체가 어떤 타입에 속하는지를 결정하는 것은 객체가 수행하는 행동이다.

어떤 객체들이 동일한 행동을 수행할 있다면 객체들은 동일한 타입으로 분류될 있다.

둘째, 객체의 내부적인 표현은 외부로부터 철저하게 감춰진다. 객체의 행동을 가장 효과적으로 수행할 수만 있다면 객체 내부의 상태를 어떤 방식으로 표현하더라도 무방하다.

 

 

행동이 우선이다

객체의 내부 표현 방식이 다르더라도 객체들이 동일하게 행동한다면 객체들은 동일한 타입에 속한다.

결과적으로 동일한 책임을 수행하는 일련의 객체는 동일한 타입에 속한다고 말할 있다.

객체가 어떤 데이터를 보유하고 있는지는 타입을 결정하는 아무런 영향도 미치지 않는다.(인터페이스, 다형성)

 

원칙을 다르게 해석해보자

동일한 타입에 속한 객체는 내부의 데이터 표현 방식이 다르더라도 동일한 메시지를 수신하고 이를 처리할 있다. 다만 내부의 표현 방식이 다르기 때문에 동일한 메시지를 처리하는 방식은 서로 다를 수밖에 없다.

이것은 다형성에 의미를 부여한다.

다형성은 동일한 요청에 대해 서로 다른 방식으로 응답할 있는 능력을 뜻한다.

 

데이터의 내부 표현 방식과 무관하게 행동만이 고려 대상이라는 사실은 외부에 데이터를 감춰야 한다는 것을 의미한다.

좋은 객체지향 설계는 외부에 행동만을 제공하고 데이터는 행동 뒤로 감춰야 한다.

원칙을 흔히 캡슐화라고 한다.

 

행동에 따라 객체를 분류하기 위해서는 객체가 내부적으로 관리해야 하는 데이터가 아니라 객체가 외부에 제공해야 하는 행동을 먼저 생각해야 한다. 이를 위해서는 객체가 외부에 제공해야 하는 책임을 먼저 결정하고 책임을 수행하는 적합한 데이터를 나중에 결정한 , 데이터를 책임을 수행하는 필요한 외부 인터페이스 뒤로 캡슐화해야 한다.

책임-주도 설계(Responsibility-Driven Design) 데이터-주도 설계(Data-Driven Design)방법의 단점을 개선하기 위해 고안됐다.

 

객체를 결정하는 것은 행동이다. 데이터는 단지 행동을 따를 뿐이다. 이것이 객체를 객체답게 만드는 가장 핵심적인 원칙이다.

+ Recent posts