[목차여기]
▶︎ 선행되면 좋은 글
▶︎ 함께 보면 좋은 글
패턴의 종류
GoF가 정의하고 있는 23가지 패턴은 3가지 종류(생성, 구조, 행위)로 분류한다.
- 정보처리기사 필기 시험에서 해당 종류에 속하는 패턴을 묻기도 한다.
생성 : 싱글톤, 팩토리 메소드, 추상팩토리, 빌더, 프로토타입
구조 : 어댑터, 브리지, 컴포지트, 퍼싸드, 데코레이터, 플라이웨이트, 프록시
행위 : 중재자, 옵서버, 책임 체인, 커맨드,이터레이터, 메멘토, 상태, 전략, 템플릿 메소드, 방문자, 인터프리터
이 글에서는 구조 패턴으로 분류되는 어댑터 패턴, 퍼싸드 패턴, 컴포지트 패턴, 브리지 패턴을 알아볼 것이다. 이 4개의 패턴은 인터페이스를 이용하여 구현된다는 공통점이 있다.
1. 어댑터 패턴 (Adaptor Pattern)
'어댑터'는 콘센트의 전원을 내가 사용하는 전자기기의 전원에 맞게 바꾸어 주는 역할을 한다.
어댑터라는 단어에서 유추할 수 있듯이 어댑터 패턴은 기존의 코드를 내가 사용할 수 있는 형태로 바꾸어준다고 생각하면 된다.
- 정의 : 어댑터는 호환이 불가능한 인터페이스 때문에 협력할 수 었는 클래스를 협력할 수 있도록 한다.
- 구조 : Client, Target, Adaptor, Adaptee
- 구현 방법 : 상속, 위임 👑
- 사용 예 : 레거시 코드 전체를 바꾸기에는 비용이 너무 많이 들 때, 어댑터 인터페이스를 이용하여 사용할 수 있도록 한다.
- 특징
- Adaptor를 wrapper라고 부른다.
- 인터페이스 설계가 필요하다. (그러나 더 간단한 인터페이스 설계는 필요없다.)
- 상속을 이용하는 경우 다형적으로 동작하는 객체가 필요하다.
기본 구조
Client는 레거시 코드(Adaptee)을 새로운 코드(Target)에 적응시키기 위해 인터페이스(Adaptor)를 설계한다.
코드 예
클라이언트는 printWeak()와 printStrong() 이라는 함수를 호출하여 Banner 클래스에 있는 코드를 사용하기 위해 Adaptor를 설계한다. 그러면 Banner 클래스를 수정하지 않고도 클라이언트에게 맞게 사용할 수 있다.
// Adaptee
public class Banner(){
private String string;
public Banner(String string)
this.string = string;
}
public void showWithParen()
System.out.println("(" + string + ")");
}
public void showWithAster()
System.out.println("*" + string + "*");
}
}
// Adpator - 위임
public class PrintBannerAdaptor implements Print{
private Banner banner;
PrintBannerAdaptor (String str){
banner.string = str;
}
public void printWeak(){
banner.showWithParen();
}
publc void printStrong{
banner.showWithAster();
}
}
// Adpator - 상속
public class PrintBannerAdaptor extends Banner implements Print{
PrintBannerAdaptor (String str){
string = str;
}
public void printWeak(){
showWithParen();
}
publc void printStrong(){
showWithAster();
}
}
2. 퍼싸드 패턴 (Facade Pattern)
'퍼싸드'는 프랑스어로 건물의 정면 외벽을 가리킨다. 아주 큰 건물의 내부로 들어가면 많은 방, 구조물, 사람 등이 있다.
정면 외벽이 이 복잡한 내부 구조를 숨기고 있다는 점에서, 퍼싸드 패턴은 복잡한 시스템을 숨겨준다고 생각하면 된다.
- 정의 : 퍼싸드는 복잡한 시스템을 숨기고, client 다뤄야 할 객체의 수를 줄여줌으로써 쉽게 사용할 수 있도록 한다.
- 구조 : Client, Facade
- 사용 예 : client가 하나의 기능을 수행하기 위해 많은 객체를 이용해야 할 때 퍼싸드 인터페이스를 사용할 수 있도록 한다.
- 특징
- Facade를 wrapper라고 부른다.
- 인터페이스 설계가 필요없다. (그러나 더 간단한 인터페이스 설계는 필요하다.)
- 다형적(상속)으로 동작하는 객체가 필요없다.
기본 구조
만약 클라이언트가 DataBase, Model, Element 각각에 직접 접근해야 한다면 매우 복잡하고 불편할 것이다. 따라서 퍼싸드가 그 역할을 담당하여 복잡 시스템 내부를 대신 처리해 준다.
코드 예
클라이언트(=Main)는 PageMaker 클래스의 객체만 사용하면 복잡한 시스템을 이용할 수 있다. makeWelcomePage(String, String) 함수만 호출하면 PageMaker가 알아서 다 해주기 때문이다!
public class PageMaker{
private String mailaddr;
private String file;
private String name;
public void makeWelcomPage(String mailaddr, String file){
this.mailaddr = mailaddr;
this.file = file;
findName();
write();
}
public String findName(){
Database database = new Database();
Properties prop = database.getProperties("maildata");
name = prop.getProperty(mailaddr);
}
public void write(){
HtmlWriter htmlWriter = new HtmlWriter();
htmlWriter.title(name + "'s web page");
htmlWriter.paragraph("Welcome to " + name + "'s web page");
htmlWriter.paragraph("Nice to meet you");
htmlWriter.link(htmlWriter.mailto(mailaddr, name), name);
htmlWriter.close();
}
}
public class HtmlWriter{
private Writer writer;
public HtmlWriter(Writer writer){
this.writer = writer;
}
public void title(String title){
writer.write("<!DOCTYPE html>");
writer.write("<html>");
writer.write("<head>");
writer.write("<title>" +title + "</title>");
writer.write("</head>");
writer.write("<body>");
writer.write("Wn");
writer.write("<h1>" + title + "</h1>");
writer.write("Wn");
}
public void paragraph(String msg){
writer.write("<p>" +msg + "</p>");
writer.write("Wn");
}
public void link(String href, String caption){
paragraph("<a href=W' +href + W" >" +caption +"</a>");
}
public vodi mailto(String mailaddr, String username){
link("mailto:" +mailaddr, username);
}
public void close(){
writer.write(*</body>");
writer.write("</html>");
writer.write("Wn");
writer.close();
}
}
public class DataBase {
private DataBase() {}
public static Properties getProperties(String dbName) {
String filename = dbName + ".txt";
Properties prop = new Properties();
prop.load(new FileReader(filename));
return prop;
}
}
3. 컴포지트 패턴 (Composite Pattern)
컴포지트 패턴은 그림판의 도형을 이동시키는 것을 떠올리면 된다. 사용자는 각각의 도형을 생성하고 이동시킬 수 있으며 마우스 드래그를 이용하여 모든 도형을 한 번에 이동시킬 수 있다. 즉, 객체와 객체 그룹을 같은 방법으로 사용하는 것이다.
- 정의 : 객체와 객체 그룹을 같은 인터페이스에서 호출한다.
- 구조 : Client, Component, Composite, Leaf'
- 구현 방법 : 객체와 그룹을 구분O, 구분X
- 사용 예 : 그림판에서 사각형, 원, 삼각형 등을 그릴 수 있고 각 객체를 이동하거나 수정할 수 있다. 또한 이들을 묶어서 이동하거나 수정할 수 있다.
- 특징
- 자기 자신을 호출하는 재귀적으로 정의된다.
- 객체들을 트리 구조로 구성한다. (Part-Whole Hierarchy)
- 그룹과 객체를 동일하게 취급한다.
기본 구조
add()와 remove()는 leaf들을 관리하는 함수다.
왼쪽 그림처럼 Component에도 관리함수를 함께 선언해 준다면 Client에서 객체와 그룹을 구분하지 않고 호출할 수 있다.
오른쪽 그림처럼 Composite에만 선언한다면 Client에서 객체와 그룹을 구분하여 호출해야 한다.
코드 예
컴퓨터의 폴더-파일 구조를 생각해보자. 폴더 안에 폴더 or 파일을 만들 수 있고 그 폴더 안에 다시 폴더 or 파일을 만들 수 있다. 따라서 계층 구조를 갖기 때문에 컴포지트 패턴을 적용할 수 있다.
public interface Entry {
public void add(Entry entry);
public String getName();
public Integer getSize();
public void printList();
}
public class File implements Entry {
private String name;
private Integer size;
public File(String name, Integer size) {
this.name = name;
this.size = size;
}
public String getName() { return name; }
public Integer getSize() { return size; }
}
public class Directory implements Entry {
private String name;
private List<Entry> directory = new ArrayList<Entry>();
public Directory(String name) { this.name = name; }
public void add(Entry entry) { directory.add(entry); }
public void getName() { return name; }
public void printList() {
for(Entry dic : directory) {
System.out.println(dic.getName());
}
}
}
4. 브리지 패턴 (Bridge Pattern)
브리지 패턴은 말 그대로 다리를 놓는 패턴이다. 추상 부분과 구현 부분 사이에 다리를 두어 분리하고, 추상 부분을 독립적으로 수정할 수 있다.
추상과 구현이란?
- 추상 : 변하지 않는 부분 (주로 정의부)
- 구현 : 변하는 부분 (주의할 점 : 추상을 구현한 concrete 클래스가 아니라, 궂은 일을 하는 로직 또는 코드를 말한다.)
- 정의 : 추상을 구현으로부터 분리하여 독립적으로 변하게 한다.
- 구조 : Client, Abstraction, Implementor
- 구현 방법 : 상속, 집합
- 사용 예 : 경우의 수가 조합수로 증가할 때 브리지 패턴을 적용한다.
- 특징
- 상속 관계
- 실행 시 구현 객체 하나만 존재한다.
- 추상 클래스와 구현 클래스가 컴파일 시에 고정된다.
- 집합 관계 👑
- 실행시 추상부분 객체와 구현부분 객체 두 개가 따로 존재한다.
- 실행시 추상부분 객체와 구현부분 객체의 연결이 바뀔 수 있다.
- 상속관계보다 유연하다.
- 상속 관계
기본 구조
코드 예
Stack을 구현한다고 생각해보자. 짝수만 저장하는 stack, 홀수만 저장하는 stack이 있고 그 스택을 배열 또는 링크로 구현할 수 있다.
만약에 짝수/홀수 저장부와 배열/링크 구현 부를 묶어놓는다면 수 많은 조합수가 생성될 것이다.
따라서 두 부분을 분리하여 다리로 연결하는 브리지 패턴을 적용할 수 있다.
public abstract class Stack {
protected StackImpl stackImpl;
public Stack(StackImpl stackImpl) {
this.stackImpl = stackImpl;
}
public abstract void pop();
public abstract void push(Integer element);
public abstract Integer top();
public abstract Boolean isEmpty();
public abstract Boolean isFull();
public abstract void printStack();
}
public abstract class StackImpl {
public abstract void popImpl();
public abstract void pushImpl(Integer element);
public abstract Integer topImpl();
public abstract Boolean isEmptyImpl();
public abstract Boolean isFullImpl();
public abstract void printStackImpl();
}
public class OddStack extends Stack {
public OddStack(StackImpl stackImpl) { super(stackImpl); }
public void pop() { stackImpl.popImpl(); }
public void push(Integer element) {
if (element % 2 == 0) return;
stackImpl.pushImpl(element);
}
public Integer top() { return stackImpl.topImpl(); }
public Boolean isEmpty() { return stackImpl.isEmptyImpl(); }
public Boolean isFull() { return stackImpl.isFullImpl(); }
public void printStack() { stackImpl.printStackImpl(); }
}
public class EvenStack extends Stack {
public EvenStack(StackImpl stackImpl) { super(stackImpl); }
public void pop() { stackImpl.popImpl(); }
public void push(Integer element) {
if (element % 2 != 0) return;
stackImpl.pushImpl(element);
}
public Integer top() { return stackImpl.topImpl(); }
public Boolean isEmpty() { return stackImpl.isEmptyImpl(); }
public Boolean isFull() { return stackImpl.isFullImpl(); }
public void printStack() { stackImpl.printStackImpl(); }
}
'Knowledge > 디자인 패턴' 카테고리의 다른 글
SOLID란? - 객체지향 개발의 원칙 (0) | 2024.04.08 |
---|---|
클래스 다이어그램 그리기 - 기본편 (0) | 2024.04.06 |
UML이란? - UML을 사용하는 이유 (2) | 2024.04.03 |