[GoF] Command 패턴

패턴명칭

Command

필요한 상황

어떤 기능이나 동작을 객체화하여 실행할 수 있는 패턴이다. 객체화된 기능은 관리되어, 실행에 대한 Undo, Redo 기능 구현이나 배치(Batch)로 처리되어 여러 개의 기능을 원하는 시점에 한번에 실행될 수 있다.

예제 코드

Command는 어떤 기능이나 동작을 객체화하기 위한 인터페이스이다. 이 Command를 구현하는 클래스들은 고유한 기능이나 동작을 수행할 수 있는 객체화가 가능하다. 먼저 Command 인터페이스는 다음과 같다.

package tstThread;

public interface Command {
	public void run();
}

다음은 Command 인터페이스를 구현하는 CleanCommand로 화면을 지우는 기능을 실행한다.

package tstThread;

public class ClearCommand implements Command {
	@Override
	public void run() {
		System.out.print("\033[H\033[2J");  
	    System.out.flush();  
	}
}

다음은 PrintCommand로 화면에 원하는 문자열을 출력하는 기능을 실행한다.

package tstThread;

public class PrintCommand implements Command {
	private String content;
	
	public PrintCommand(String content) {
		this.content = content;
	}
	
	@Override
	public void run() {
		System.out.print(content);
	}
}

다음은 MoveCommand로 출력할 위치를 지정하는 기능을 실행한다.

package tstThread;

public class MoveCommand implements Command {
	private int x;
	private int y;
	
	public MoveCommand(int x, int y) {
		this.x = x;
		this.y = y;
	}
	
	@Override
	public void run() {
		System.out.print(String.format("%c[%d;%df", 0x1B, y, x));
	}
}

다음은 TextColorCommand로 문자열을 출력할때 색상을 지정하는 기능을 실행한다.

package tstThread;

public class TextColorCommand implements Command {
	public enum Color {
		BLACK("\u001B[30m"), RED("\u001B[31m"), 
		GREEN("\u001B[32m"), YELLOW("\u001B[33m"), BLUE("\u001B[34m"), 
		PURPLE("\u001B[35m"), CYAN("\u001B[36m"), WHITE("\u001B[37m");
		
		final private String code; 
		
		private Color(String name) { 
			this.code = name; 
		} 
		
		public String getCode() { 
			return code; 
		}
	};
	
	private Color color;
	
    public TextColorCommand(Color color) {
    	this.color = color;
    }
    
	@Override
	public void run() {
		System.out.print(color.getCode());
	}
}

다음은 BkColorCommand로 문자열을 출력할때 배경 색상을 지정하는 기능을 실행한다.

package tstThread;

public class BkColorCommand implements Command {
	public enum Color {
		BLACK("\u001B[40m"), RED("\u001B[41m"), 
		GREEN("\u001B[42m"), YELLOW("\u001B[43m"), BLUE("\u001B[44m"), 
		PURPLE("\u001B[45m"), CYAN("\u001B[46m"), WHITE("\u001B[47m");
		
		final private String code; 
		
		private Color(String name) { 
			this.code = name; 
		} 
		
		public String getCode() { 
			return code; 
		}
	};
	
	private Color color;
	
    public BkColorCommand(Color color) {
    	this.color = color;
    }
    
	@Override
	public void run() {
		System.out.print(color.getCode());
	}
}

다음은 MultiCommand로 여러개의 Command 객체를 저장해 한꺼번에 실행하는 기능을 실행한다.

package tstThread;

import java.util.ArrayList;

public class MultiCommand implements Command {
	private ArrayList<Command> commands = new ArrayList<Command>();
	
	public void add(Command command) {
		commands.add(command);
	}
	
	@Override
	public void run() {
		int cntCommands = commands.size();
		for(int i=0; i<cntCommands; i++) {
			Command command = commands.get(i);
			command.run();
		}
	}
}

지금까지의 클래스를 사용하는 예제 코드는 다음과 같다.

package tstThread;

public class Main {
	public static void main(String[] args) {
		MultiCommand redTextWhiteBkColorCommand = new MultiCommand();
		redTextWhiteBkColorCommand.add(new TextColorCommand(TextColorCommand.Color.RED));
		redTextWhiteBkColorCommand.add(new BkColorCommand(BkColorCommand.Color.WHITE));
		
		MultiCommand blackTextGreenBkColorCommand = new MultiCommand();
		blackTextGreenBkColorCommand.add(new TextColorCommand(TextColorCommand.Color.BLACK));
		blackTextGreenBkColorCommand.add(new BkColorCommand(BkColorCommand.Color.GREEN));
		
		MultiCommand endCommand = new MultiCommand();
		endCommand.add(new TextColorCommand(TextColorCommand.Color.WHITE));
		endCommand.add(new BkColorCommand(BkColorCommand.Color.BLACK));
		endCommand.add(new MoveCommand(0, 24));
		endCommand.run();
		
		MultiCommand yellowTextRedBkColorCommand = new MultiCommand();
		yellowTextRedBkColorCommand.add(new TextColorCommand(TextColorCommand.Color.YELLOW));
		yellowTextRedBkColorCommand.add(new BkColorCommand(BkColorCommand.Color.RED));
		
		Command clear = new ClearCommand();
		clear.run();
		
		redTextWhiteBkColorCommand.run();
		(new MoveCommand(10, 2)).run();
		(new PrintCommand("GIS")).run();
		
		blackTextGreenBkColorCommand.run();
		(new MoveCommand(60, 4)).run();
		(new PrintCommand("Developer")).run();
		
		yellowTextRedBkColorCommand.run();
		(new MoveCommand(30, 6)).run();
		(new PrintCommand("Dip2K")).run();
		
		endCommand.run();
	}
}

실행결과는 다음과 같다.

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

[GoF] Builder 패턴

패턴명칭

Builder

필요한 상황

어떤 과정 거쳐 하나의 객체를 생성하는데, 그 과정에 대한 종류가 하나가 아닌 여러 개일때 사용할 수 있는 패턴이다.

예제 코드

Data 클래스는 이름과 나이에 대한 데이터를 담고 있다. 이 Data 클래스에 대한 객체를 필요에 따라 평문 문자열 포맷이나 JSON 포맷 또는 XML 포맷의 문자열로 구성한다. 먼저 Data 클래스는 다음과 같다.

package pattern;

public class Data {
	private String name;
	private int age;
	
	public Data(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public String getName() {
		return name;
	}
	
	public int getAge() {
		return age;
	}
}

Builder 추상 클래스는 위의 Data를 다양한 포맷으로 구성하기 위한 공통된 인터페이스를 제공하는 부모 클래스이며 다음과 같다.

package pattern;

public abstract class Builder {
	protected Data data;
	
	public Builder(Data data) {
		this.data = data;
	}
	
	public abstract String head();
	public abstract String body();
	public abstract String foot();
}

위의 Builder 클래스는 Facade 클래스에서 사용되는데, Facade 클래스는 다음과 같다.

package pattern;

public class Facade {
	private Builder builder;

	public Facade(Builder builder) {
		this.builder = builder;
	}
	
	public String build() {
		StringBuilder sb = new StringBuilder();
		
		sb.append(builder.head());
		sb.append(builder.body());
		sb.append(builder.foot());
		
		return sb.toString();
	}
}

Builder 클래스를 구현하는 파생 클래스를 보자. 먼저 평문 문자열 포맷으로 문자열을 구성하는 클래스인 PlainTextBuilder는 다음과 같다.

package pattern;

public class PlainTextBuilder extends Builder {
	public PlainTextBuilder(Data data) {
		super(data);
	}
	
	@Override
	public String head() {
		return "";
	}

	@Override
	public String body() {
		StringBuilder sb = new StringBuilder();
		
		sb.append("Name: ");
		sb.append(data.getName());
		sb.append(", Age: ");
		sb.append(data.getAge());
		
		return sb.toString();
	}

	@Override
	public String foot() {
		return "";
	}
}

다음은 JSON 포맷으로 구성하는 JSONBuilder 클래스이다.

package pattern;

public class JSONBuilder extends Builder {

	public JSONBuilder(Data data) {
		super(data);
	}

	@Override
	public String head() {
		return "{ ";
	}

	@Override
	public String body() {
		StringBuilder sb = new StringBuilder();
		
		sb.append("\"Name\": ");
		sb.append("\"" + data.getName() + "\"");
		sb.append(", \"Age\": ");
		sb.append(data.getAge());
		
		return sb.toString();
	}

	@Override
	public String foot() {
		return " }";
	}
}

다음은 XML 포맷으로 구성하는 XMLBuilder 클래스이다.

package pattern;

public class XMLBuilder extends Builder {

	public XMLBuilder(Data data) {
		super(data);
	}

	@Override
	public String head() {
		StringBuilder sb = new StringBuilder();
		
		sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
		sb.append("<DATA>");
		
		return sb.toString();
	}

	@Override
	public String body() {
		StringBuilder sb = new StringBuilder();
		
		sb.append("<NAME>");
		sb.append(data.getName());
		sb.append("</NAME>");
		sb.append("<AGE>");
		sb.append(data.getAge());
		sb.append("</AGE>");
		
		return sb.toString();
	}

	@Override
	public String foot() {
		return "</DATA>";
	}
}

지금까지의 클래스를 사용하는 예제 코드는 다음과 같다.

package pattern;

public class Main {
	public static void main(String[] args) {
		Data data = new Data("Jane", 25);
		
		Builder builder = new PlainTextBuilder(data);
		Facade facade = new Facade(builder);
		String result = facade.build();
		System.out.println(result);
		
		builder = new JSONBuilder(data);
		facade = new Facade(builder);
		result = facade.build();
		System.out.println(result);
		
		builder = new XMLBuilder(data);
		facade = new Facade(builder);
		result = facade.build();
		System.out.println(result);
	}
}

실행결과는 다음과 같다.

Name: Jane, Age: 25
{ "Name" :"Jane", "Age": 25 }
<?xml version="1.0" encoding="utf-8"?><DATA><NAME>Jane</NAME><AGE>25</AGE></DATA>
이 글은 소프트웨어 설계의 기반이 되는 GoF의 디자인패턴에 대한 강의자료입니다. 완전한 실습을 위해 이 글에서 소개하는 클래스 다이어그램과 예제 코드는 완전하게 실행되도록 제공되지만, 상대적으로 예제 코드와 관련된 설명이 함축적으로 제공되고 있습니다. 이 글에 대해 궁금한 점이 있으면 댓글을 통해 남겨주시기 바랍니다.

[GoF] Facade 패턴

패턴명칭

Facade

필요한 상황

어떤 기능을 실행할때 다양한 타입의 객체들의 함수를 정확한 순서로 호출해야 할때 발생하는 복잡도를 단순화시켜주는 패턴이다.

예제 코드

데이터베이스부터 사용자의 정보를 가져오기 전에 먼저 캐쉬를 통해 정보를 가져오고, 만약 캐쉬에 없다면 데이터베이스로부터 사용자 정보를 가져와 캐쉬에 저장한다. 그 다음은 이 정보를 원하는 형태의 메세지로 출력하기 위한 기능을 개발한다고 하자. 이 기능에 대해서 사용자 정보는 Row 클래스, 데이터베이스는 DBMS 클래스, 캐쉬는 Cache 클래스, 메세지 출력을 위한 내용의 구성은 Message 클래스가 담당한다. 이러한 클래스들 간의 정확한 관계와 매서드들간의 복잡한 관계를 Facade 클래스를 도입하여 단순화 시킨다. 먼저 사용자 정보는 Row는 다음과 같다.

package pattern;

public class Row {
	private String name;
	private String birthday;
	private String email;
	
	public Row(String name, String birthday, String email) {
		this.name = name;
		this.birthday = birthday;
		this.email = email;
	}
	
	public String getName() {
		return name;
	}
	
	public String getBirthday() {
		return birthday;
	}
	
	public String getEmail() {
		return email;
	}
}

DBMS 클래스는 다음과 같다.

package pattern;

import java.util.HashMap;

public class DBMS {
	private HashMap<String, Row> db;
	
	public DBMS() {
		db = new HashMap<String, Row>();
		
		db.put("jane", new Row("Jane", "1990-02-14", "jane09@geosee.co.kr"));
		db.put("robert", new Row("Robert", "1979-11-05", "nice@googl.com"));
		db.put("dorosh", new Row("Dorosh", "1985-08-21", "doshdo@nave.net"));
	}
	
	public Row query(String name) {
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		return db.get(name.toLowerCase());
	}
}

Cache 클래스는 다음과 같다.

package pattern;

import java.util.HashMap;

public class Cache {
    private HashMap<String, Row> cache;
    
    public Cache() {
        cache = new HashMap<String, Row>();
    }
    
    public void put(Row row) {
        cache.put(row.getName(), row);
    }

    public Row get(String name) {
        return cache.get(name);
    }
}

Message 클래스는 다음과 같다.

package pattern;

public class Message {
	private Row row;
	
	public Message(Row row) {
		this.row = row;
	}
	
	public String makeName() {
		return "Name: \"" + row.getName() + "\"";
	}
	
	public String makeBirthday() {
		return "Birthday: " + row.getBirthday();
	}
	
	public String makeEmail() {
		return "Email: " + row.getEmail();
	}
}

이제 위의 클래스들에 대한 상호간의 API 호출을 단순화시킨 Facade 클래스는 다음과 같다.

package pattern;

public class Facade {
	public void run(DBMS dbms, Cache cache, String name) {
		Row row = cache.get(name);
		if(row == null) {
			row = dbms.query(name);
			if(row != null) {
				cache.put(row);
			}
		}
		
		if(row != null) {
			Message message = new Message(row);
			System.out.println(message.makeName());
			System.out.println(message.makeBirthday());
			System.out.println(message.makeEmail());
		} else {
			System.out.println(name + " is not exists.");
		}
	}
}

실제 사용 예제 코드는 다음과 같다.

package pattern;

public class Main {
	public static void main(String[] args) {
		DBMS dbms = new DBMS();
		Cache cache = new Cache();
		
		String name = "Dorosh";
		
		Facade facade = new Facade();
		facade.run(dbms, cache, name);
	}
}

실행 결과는 다음과 같다.

Name: "Dorosh"
Birthday: 1985-08-21
Email: doshdo@nave.net

Facade 패턴의 도입으로 복잡한 클래스간의 함수 호출이 단순화되고 재사용성이 높아진다.

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

[GoF] Prototype 패턴

패턴명칭

Prototype

필요한 상황

어떤 객체가 생성되고, 그 객체의 상태값이 변경된 후 그 객체와 동일한 상태의 또 다른 복사본을 생성하고자 할때 사용할 수 있는 패턴이다. 복사본의 상태의 변경으로인해 원본의 상태는 변경되지 않는다.

예제 코드

Point와 Circle 그리고 Line은 Prototype 인터페이스를 구현하는 클래스이다. Prototype을 구현한 클래스는 자신의 상태값을 완전하게 복사할 책임을 갖는다. Prototype의 코드는 다음과 같다.

package tstThread;

public interface Prototype {
	Prototype clone();
}

Point 클래스는 다음과 같다.

package tstThread;

public class Point implements Prototype {
	private int x;
	private int y;
	
	public Point setX(int x) {
		this.x = x;
		return this;
	}
	
	public Point setY(int y) {
		this.y = y;
		return this;
	}
	
	public int getX() {
		return x;
	}
	
	public int getY() {
		return y;
	}
	
	@Override
	public Prototype clone() {
		Point newPoint = new Point();
		newPoint.x = this.x;
		newPoint.y = this.y;
		
		return newPoint;
	}
	
	@Override
	public String toString() {
		return "POINT(" + x + " " + y + ")";
	}
}

Circle 클래스는 다음과 같다.

package tstThread;

public class Circle implements Prototype {
	private Point center;
	private int radius;
	
	public Circle setCenter(Point center) {
		this.center = (Point)center.clone();
		return this;
	}
	
	public Circle setRadius(int radius) {
		this.radius = radius;
		return this;
	}
	
	public Point getCenter() {
		return (Point)center.clone();
	}
	
	public int getRadius() {
		return radius;
	}
	
	@Override
	public Prototype clone() {
		Circle newCircle = new Circle();
		
		newCircle.center = (Point)center.clone();
		newCircle.radius = this.radius;
		
		return newCircle;
	}

	@Override
	public String toString() {
		return "CIRCLE(center: " + center + ", radius: " + radius + ")";
	}
}

Line 클래스는 다음과 같다.

package tstThread;

public class Line implements Prototype {
	private Point startPt;
	private Point endPt;
	
	public Line setStartPoint(Point pt) {
		this.startPt = (Point)pt.clone();
		return this;
	}
	
	public Point getStartPoint() {
		return (Point)startPt.clone();
	}
	
	public Line setEndPoint(Point pt) {
		this.endPt = (Point)pt.clone();
		return this;
	}
	
	public Point getEndPoint() {
		return (Point)endPt.clone();
	}
	
	@Override
	public Prototype clone() {
		Line newLine = new Line();
		
		newLine.startPt = (Point)startPt.clone();
		newLine.endPt = (Point)endPt.clone();
		
		return newLine;
	}
	
	@Override
	public String toString() {
		return "LINE(" + startPt + ", " + endPt + ")";
	}
}

지금까지의 클래스를 사용하는 예제는 다음과 같다.

package tstThread;

import java.io.UnsupportedEncodingException;

public class Main {
	public static void main(String[] args) throws UnsupportedEncodingException {
		Point center = new Point();
		center.setX(100);
		center.setY(200);
		
		Circle circle = new Circle();
		circle.setCenter(center);
		circle.setRadius(50);
		
		Point startPt = new Point();
		startPt.setX(10);
		startPt.setY(20);
		
		Point endPt = new Point();
		endPt.setX(40);
		endPt.setY(60);
		
		Line line = new Line();
		line.setStartPoint(startPt);
		line.setEndPoint(endPt);
		
		System.out.println(circle);
		System.out.println(line);
		
		Circle otherCircle = (Circle)circle.clone();
		Line otherLine = (Line)line.clone();
		
		System.out.println(otherCircle);
		System.out.println(otherLine);
	}
}

실행 결과는 다음과 같다.

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