[GoF] Abstract Factory 패턴

패턴명칭

Abstract Factory

필요한 상황

어떤 기능 또는 출력결과를 생성할때 그 기능 또는 출력결과를 구성하는 각각의 요소를 생성하는 방법을 추상화할 수 있는 패턴이다.

예제 코드

Factory는 어떤 기능이나 출력결과를 생성하기 위해 구성되는 각각의 요소를 생성하는 구체적인 클래슬르 생성하는 클래스이다. Member와 Club 그리고 Page가 각각의 요소에 대한 추상 클래스이다. Item 추상 클래스는 Member와 Club을 같은 개념으로 다루기 위해 존재한다. 실제 기능 또는 출력결과를 생성하는 구체 클래스는 HtmlMember, HtmlClub, htmlPage 그리고 HtmlFactory이다.

Factory 클래스를 먼저 보자

package tstThread;

public abstract class Factory {
	public abstract Member createMember(String name, String duty, String email);
	public abstract Club createClub(String title);
	public abstract Page createPage(String title, String footer);
}

Factory 클래스는 Member, Club, Page 객체를 생성한다. Member와 Club은 동일한 개념으로 취급되기 위해 Item이라는 추상클래스를 상속받는데 다음과 같다.

package tstThread;

public abstract class Item {
	protected String name;
	
	public Item(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
	public abstract String getResult();
}

이 Item 클래스를 상속받는 Member 클래스는 다음과 같다.

package tstThread;

public abstract class Member extends Item {
	private String duty;
	private String email;
	
	public Member(String name, String duty, String email) {
		super(name);
		this.duty = duty;
		this.email = email;
	}
	
	public String getDuty() {
		return duty;
	}
	
	public String getEmail() {
		return email;
	}
}

Item을 상속받는 Club 클래스는 다음과 같다.

package tstThread;

import java.util.ArrayList;
import java.util.Iterator;

public abstract class Club extends Item {
	private ArrayList<Item> items = new ArrayList<Item>();
	public Club(String name) {
		super(name);
	}
	
	public void add(Item item) {
		items.add(item);
	}

	public Iterator getIterator() {
		return items.iterator();
	}
}

Page 클래스는 다음과 같다.

package tstThread;

import java.util.ArrayList;
import java.util.Iterator;

public abstract class Page {
	private String title;
	private String footer;	
	private ArrayList<Item> items = new ArrayList<Item>();
	
	public Page(String title, String footer) {
		this.title = title;
		this.footer = footer;
	}
	
	public void add(Item item) {
		items.add(item);
	}
	
	public String getTitle() {
		return this.title;
	}
	
	public String getFooter() {
		return this.footer;
	}
	
	public Iterator getIterator() {
		return items.iterator();
	}
	
	public abstract String getResult();
}

이제 구체적인 부품에 해당하는 클래스들을 살펴보자. 먼저 Member의 파생 클래스인 HtmlMember이다.

package tstThread;

public class HtmlMember extends Member {

	public HtmlMember(String name, String duty, String email) {
		super(name, duty, email);
	}

	@Override
	public String getResult() {
		StringBuilder sb = new StringBuilder();
		
		sb.append("
    "); sb.append("
  • Name: "); sb.append(this.getName()); sb.append("
  • "); sb.append("
      "); sb.append("
    • Duty: "); sb.append(this.getDuty()); sb.append("
    • "); sb.append("
    • Email: "); sb.append(this.getEmail()); sb.append("
    • "); sb.append("
    "); sb.append("
"); return sb.toString(); } }

다음은 Club의 파생 클래스인 HtmlClub이다.

package tstThread;

import java.util.Iterator;

public class HtmlClub extends Club {
	public HtmlClub(String name) {
		super(name);
	}

	@Override
	public String getResult() {
		StringBuilder sb = new StringBuilder();
		
		sb.append("
    "); sb.append("
  • Club Name: "); sb.append(getName()); sb.append("
  • "); Iterator iter = getIterator(); while(iter.hasNext()) { Item item = iter.next(); sb.append(item.getResult()); } sb.append("
"); return sb.toString(); } }

다음은 Page의 파생 클래스인 HtmlPage이다.

package tstThread;

import java.util.Iterator;

public class HtmlPage extends Page {
	public HtmlPage(String title, String footer) {
		super(title, footer);
	}

	@Override
	public String getResult() {
		StringBuilder sb = new StringBuilder();
		
		sb.append("");
		sb.append("

"); sb.append(getTitle()); sb.append("

"); Iterator iter = getIterator(); while(iter.hasNext()) { Item item = iter.next(); sb.append(item.getResult()); } sb.append("
"); sb.append(""); sb.append(getFooter()); sb.append(""); sb.append(""); return sb.toString(); } }

다음은 Factory의 파생 클래스인 HtmlFactory이다.

package tstThread;

public class HtmlFactory extends Factory {

	@Override
	public Member createMember(String name, String duty, String email) {
		return new HtmlMember(name, duty, email);
	}

	@Override
	public Club createClub(String title) {
		return new HtmlClub(title);
	}

	@Override
	public Page createPage(String title, String footer) {
		return new HtmlPage(title, footer);
	}
}

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

package tstThread;

public class Main {
	public static void main(String[] args) {
		Page page = new HtmlPage("OrangeTree", "since 2021");
		Factory factory = new HtmlFactory();
		
		Member ceo = factory.createMember("Jany", "CEO", "jany@orangetree.com");
		page.add(ceo);
		
		Club club1 = factory.createClub("GAME");
		Member mem1_club1 = factory.createMember("Toms", "Manager", "toms@orangetree.com");
		Member mem2_club1 = factory.createMember("Sujin", "Assist", "sujin@orangetree.com");
		
		club1.add(mem1_club1);
		club1.add(mem2_club1);
		page.add(club1);
		
		Club club2 = factory.createClub("STUDY");
		Member mem1_club2 = factory.createMember("Jack", "Manager", "jack@orangetree.com");
		Member mem2_club2 = factory.createMember("Robert", "Assist", "robert@orangetree.com");
		club2.add(mem1_club2);
		club2.add(mem2_club2);
		page.add(club2);

		System.out.println(page.getResult());
	}
}

실행 결과는 HTML 출력인데, 이를 웹브라우저에서 보면 다음과 같다.

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

[GoF] State 패턴

패턴명칭

State

필요한 상황

상태에 따라 실행되는 기능이 달라질때 유연하게 대응할 수 있는 패턴이다.

예제 코드

7일간 하루 하루에 대한 일정을 주사위를 던져 짝수날에는 놀고 홀수날에는 공부를 하는 시스템이다. 여기서 상태는 주사위가 짝수인지 홀수인지이다. 다양한 상태를 동일한 인터페이스로 다룰 수 있도록 State 인터페이스를 두고 이 State를 PlayState와 StudyState가 구현한다. 각각은 상태에 따른 놀기와 공부하기이다. Schedule는 상태를 변경하는 클래스이다. 먼저 State 인터페이스는 다음과 같다.

package tstThread;

public interface State {
	void morning();
	void afternoon();
	void night();
}

다음은 공부하기 상태에 대한 StudyState 클래스이다.

package tstThread;

public class StudyState implements State {
	@Override
	public void morning() {
		System.out.println("I'm studying the math.");
	}

	@Override
	public void afternoon() {
		System.out.println("I'm studying the programming.");
	}

	@Override
	public void night() {
		System.out.println("I'm studying the physics.");
	}
}

다음은 놀기 상태에 대한 PlayState 클래스이다.

package tstThread;

public class PlayState implements State {

	@Override
	public void morning() {
		System.out.println("I am playing the piano.");
	}

	@Override
	public void afternoon() {
		System.out.println("I am playing the starcraft game.");
	}

	@Override
	public void night() {
		System.out.println("I am listening the pop song.");
	}
}

다음은 일주일에 대한 시간 흐름을 제어하고 주사위를 던저 상태를 변경하는 Schedule 클래스이다.

package tstThread;

public class Schedule {
	private State state;
	
	public void setState(State state) {
		this.state = state;
	}
	
	public void doInTheMorning() {
		if(state != null) {
			System.out.print("[Morning] ");
			state.morning();
		}
	}
	
	public void doInTheAfternoon() {
		if(state != null) {
			System.out.print("[Afternoon] ");
			state.afternoon();
		}
	}
	
	public void doInTheNight() {
		if(state != null) {
			System.out.print("[Night] ");
			state.night();
		}
	}
}

지금까지의 클래스를 실행하는 코드는 다음과 같다.

package tstThread;

import java.util.Random;

public class Main {
	private static Random dice = new Random();
	public static void main(String[] args) {
		Schedule schedule = new Schedule();
		
		String days[] = { "Sunday", "Monday", "Tuesday", "Thursday", "Friday", "Saturday" };
		for(int nDay=0; nDay<6; nDay++) {
			int nDice = dice.nextInt(7);
			
			if(nDice % 2 == 0) {
				schedule.setState(new PlayState());
			} else {
				schedule.setState(new StudyState());
			}
			
			System.out.println();
			System.out.println("# " + days[nDay]);
			schedule.doInTheMorning();
			schedule.doInTheAfternoon();
			schedule.doInTheNight();
		}
	}
}

실행결과는 다음과 같다.

# Sunday
[Morning] I am playing the piano.
[Afternoon] I am playing the starcraft game.
[Night] I am listening the pop song.

# Monday
[Morning] I am playing the piano.
[Afternoon] I am playing the starcraft game.
[Night] I am listening the pop song.

# Tuesday
[Morning] I am playing the piano.
[Afternoon] I am playing the starcraft game.
[Night] I am listening the pop song.

# Thursday
[Morning] I am playing the piano.
[Afternoon] I am playing the starcraft game.
[Night] I am listening the pop song.

# Friday
[Morning] I'm studying the math.
[Afternoon] I'm studying the programming.
[Night] I'm studying the physics.

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

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

잘못된 Geometry를 수정하기

PostGIS의 공간 함수 중 ST_MakeValid는 이미 저장된 올바르지 못한 Geometry를 수정해 준다. 아래의 쿼리는 이에 대한 내용이다.

select ST_IsValid(the_geom) from sify_li;
update sify_li set the_geom = ST_MakeValid(the_geom);

위의 SQL 구문 중 sify_li는 Table 이름이며 the_geom은 Geometry 타입에 대한 필드명에 대한 예시이다.

멀티 폴리곤의 경우 실패하는 경우가 있는데 아래의 SQL문을 사용하면 해결되는 경우가 있음

update sig set the_geom = st_multi(st_collectionextract(st_makevalid(the_geom),3)) where not st_isvalid(the_geom);

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