1. 디자인 패턴(Design Pattern)
디자인 패턴(Design Pattern)은 객체지향 프로그래밍을 할때 자주 접할 수 있는 문제를 해결하고자 만든 일종의 코드 패턴에 대한 템플릿(template)입니다. 1990년대 초반 에리히 감마(Erich Gamma)에 의해 첫 소개된 이후 1995년에 이분야의 GoF(Gang of Four)라 불리는 에리히 감마(Erich Gamma), 리처드 헬름(Richard Helm), 랄프 존슨(Ralph Johnson), 존 블리시데스(John Vlissides)에 의해 집대성 되었고, 이것이 GoF의 디자인 패턴(Design Pattern)으로 널리 알려졌습니다.
디자인 패턴은 객체지향 모델링의 장점인 재사용성과 모듈성을 극대화 시켜서 이를 적용하면 시스템 개발은 물론 유지보수에도 큰 효과를 얻을 수 있습니다. 반면 객체지향 모델에 사용되므로 초기 개발시간이 길어져 소규모 프로젝트에는 적합하지 않을 수도 있습니다.
2. 디자인 패턴의 분류
디자인 패턴은 크게 생성 패턴(Creational Pattern)과 구조 패턴(Structural Patterns), 행위 패턴(Behavioral Patterns)으로 나눠집니다.
생성 패턴(Creational Pattern)은 객체(Object)의 인스턴스(Instance) 생성을 위한 패턴입니다. 클라이언트와 객체 인스턴스 사이의 연결을 끊어줍니다. 생성 패턴에는 싱글톤 패턴(Singleton Pattern), 빌더 패턴(Builder Pattern), 팩토리 메서드 패턴(Factory Method Pattern), 추상 팩토리 패턴(Abstract Factory Pattern), 프로토타입 패턴(Prototype Pattern)이 있습니다.
구조 패턴(Structural Patterns)은 객체(Object)의 합성에 관한 패턴입니다. 객체의 조직화에 대한 패턴을 제공합니다. 구조 패턴에는 어댑터 패턴(Adapter Pattern), 브리지 패턴(Bridge Pattern), 컴포지트 패턴(Composit Pattern), 데코레이터 패턴(Decorator Pattern), 퍼사드 패턴(Facade Pattern), 플라이웨이트 패턴(Flyweight Pattern), 프록시 패턴(Proxy Pattern)이 있습니다.
행위 패턴(Behavioral Patterns)은 객체(Object)의 상호작용과 책임 분산에 대한 패턴을 제공합니다. 행위 패턴에는 책임 연쇄 패턴(Chain of Responsibility Pattern), 커맨드 패턴(Command Pattern), 인터프리터 패턴(Interpreter Pattern), 이터레이터 패턴(Iterator Pattern), 메디에이터 패턴(Mediator Pattern), 메멘토 패턴(Memento Pattern), 옵저버 패턴(Observer Pattern), 상태 패턴(State Pattern), 전략 패턴(Strategy Pattern), 템플릿 메소드 패턴(Template Method Pattern), 비지터 패턴(Visitor Pattern)이 있습니다.
3. 객체지향의 기본 용어
디자인 패턴을 하나씩 알아보기 전에 먼저 객체지향 언어에서 사용하는 용어부터 알고 가겠습니다.
1) 객체(Object) : 객체지향 프로그래밍의 기본 단위이며, 현실세계에 존재할 수 있는 유형 · 무형의 모든 대상을 말합니다. 객체는 속성(Attribute)과 메소드(Method)로 정의됩니다.
2) 속성(Attribute) : 객체를 구성하는 특성으로, 객체의 상태를 나타냅니다. 하나의 자동차 객체가 있다면 브랜드, 색상, 엔진, 연비 등등은 모두 자동차의 속성에 해당합니다.
3) 메소드(Method) : 속성과 같이 객체를 구성하는 특성으로, 객체의 행위를 나타내며 속성값을 변경시킵니다. 예를 들어 자동차 객체는 "도색하기"라는 메소드를 통해서 색상이라는 속성을 변경할 수 있습니다.
4) 클래스(Class) : 데이터를 추상화한 단위이며, 공통된 행위와 특성을 갖는 객체의 집합입니다. 자동차를 객체라고 한다면 클래스는 자동차 설계도에 해당합니다.
5) 인스턴스(Instance) : 클래스로부터 생성된 객체를 인스턴스라고 합니다. 객체와 비슷한 개념으로 사용되지만 약간의 차이가 있습니다. 현실세계의 자동차를 하나의 객체라고 한다면 클래스는 컴퓨터 세계에 자동차를 구현하기 위한 설계도이며, 인스턴스는 자동차 클래스라는 설계도에 의해 컴퓨터 세계에 구현된 자동차라고 할 수 있습니다.
3. 싱글톤 패턴(Singleton Pattern)
싱글톤 패턴은 특정 클래스의 인스턴스가 오직 하나임을 보장하고, 이 인스턴스에 접근할 수 있는 방법을 제공합니다. 즉, 특정 클래스의 객체는 하나만 생성되도록 하여 동일 인스턴스를 재사용 하는 패턴입니다.
싱글톤 패턴을 클래스 다이어그램으로 나타내면 다음과 같은 구조를 가집니다. 다이어그램에 대해서는 여기서 따로 설명하지 않겠습니다.
자바를 통해 싱글톤 패턴 예제 코드를 작성해보겠습니다.
Singleton.java :
INSTANCE 라는 클래스 변수에는 Singleton 인스턴스가 대입되며, getInstance 라는 클래스 메소드에 의해서만 인스턴스에 접근이 가능합니다. 생성자인 Singleton 객체 메소드는 private 접근자로 선언되었기 때문에 클라이언트에서 new 예약어로 Singleton 인스턴스를 다시 생성하지 못합니다.
실례를 들어보겠습니다. 기부금을 모운다고 가정하여 Donation이라는 클래스를 작성하겠습니다.
Donation.java:
싱글톤 패턴을 사용하여 Donation 인스턴스는 한번만 생성되며, Donation 객체는 total 이라는 기부금 총 액수와 contributors라는 기부자 명단을 속성으로 가지고 있습니다. 또한 donation 이라는 기부행위 메소드를 통해 total을 증가시키고 contributors에 이름을 추가할 수 있습니다. getTotal 메소드와 getContributors 메소드를 통해 총 액수와 기부자 명단을 확인할 수도 있습니다.
이제 Person 클래스를 작성하겠습니다.
Person.java:
Person 클래스의 객체는 name 속성과 donation 메소드를 가집니다.
Main.java:
범죄도시2 보고 왔더니 문득 생각난 이름을 기부자로 입력했습니다 ㅎ.ㅎ 마석도가 100만원, 강해상이 20만원, 장이수가 60만원을 기부했습니다.
다음과 같이 입력후 실행시키면 총 기부액과 기부자 명단을 확인할 수 있습니다.
싱글톤 패턴을 위와 같이 선언하면 단일 스레드 환경에서는 문제가 없지만 멀티스레딩 환경에서 동시에 접근할 경우 인스턴스가 두 개가 생성될 가능성이 있습니다. 해결책으로는 synchronized 키워드를 통해 getInstance 메소드를 동기화 시켜주면 됩니다.
그러나 이 방식도 문제점은 있습니다. getInstance를 통해 인스턴스를 호출할때마다 동기화가 되므로 내부적으로 비용(시간)이 많이 들고 성능저하가 일어나기 쉽습니다.
이를 해결하기 위해 Double-checked locking(DCL) 이라는 기법을 사용합니다.
synchronized 블록을 통해 getInstance 메소드 전체에 락을 거는 것이 아니라 객체가 있는지 확인 후 락을 걸고 다시 객체가 있는지 확인하여 성능 저하 문제를 어느 정도 해소했습니다. 이때 INSTANCE에는 volatile 키워드를 붙입니다. 멀티스레딩 환경에서는 성능향상을 위해 옵티마이져에 의하여 전역변수의 값을 메인메모리에서 불러오는 것이 아니라 캐쉬메모리에 저장하여 불러오게 됩니다. 이렇게 되면 쓰레드마다 변수값이 달라질 수 있으므로 여전히 인스턴스가 두 개가 생성될 가능성이 존재하게 됩니다. volatile 키워드를 붙이면 캐쉬메모리에서 값을 불러오지 않고 메인메모리를 통해 변수값을 불러오므로 항상 최신값을 유지할 수 있어서 싱글톤 패턴을 만들어 낼 수 있습니다.
앞의 방식들은 모두 인스턴스가 필요할 때에만 객체가 생성됩니다. 이런 방식을 지연된 초기화(lazy Initalization) 이라고 합니다. 이와 반대로 클래스가 초기화 될때 객체를 생성하는 이른 초기화(Eager Initalization) 방식이 있습니다.
클래스 변수가 1번만 초기화되는 특성을 이용하여 인스턴스화를 거치지 않고 클래스 초기화때 객체를 생성하게 됩니다. 멀티 쓰레드 문제는 해결할 수 있지만 인스턴스의 사용이 없어도 프로그램이 시작할 때부터 종료될 때까지 메모리에 존재하게 되어 비효율적일 수 있습니다.
중첩 클래스 Holder를 이용하는 방식도 있습니다. 이 방식은 클래스 변수 안에 클래스 변수(Holder)를 선언합니다. 그러므로 클래스 변수의 특성에 의해 멀티 쓰레드 문제를 해결할 수 있을 뿐만 아니라 지연된 초기화 방식을 사용하므로 성능저하 문제도 해결할 수 있어 현재까지 가장 많이 사용하는 방식으로 알려져 있습니다.
마지막으로 enum 클래스를 사용하는 방식이 있습니다.
enum 클래스도 최초 한번만 초기화되므로 이른 초기화 방식처럼 프로그램이 시작할 때부터 종료될 때까지 메모리상에 남아있게 됩니다. |