Challenge, and Growth ! Introduce
                                                                                                             

Welcome to my blog 🙌🏻

Total

Today

Yesterday













Knowledge/디자인 패턴

[구조 패턴] 어댑터 | 퍼싸드 | 컴포지트 | 브리지 패턴 (with 예시 코드)

뽀시라운 2024. 7. 25. 16:16
반응형
SMALL

[목차여기]

 

 

▶︎ 선행되면 좋은 글 

 

SOLID란? - 객체지향 개발의 원칙

[목차여기] UML이란? - UML을 사용하는 이유 [목차여기] UML(Unified Modeling Language) UML은 통합 모델링 언어이다. '통합', '모델링', '언어' 이렇게 단어를 하나씩 떼어 보면 그 정의를 짐작할 수 있다. 소

proysm.tistory.com

▶︎ 함께 보면 좋은 글 

 

 

 

 

패턴의 종류

GoF가 정의하고 있는 23가지 패턴은 3가지 종류(생성, 구조, 행위)로 분류한다. 

- 정보처리기사 필기 시험에서 해당 종류에 속하는 패턴을 묻기도 한다.

 

생성 : 싱글톤, 팩토리 메소드, 추상팩토리, 빌더, 프로토타입

구조 : 어댑터, 브리지, 컴포지트, 퍼싸드, 데코레이터, 플라이웨이트, 프록시

행위 : 중재자, 옵서버, 책임 체인, 커맨드,이터레이터, 메멘토, 상태, 전략, 템플릿 메소드, 방문자, 인터프리터

 

 

이 글에서는 구조 패턴으로 분류되는 어댑터 패턴, 퍼싸드 패턴, 컴포지트 패턴, 브리지 패턴을 알아볼 것이다. 이 4개의 패턴은 인터페이스를 이용하여 구현된다는 공통점이 있다.

 

 

 

1. 어댑터 패턴 (Adaptor Pattern)

'어댑터'는 콘센트의 전원을 내가 사용하는 전자기기의 전원에 맞게 바꾸어 주는 역할을 한다.

어댑터라는 단어에서 유추할 수 있듯이 어댑터 패턴은 기존의 코드를 내가 사용할 수 있는 형태로 바꾸어준다고 생각하면 된다.

 

  • 정의 : 어댑터는 호환이 불가능한 인터페이스 때문에 협력할 수 었는 클래스를 협력할 수 있도록 한다.
  • 구조 : Client, Target, Adaptor, Adaptee
  • 구현 방법 : 상속, 위임 👑
  • 사용 예 : 레거시 코드 전체를 바꾸기에는 비용이 너무 많이 들 때, 어댑터 인터페이스를 이용하여 사용할 수 있도록 한다.
  • 특징
    1. Adaptor를 wrapper라고 부른다.
    2. 인터페이스 설계가 필요하다. (그러나 더 간단한 인터페이스 설계는 필요없다.)
    3. 상속을 이용하는 경우 다형적으로 동작하는 객체가 필요하다.

 

 기본 구조 

[그림1] 어댑터 패턴 기본 구조

 

Client는 레거시 코드(Adaptee)을 새로운 코드(Target)에 적응시키기 위해 인터페이스(Adaptor)를 설계한다.

 

 

 코드 예 

 

[그림 1-1] 어댑터 패턴 예시 클래스 다이어그램

 

클라이언트는 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가 하나의 기능을 수행하기 위해 많은 객체를 이용해야 할 때 퍼싸드 인터페이스를 사용할 수 있도록 한다.
  • 특징
    1. Facade를 wrapper라고 부른다.
    2. 인터페이스 설계가 필요없다. (그러나 더 간단한 인터페이스 설계는 필요하다.)
    3. 다형적(상속)으로 동작하는 객체가 필요없다.

 

 기본 구조 

[그림2] 퍼싸드 패턴 기본 구조

 

 

만약 클라이언트가 DataBase, Model, Element 각각에 직접 접근해야 한다면 매우 복잡하고 불편할 것이다. 따라서 퍼싸드가 그 역할을 담당하여 복잡 시스템 내부를 대신 처리해 준다.

 

 

 코드 예 

 

[그림 2-1] 퍼싸드 패턴 예시 클래스 다이어그램

 

클라이언트(=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
  • 사용 예 : 그림판에서 사각형, 원, 삼각형 등을 그릴 수 있고 각 객체를 이동하거나 수정할 수 있다. 또한 이들을 묶어서 이동하거나 수정할 수 있다.
  • 특징
    1. 자기 자신을 호출하는 재귀적으로 정의된다.
    2. 객체들을 트리 구조로 구성한다. (Part-Whole Hierarchy)
    3. 그룹과 객체를 동일하게 취급한다.

 

기본 구조

[그림3] 컴포지트 패턴 기본 구조

 

add()와 remove()는 leaf들을 관리하는 함수다. 

 

왼쪽 그림처럼 Component에도 관리함수를 함께 선언해 준다면 Client에서 객체와 그룹을 구분하지 않고 호출할 수 있다.

오른쪽 그림처럼 Composite에만 선언한다면 Client에서 객체와 그룹을 구분하여 호출해야 한다.

 

 

코드 예

 

[그림 3-1] 컴포지트 패턴 예시 클래스 다이어그램

 

컴퓨터의 폴더-파일 구조를 생각해보자. 폴더 안에 폴더 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
  • 구현 방법 : 상속, 집합
  • 사용 예 : 경우의 수가 조합수로 증가할 때 브리지 패턴을 적용한다.
  • 특징
    • 상속 관계
      1. 실행 시 구현 객체 하나만 존재한다.
      2. 추상 클래스와 구현 클래스가 컴파일 시에 고정된다.
    • 집합 관계 👑 
      1. 실행시 추상부분 객체와 구현부분 객체 두 개가 따로 존재한다.
      2. 실행시 추상부분 객체와 구현부분 객체의 연결이 바뀔 수 있다.
      3. 상속관계보다 유연하다.

 

기본 구조

[그림 4] 컴포지트 패턴 기본 구조

 

 

코드 예

[그림 4-1] 컴포지트 패턴 예시 클래스 다이어그램

 

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(); }
}
반응형
LIST
loading