디자인패턴 (Design Pattern)
디자인패턴이란 소프트웨어 개발 과정에서 발견된 설계의 노하우를 축적하여 그 방법에 이름을 붙여서 이후에 재사용하기 좋은 형태로 특정 규약을 만들어서 정리한 것이다.
즉, 효율적인 코드를 만들기 위한 방법론이라고 생각하면 된다.
디자인패턴에는 생성패턴, 구조패턴, 행동패턴으로 분류된다.
생성패턴 | 구조패턴 | 행위패턴 |
---|---|---|
- 싱글톤(Singleton) - 팩토리 메소드(Factory Methods) - 추상 팩토리(Abstract Factory) - 빌더(Builder) - 프로토타입(Prototype) | - 어댑터(Adapter) - 브릿지(Bridge) - 컴포짓(Composite) - 데코레이터(Decorator) - 퍼사드(Facade) - 플라이웨이트(Flyweight) - 프록시(Proxy) | - 책임 연쇄(Chain of Responsibility - 커맨드(Command) - 인터프리터(Interpreter) - 이터레이터(Iterator) - 중재자(Mediator) - 메멘토(Memento) - 옵저버(Observer) - 상태(State) - 전략(Strategy) - 템플릿 메소드(Template Method) - 비지터(Visitor) |
이제부터 생성 패턴의 기능과 어디에 사용되는지 알아보겠다.
생성패턴
객체 생성에 관련된 패턴이다.
객체의 생성과 조합을 캡슐화해서 특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 제공한다.
싱글톤(Singleton)
클래스의 인스턴스가 하나만 만들어지고, 어디서든지 그 인스턴스에 접근할 수 있는 접근점을 제공하는 패턴이다.
사용
주로 특정 객체를 여러 곳에서 공유해야할 때
예시
- DB Connection pool
장/단점
- 장점
- 메모리에 인스턴스를 하나를 등록해 여러 쓰레드에서 동시에 하나의 객체를 이용할 수 있게 할 수 있다.
- 단점
- 여러 곳에서 동시에 접근해서 생길 수 있는 문제(동기화 문제)를 잘 파악하고 설계해야 한다.
팩토리 메소드(Factory Methods)
객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정한다. 팩토리 메서드를 사용하면 인스턴스 만드는 것을 서브클래스에 맡길 수 있다.
사용
객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지에 대한 결정은 서브클래스에서 이루어지도록하여 재정의 가능한 것으로 설계하지만, 복잡해지지 않게 한다.
- 생성할 객체 타입을 예측할 수 없을 때
- 생성할 객체를 기술하는 책임을 서브클래스에게 정의하고자 할 때
- 객체 생성의 책임을 서브 클래스에 위임시키고 서브클래스에 대한 정보를 은닉하고자 할 때
예시
- BeanFactory : Product에 해당하는 것은 Object, ConcreteProduct에 해당하는 것은 xml 또는 class Bean이다.
장/단점
- 장점
- 수정에 닫혀있고 확장에 열려있는 OCP 원칙을 지킬 수 있다.
- 기존 코드(인스턴스를 만드는 과정)을 수정하지 않고 새로운 인스턴스를 다른 방법으로 생성하도록 확장할 수 있다.
- Product와 Creator 간의 결합이 느슨하다.
- 수정에 닫혀있고 확장에 열려있는 OCP 원칙을 지킬 수 있다.
- 단점
- 간단한 기능을 사용할 때보다 많은 클래스를 정의해야해서 코드량이 증가한다.
- 제품 클래스가 바뀔 때마다 새로운 서브클래스를 생성해야 한다.
- 클라이언트가 Creator 클래스를 반드시 상속해 Product를 생성해야 한다.
- 간단한 기능을 사용할 때보다 많은 클래스를 정의해야해서 코드량이 증가한다.
추상 팩토리(Abstract Factory)
구체적인 클래스(구상 클래스)를 지정하지 않고, 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생성하기 위한 인터페이스를 제공하는 패턴입니다.
구상 클래스는 서브 클래스에서 만든다.
사용
클라이언트 코드(팩토리에서 인스턴스를 생성해서 사용하는 코드)를 인터페이스 기반으로 활용할 때 사용한다.
장/단점
- 장점
- 관련성이 있는 여러 객체를 일관적으로 생성하는 다양한 case가 생길 가능성이 있다면, Client와 객체 간의 결합을 피할 수 있다. SRP와 OCP를 지킬 수 있다.
- 단점
- 새로운 종류의 제품을 제공하기 어렵다.
- 새롭게 생성되는 제품은 추상 팩토리가 생성할 수 있는 제품 집합이어야하기 때문이다. 만약 새로운 종류의 제품이 등장하면 팩토리의 구현을 변경해야 한다.
- 새로운 종류의 제품을 제공하기 어렵다.
빌더(Builder)
동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 생성할 수 있는 패턴이다.
객체를 생성하는 클래스와 표현하는 클래스를 분리하여, 동일한 절차에서도 서로 다른 표현을 생성하는 방법을 제공한다.
사용
생성자 파라미터가 많을 경우 사용한다. 생성자 파라미터가 많으면 가독성이 떨어지는데, 이를 빌더 패턴으로 구현하면 각 값들을 함수로 설정하면서 무슨 값을 의미하는지 파악하기 쉬워진다.
장/단점
- 장점
- 불필요한 생성자 제거
- 데이터 순서에 상관없이 객체 생성이 가능
- 명시적 선언으로 이해하기 쉽고, 각 인자가 어떤 의미인지 알기 쉽다. (가독성)
- setter 메서드가 없으므로 변경 불가능한 객체를 만들 수 있다. (객체 불변성)
- 한 번에 객체를 생성하므로 객체 일관성이 깨지지 않는다.
- build() 가 null 여부를 체크해주므로 검증이 가능하다.
- 단점
- 객체를 생성하려면 우선 빌더 객체를 생성해야하고, 다른 패턴들보다 많은 코드를 요구하기 때문에 인자가 충분이 많은 상황해서 이용하여야 한다.
프로토타입(Prototype)
기존 객체를 응용하여 새로운 객체를 만드는 방법이다.
사용
객체를 생성하는 데 비용(시간 혹은 메모리)가 많이 들고, 비슷한 객체가 이미 있는 경우에 사용한다.
예시
ModelMapper : 객체의 값들을 다른 객체로 복사해주는 라이브러리
1 2 3 4 5
MyDataRepository myDataRepository = new MyDataRepository(); MyData myData = new MyData(myDataRepository); ModelMapper modelMapper = new ModelMapper(); MyDataMap map = modelMapper.map(myData, MyDataMap.class);
장/단점
- 장점
- 복잡한 객체를 만드는 과정을 숨길 수 있다.
- 기존 객체를 복제하는 과정이 새 인스턴스를 만드는 것보다 비용(시간 혹은 메모리)적인 면에서 효율적일 수 있다.
- 추상적인 타입을 리턴할 수 있다.
- 단점
- 복잡한 객체를 만드는 과정 자체가 복잡해질 수 있다. (특히 순환 참조가 있는 경우)
출처
- https://dev-youngjun.tistory.com/195
- https://cjw-awdsd.tistory.com/42
- https://bcp0109.tistory.com/367
- https://devowen.com/326
- https://sudo-minz.tistory.com/133
- https://kingchan223.tistory.com/295