[GoF] Adapter 패턴

패턴명칭

Adapter

필요한 상황

이미 만들어져 제공되는 클래스 또는 API가 수정할 수 없거나 수정이 불가능한 경우, 해당 클래스 또는 API를 다른 클래스의 타입 또는 다른 API로 사용해야만 할때 사용할 수 있는 패턴이다.

예제 코드

위의 클래스다어그램에서 SamsungScanner는 컴파일된 바이너리 형태의 라이브러리로만 제공된다고 하자. 이 SamsungScanner API를 이용하여 삼성 스캐너를 제어한다. 소스코드는 제공되지 않으나 API를 소스코드 형태의 문서로 확인해보자. 소스코드는 존재하지 않으나, 실습을 전제로 융통성을 발휘하기 바란다.

package tstThread;

public class SamsungScanner {
	public boolean ____initialize() {
		System.out.println("SAMSUNG SCANNER DRIVER LOADED");
		return true;
	}
	
	public boolean ____startScanning() {
		System.out.println("START SCANNING");
		
		try {
			Thread.sleep(1000);
			System.out.println("END SCANNING");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
				
		return true;
	}
	
	public boolean ____cancelScanning() {
		System.out.println("CANCEL SCANNING");
		return true;
	}
	
	public boolean ____release() {
		System.out.println("FREE ALL RESOURCES FOR SAMSUNG SCANNER DRIVER");
		return true;
	}
}

모든 스캐너 장치에 대해서, 스캐너를 이용하기 위해 필요한 인터페이스는 다음과 같다.

package tstThread;

public interface IScanner {
	boolean setup();
	boolean start();
	boolean cancel();
	void release();
}

SamsungScanner API를 IScanner API에 맞춰 사용할 수 있도록 ScannerAdapter 클래스를 추가했으며, 다음과 같다.

package tstThread;

public class ScannerAdapter implements IScanner {
	private SamsungScanner scanner;
	public ScannerAdapter(SamsungScanner scanner) {
		this.scanner = scanner;
	}
	
	@Override
	public boolean setup() {
		return scanner.____initialize();
	}

	@Override
	public boolean start() {
		return scanner.____startScanning();
	}

	@Override
	public boolean cancel() {
		return scanner.____cancelScanning();
	}

	@Override
	public void release() {
		scanner.____release();
	}

}

이 클래스들을 사용하는 User 클래스는 다음과 같다.

package tstThread;

public class User {
	public static void main(String[] args) {
		IScanner scanner = new ScannerAdapter(new SamsungScanner());
		
		if(scanner.setup()) {
			scanner.start();
			scanner.release();
		}
	}
}

실행결과는 다음과 같다.

SAMSUNG SCANNER DRIVER LOADED
START SCANNING
END SCANNING
FREE ALL RESOURCES FOR SAMSUNG SCANNER DRIVER

수정할 수 없는 API인 SamsungScanner를 원하는 형태의 API인 IScanner로 사용할 수 있는 Adapter로써 ScannerAdapter를 새롭게 정의하여 SamsungScanner의 API를 성공적으로 사용하고 있다.

이 글은 소프트웨어 설계의 기반이 되는 GoF의 디자인패턴에 대한 강의자료입니다. 완전한 실습을 위해 이 글에서 소개하는 클래스 다이어그램과 예제 코드는 완전하게 실행되도록 제공되지만, 상대적으로 예제 코드와 관련된 설명이 함축적으로 제공되고 있습니다. 이 글에 대해 궁금한 점이 있으면 댓글을 통해 남겨주시기 바랍니다.

[GoF] Bridge 패턴

패턴명칭

Bridge

필요한 상황

어떤 처리에 대한 큰 로직을 상황에 따라 다르게 정의해 실행해야한다. 이와 동시에 해당 로직을 구성하는 세부 항목에 대한 처리들 역시 다르게 정의하고자할때 사용할 수 있는 패턴이다. 즉, 구현의 차원이 2개 이상일때 이를 효과적으로 분리할 수 있는 패턴이다.

예제 코드

위의 예제는 Article 클래스에 정의된 데이터를 화면에 출력할때, 출력에 대한 구현을 Bridge 패턴을 이용해 구현하도록 설계한 클래스다이어그램이다. 먼저 Article 클래스는 다음과 같다.

package tstThread;

import java.util.ArrayList;

public class Article {
    private String title;
    private ArrayList<String> content;
    private String footer;
    
    public Article(String title, ArrayList<String> content, String footer) {
        this.title = title;
        this.content = content;
        this.footer = footer;
    }
    
    public String getTitle() {
        return title;
    }
    
    public ArrayList<String> getContent() {
        return content;
    }
    
    public String getFooter() {
        return this.footer;
    }
}

제목과 내용 그리고 꽁지글이 각각 title, content, footer 필드에 저장된다. 이 세개를 출력하는 방식은 IDisplay 인터페이스로 정의되며 다음과 같다.

package tstThread;

public interface IDisplay {
    void title(Article article);
    void content(Article article);
    void footer(Article article);
}

이 인터페이스를 구현하는 클래스는 모두 3개로 먼저 SimpleDisplay는 다음과 같다.

package tstThread;

import java.util.ArrayList;

public class SimpleDisplay implements IDisplay {
	@Override
    public void title(Article article) {
        System.out.println(article.getTitle());
    }

	@Override
    public void content(Article article) {
        ArrayList<String> content = article.getContent();
        int cntLines = content.size();
        for(int i=0; i<cntLines; i++) {
            System.out.println(content.get(i));
        }
    }

	@Override
    public void footer(Article article) {
        System.out.println(article.getFooter());
    }
}

ContentReverseDisplay 클래스는 Article의 content를 구성하는 문자열 요소를 역순으로 출력하며 다음과 같다.

package tstThread;

import java.util.ArrayList;

public class ContentReverseDisplay extends SimpleDisplay {
	@Override
    public void content(Article article) {
        ArrayList<String> content = article.getContent();
        for(int i=content.size()-1; i>=0; i--) {
            System.out.println(content.get(i));
        }
    }
}

ContentVerticalDisplay 클래스는 Article의 content를 세로방향으로 출력하며 다음과 같다.

package tstThread;

import java.util.ArrayList;

public class ContentVerticalDisplay extends SimpleDisplay {
	@Override
    public void content(Article article) {
		ArrayList<String> content = article.getContent();
		
		int maxChars = 0;
		int cntLines = content.size();
		
        for(int i=0; i<cntLines; i++) {
            int len = content.get(i).length();
        	if(len > maxChars) maxChars = len;
        }
        
        for(int iCh=0; iCh<maxChars; iCh++) {
        	for(int iLine=0; iLine<cntLines; iLine++) {
        		String line = content.get(iLine);
        		if(line.length() > iCh) {
        			System.out.print(line.charAt(iCh) + " ");
        		} else {
        			System.out.print("  ");
        		}
        	}
        	System.out.println();
        }
    }
}

IDisplay를 구현하는 SimpleDisplay, ContentReverseDisplay, ContentVerticalDisplay는 Article을 구성하는 title, content, footer 필드를 각각 어떻게 출력할지를 결정하는 구현에 대한 차원이다. 이제 이 IDisplay를 이용해 Article을 실제로 화면에 출력하는 클래스인 User는 다음과 같다.

package tstThread;

public class User {
	protected Article article;
	
	public User(Article article) {
		this.article = article;
	}
	
	public void print(IDisplay display) {
		display.title(article);
		display.content(article);
		display.footer(article);
	}
}

print 매서드를 통해 Article 객체를 IDisplay 인터페이스를 통해 출력하고 있는데, 출력하는 순서는 title, content, footer이다. 그런데 또 다른 요구사항으로 footer, content, title라는 순서로 출력하는 기능을 처리하기 위해 User를 상속받아 ReverseUser 클래스를 정의하며 다음과 같다.

package tstThread;

public class ReverseUser extends User {

	public ReverseUser(Article article) {
		super(article);
	}

	public void print(IDisplay display) {
		display.footer(article);
		display.content(article);
		display.title(article);		
	}
}

User와 ReverseUser는 Article을 구성하는 요소를 어떻게 조립할지를 결정하는 구현에 대한 차원이다. 이제 지금까지 정의한 클래스를 사용하는 코드는 다음과 같다.

package tstThread;

import java.util.ArrayList;

public class Main {
	public static void main(String[] args) {
		String title = "GIS, powerful tool";
        
        ArrayList<String> content = new ArrayList<String>();
        content.add("GIS is a geographic information system.");
        content.add("It is base on real spatial data.");
        content.add("It provides analyzing spatial data");
        
        String footer = "2020.10.08, written by Dip2K";
        
        Article article = new Article(title, content, footer);
        
        System.out.println("[CASE-1]");
        User case1 = new User(article);
        case1.print(new SimpleDisplay());
        System.out.println();
        
        System.out.println("[CASE-2]");
        User case2 = new ReverseUser(article);
        case2.print(new SimpleDisplay());
        System.out.println();
        
        System.out.println("[CASE-3]");
        User case3 = new ReverseUser(article);
        case3.print(new ContentReverseDisplay());
        System.out.println();
        
        System.out.println("[CASE-4]");
        User case4 = new User(article);
        case4.print(new ContentVerticalDisplay());
        System.out.println();
	}
}

실행 결과는 다음과 같다.

[CASE-1]
GIS, powerful tool
GIS is a geographic information system.
It is base on real spatial data.
It provides analyzing spatial data
2020.10.08, written by Dip2K

[CASE-2]
2020.10.08, written by Dip2K
GIS is a geographic information system.
It is base on real spatial data.
It provides analyzing spatial data
GIS, powerful tool

[CASE-3]
2020.10.08, written by Dip2K
It provides analyzing spatial data
It is base on real spatial data.
GIS is a geographic information system.
GIS, powerful tool

[CASE-4]
2020.10.08, written by Dip2K
G I I 
I t t 
S     
  i p 
i s r 
s   o 
  b v 
a a i 
  s d 
g e e 
e   s 
o o   
g n a 
r   n 
a r a 
p e l 
h a y 
i l z 
c   i 
  s n 
i p g 
n a   
f t s 
o i p 
r a a 
m l t 
a   i 
t d a 
i a l 
o t   
n a d 
  . a 
s   t 
y   a 
s     
t     
e     
m     
.     
GIS, powerful tool

Bridge 패턴을 통해 차원이 다른 구현을 분리함으로써 구현을 효과적으로 할 수 있으며, 구현된 기능을 다양하게 조합할 수 있다.

이 글은 소프트웨어 설계의 기반이 되는 GoF의 디자인패턴에 대한 강의자료입니다. 완전한 실습을 위해 이 글에서 소개하는 클래스 다이어그램과 예제 코드는 완전하게 실행되도록 제공되지만, 상대적으로 예제 코드와 관련된 설명이 함축적으로 제공되고 있습니다. 이 글에 대해 궁금한 점이 있으면 댓글을 통해 남겨주시기 바랍니다.

[GoF] Singleton 패턴

패턴명칭

Singleton

필요한 상황

프로그램에서 오직 딱 하나의 객체만 생성하도록 하는 장치이다.

예제 코드

생성자를 private로 지정하여 외부에서 절대로 생성할 수 없도록 한다.

package pattern;

public class Singleton {
	private static Singleton self = null;
	private Singleton() {}
	
	public synchronized static Singleton getInstance() {
		if(self == null) {
			self = new Singleton();
		}
		return self;
	}
	
	public void print() {
		System.out.println("I am only one.");
	}
}

사용하는 코드는 다음과 같다.

package pattern;

public class Main {
	public static void main(String[] args) {
		Singleton s = Singleton.getInstance();
		s.print();
	}
}
이 글은 소프트웨어 설계의 기반이 되는 GoF의 디자인패턴에 대한 강의자료입니다. 완전한 실습을 위해 이 글에서 소개하는 클래스 다이어그램과 예제 코드는 완전하게 실행되도록 제공되지만, 상대적으로 예제 코드와 관련된 설명이 함축적으로 제공되고 있습니다. 이 글에 대해 궁금한 점이 있으면 댓글을 통해 남겨주시기 바랍니다.

[GoF] Template 패턴

패턴명칭

Template

필요한 상황

처리에 대한 로직은 정해져 있을 때, 이 로직을 구성하는 각각의 세부 항목에 대한 처리들을 다르게 정의하고자할때 사용할 수 있는 패턴이다.

예제 코드

위의 클래스다이어그램에서 DisplayArticleTemplate은 이미 정해진 로직을 정의하는 클래스이며, 이 로직을 구성하는 항목들의 세부 처리에 대한 인터페이스만을 정의하고 있는 추상클래스이다. 이 클래스를 상속받아 각 세부 항목을 구현해야 하는데, SimpleDisplayArticle과 CaptionDisplayArticle이 바로 그 클래스이다. DisplayArticleTemplate 클래스는 어떤 데이터를 화면에 출력하는 일을 하는데, 출력하는 대상이 되는 데이터는 Article 클래스의 인스턴스에 저장된다.

먼저 Article 클래스는 다음과 같다.

package pattern;

import java.util.ArrayList;

public class Article {
	private String title;
	private ArrayList<String> content;
	private String footer;
	
	public Article(String title, ArrayList<String> content, String footer) {
		this.title = title;
		this.content = content;
		this.footer = footer;
	}
	
	public String getTitle() {
		return title;
	}
	
	public ArrayList<String> getContent() {
		return content;
	}
	
	public String getFooter() {
		return this.footer;
	}
}

제목, 내용, 끝내용은 각각 title, content, footer 필드에 저장된다. 이제 이 데이터를 출력하는 로직을 담당하는 DisplayArticleTemplate 클래스는 다음과 같다.

package pattern;

public abstract class DisplayArticleTemplate {
	protected abstract void title();
	protected abstract void content();
	protected abstract void footer();
	
	protected Article article;

	public DisplayArticleTemplate(Article article) {
		this.article = article;
	}
	
	public final void display() {
		title();
		content();
		footer();
	}
}

display 매서드가 로직이고, title, content, footer 매서드가 로직을 구성하는 상세 처리이다. 이제 DisplayArticleTemplate 클래스를 상속해 각 상세 처리를 구현하는 클래스를 살펴보자. 먼저 SimpleDisplayArticle 클래스이다.

package pattern;

import java.util.ArrayList;

public class SimpleArcticle extends DisplayArticleTemplate {

	public SimpleArcticle(Article article) {
		super(article);
	}

	@Override
	protected void title() {
		System.out.println(article.getTitle());
	}

	@Override
	protected void content() {
		ArrayList<String> content = article.getContent();
		int cntLines = content.size();
		for(int i=0; i<cntLines; i++) {
			System.out.println(content.get(i));
		}
	}

	@Override
	protected void footer() {
		System.out.println(article.getFooter());
	}
}

다음은 CaptionDisplayArticle 클래스이다.

package pattern;

import java.util.ArrayList;

public class CaptionArticle extends DisplayArticleTemplate {

	public CaptionArticle(Article article) {
		super(article);
	}

	@Override
	protected void title() {
		System.out.println("TITLE: " + article.getTitle());
	}

	@Override
	protected void content() {
		System.out.println("CONTENT:");
		
		ArrayList<String> content = article.getContent();
		int cntLines = content.size();
		for(int i=0; i<cntLines; i++) {
			System.out.println("    " + content.get(i));
		}
	}

	@Override
	protected void footer() {
		System.out.println("FOOTER: " + article.getFooter());
	}
}

이제 이 클래스들을 사용하는 코드는 다음과 같다.

package pattern;

import java.util.ArrayList;

public class Main {
	public static void main(String[] args) {
		String title = "GIS, powerful tool";
		
		ArrayList<String> content = new ArrayList<String>();
		content.add("GIS is a geographic information system.");
		content.add("It is base on real spatial data.");
		content.add("It provides analyzing spatial and geographic data");
		
		String footer = "2020.10.08, written by Dip2K";
		
		Article article = new Article(title, content, footer);
		
		System.out.println("[CASE-1]");
		DisplayArticleTemplate style1 = new SimpleArcticle(article);
		style1.display();
		
		System.out.println();
		
		System.out.println("[CASE-2]");
		DisplayArticleTemplate style2 = new CaptionArticle(article);
		style2.display();
	}
}

하나의 데이터에 대해 2가지의 표현 방법을 확인하는 코드인데, 실행 결과는 다음과 같다.

[CASE-1]
GIS, powerful tool
GIS is a geographic information system.
It is base on real spatial data.
It provides analyzing spatial and geographic data
2020.10.08, written by Dip2K

[CASE-2]
TITLE: GIS, powerful tool
CONTENT:
    GIS is a geographic information system.
    It is base on real spatial data.
    It provides analyzing spatial and geographic data
FOOTER: 2020.10.08, written by Dip2K
이 글은 소프트웨어 설계의 기반이 되는 GoF의 디자인패턴에 대한 강의자료입니다. 완전한 실습을 위해 이 글에서 소개하는 클래스 다이어그램과 예제 코드는 완전하게 실행되도록 제공되지만, 상대적으로 예제 코드와 관련된 설명이 함축적으로 제공되고 있습니다. 이 글에 대해 궁금한 점이 있으면 댓글을 통해 남겨주시기 바랍니다.

[GoF] Strategy 패턴

패턴명칭

Strategy

필요한 상황

변경될 가능성이 높은 어떤 알고리즘을 쉽고 효과적으로 교체할 수 있도록 하는 패턴이다.

예제 코드

예시를 위해 이 글에서는 변경될 가능성이 높은 알고리즘을 1부터 N까지의 합계를 구하는 것으로 한다. 위의 클래스 다이어그램에서 NSumStrategy는 이 알고리즘의 연산 결과를 얻기 위한 인터페이스만을 정의하는 인터페이스이며 Strategy 패턴의 핵심이다. 코드는 아래와 같다.

package pattern;

public interface NSumStrategy {
	long sum(long N);
}

Calculator 클래스는 어떤 복잡한 연산을 수행하는 기능을 하는데, 복잡한 연산 속에 1부터 N까지 합계를 내는 연산이 필요하다. 코드는 아래와 같다.

package pattern;

public class Calculator {
	private NSumStrategy strategy;
	
	public Calculator(NSumStrategy strategy) {
		this.strategy = strategy;
	}
	
	public double run(int N) {
		return Math.log(strategy.sum(N));	
	}
}

이제 1부터 N까지 합계를 내는 연산에 대한 구체적인 클래스를 정의해보자. 먼저 SimpleSumStrategy 클래스는 다음과 같다. 가장 흔하게 사용되며 매우 직관적인 코드이지만, 반복문을 사용함으로써 수행속도는 느린 방법이다.

package pattern;

public class SimpleNSumStrategy implements NSumStrategy {
	@Override
	public long sum(long N) {
		long sum = N;
		
		for(long i=1; i<N; i++) {
			sum += i;
		}
		
		return sum;
	}
}

다음은 가우스 방식을 사용하는 GaussSumStrategy 클래스이다. 반복문을 사용하지 않아 매우 속도가 빠르다.

package pattern;

public class GaussSumStrategy implements NSumStrategy {
	@Override
	public long sum(long N) {
		return (N+1)*N/2;
	}
}

아래는 실제 1부터 N까지의 합을 필요로 하는 복잡한 연산을 실제로 수행하는 코드이다.

package pattern;

public class Main {
	public static void main(String[] args) {
		Calculator cal1 = new Calculator(new SimpleNSumStrategy()); 
		Calculator cal2 = new Calculator(new GaussSumStrategy());
		
		double result1 = cal1.run(10000000);
		double result2 = cal2.run(10000000);
		
		System.out.println(result1 + " " + result2);
		
	}
}

복잡한 연산 중 일부분인 1부터 N까지의 합을 구하는 방식을 분리하여 쉽게 교체가 가능한 것을 볼 수 있다.

이 글은 소프트웨어 설계의 기반이 되는 GoF의 디자인패턴에 대한 강의자료입니다. 완전한 실습을 위해 이 글에서 소개하는 클래스 다이어그램과 예제 코드는 완전하게 실행되도록 제공되지만, 상대적으로 예제 코드와 관련된 설명이 함축적으로 제공되고 있습니다. 이 글에 대해 궁금한 점이 있으면 댓글을 통해 남겨주시기 바랍니다.