실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계하는 패턴

  • 즉, 이벤트가 발생했을 때 실행될 기능이 다양하면서도 변경이 필요한 경우에 이벤트를 발생시키는 클래스를 변경하지 않고 재사용하고자 할 때 유용하다.
  • ‘행위(Behavioral) 패턴’의 하나 (아래 참고)

실행될 기능을 캡슐화함으로써 기능의 실행을 요구하는 호출자(Invoker) 클래스와 실제 기능을 실행하는 수신자(Receiver) 클래스 사이의 의존성을 제거한다.

따라서 실행될 기능의 변경에도 호출자 클래스를 수정 없이 그대로 사용 할 수 있도록 해준다.

 

역할이 수행하는 작업
- Command
  실행될 기능에 대한 인터페이스
  실행될 기능을 execute 메서드로 선언함
- ConcreteCommand
  실제로 실행되는 기능을 구현
  즉, Command라는 인터페이스를 구현함
- Invoker
  기능의 실행을 요청하는 호출자 클래스
- Receiver
  ConcreteCommand에서 execute 메서드를 구현할 때 필요한 클래스
  즉, ConcreteCommand의 기능을 실행하기 위해 사용하는 수신자 클래스

 

예시
   만능 버튼 만들기


버튼이 눌리면 램프의 불이 켜지는 프로그램
Lamp 생성에 Button 클래스에 넘기고  press하면 램프 켜짐

package com.patterns.CommandPattern;

public class Lamp {
	public void turnOn() {
		System.out.println("Lamp On");
	}
	
	public static void main(String[] args) {
		Lamp lamp = new Lamp();
		Button1 lampButton = new Button1(lamp);
		lampButton.pressed();
	}
}

class Button1 {
	private Lamp theLamp;

	public Button1(Lamp theLamp) {
		this.theLamp = theLamp;
	}

	public void pressed() {
		theLamp.turnOn();
	}
}
  • Button 클래스의 생성자를 이용해 불을 켤 Lamp 객체를 전달한다.
  • Buttom 클래스의 pressed()가 호출되면 생성자를 통해 전달받은 Lamp 객체의 turnOn()를 호출해 불을 켠다.


문제점
1. 버튼을 눌렀을 때 다른 기능을 실행하는 경우
   버튼을 눌렀을 때 알람이 시작되게 하려면?

package com.patterns.CommandPattern;

public class Alarm {
	public void start() {
		System.out.println("Alarming");
	}

	public static void main(String[] args) {
		 Alarm alarm = new Alarm();
		 Button2 alarmButton = new Button2(alarm);
		 alarmButton.pressed();
		  }
		
}

class Button2 {
	private Alarm theAlarm;

	public Button2(Alarm theAlarm) {
		this.theAlarm = theAlarm;
	}

	public void pressed() {
		theAlarm.start();
	}
}
  • 새로운 기능으로 변경하려고 기존 코드(Button 클래스)의 내용을 수정해야 하므로 OCP에 위배된다.
  • Button 클래스의 pressed() 전체를 변경해야 한다.


2. 버튼을 누르는 동작에 따라 다른 기능을 실행하는 경우
    버튼을 처음 눌렀을 때는 램프를 켜고, 두 번째 눌렀을 때는 알람을 동작하게 하려면?

package com.patterns.CommandPattern;

enum Mode {
	LAMP, ALARM
};

public class Button3 {
	private Lamp theLamp;
	private Alarm theAlarm;
	private Mode theMode;

	// 생성자에서 버튼을 눌렀을 때 필요한 기능을 인지로 받는다.
	public Button3(Lamp theLamp, Alarm theAlarm) {
	   this.theLamp = theLamp;
	   this.theAlarm = theAlarm;
	  }

	// 램프 모드 또는 알람 모드를 설정
	public void setMode(Mode mode) {
		this.theMode = mode;
	}

	// 설정된 모드에 따라 램프를 켜거나 알람을 울림
	public void pressed() {
		switch (theMode) {
		case LAMP:
			theLamp.turnOn();
			break;
		case ALARM:
			theAlarm.start();
			break;
		}
	}
}
  • 필요한 기능을 새로 추가할 때마다 Button 클래스의 코드를 수정해야 하므로 재사용하기 어렵다.

 

해결책

  • 문제를 해결하기 위해서는 구체적인 기능을 직접 구현하는 대신 실행될 기능을 캡슐화해야 한다.

    즉, Button 클래스의 pressed 메서드에서 구체적인 기능(램프 켜기, 알람 동작 등)을 직접 구현하는 대신 버튼을 눌렀을 때 실행될 기능을 Button 클래스 외부에서 제공받아 캡슐화해 pressed 메서드에서 호출한다.
  • 이를 통해 Button 클래스 코드를 수정하지 않고 그대로 사용할 수 있다.

Button 클래스는 미리 약속된 Command 인터페이스의 execute 메서드를 호출한다.
램프를 켜는 경우에는 theLamp.turnOn 메서드를 호출하고 알람이 동작하는 경우에는 theAlarm.start 메서드를 호출하도록 pressed 메서드를 수정한다.
LampOnCommand 클래스에서는 Command 인터페이스의 execute 메서드를 구현해 Lamp 클래스의 turnOn 메서드(램프 켜는 기능)를 호출한다.
마찬가지로 AlarmStartCommand 클래스는 Command 인터페이스의 execute 메서드를 구현해 Alarm 클래스의 start 메서드(알람이 울리는 기능)를 호출한다.

 

Command.java : 인터페이스 

package com.patterns.CommandPattern;

public interface Command {
	public abstract void execute(); 
}

Button.java : 생성시 setCommand, 별도 setCommand, press

package com.patterns.CommandPattern;

public class Button {
	private Command theCommand;

	// 생성자에서 버튼을 눌렀을 때 필요한 기능을 인지로 받는다.
	public Button(Command theCommand) {
		setCommand(theCommand);
	}

	public void setCommand(Command newCommand) {
		this.theCommand = newCommand;
	}

	// 버튼이 눌리면 주어진 Command의 execute 메서드를 호출한다.
	public void pressed() {
		theCommand.execute();
	}
}

각각 Command를 상속받아 ConcreateCommand를 구성

package com.patterns.CommandPattern;

/* 램프를 켜는 LampOnCommand 클래스 */
public class LampOnCommand implements Command {
	private Lamp theLamp;

	public LampOnCommand(Lamp theLamp) {
		this.theLamp = theLamp;
	}

// Command 인터페이스의 execute 메서드
	public void execute() {
		theLamp.turnOn();
	}
}
package com.patterns.CommandPattern;

/* 알람을 울리는 AlarmStartCommand 클래스 */
public class AlarmStartCommand implements Command {
	private Alarm theAlarm;

	public AlarmStartCommand(Alarm theAlarm) {
		this.theAlarm = theAlarm;
	}

// Command 인터페이스의 execute 메서드
	public void execute() {
		theAlarm.start();
	}
}

수신자 구체적인 기능을 구현 : Lamp.java, Alarm.java

package com.patterns.CommandPattern;

public class Lamp {

	public void turnOn() {
		System.out.println("Lamp On");
	}
}
package com.patterns.CommandPattern;

public class Alarm {
	public void start() {
		System.out.println("Alarming");
	}
}

테스트

package com.patterns.CommandPattern;

public class Client {

	public static void main(String[] args) {
		Lamp lamp = new Lamp();
		Command lampOnCommand = new LampOnCommand(lamp);
		Alarm alarm = new Alarm();
		Command alarmStartCommand = new AlarmStartCommand(alarm);

		Button button1 = new Button(lampOnCommand); // 램프 켜는 Command 설정
		button1.pressed(); // 램프 켜는 기능 수행

		Button button2 = new Button(alarmStartCommand); // 알람 울리는 Command 설정
		button2.pressed(); // 알람 울리는 기능 수행
		button2.setCommand(lampOnCommand); // 다시 램프 켜는 Command로 설정
		button2.pressed(); // 램프 켜는 기능 수행
	}
}


Command 인터페이스를 구현하는 LampOnCommand와 AlarmStartCommand 객체를 Button 객체에 설정한다.
Button 클래스의 pressed 메서드에서 Command 인터페이스의 execute 메서드를 호출한다.
즉, 버튼을 눌렀을 때 필요한 임의의 기능은 Command 인터페이스를 구현한 클래스의 객체를 Button 객체에 설정해서 실행할 수 있다.
이렇게 Command 패턴을 이용하면 Button 클래스의 코드를 변경하지 않으면서 다양한 동작을 구현할 수 있게 된다.

 

확장설계

참고 원문 : https://gmlwjd9405.github.io/2018/07/07/command-pattern.html

문법적 규칙을 클래스화 하여, 일련의 규칙을 통해 언어/문법을 해석하는 패턴이다.

 사용되는 곳

​ SQL 문은 SELECT (어쩌구) FROM (저쩌구) WHERE (이러쿵저러쿵) 과 같이

​ 특정 문법을 지키면 MYSQL에서 알아서 쿼리결과를 짠 하고 내줍니다.

​ 덕분에 개발자들은 손쉽게 DB에서 결과를 얻게 됩니다.

​ 이렇듯 간단한 문법적 규칙(대체로 문자열을 해석하는)을 클래스화해서 사용자가 원하는 답을 얻게 해주는 패턴이 Interpreter 패턴입니다.

​ 주로 SQL 해석, shell 명령어 해석기 등에 사용하는 패턴입니다.

 구현을 어떻게 할까 (구현 예시)

​ Interpreter 패턴으로 후위 표기법 을 구현해보겠습니다.

​ 후위 표기법에서는 1+2 를 1 2 + 로 표현합니다.

​ (3 + 5) (4 + 2) => 3 5 + 4 2 +

​ 우리가 "a b +" 를 요청해도, "w x z - +" 를 요청해도

​ 문법 해석기가 알아서 해석해서 결과를 줄 수 있어야 합니다.

 

코드로 살펴볼까요!

  1. 연산자들
  • 연산자 interface
package com.patterns.InterpreterPattern;

import java.util.Map;

public interface Expression {
	public int interpret(Map<String, Expression> variables);
}
  • Plus 연산자 구현
package com.patterns.InterpreterPattern;

import java.util.Map;

public class Plus implements Expression {

	//+의 왼쪽에 올 표현(숫자일 수도 있고 이미 연산된 결과일 수 있음)
    Expression leftOperand;
     //+의 오른쪽에 올 표현
    Expression rightOperand;

    public Plus(Expression leftOperand, Expression rightOperand) {
        this.leftOperand = leftOperand;
        this.rightOperand = rightOperand;
    }

    @Override
    public int interpret(Map variables) {
        //왼쪽과 오른쪽의 결과를 더해서 반환
        return leftOperand.interpret(variables)+rightOperand.interpret(variables);
    }

}
  • Minus 연산자 구현
package com.patterns.InterpreterPattern;

import java.util.Map;

public class Minus implements Expression{
	//-의 왼쪽에 올 표현(숫자일 수도 있고 이미 연산된 결과일 수 있음)
    Expression leftOperand;
    //-의 왼쪽에 올 표현(숫자일 수도 있고 이미 연산된 결과일 수 있음)
    Expression rightOperand;

    public Minus(Expression leftOperand, Expression rightOperand) {
        this.leftOperand = leftOperand;
        this.rightOperand = rightOperand;
    }

    @Override
    public int interpret(Map variables) {
         //왼쪽에서 오른쪽의 결과를 빼서 반환
        return leftOperand.interpret(variables)-rightOperand.interpret(variables);
    }
}
  • Variable(알파벳에 해당) 연산자 구현
package com.patterns.InterpreterPattern;

import java.util.Map;

public class Variable implements Expression {

	private String alphabet;

    public Variable(String alphabet) {
        this.alphabet = alphabet;
    }

    @Override
    public int interpret(Map<String, Expression> variables) {
        // 사용자가 입력한 Map에 alphabet이 있으면 그 알파벳에 해당하는 숫자 반환
        // 없으면 0 반환 
        if(variables.get(alphabet) == null)
            return 0;
        return variables.get(alphabet).interpret(variables);
    }

}
  • Number(사용자가 입력한 숫자) 연산자 구현
public class Number implements Expression{
    private int num;

    public Number(int num) {
        this.num = num;
    }

    @Override
    public int interpret(Map variables) {
        return num; //숫자를 반환
    }
}
  1. 문법에 따라 연산 제조
  • Evaluator(해석기) 구현
  • 후위 연산법은 스택을 통해 구현합니다.(참고 : 후위 표기법)
package com.patterns.InterpreterPattern;

import java.util.Map;
import java.util.Stack;

public class Evaluator implements Expression{
    private Expression syntax; //만들 최종 해석
    
    public Evaluator(String expression){
        //expression을 입력받음
        Stack<Expression> stack = new Stack<Expression>();

        for(String strToken : expression.split(" ")){
            if(strToken.equals("+")){
                Expression expressionPlus = new Plus(stack.pop(),stack.pop());
                stack.push(expressionPlus);
            }else if(strToken.equals("-")){
                Expression expressionMinus = new Minus(stack.pop(),stack.pop());
                stack.push(expressionMinus);
            }else {
                stack.push(new Variable(strToken));
            }
        }
        //해석 결과로 나온 계산기 
        syntax = stack.pop();
    }

    //사용자가 입력한 숫자로 계산기에 계산돌리기
    @Override
    public int interpret(Map<String, Expression> variables) {
        return syntax.interpret(variables);
    }
}

 

테스트

package com.patterns.InterpreterPattern;

import java.util.HashMap;
import java.util.Map;

public class InterpreteTest {
	public static void main(String[] args) {
		//해석을 원하는 문장
        String expression ="w x z - +";
        
        //문장에 따른 해석기를 제작
        Evaluator sentence = new Evaluator(expression);

        //사용자가 w = 5, x = 10, z =42 를 입력
        Map<String, Expression> variables = new HashMap<String, Expression> ();
        variables.put("w",new Number(5));
        variables.put("x",new Number(10));
        variables.put("z",new Number(42));

        //해석기에 입력을 넣고 결과 얻기
        int result = sentence.interpret(variables);
        System.out.println(result);
    }
}

결과

"w x z - +"

(z-x) + w == (42-10) + 5

37

 

 참고한 링크들

https://velog.io/@hoit_98/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Interpreter-%ED%8C%A8%ED%84%B4

 

[디자인 패턴] Interpreter 패턴

​ 문법적 규칙을 클래스화 하여, 일련의 규칙을 통해 언어/문법을 해석하는 패턴이다.​ SQL 문은 SELECT (어쩌구) FROM (저쩌구) WHERE (이러쿵저러쿵) 과 같이​ 특정 문법을 지키면 MYSQL에서 알아서

velog.io

  1. 기본적인 Interpreter 패턴이해
  2. https://beomseok95.tistory.com/288
  3. 구체적인 Interpreter 패턴 구현(후위 표기법 구현)
  4. https://hamait.tistory.com/199
 

[Design Pattern] Interpreter Pattern

[Design Pattern] Interpreter Pattern Interpreter Pattern에 대하여 알아보고 , 자바 코틀린 예제를 봐보도록 하겠습니다. Interpreter Pattern이란???? 문법 규칙을 클래스화 한 구조로써, 일련의 규칙으로 정..

beomseok95.tistory.com

 

Interpreter 패턴

GOF 의 디자인패턴중 하나인 인터프리터 패턴에 대해서 살펴보도록 하자. 인터프리터 패턴은 모든 종류의 GoF 패턴들 중에 가장 어렵다. 물론 인터프리터 자체를 만드는게 어렵 기때문에 덩달아

hamait.tistory.com

 

Visitor는 사전적인 의미로 어떤 장소에 찾아오는 사람이라는 의미를 갖고 있다. 방문자 패턴에서는 데이터 구조와 처리를 분리한다. 데이터 구조 안을 돌아다니는 주체인 방문자를 나타내는 클래스를 준비해서 처리를 맡긴다. 새로운 처리를 추가하고 싶을 땐 새로운 방문자를 만들고 데이터 구조는 방문자를 받아들이면 된다.

방문자 패턴은 개방-폐쇠 원칙을 적용하는 방법 중 한 가지이다.
[확장에 대해 열려있다.]
클래스를 설계할 때, 특별한 이유가 없는 한 확장을 금지해서는 안된다.

[수정에 대해 닫혀있다.]
확장을 할 때 마다 기존의 클래스를 수정하면 안된다.

[Visitor]
데이터 구조 내 각각의 구체적인 요소에 visit메소드를 선언한다.

[ConcreteVisitor]
Visitor 인터페이스를 구현하고, ConcreteAccepter 역할을 처리한다.

[Acceptor]
Acceptor는 Visitor역할이 방문할 곳을 나타낸은 역할을 하고 있다. 방문자를 받아들이는 accept메소드를 선언한다.

[ConcreteAcceptor]
Acceptor 인터페이스를 구현하는 역할을 한다.

[ObjectStructure]
Acceptor역할의 집할을 취급하는 역할을 한다.

연습

위 개념을 토대로 방문자 패턴을 적용하여 마트에서 물건을 사는 사람을 예로 들어보자!

 

Element.java  : Element는 인터페이스로, 방문자를 받아들이기 위한 accept메소드를 가지고 있다.

package com.patterns.VistorPattern;

public interface Element {
	public void accept(Visitor visitor);
}

 

Cart.java :담을 물품을 추가한다. 카트에 추가된 물품의 개수 만큼 반복문을 돌며 해당 요소의 accept메소드를 실행한다.

 

package com.patterns.VistorPattern;

import java.util.ArrayList;

public class Cart implements Element {

	ArrayList<Element> cart = new ArrayList<>();

	public Cart() {
		cart.add(new Snack());
		cart.add(new Milk());
	}

	@Override
	public void accept(Visitor visitor) {
		System.out.println("카트가 준비되었습니다.");
		visitor.visit(this);

		for (Element element : cart) {
			element.accept(visitor);
		}
	}

}

Snack.java

package com.patterns.VistorPattern;

public class Snack implements Element {

	@Override
	public void accept(Visitor visitor) {
		System.out.println("과자가 준비되었습니다.");
        visitor.visit(this);
	}

}

Milk.java

package com.patterns.VistorPattern;

public class Milk implements Element {

	@Override
	public void accept(Visitor visitor) {
		System.out.println("우유가 준비되었습니다.");
        visitor.visit(this);
	}

}

 

Visitor.java : 인터페이스 정의

package com.patterns.VistorPattern;

public interface  Visitor {
    public void visit(Cart cart);
    public void visit(Snack snack);
    public void visit(Milk milk);
}

Shopper.java : Visitor 의 구현체

package com.patterns.VistorPattern;

public class Shopper implements Visitor {

	@Override
    public void visit(Cart cart) {
    	System.out.println("카트를 사용합니다.");
    }
    
    @Override
    public void visit(Snack snack) {
    	System.out.println("과자를 카트에 넣습니다.");
    }
    
    @Override
    public void visit(Milk milk) {
    	System.out.println("우유를 카트에 넣습니다.");
    }

}

테스트 

package com.patterns.VistorPattern;

public class Client {
	
	public static void main(String args[]) {
        Shopper shopper = new Shopper();
        Cart cart = new Cart();
        cart.accept(shopper);
    }
}

 

정리

- 정리하자면 방문자 패턴은, 기존 클래스 필드 정보를 유지하면서 새로운 연산을 추가하는 방식이다!

참고

비지터 패턴 - 위키 백과
방문자 패턴(Visitor Pattern)

 

참고 원문 : https://velog.io/@newtownboy/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-%EB%B0%A9%EB%AC%B8%EC%9E%90%ED%8C%A8%ED%84%B4Visitor-Pattern

 

[디자인패턴] 방문자패턴(Visitor Pattern)

Visitor는 사전적인 의미로 어떤 장소에 찾아오는 사람이라는 의미를 갖고 있다. 방문자 패턴에서는 데이터 구조와 처리를 분리한다. 데이터 구조 안을 돌아다니는 주체인 방문자를 나타내는 클

velog.io

 

메멘토 패턴은 객체의 상태 정보를 저장하고 사용자의 필요에 의하여 원하는 시점의 데이터를 복원 할 수 있는 패턴을 의미합니다.

 

메멘토 패턴 예제 구조

 

 실제로 메멘토 패턴을 사용하여 객체 정보를 저장하고 복원하는 예제를 살펴 보겠습니다. 예제는 간단히 String형 데이터 하나와 Int형 데이터 하나에 대한 정보로 가지는 Information 객체를 구현하였습니다.

 

 User : 메멘토 패턴이 적용 된 Information 객체를 실제로 사용하는 사용자입니다.

 Information : 상태를 저장하고 복원 할 데이터를 가지고 있는 클래스입니다.

 Memento : 특정 시점의 Information의 상태정보를 저장하는 클래스입니다.

 CareTaker : 상태 정보가 저장되어 있는 Memento들을 관리하는 클래스입니다. 내부에 Stack 자료형 변수를 가짐으로써 Memento 객체를 저장하고 복원을 합니다.

 

 

■ 구현

Information.java

package com.patterns.MementoPattern;

public class Information {
	private String Data1; 	// Information이 가지고 있는 데이터1
	private int Data2; 		// Information이 가지고 있는 데이터2

	public Information(String Data1, int Data2) // 생성자입니다.
	{
		this.Data1 = Data1;
		this.Data2 = Data2;
	}

	public Memento CreateMemento() // Memento를 생성합니다 (상태저장)
	{
		return new Memento(this.Data1, this.Data2);
	}

	public void RestorMemento(Memento memento) // Memento를 복원합니다 (상태복원)
	{
		this.Data1 = memento.getData1();
		this.Data2 = memento.getData2();
	}

	public void set_Data1(String Data1) // 데이터1의 값을 지정
	{
		this.Data1 = Data1;
	}

	public void set_Data2(int Data2) // 데이터2의 값을 지정
	{
		this.Data2 = Data2;
	}

	public String get_Data1() // 데이터 1의 값 반환
	{
		return this.Data1;
	}

	public int get_Data2() // 데이터 2의 값 반환
	{
		return this.Data2;
	}
}
 

▶실제 데이터 값을 가지고 있는 Information 클래스입니다. Information 클래스는 String형 데이터 Data1과 int형 데이터 Data2를 속성으로 가지고 각각 setter/getter 메서드가 구현되어 있습니다. 현재 Information 상태를 저장하기 위한 CreateMemento 메서드 ( 내부적으로 Data1과 Data2의 데이터 값을 가지는 Memento 객체를 생성)와 상태복원을 위한 RestorMemento가 구현되어 있습니다. RestoreMemento는 이전에 생성된 Memento 객체의 정보를 가져와 Information 상태 정보로 setting을 하게 됩니다.

Memento.java

package com.patterns.MementoPattern;

public class Memento {
    
    //Information의 상태정보를 가지고 있음
    private String Data1;
    private int Data2;
    
    public Memento(String Data1,int Data2)
    {
        this.Data1 = Data1;
        this.Data2 = Data2;
    }
    public String getData1()
    {
        return this.Data1;
    }
    public int getData2()
    {
        return this.Data2;
    }
}
 

▶Memento는 Information의 특정 시점에 대한 내부 상태 정보를 가지고 있습니다.

CareTaker.java

package com.patterns.MementoPattern;

import java.util.Stack;

public class CareTaker {

	Stack<Memento> mementos = new Stack<>(); // Memento 관리를 위한 Stack

	public void push(Memento memento) // 특정 시점에 생성된 Memento를 Push
	{
		mementos.push(memento);
	}

	public Memento pop() // 복원을 위한 Memento 객체 반환
	{
		return mementos.pop();
	}
}
 
 

CareTaker는 Inforamtion의 상태 정보를 저장하고 있는 Memento들을 관리하기 위한 클래스입니다. 내부적으로 Memento를 저장하기 위한 Stack 자료 구조가 존재하며 push와 pop을 통해 Memento객체를 저장하고 복원을 하게 됩니다.

User.java

package com.patterns.MementoPattern;

public class User {

	Information info;
	CareTaker caretaker;

	public void exe() {
		info = new Information("Data1", 10); // Information 객체 생성
		caretaker = new CareTaker(); // CareTaker 객체 생성
		// 현재 Information의 상태 정보를 가지는 Memento를 생성하여 CareTaker에 추가합니다.
		caretaker.push(info.CreateMemento());

		// Information 정보를 수정합니다.
		info.set_Data1("Data2");
		info.set_Data2(20);
		// 현재 Information의 상태정보를 출력합니다.
		System.out.println("현재 Data1 : " + info.get_Data1());
		System.out.println("현재 Data2 : " + info.get_Data2());
		// 가장 최근에 생성 된 Memento를 가지고와서 상태 정보를 복원합니다.
		info.RestorMemento(caretaker.pop());
		// 상태 정보를 복원 한 후에 Information의 상태 정보를 출력합니다.
		System.out.println("복구된 Data1 : " + info.get_Data1());
		System.out.println("복구된 Data2 : " + info.get_Data2());
	}
	
	public static void main(String[] args) {
		User user = new User();
		user.exe();
	}
}
 
 

▶ 실제로 Information의 정보를 다루는 User입니다. 10번 라인에서 특정 시점의 Information의 상태 정보를 저장하고 19번 라인에서 상태 정보를 복원하는 모습입니다.

 

 

참고 원본 : https://lktprogrammer.tistory.com/65

 

14 메멘토 패턴 (Memento Pattern)

메멘토 패턴 (Memento Pattern) 메멘토 패턴은 객체의 상태 정보를 저장하고 사용자의 필요에 의하여 원하는 시점의 데이터를 복원 할 수 있는 패턴을 의미합니다. ■메멘토 패턴 예제 구조 ▶ 실제

lktprogrammer.tistory.com

 

정의

  • 명령 객체와 일련의 처리 객체를 포함하는 디자인 패턴이다. 각각의 처리 객체는 명령 객체를 처리할 수 있는 연산의 집합이고, 체인 안의 처리 객체가 핸들할 수 없는 명령은 다음 처리 객체로 넘겨진다.
  • 이 작동방식은 새로운 처리 객체부터 체인의 끝까지 다시 반복된다.

쉽게 설명하면

  • 어떤 요청이 그 요청을 담당하는 객체에 들어오면 각각의 요청에 대해서 특정한 객체가 담당하는 것이 일반적이지만 객체를 연결리스트와 같은 사슬 방식으로 연결한 후에 요청을 수행하지 못하는 객체라면 다음 객체에 넘기며 책임을 넘기는 형태의 패턴을 말한다.

목적

  • 이 패턴은 결합을 느슨하게 하기 위해 고안되었으며 가장 좋은 프로그래밍 사례로 꼽힌다. 요청을 보내는 객체와 이를 처리하는 객체간의 결합도를 느슨하게 하기 위한 방법이며 여러 객체에 처리 기회를 준다.
  • 책임연쇄 패턴 장점
  1. 결합도를 낮추며, 요청의 발신자와 수신자를 분리시킬 수 있다.
  2. 클라이언트는 처리객체의 집합 내부의 구조를 알 필요가 없다.
  3. 집합 내의 처리 순서를 변경하거나 처리객체를 추가 또는 삭제할 수 있어 유연성이 향상된다.
  4. 새로운 요청에 대한 처리객체 생성이 매우 편리하다.
  • 책임연쇄 패턴 단점
  1. 충분한 디버깅을 거치지 않았을 경우 집합 내부에서 사이클이 발생할 수 있다.
  2. 디버깅 및 테스트가 쉽지 않다.
  • 책임연쇄 패턴 구조

  1. Handler
    요청을 수신하고 처리객체들의 집합에 전달하는 인터페이스이다.
    집합의 첫 번째 핸들러에 대한 정보만 가지고 있으며 그 이후의 핸들러에 대해서는 알 수 없다.
  2. Concrete Handler
    요청을 처리하는 실제 처리객체입니다.
  3. Client
    요청을 전달하는 클라이언트입니다.
  • 예제 코드

       가장 대표적인 연쇄 책임 패턴의 예는 Java 에서의 try catch 문의 있다.

    try {
        ...     
    } catch (IllegalArgumentException e) {
        someCode();
    } catch (SecurityException e) {
        someCode();
    } catch (IllegalAccessException e) {
        someCode();
    } catch (NoSuchFieldException e) {
        someCode();
    } finally {
        ...
    }
  • 동전을 거슬러주는 프로그램을 만든다고 해보자.
  • 그리고 동전은 100원, 10원, 1원 으로만 거슬러줄수 있다고 가정해보자.
  • 이경우는 100원 -> 10원 -> 1원으로 넘겨주는 방식으로 연쇄책임패턴을 구현할수있다.

Currency.java : PJO 선언

package com.patterns.ChainofResponsibility;

public class Currency {
	private int amount;

	public Currency(int amt) {
		this.amount = amt;
	}

	public int getAmount() {
		return this.amount;
	}
}

 

DispenseChain.java : 인터페이스로 선언

package com.patterns.ChainofResponsibility;

public interface DispenseChain {
	void setNextChain(DispenseChain nextChain);  //다음체인 넘겨줌
    void dispense(Currency cur);	  //분배
}

Won100Dispenser.java 

package com.patterns.ChainofResponsibility;

public class Won100Dispenser implements DispenseChain {

	private DispenseChain chain;

	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain = nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		if (cur.getAmount() >= 100) {
			int num = cur.getAmount() / 100;
			int remainder = cur.getAmount() % 100;

			System.out.println("Dispensing " + num + " 100₩ note");

			if (remainder != 0)
				this.chain.dispense(new Currency(remainder));
		} else
			this.chain.dispense(cur);
	}

}

Won10Dispenser.java

package com.patterns.ChainofResponsibility;

public class Won10Dispenser implements DispenseChain {

	private DispenseChain chain;
	
	@Override
    public void setNextChain(DispenseChain nextChain) {
        this.chain=nextChain;
    }
    
    @Override
    public void dispense(Currency cur) {
        if(cur.getAmount()>=10){
            int num=cur.getAmount()/10;
            int remainder=cur.getAmount()%10;
            
            System.out.println("Dispensing " +num+" 10₩ note");
            
            if(remainder!=0) this.chain.dispense(new Currency(remainder));    
        }
        else this.chain.dispense(cur);
    }
}

Won1Dispenser.java

package com.patterns.ChainofResponsibility;

public class Won1Dispenser implements DispenseChain {

	private DispenseChain chain;

	@Override
	public void setNextChain(DispenseChain nextChain) {
		this.chain = nextChain;
	}

	@Override
	public void dispense(Currency cur) {
		int num = cur.getAmount() / 1;
		System.out.println("Dispensing " + num + " 1₩ note");
	}

}

분배체인 인터페이스(DispenseChain) 통해 Won100Dispenser - > Won10Dispenser -> Won1Dispenser로 넘김

 

테스트

package com.patterns.ChainofResponsibility;

public class ChainTest {
    
	private DispenseChain c1;
	
    public ChainTest(){
        this.c1=new Won100Dispenser();
        DispenseChain c2=new Won10Dispenser();
        DispenseChain c3=new Won1Dispenser();
        
        c1.setNextChain(c2);
        c2.setNextChain(c3);
    }
    
    public static void main(String[] args) {
    	
    	
    	ChainTest atmDispenser = new ChainTest();
        atmDispenser.c1.dispense(new Currency(378));
    }
}

 

참고원문 : https://k0102575.github.io/articles/2020-02/chain-of-responsibility-pattern

 

책임 연쇄 패턴(chain-of-responsibility pattern)

연쇄책임패턴 (Chain-of-responsibility pattern)

k0102575.github.io

 

상태 패턴(State Pattern)

state 패턴은 객체가 특정 상태에 따라 행위를 달리하는 상황에서 자신이 직접 상태를 체크하여 상태에 따라 행위를 호출하지 않고 상태를 객체화하여 상태가 행동을 할 수 있도록 위임하는 패턴이다.

 

즉, 객체의 특정 상태를 클래스로 선언하고,클래스에서는 해당 스테이트 패턴은 객체가 특정 상태에 따라 행위를 달리하는 상황에서,자신이 직접 상태를 체크하여 상태에 따라 행위를 호출하지 않고,상태를 객체화 하여 상태가 행동을 할 수 있도록 위임하는 패턴을 말합니다.

 

즉, 객체의 특정 상태를 클래스로 선언하고, 클래스에서는 해당 상태에서 할 수 있는 행위들을 메서드로 정의한다.
그리고 이러한 각 상태 클래스들을 인터페이스로 캡슐화 하여, 클라이언트에서 인터페이스를 호출하는 방식을 말한다.

상태에 기반한 behavior object를 만들 때 이용하고 State machine diagram을 쓸 때 만드는 패턴이다.

 

쓰는 시점은, 기존 State machine diagram을 개선하기 힘든 경우에 쓰이고

이때 상속 보다는 객체의 합성을 더 선호하라는 디자인 원칙이 이용된다.

상태 패턴 클래스 다이어그램

 

하나의 클래스에서 상태, 행동을 모두 정의하는 것이 아닌

인터페이스로 상태에 따른 행동을 정의해주고

다른 클래스에서는 상태를 객체화하여 상태가 행동 할 수 있도록 위임 해준다.

 

HandState 인터페이스

상태에 대한 행동을 정의해준다.

package com.patterns.StatePattern;

public interface HandState {
	public String getState();
}

Rock, Scissors, Paper 클래스

HandState를 직접 구현하는 클래스로 상태에 대한 행동을 구체화한다.

package com.patterns.StatePattern;

public class Rock implements HandState {
	@Override
	public String getState() {
		return "바위";
	}
}
package com.patterns.StatePattern;

public class Scissors implements HandState {
	@Override
	public String getState() {
		return "가위";
	}
}
package com.patterns.StatePattern;

public class Paper implements HandState {
	@Override
	public String getState() {
		return "보";
	}
}

 

Person 클래스

Person의 상태를 set, get해준다.

package com.patterns.StatePattern;

public class Person {
	private HandState handState;

	public Person() {
		handState = new Rock();
	}

	public void setState(HandState handState) {
		this.handState = handState;
	}

	public String getState() {
		return handState.getState();
	}
}

 

StateTest : 상태를 객체화 하여 행동을 위임하고 있는 것을 볼 수 있다.

package com.patterns.StatePattern;

public class StateTest {
	
	public static void main(String[] args) {
		Person person = new Person();
		System.out.println(person.getState());

		person.setState(new Scissors());
		System.out.println(person.getState());

		person.setState(new Paper());
		System.out.println(person.getState());
	}
}

 

바위
가위

 

정리

상태 패턴은 내부 상태 객체들이 캡슐화되어 외부에서 전혀 알 수 없게되고,

외부에서는 Context 객체의 각 Action들을 호출하기만 할 뿐이다.

이를 통해 Decoupling을 하여 코드의 수정 및 확장을 용이하게 한다.

 

참고원문: https://www.crocus.co.kr/1541?category=337544 

 

상태 패턴(State Pattern)

상태 패턴(State Pattern) 스테이트 패턴은 객체가 특정 상태에 따라 행위를 달리하는 상황에서 자신이 직접 상태를 체크하여 상태에 따라 행위를 호출하지 않고 상태를 객체화하여 상태가 행동을

www.crocus.co.kr

 

스트래티지 패턴(Strategy pattern)은 각각을 캡슐화하여 교환해서 사용할 수 있도록 만든다. 
스트래티지를 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.
즉, 행위를 클래스로 캡슐화 하여 동적으로 행위를 바꾸어도 코드가 복잡해 지지 않도록 한다.
여기서의 패턴 이름과 같이 전략을 쉽게 바꾸도록 해주는 디자인 패턴이다.
ex) sorting 알고리즘 여러개가 있는데 이중 하나를 고르려고 할 때

이때, 스트래티지 패턴을 이용하면 condition statement가 현저히 줄어드는 장점이 있다.


디자인 원칙
코드에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다. (달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 "캡술화")
상속보다는 구성을 활용한다.(상속 보다는 객체의 합성을 중요시 하자)
상속보다는 구성을 활용한다. 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.
Strategy
  외부에서 동일한 방식으로 알고리즘을 호출 할 수 있도록 명시해둔 인터페이스
ConcreteStrategy
  스트래티지 인터페이스에서 명시해둔 코드를 실제로 구현한 클래스
Context
    스트래티지 패턴을 이용하는 역할을 수행하는 추상 클래스필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 하기위해 setter 집약 관계 메서드를 제공한다.
집약 관계
- 참조값을 인자로 받아 필드를 세팅하는 경우- 전체 객체의 라이프타임과 부분 객체의 라이프 타임은 독립적이다.- 즉, 전체 객체가 메모리에서 사라진다 해도 부분 객체는 사라지지 않는다.

Stragety Pattern을 오리 게임을 만드는 것을 이용하여 한번 생각해보자.


Strategy, ConcreteStrategy를 통해 fly, quack에 대한 행동을 먼저 만들어보자

외부에서 명세 Strategy FlyBehavior를 통해 ContreateStartegy인 FlyWithWings, FlyNoWay 행동을 먼저 만들자

package com.patterns.StrategyPattern;

public interface FlyBehavior {
	public void fly();
}
package com.patterns.StrategyPattern;

public class FlyWithWings implements FlyBehavior {
	@Override
	public void fly() {
		System.out.println("날고 있습니다.");
	}
}
package com.patterns.StrategyPattern;

public class FlyNoWay implements FlyBehavior {

	@Override
	public void fly() {
		System.out.println("날지 못하고 있습니다.");
	}

}

마찬가지로 외부에서 명세 Strategy QuackBehavior를 통해 ContreateStartegy인 Quack, NoQuack 행동을 만들자

package com.patterns.StrategyPattern;

public interface  QuackBehavior {
	public void quack();
}
package com.patterns.StrategyPattern;

public class Quack implements QuackBehavior{

	@Override
	public void quack() {
		  System.out.println("꽥꽥");
	}

}
package com.patterns.StrategyPattern;

public class NoQuack  implements QuackBehavior {

	@Override
	public void quack() {
		System.out.println("소리를 못냅니다.");
	}

}
그리고 Duck 추상 클래스를 만들어준다.
package com.patterns.StrategyPattern;

public abstract class Duck {

	FlyBehavior 	flyBehavior;
	QuackBehavior 	quackBehavior;

	public void swim() {
		System.out.println("물에 떠있습니다.");
	}

	public abstract void display();

	public void setFlyBehavior(FlyBehavior flyBehavior) {
		this.flyBehavior = flyBehavior;
	}

	public void getFlyBehavior() {
		flyBehavior.fly();
	}

	public void setQuackBehavior(QuackBehavior quackBehavior) {
		this.quackBehavior = quackBehavior;
	}

	public void getQuackBehavior() {
		quackBehavior.quack();
	}
}​

실제 오리객체인 청둥오리객체 생성

package com.patterns.StrategyPattern;

public class MallardDuck extends Duck {

	public MallardDuck() {
		flyBehavior = new FlyWithWings();
		quackBehavior = new Quack();
	}

	@Override
	public void display() {
		System.out.println("청둥오리 입니다.");
	}

}

 

개발내용 테스트

package com.patterns.StrategyPattern;

public class DuckGame {
	public static void main(String[] args) {
		Duck mallard = new MallardDuck();
		mallard.getQuackBehavior();
		mallard.getFlyBehavior();
		System.out.println();
		mallard.setFlyBehavior(new FlyNoWay());
		mallard.getQuackBehavior();
		mallard.getFlyBehavior();
		System.out.println();
		mallard.setQuackBehavior(new NoQuack());
		mallard.getQuackBehavior();
		mallard.getFlyBehavior();
	}
}

 마지막으로 실제 오리 게임에서

오리 객체를 생성해주면 오리는 fly, quack에 대해 기본 상태를 가지게 되고
지금부터는 코드의 복잡함 없이 각 클래스에서 필요한 메서드만 호출하면 되고
FlyBehavior, QuackBehavior쪽에서는 그에 맞는 행동만 정의하고 구현해주면 된다.

참고 원문: https://www.crocus.co.kr/1526?category=337544 

 

모든 클래스간의 복잡한 로직(상호작용)을 캡슐화하여 하나의 클래스에 위임하여 처리하는 패턴이다.

즉, M:N의 관계에서 M:1의 관계로 복잡도를 떨어뜨려 유지 보수 및 재사용의 확장성에 유리한 패턴이다.

 

커뮤니케이션을 하고자 하는 객체가 있을 때 서로가 커뮤니케이션 하기 복잡한 경우 이를 해결해주고 서로 간 쉽게 해주며 커플링을 약화시켜주는 패턴이다.

 

객체들간 커뮤니케이션 정의가 잘 돼있으나 복잡할 때(종합이 필요할 때) 사용한다.

이와 비슷한 패턴은 파사드 패턴(Facade Pattern)과 옵저버 패턴(Observer Pattern)이 있다.

 

위의 그림에서 관제탑이 중재자가 되고 각 비행기가 서로 커뮤니케이션을 하기 위해서는

관제탑을 거쳐 M:N이 아닌 M:1의 관계로 커뮤니케이션을 진행하게 된다.

 

프로그램을 작성하다 보면 상호작용을 해야하는 경우 객체들 간의 결합도가 증가하고 유연성이 떨어지는 경우가 발생하게 된다.

이때 중재자 패턴에서는 서로 명령을 주고 받을 수 있는 형식이 있다고 했을 때 서로 명령을 주고 받는 부분을 중재하는 형식을 정의를 한다.

그리고, 원래 서로 명령을 주고 받았던 개체들은 중재자 개체를 알게 하고 중재자 개체는 이들 개체를 알게 한다.

 

이제 특정 개체가 명령을 내릴 필요가 있으면 중재자 개체에게 전달하기만 하면 되고, 

중재자는 해당 명령을 자신이 알고 있는 개체들 중에 적절한 개체에게 전달만 하면 된다.

 

이처럼 중재자 패턴을 사용하면 복잡한 상호작용을 하기 위한 복잡한 관계를 단순화시킬 수 있게 된다.

 

그리고 중재자가 notify한다는 의미로 옵저버 패턴과 매우 유사한데,

옵저버는 Subscriber이 받기만 하는데 Mediator은 서로 통신한다는 것에서 차이가 있다.

 

중재자 패턴 클래스 다이어그램

+Mediator은 Colleague(동료)가 Mediator에서 알려주는 것을 의미하고

ColleagueA도 마찬가지로 Mediator가 ColleagueA,B에게 알리는 것을 의미한다.

 

중재자 패턴 예제

ISource 인터페이스

package com.patterns.MediatorPattern;

public interface ISource {
	public void setMediator(Mediator mediator);
	public void eventOccured(String event);
}

 

TcpComm 클래스

  ISource를 구현하는 Concrete 부분.

  mediator을 설정하고 mediator에게 onEvent로 메시지를 전달한다.

package com.patterns.MediatorPattern;

public class TcpComm implements ISource {
	Mediator mediator;

	@Override
	public void setMediator(Mediator mediator) { // 중재자 설정
		this.mediator = mediator;
	}

	@Override
	public void eventOccured(String event) { // 이벤트의 전달
		mediator.onEvent("TCP comm", event);
	}
}

 

SystemSignal 클래스 : TcpComm 클래스와 같다.

package com.patterns.MediatorPattern;

public class SystemSignal implements ISource{
	  Mediator mediator;

	@Override
	public void setMediator(Mediator mediator) { // 중재자 설정
		this.mediator = mediator;
	}

	@Override
	public void eventOccured(String event) { // 이벤트의 전달
		mediator.onEvent("System", event);
	}
}

 

Mediator 클래스 : onEvent를 받으면 해당하는 메시지를 receiveEvent를 통해 보낸다.

package com.patterns.MediatorPattern;

import java.util.ArrayList;
import java.util.List;

public class Mediator {
	
	List<IDestination> list = new ArrayList<IDestination>();

	public void addDestination(IDestination destination) {
		list.add(destination);
	}

	public void onEvent(String from, String event) {
		for (IDestination each : list) { // 이벤트의 전송
			each.receiveEvent(from, event);
		}
	}
}

 

IDestionation 인터페이스

package com.patterns.MediatorPattern;

public interface IDestination {
	public void receiveEvent(String from, String event);
}

 

Display 클래스 : Idestianation 인터페이스를 구현하고 있다.

package com.patterns.MediatorPattern;

public class Display implements IDestination {
	@Override
	public void receiveEvent(String from, String event) {
		System.out.println("Display : from " + from + " event : " + event);
	}
}

 

Log 클래스 : Display 클래스와 같다.

package com.patterns.MediatorPattern;

public class Log implements IDestination{

	@Override
	public void receiveEvent(String from, String event) {
		 System.out.println("Log : from " + from + " event : " + event);
	}
}

 

MediatorTest 

각 생성자를 만들고 destination은 Display, Log로 해준후 이벤트를 전달하여 아래와 같은 결과값을 얻을 수 있다.

package com.patterns.MediatorPattern;

public class MediatorTest {
	public static void main(String[] args) {
		Mediator mediator = new Mediator();
		  
	    ISource tcp = new TcpComm();
	    tcp.setMediator(mediator);
	    ISource system = new SystemSignal();
	    system.setMediator(mediator);
	  
	    mediator.addDestination(new Display());
	    mediator.addDestination(new Log());
	    tcp.eventOccured("connected");
	    tcp.eventOccured("disconnected");
	  
	    system.eventOccured("key input");
	    system.eventOccured("mouse input");
	}
}
Display : from TCP comm event : connected
Log : from TCP comm event : connected
Display : from TCP comm event : disconnected
Log : from TCP comm event : disconnected
Display : from System event : key input
Log : from System event : key input
Display : from System event : mouse input
Log : from System event : mouse input

출처: https://www.crocus.co.kr/1542?category=337544 

 

중재자 패턴(Mediator Pattern)

중재자 패턴(Mediator Pattern) 모든 클래스간의 복잡한 로직(상호작용)을 캡슐화하여 하나의 클래스에 위임하여 처리하는 패턴이다. 즉, M:N의 관계에서 M:1의 관계로 복잡도를 떨어뜨려 유지 보수

www.crocus.co.kr

 

반복자 패턴(Iterator Pattern)

  반복자 패턴 이터레이터 패턴이라고도 하고 Cursor라고도 한다.

즉, 대상으로 삼는 Context는 객체를 여러개 담고 있는 Aggregate Object(Collection Obj)이다.(ex : ArrayList)

 

이런 각각의 Aggregate Objelement를 방문을 원할 때 반복자 패턴을 이용한다.

하지만 Aggregate Obj의 구현 외부에는 노출되면 안된다는 전제가 있다.

 

일반적인 ArrayList나 Built-in Array를 쓰면 매 수정마다 Client 프로그램 수정이 계속 필요하다.

이를 개성하기 위해 Iterator 인터페이스를 하나만 구현한다면

클라이언트에서는 그에 맞게만 작성하면 되는 이점이 있다.

 

다시 말하자면 컬렉션 객체 안에 들어있는 모든 항목에 접근하는 방식이 통일되어 있으면 어떤 종류의 집합체에 대해서도 사용할 수 있는 다형적인 코드를 만들수 있다.

이터레이터 패턴을 사용하면 모든 항목에 일일이 접근하는 작업을 컬렉션 객체가 아닌 반복자 객체에서 맡게 된다

이렇게 하면 집합체의 인터페이스 및 구현이 간단해질 뿐 아니라, 집합체에서는 반복작업에서 손을 떼고 원래 자신이 할 일(객체 컬렉션 관리)에만 전념할 수 있다.

반복자 패턴에서 중요하게 여겨지는 디자인 원칙 SRP(Single Responsibility Principle)이다.

클래스는 단 하나만의 변경이 되어야하고 여러개면 분해해야 한다.(클래스 단일 책임 원칙)

 

반복자 패턴 클래스 다이어 그램

반복자 패턴 예제

 Aggregate(골재) 인터페이스 : 추상 메서드를 하나 가지고 있다.

package com.patterns.IteratorPattern;

public interface Aggregate {
	public abstract Iterator createIterator();
}

Iterator 인터페이스의  위의 클래스 다이어그램에 있는 3가지 메서드를 가진다.

package com.patterns.IteratorPattern;

public interface Iterator {
	
	public boolean hasNext();
	public Object next();
	public void remove();
}

CoffeeMenuIterator 클래스 : 위의 내용을 구현해두고 있다.

package com.patterns.IteratorPattern;

public class CoffeeMenuIterator implements Iterator {

	private CoffeeMenu coffeeMenu;
	private int index;

	public CoffeeMenuIterator(CoffeeMenu coffeeMenu) {
		this.coffeeMenu = coffeeMenu;
		this.index = 0;
	}

	@Override
	public boolean hasNext() {
		return index < coffeeMenu.getLength();
	}

	@Override
	public Object next() {
		Coffee coffee = coffeeMenu.getCoffee(index);
		index++;
		return coffee;
	}

	@Override
	public void remove() {
		coffeeMenu.removeCoffee(index);
	}

}

 

Coffee 클래스 :  Coffee에 대한 정보를 가진다.

package com.patterns.IteratorPattern;

public class Coffee {
	private String name;
	private int cost;

	public Coffee(String name, int cost) {
		this.name = name;
		this.cost = cost;
	}

	public String getName() {
		return name;
	}

	public int getCost() {
		return cost;
	}
}

CoffeeMenu 클래스

Coffee의 객체를 배열로 만들어 이용한다.

이때 CoffeeMenuIterator createIterator()을 통해 현재 객체를 보냄으로써 이터레이터 객체를 생성 할 수 있다.

package com.patterns.IteratorPattern;

public class CoffeeMenu implements Aggregate {
	private Coffee[] coffees;
	private int size = 0;

	public CoffeeMenu(int size) {
		coffees = new Coffee[size];
	}

	public Coffee getCoffee(int index) {
		return coffees[index];
	}

	public void addCoffee(Coffee coffee) {
		coffees[size] = coffee;
		size++;
	}

	public int getLength() {
		return size;
	}

	public void removeCoffee(int index) {
		if (index + 1 == size) {
			return;
		}
		for (int i = index; i < size - 1; i++) {
			coffees[i] = coffees[i + 1];
		}
		size--;
	}

	public CoffeeMenuIterator createIterator() {
		return new CoffeeMenuIterator(this);
	}
}

CoffeeTest 클래스

package com.patterns.IteratorPattern;

public class CoffeeTest {
	public static void main(String[] args) {
		CoffeeMenu coffeeMenu = new CoffeeMenu(3);

		coffeeMenu.addCoffee(new Coffee("아이스 아메리카노", 3000));
		coffeeMenu.addCoffee(new Coffee("아이스 라떼", 4000));
		coffeeMenu.addCoffee(new Coffee("더치 블랙", 3800));

		CoffeeMenuIterator iterator = coffeeMenu.createIterator();

		while (iterator.hasNext()) {
			Coffee coffee = (Coffee) iterator.next();
			System.out.println("커피 이름 : " + coffee.getName());
			System.out.println("커피 가격 : " + coffee.getCost());
		}

		System.out.println("=== Remove call ===");
		iterator = coffeeMenu.createIterator();
		iterator.remove();

		while (iterator.hasNext()) {
			Coffee coffee = (Coffee) iterator.next();
			System.out.println("커피 이름 : " + coffee.getName());
			System.out.println("커피 가격 : " + coffee.getCost());
		}
	}
}

 

커피 이름 : 아이스 아메리카노
커피 가격 : 3000
커피 이름 : 아이스 라떼
커피 가격 : 4000
커피 이름 : 더치 블랙
커피 가격 : 3800
=== Remove call ===
커피 이름 : 아이스 라떼
커피 가격 : 4000
커피 이름 : 더치 블랙
커피 가격 : 3800

 

정리

컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있는 방법을 제공한다.
Iterator 패턴은 일련의 순서를 가진 데이터 집합에 대하여 순차적인 접근을 지원하는 패턴이다.

단일 책임 원칙 (SRP: Single Responsibility Principle) 을 따른다.

 

코드를 변경할 만한 이유가 두가지가 되면 그만큼 그 클래스를 나중에 고쳐야 할 가능성이 커지게 될 뿐 아니라, 

디자인에 있어서 두 가지 부분이 동시에 영향이 미치게 된다.

따라서 SRP 원칙에 따르면 한 역할은 한 클래스에서만 맡게 해야 한다.

 

참고원문: https://www.crocus.co.kr/1532?category=337544

 

반복자 패턴(Iterator Pattern, Cursor)

반복자 패턴(Iterator Pattern) 반복자 패턴은 이터레이터 패턴이라고도 하고 Cursor라고도 한다. 즉, 대상으로 삼는 Context는 객체를 여러개 담고 있는 Aggregate Object(Collection Obj)이다.(ex : ArrayList..

www.crocus.co.kr

 

옵저버 패턴(Observer Pattern)

  한 객체의 상태가바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다. 

 즉, A 오브젝트가 B 오브젝트 상태에 관심이 있어서 B 오브젝트 상태가 변할때 마다 A 오브젝트가 파악할 수 있게 해주는 패턴이다.

 

신문 구독이 대표적인 예이다.(신문 발행자, 구독자 관계)

 

옵저버 패턴의 특징은 Loose Coupling을 잘 만족하여 서로 아는 지식은 적지만, 커뮤니케이션은 가능하다는 점이다.

즉, 사용되는 디자인 원칙은 다음과 같다.

 

서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.

 

옵저버 패턴 클래스 다이어그램

 

 

옵저버 패턴 예제

Observer 인터페이스 정의

인터페이스 내에 update 메서드를 정의한다.

package com.patterns.ObserverPattern;

public interface Observer {
	public void update(String title, String news);
}

 

Publisher 인터페이스 정의

인터페이스 내에 아래 3가지 메서드를 정의하여 publisher이 행동 할 수 있는 것들을 명시한다.

package com.patterns.ObserverPattern;

public interface Publisher {
	
	public void registerObserver(Observer observer);
	public void removeObserver(Observer observer);
	public void notifyObservers();
}

NewsPublisher 클래스 구현

   Publisher 인터페이스를 직접 이용하는 클래스

 

   observers를 ArrayList에 모을 수 있게 한다.

   그 외에는 아래 코드를 참고한다.

package com.patterns.ObserverPattern;

import java.util.ArrayList;

public class NewsPublisher implements Publisher {
	
	private ArrayList<Observer> observers;
	private String title;
	private String news;

	public NewsPublisher() {
	    observers = new ArrayList<>();
	    title = null;
	    news = null;
	  }

	@Override
	public void registerObserver(Observer observer) {
		observers.add(observer);
	}

	@Override
	public void removeObserver(Observer observer) {
		int index = observers.indexOf(observer);
		observers.remove(index);
	}

	@Override
	public void notifyObservers() {
		for (Observer observer : observers) {
			observer.update(title, news);
		}
	}

	public void setNews(String title, String news) {
		this.title = title;
		this.news = news;
		notifyObservers();
	}
}

 

NewsSubscriber 클래스 구현

   Observer 인터페이스를 직접 이용하는 클래스 

 

   NewsSubscriber이 생성될 때 자신의 publisher이 누구인지 지정해준다.

   그때 자신의 publisher의 registerObserver에 의해 등록이 될 것이고

   추후 publisher의 notifyObservers()에서 publisher의 모든 observer가 내용을 전달받게 된다.

package com.patterns.ObserverPattern;

public class NewsSubscriber implements Observer {

	private String observerName;
	private String news;
	private Publisher publisher;

	public NewsSubscriber(String subscriber, Publisher publisher) {
	    this.observerName = subscriber;
	    this.publisher = publisher;
	    publisher.registerObserver(this);
	  }

	@Override
	public void update(String title, String news) {
		this.news = title + "!!! " + news;
		display();
	}

	private void display() {
		System.out.println("=== " + observerName + " 수신 내용 ===\n" + news + "\n");
	}

}

 

ObserverPatternTest 클래스 구현

package com.patterns.ObserverPattern;

public class ObserverPatternTest {
	public static void main(String[] args) {
		
		PublisherImpl newsPublisher = new PublisherImpl();

		SubscriberImpl newsSubscriber1 = new SubscriberImpl("옵저버1", newsPublisher);
		SubscriberImpl newsSubscriber2 = new SubscriberImpl("옵저버2", newsPublisher);

		newsPublisher.setNews("특보", "옵저버 패턴이 만들어졌습니다.");
		newsPublisher.setNews("정정", "옵저버 패턴으로 내용이 정정됨을 알립니다.");

		newsPublisher.removeObserver(newsSubscriber1);
		newsPublisher.setNews("속보", "누군가 구독 해제를 했습니다.");
	}
}

 

 

정리

옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에게 연락이 가고 자동으로 정보가 갱신되는 1:N 의 관계를 정의한다.


옵저버 패턴은 주제와 옵저버가 느슨하게 결합되어있는 객체 디자인을 제공한다.

 

주제가 옵저버에 대해서 아는 것은 옵저버가 특정 인터페이스(Observer 인터페이스)를 구현 한다는 것 뿐. 
옵저버는 언제든지 새로 추가할 수 있음. (주제는 Observer인터페이스 구현하는 객체 목록에만 의존하기때문)


주제나 옵저버가 바뀌더라도 서로에게 전혀 영향을 주지않는다. 

그래서 주제와 옵저버는 서로 독립적으로 재 사용할수 있다. 

느슨하게 결합하는 디자인을 사용하면 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할수 있다. (객체 사이의 상호 의존성을 최소화 할 수 있기 때문) 

새로운 형식의 옵저버를 추가하려해도 주제를 전혀 변경할 필요가 없음. (새로운 클래스에서 Observer 인터페이스만 구현해주면됨) 

옵저버 패턴은 푸시 방식과 풀 방식으로 언제든지 구현할 수 있다.

JAVA에서 기본으로 Observable 클래스와 Observer 인터페이스를 제공한다.


Swing, Android 등에서 UI관련된 곳에서 이 옵저버 패턴이 많이 사용된다.

참고 원문: https://www.crocus.co.kr/1530?category=337544 

 

옵저버 패턴(Observer Pattern)

옵저버 패턴(Observer Pattern) 한객체의 상태가바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다. 즉, A

www.crocus.co.kr

 

템플릿 메소드 패턴(Template Method Pattern)

알고리즘의 골격을 정의합니다. 템플릿 메소드를 사용하면 알고리즘 일부 단계를 서브클래스에서 구현할 수 있으며, 알고리즘의 구조는 그대로 유지하면서 알고리즘의 특정 단계를 서브클래스에서 재정의할 수도 있습니다.

 

 

 

 

 

템플릿 메서드 패턴(Template Method Pattern)


어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화해 전체 일을 수행하는 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내역을 바꾸는 패턴이다.


즉, 전체적으로는 동일하면서 부분적으로는 다른 구문으로 구성된 메서드의 코드 중복을 최소화 할 때 유용하다. 
다른 관점에서 보면 동일한 기능을 상위 클래스에서 정의하면서 확장/변화가 필요한 부분만 서브 클래스에서 구현할 수 있도록 한다. 

 

좀더 이야기 해보자면 알고리즘에 대한 Framework 정의를 통해 Subclass의 다양한 method calls를 이룬다.

실질적인 behavior은 하위 클래스에서 implement 하도록 만든다.

 

상위 클래스에서는 공통적, 하위 클래스에서는 다양하게 코딩을 한다.

즉, 확장적이고 공통적인건 상위 클래스에서 정의하고 그렇지 않은 것은 imterface로 하위 클래스에서 정의한다.

 

즉, Template method는 각 스텝이 method의 call로 진행된다.

(알고리즘의 큰 step를 정해주고 부가적인 것은 subclass가 해결하도록 하는 것이다.)

 

템플릿 메서드 패턴의 특징 하나의 클래스가 전체 알고리즘은 보호하고 진행 할 수 있다.(Framework 생성과 유사)

재사용을 통해 코드 중복을 없애고 새로운 객체가 나타나도 그에 특화된 것만 코딩을 하면 되는 특징이 있다.

 

템플릿 메서드 패턴(Template Method Pattern)

 

AbstractClass
  템플릿 메서드를 정의하는 클래스

  하위 클래스에 공통 알고리즘을 정의하고 하위 클래스에서 구현될 기능을 primitive 메서드 등으로 정의하는 클래스이다.


ConcreteClass
  물려받은 primitive 메서드 또는 hook 메서드를 구현하는 클래스

 

상위 클래스에 구현된 템플릿 메서드의 일반적인 알고리즘에서 하위 클래스에 적합하게 primitive 메서드 등을 오버라이드 하는 클래스이다.

 

템플릿 메서드 패턴 예제

아이스 아메리카노와 아이스 라떼를 만드는 법을 비교해보자.

(지극히 제 생각으로 만드는 것이고 일반 카페에서 만드는 방법과 다를 수 있습니다.)

 

아이스 아메리카노 아이스 라떼
1. 물을 끓인다.
2. 끓는 물에 에스프레소를 넣는다.
3. 얼음을 넣는다.
4. 시럽을 넣는다.
1. 물을 끓인다.
2. 끓는 물에 에스프레소를 넣는다
3. 얼음을 넣는다
4, 우유를 넣는다.

ㅁ 일반코딩 방법

IceAmericano.java

package com.patterns.TemplateMethodPattern.Before;

public class IceAmericano {
	public void makeIceAmericano() {
		boilWater();
		putEspresso();
		putIce();
		putSyrup();
	}

	private void boilWater() {
		System.out.println("물을 끓인다.");
	}

	private void putEspresso() {
		System.out.println("끓는 물에 에스프레소를 넣는다.");
	}

	private void putIce() {
		System.out.println("얼음을 넣는다.");
	}

	private void putSyrup() {
		System.out.println("시럽을 넣는다.");
	}
}

IceLatte.java

package com.patterns.TemplateMethodPattern.Before;

public class IceLatte {
	public void makeIceLatte() {
		boilWater();
		putEspresso();
		putIce();
		putMilk();
	}

	private void boilWater() {
		System.out.println("물을 끓인다.");
	}

	private void putEspresso() {
		System.out.println("끓는 물에 에스프레소를 넣는다.");
	}

	private void putIce() {
		System.out.println("얼음을 넣는다.");
	}

	private void putMilk() {
		System.out.println("우유를 넣는다.");
	}

}

NoTemplateMethodPattern.java 

package com.patterns.TemplateMethodPattern.Before;

public class NoTemplateMethodPattern {
	public static void main(String[] args) {
		
		IceAmericano americano = new IceAmericano();
		IceLatte latte = new IceLatte();

		americano.makeIceAmericano();
		System.out.println("===");
		latte.makeIceLatte();
	}
}

 

그런데 위의 코드는 중복되는 코드가 많다.

따라서 한번 추상화 시켜 클래스 다이어그램으로 나타내보자.

 

1단계 추상화  추가 추상화

 

무언가 Coffee 클래스에 공통된 것을 두고, 상속을 통해 자식 클래스에서 마무리하면 될 것 같다.

하지만 여기서 makeCoffee를 한단계 더 추상화 할 수 있다.

하지만 여기서 한단계 더 추상화 할 수 있다.

ㅁ 템플릿 메소드 패턴코딩 방법

Coffee.java

package com.patterns.TemplateMethodPattern;

public abstract class Coffee {
	final void makeCoffee() {
		boilWater();
		putEspresso();
		putIce();
		putExtra();
	}

	// SubClass에게 확장/변화가 필요한 코드만 코딩하도록 한다.
	abstract void putExtra();

	// 공통된 부분은 상위 클래스에서 해결하여 코드 중복을 최소화 시킨다.
	private void boilWater() {
		System.out.println("물을 끓인다.");
	}

	private void putEspresso() {
		System.out.println("끓는 물에 에스프레소를 넣는다.");
	}

	private void putIce() {
		System.out.println("얼음을 넣는다.");
	}
}

IceAmericano.java

package com.patterns.TemplateMethodPattern;

public class IceAmericano extends Coffee {
	@Override
	void putExtra() {
		System.out.println("시럽을 넣는다.");
	}
}

IceLatte.java

package com.patterns.TemplateMethodPattern;

public class IceLatte extends Coffee {
	@Override
	void putExtra() {
		System.out.println("우유를 넣는다.");
	}
}

 

CoffeeTest.java : 테스트 

package com.patterns.TemplateMethodPattern;

public class CoffeeTest {
	public static void main(String[] args) {
		
		IceAmericano americano = new IceAmericano();
		IceLatte latte = new IceLatte();

		americano.makeCoffee();
		System.out.println("===");
		latte.makeCoffee();
	}

}

 

정리

 

템플릿 메소드 패턴은 "알고리즘의 뼈대"를 맞추는 것에 있다. 

즉, 전체적인 레이아웃을 통일 시키지만 상속받은 클래스로 하여금 어느정도 유연성을 주도록하는 디자인 패턴이다. 

추상 메소드(abstrcat method)와 훅 메소드(hook method)를 적절히 사용해서 

전체적인 알고리즘의 뼈대를 유지하되 유연하게 기능을 변경할 수 있도록 하고자 할 때 사용하면 유용하다.

참고 출처: https://www.crocus.co.kr/1531?category=337544 

 

템플릿 메서드 패턴(Template Method Pattern)

템플릿 메서드 패턴(Template Method Pattern) 어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화해 전체 일을 수행하는 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내역을 바

www.crocus.co.kr

 

 

데코레이터 패턴(Decorator Pattern)은 Flyweight 패턴, Adapter 패턴, Bridge 패턴처럼 구조 패턴 중 하나로, 런타임에서 객체의 기능을 수정하는데 사용되는 패턴입니다.

 

데코레이터 패턴 이해 및 예제

데코레이터 패턴은 상속(Inheritance)과 합성(Composition)을 사용하여 객체에 동적으로 책임을 추가할 수 있게 합니다. 이 방법은 서브 클래스를 생성하는 것보다 유연한 방법을 제공합니다.

 

예를 들어 다양한 종류의 자동차를 구현한다고 가정해보겠습니다. Car 라는 인터페이스를 정의하고 이를 구현하는 Basic Car 를 두고, 이를 확장하여 구현하는 Sports Car, Luxury Car 를 정의해보겠습니다.

 

그림으로 나타내면 다음과 같습니다.

그런데 만약 런타임 단계에서 Sports Car의 특징과 Luxury Car의 특징을 모두 갖고 있는 Car 를 얻고 싶다면 어떻게 해야할까요? 일반적으로 둘의 기능을 갖는 또다른 서브 클래스를 정의할 수도 있겠습니다만, 데코레이터 패턴을 사용하여 해결해보도록 하겠습니다.

 

Car.java

package com.patterns.DecoratorPattern;

public interface Car {
	public void assemble();
}

 

BasicCar.java

package com.patterns.DecoratorPattern;

public class BasicCar implements Car {
	 
	@Override
	public void assemble() {
		System.out.print("Basic Car.");
	}
}

 

CarDecorator.java

package com.patterns.DecoratorPattern;

public class CarDecorator implements Car {
	 
	protected Car car;
	
	public CarDecorator(Car c){
		this.car=c;
	}
	
	@Override
	public void assemble() {
		this.car.assemble();
	}
}

 

SportsCar.java

package com.patterns.DecoratorPattern;

public class SportsCar extends CarDecorator {
	 
	public SportsCar(Car c) {
		super(c);
	}
 
	@Override
	public void assemble(){
		super.assemble();
		System.out.print(" Adding features of Sports Car.");
	}
}

 

LuxuryCar.java

package com.patterns.DecoratorPattern;

public class LuxuryCar extends CarDecorator {
	 
	public LuxuryCar(Car c) {
		super(c);
	}
	
	@Override
	public void assemble(){
		super.assemble();
		System.out.print(" Adding features of Luxury Car.");
	}
}

 

이제 모든 클래스가 준비되었으니 테스트 코드를 작성해보겠습니다.

 

DecoratorPatternTest.java

package com.patterns.DecoratorPattern;

public class DecoratorPatternTest {
	 
	public static void main(String[] args) {
		Car sportsCar = new SportsCar(new BasicCar());
		sportsCar.assemble();
		System.out.println("\n*****");
		
		Car sportsLuxuryCar = new SportsCar(new LuxuryCar(new BasicCar()));
		sportsLuxuryCar.assemble();
	}
}
Basic Car. Adding features of Sports Car.
*****
Basic Car. Adding features of Luxury Car. Adding features of Sports Car.

보시다시피 CarDecorator 를 사용하여 SportsCar와 LuxuryCar 의 기능을 합성할 수 있게 됐습니다. 이렇게 작성할 경우 클래스의 종류가 많아질 수록 큰 효과를 발휘할 수 있는데요. 런타임에서 다양한 기능을 조합하여 기능을 사용할 수 있습니다.

 

중요 포인트

  • 데코레이터 패턴은 런타임에서 유연하게 객체의 기능들을 수정하고 조합하는데 유용하게 사용되는 패턴입니다.
  • 단점이 있다면, 다수의 데코레이터 객체를 생성하고 사용해야 한다는 것입니다.
  • JDK 에서 FileReader, BufferedReader 등 IO 클래스에 사용되는 패턴입니다.

 

참고 원문 : https://readystory.tistory.com/195?category=822867 

 

[구조 패턴] 데코레이터 패턴(Decorator Pattern) 이해 및 예제

데코레이터 패턴(Decorator Pattern)은 Flyweight 패턴, Adapter 패턴, Bridge 패턴처럼 구조 패턴 중 하나로, 런타임에서 객체의 기능을 수정하는데 사용되는 패턴입니다. 구조 패턴(Structural Pattern)이란? 구..

readystory.tistory.com

 

브릿지 패턴(Bridge Pattern)은 Flyweight 패턴, Adapter 패턴, Decorator 패턴처럼 구조 패턴 중 하나로, 두 인터페이스에 계층 구조(Hierarchy)를 가지고 있을 때 인터페이스를 구현(implements)으로부터 분리하고 클라이언트 프로그램으로부터 구현 세부사항을 숨기기 위해 사용되는 패턴입니다.

 

브릿지 패턴 이해 및 예제

디자인 패턴의 교과서인 GoF에서는 브릿지 패턴에 대해 다음과 같이 정의하고 있습니다.

추상화(abstraction)를 구현으로부터 분리하여 각각 독립적으로 변화할 수 있도록 하는 패턴

 

예제를 통해 이해를 돕도록 하겠습니다.

예를 들어 다음과 같은 구조의 인터페이스를 구현하는 프로그램을 만든다고 가정해보겠습니다.

 

보시다시피 Shape 과 Color 라는 2가지 인터페이스가 있습니다.

이제 브릿지 패턴을 사용하여 두 인터페이스의 compoisition 을 구성해보도록 하겠습니다.

 

Color.java

package com.patterns.BridgePattern;

public interface Color {
	 
	public void applyColor();
}

Shape.java

package com.patterns.BridgePattern;

public abstract class Shape {
	//Composition
	protected Color color;
	
	//constructor with implementor as input argument
	public Shape(Color c){
		this.color=c;
	}
	
	abstract public void applyColor();
}

Shape 클래스가 Color 인터페이스를 소유하고 있고, applyColor() 메소드는 abstract 로 선언하여 하위 클래스에게 구현을 위임합니다.

 

이번에는 이 Shape 추상 클래스를 상속하여 구체화 하는 Triangle 클래스와 Pentagon 클래스를 정의해보겠습니다.

 

Triangle.java

package com.patterns.BridgePattern;

public class Triangle extends Shape {
	 
	public Triangle(Color c) {
		super(c);
	}
 
	@Override
	public void applyColor() {
		System.out.print("Triangle filled with color ");
		color.applyColor();
	} 
 
}

Pentagon.java

package com.patterns.BridgePattern;

public class Pentagon extends Shape {
	 
	public Pentagon(Color c) {
		super(c);
	}
 
	@Override
	public void applyColor() {
		System.out.print("Pentagon filled with color ");
		color.applyColor();
	} 
 
}

마지막으로 각 Shape 클래스가 소유할 Color 인터페이스의 구현 객체를 정의해보겠습니다. 컬러는 RedColor 와 GreenColor 로 선언하겠습니다.

 

RedColor.java

package com.patterns.BridgePattern;

public class RedColor implements Color{
	 
	public void applyColor(){
		System.out.println("red.");
	}
}

GreenColor.java

package com.patterns.BridgePattern;

public class GreenColor implements Color{
	 
	public void applyColor(){
		System.out.println("green.");
	}
}

 

이제 위에서 정의한 클래스들을 사용하여 테스트 해보도록 하겠습니다.

 

BridgePatternTest.java

package com.patterns.BridgePattern;

public class BridgePatternTest {
	 
	public static void main(String[] args) {
		Shape tri = new Triangle(new RedColor());
		tri.applyColor();
		
		Shape pent = new Pentagon(new GreenColor());
		pent.applyColor();
	}
}

 

결과

Triangle filled with color red.
Pentagon filled with color green.

 

브릿지 디자인 패턴은 추상화(abstraction)와 구현(implement)이 독립적으로 다른 계층 구조를 가질 수 있고, 클라이언트 어플리케이션으로부터 구현을 숨기고 싶을 때 사용될 수 있습니다.

 

참고 원문 : https://readystory.tistory.com/194?category=822867 

 

[구조 패턴] 브릿지 패턴(Bridge Pattern) 이해 및 예제

브릿지 패턴(Bridge Pattern)은 Flyweight 패턴, Adapter 패턴, Decorator 패턴처럼 구조 패턴 중 하나로, 두 인터페이스에 계층 구조(Hierarchy)를 가지고 있을 때 인터페이스를 구현(implements)으로부터 분리하..

readystory.tistory.com

 

퍼사드 패턴(Facade Pattern)은 Flyweight 패턴, Adapter 패턴, Decorator 패턴처럼 구조 패턴 중 하나로, 클라이언트가 쉽게 시스템과 상호작용 할 수 있도록 도와주는 패턴입니다.

퍼사드 패턴 이해 및 예제

디자인 패턴의 교과서인 GoF에서는 퍼사드 패턴에 대해 다음과 같이 정의하고 있습니다.

퍼사드 패턴은 서브시스템을 더 쉽게 사용할 수 있도록 higher-level 인터페이스를 정의하고, 제공합니다. 

 

이해를 돕기 위해 예시를 들어보겠습니다.

MySql 과 Oracle 데이터베이스를 사용하는 인터페이스가 있고, 이를 이용해 HTML 리포트 또는 PDF 리포트를 생성해야 한다고 가정해보도록 하겠습니다.

 

우리는 이 인터페이스들을 서로 조합하여 리포트를 만들어야 합니다. 지금은 database connection 방법 2가지, generate report 방법 2가지의 인터페이스를 가지고서 조합을 해야 하지만 만약 경우의 수가 더 복잡해질 경우 일반적인 방법으로는 관리하기가 어려워질 것입니다.

 

그래서 우리는 퍼사드 패턴을 적용하여 wrapper 인터페이스를 제공하여 클라이언트가 보다 쉽게 관리할 수 있도록 도와보겠습니다.

 

MySqlHelper.java

package com.patterns.facadepattern;

import java.sql.Connection;

public class MySqlHelper {
	
	public static Connection getMySqlDBConnection(){
		// 실제 커넥션을 리턴해야 하지만, 예제이기에 null 을 리턴하겠습니다.
		return null;
	}
	
	public void generateMySqlPDFReport(String tableName, Connection con){
		// get data from table and generate pdf report
	}
	
	public void generateMySqlHTMLReport(String tableName, Connection con){
		// get data from table and generate pdf report
	}
}

 

OracleHelper.java

package com.patterns.facadepattern;

import java.sql.Connection;

public class OracleHelper {
 
	public static Connection getOracleDBConnection(){
		// 실제 커넥션을 리턴해야 하지만, 예제이기에 null 을 리턴하겠습니다.
		return null;
	}
	
	public void generateOraclePDFReport(String tableName, Connection con){
		// get data from table and generate pdf report
	}
	
	public void generateOracleHTMLReport(String tableName, Connection con){
		// get data from table and generate pdf report
	}
	
}

 

이번에는 퍼사드 패턴 인터페이스를 생성해보겠습니다. 타입 안정성을 위해서 디비 타입과 리포트 타입에 대해서는 Enum을 사용하도록 하겠습니다.

 

HelperFacade.java

package com.patterns.facadepattern;

import java.sql.Connection;

public class HelperFacade {
 
	public static void generateReport(DBTypes dbType, ReportTypes reportType, String tableName){
		Connection con = null;
		switch (dbType){
		case MYSQL: 
			con = MySqlHelper.getMySqlDBConnection();
			MySqlHelper mySqlHelper = new MySqlHelper();
			switch(reportType){
			case HTML:
				mySqlHelper.generateMySqlHTMLReport(tableName, con);
				break;
			case PDF:
				mySqlHelper.generateMySqlPDFReport(tableName, con);
				break;
			}
			break;
		case ORACLE: 
			con = OracleHelper.getOracleDBConnection();
			OracleHelper oracleHelper = new OracleHelper();
			switch(reportType){
			case HTML:
				oracleHelper.generateOracleHTMLReport(tableName, con);
				break;
			case PDF:
				oracleHelper.generateOraclePDFReport(tableName, con);
				break;
			}
			break;
		}
		
	}
	
	public static enum DBTypes{
		MYSQL,ORACLE;
	}
	
	public static enum ReportTypes{
		HTML,PDF;
	}
}

 

위 코드가 모두 준비되었다면 이제 메인 함수를 통해 테스트해보겠습니다.

 

FacadePatternTest.java

package com.patterns.facadepattern;

public class FacadePatternTest {
 
	public static void main(String[] args) {
		String tableName="Employee";
		
		//generating MySql HTML report and Oracle PDF report using Facade
		HelperFacade.generateReport(HelperFacade.DBTypes.MYSQL, HelperFacade.ReportTypes.HTML, tableName);
		HelperFacade.generateReport(HelperFacade.DBTypes.ORACLE, HelperFacade.ReportTypes.PDF, tableName);
	}
}

 

시다시피 퍼사드 패턴을 사용하게 되면 클라이언트 사이드에서 무겁게 로직을 작성할 필요 없이 쉽고 깔끔하게 리포트를 생성할 수 있게 됩니다.

 

퍼사드 패턴의 중요 포인트!

  • 퍼사드 패턴은 클라이언트 어플리케이션의 헬퍼 역할을 하는 것이지, 서브시스템 인터페이스를 숨기는 것은 아닙니다.
  • 퍼사드 패턴은 특정 기능에 대해 인터페이스의 수가 확장되고(위 예제로 치면 디비 종류나 리포트 종류가 늘어난다는 등), 시스템이 복잡해질 수 있는 상황에서 사용하기 적합합니다.
  • 퍼사드 패턴은 비슷한 작업을 해야하는 다양한 인터페이스들 중 하나의 인터페이스를 클라이언트에 제공해야 할 때 적용하는 것이 좋습니다.
  • 팩토리 패턴과 종종 함께 사용됩니다.

참조원문 : https://readystory.tistory.com/193?category=822867

 

플라이웨이트 패턴(Flyweight Pattern)은 Facade 패턴, Adapter 패턴, Decorator 패턴처럼 구조 패턴 중 하나로, 많은 수의 객체를 생성해야 할 때 사용하는 패턴입니다.

플라이웨이트 패턴 이해 및 예제

디자인 패턴의 교과서인 GoF에서는 플라이웨이트 패턴에 대해 다음과 같이 정의하고 있습니다.

'공유(Sharing)'를 통하여 대량의 객체들을 효과적으로 지원하는 방법

이처럼 플라이웨이트 패턴은 많은 수의 객체를 생성해야 할 때 주로 쓰입니다.

 

모든 객체는 저마다의 메모리를 할당받습니다. 따라서 모바일 기기나 임베디드 시스템 등 저용량 메모리를 지원하는 기기에서는 메모리를 관리하는 것이 아주 중요합니다.

 

플라이웨이트 패턴은 공유 객체에 의해 메모리에 로드 되는 객체의 개수를 줄일 수 있습니다.

 

플라이웨이트를 적용하기에 앞서 몇 가지 확인할 것이 있습니다.

아래 중 해당하는 사항이 많은 상황일수록 플라이웨이트 패턴을 적용하기에 적합합니다.

  • 어플리케이션에 의해 생성되는 객체의 수가 많아야 한다.
  • 생성된 객체가 오래도록 메모리에 상주하며, 사용되는 횟수가 많다.
  • 객체의 특성을 내적 속성(Intrinsic Properties)과 외적 속성(Extrinsic Properties)으로 나눴을 때, 객체의 외적 특성이 클라이언트 프로그램으로부터 정의되어야 한다.

다른 건 어느 정도 알겠는데, 객체의 내적 속성과 외적 속성이라니 생소하게 느껴지셨을 겁니다.

하지만 플라이웨이트 패턴을 적용하기 위해서는 객체의 속성을 내적 속성과 외적 속성으로 나누어야 합니다.

그렇다면 내적 속성과 외적 속성은 무엇일까요?

 

객체의 내적 속성은 객체를 유니크하게 하는 것이고, 외적 속성은 클라이언트의 코드로부터 설정되어 다른 동작을 수행하도록 사용되는 특성입니다. 예를 들어, Circle 이라는 객체는 color와 width라는 외적 속성을 가질 수 있습니다.

 

자, 이제 플라이웨이트 패턴의 예제를 살펴볼 텐데, 그러기 위해서 우리는 Shared Objects를 리턴하는 Flyweight Factory를 생성해야 합니다. 이번 예제에서는 Line과 Oval을 통해 도형을 그리는 코드를 작성해보도록 하겠습니다.

 

우리는 먼저 Shape 이라는 인터페이스를 만들고, 그에 대한 구현 객체로 각각 Line과 Oval을 만들어주도록 하겠습니다.

이때 Oval과 Line에 각각 다른 특성을 부여할 건데, Oval은 주어진 색상으로 Oval을 채울지 결정하기 위한 내적 속성(Intrinsic Property)를 가질 것이고, Line은 그러한 내적 속성을 갖고 있지 않을 것입니다.

 

Shape.java

package com.patterns.flyweightpattern;

import java.awt.Color;
import java.awt.Graphics;
 
public interface Shape {
 
    public void draw(Graphics g, int x, int y, int width, int height, Color color);
}

 

Line.java

package com.patterns.flyweightpattern;

import java.awt.Color;
import java.awt.Graphics;
 
public class Line implements Shape {
 
    public Line(){
        System.out.println("Creating Line object");
        //adding time delay
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void draw(Graphics line, int x1, int y1, int x2, int y2, Color color) {
        line.setColor(color);
        line.drawLine(x1, y1, x2, y2);
    }
 
}

Oval.java

package com.patterns.flyweightpattern;

import java.awt.Color;
import java.awt.Graphics;
 
public class Oval implements Shape {
	
    //intrinsic property
    private boolean fill;
	
    public Oval(boolean f){
        this.fill = f;
        System.out.println("Creating Oval object with fill=" + f);
        //adding time delay
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void draw(Graphics circle, int x, int y, int width, int height, Color color) {
        circle.setColor(color);
        circle.drawOval(x, y, width, height);
        if(fill){
            circle.fillOval(x, y, width, height);
        }
    }
 
}

기서 눈여겨 보실 부분은 Line과 Oval 클래스의 생성자에 Thread.sleep을 적용했다는 것인데요, 이는 인스턴스화 할 때 시간이 많이 걸린다는 것을 조금 더 과장해서 보여주기 위해 넣은 코드입니다. 이를 통해 플라이웨이트 패턴을 쓰면 객체 생성에 있어서 얼마나 시간을 줄일 수 있는지 직관적으로 확인할 수 있습니다.

 

이번에는 위 객체들을 생성하는 데 사용되는 Flyweight Factory를 작성해보겠습니다.

플라이웨이트 팩토리는 클라이언트가 객체의 인스턴스를 생성하는 데 사용됩니다. 그래서 우리는 팩토리 안에 클라이언트는 접근할 수 없는 Map을 두어 객체들을 관리하도록 하겠습니다.

 

클라이언트가 객체에 대한 인스턴스를 얻기 위해 호출할 때, 팩토리 안에 정의해둔 Map에 해당 객체가 있다면 별도의 인스턴스 생성 없이 그대로 Map에서 리턴하고, 만약 없다면 새로 인스턴스를 생성하여 맵에 저장(put) 한 후에 그 객체를 리턴하게 합니다.

 

플라이웨이트 팩토리 클래스는 아래와 같이 작성하겠습니다.

 

ShapeFactory.java

package com.patterns.flyweightpattern;

import java.util.HashMap;

public class ShapeFactory {
 
    private static final HashMap<ShapeType, Shape> shapes = new HashMap<ShapeType, Shape>();
 
    public static Shape getShape(ShapeType type) {
        Shape shapeImpl = shapes.get(type);
 
        if (shapeImpl == null) {
            if (type.equals(ShapeType.OVAL_FILL)) {
                shapeImpl = new Oval(true);
            } else if (type.equals(ShapeType.OVAL_NOFILL)) {
                shapeImpl = new Oval(false);
            } else if (type.equals(ShapeType.LINE)) {
                shapeImpl = new Line();
            }
            shapes.put(type, shapeImpl);
        }
        return shapeImpl;
    }
	
    public static enum ShapeType {
        OVAL_FILL, OVAL_NOFILL, LINE;
    }
}

여기서 눈여겨 보실 것은 Map의 Key 값으로 ShapeType이라는 Enum을 사용한 것인데요, 이렇게 사용하면 타입 안전(Type-safety)하게 사용할 수 있습니다.

 

위 팩토리 클래스를 보시면 getShape() 메소드의 인자 값으로 들어오는 Enum 객체를 shapes라는 HashMap의 키 값으로 해서 이미 저장되어 있는 값인지 검사를 합니다.

만약 맵에 객체가 있다면 별도의 인스턴스 생성 없이 맵에 들어있던 객체를 그대로 리턴하고, 맵에 객체가 없다면 새로운 인스턴스를 만들고 맵에 저장한 후에 생성한 객체를 리턴하게 됩니다.

 

이렇게 플라이웨이트 패턴을 사용하게 된다면 불필요한 인스턴스 생성을 최소화하기 때문에, 필요할 때마다 매번 새로운 인스턴스를 생성할 때보다 훨씬 메모리를 적게 사용하고, 빠른 프로그램을 작성할 수 있게 됩니다.

 

이제 위 코드를 한 번 테스트 해보겠습니다.

 

DrawingClient.java

package com.patterns.flyweightpattern;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import com.patterns.flyweightpattern.ShapeFactory.ShapeType;
 
public class DrawingClient extends JFrame{
 
    private static final long serialVersionUID = -1350200437285282550L;
    private final int WIDTH;
    private final int HEIGHT;
 
    private static final ShapeType shapes[] = { ShapeType.LINE, ShapeType.OVAL_FILL,ShapeType.OVAL_NOFILL };
    private static final Color colors[] = { Color.RED, Color.GREEN, Color.YELLOW };
	
    public DrawingClient(int width, int height) {
        this.WIDTH = width;
        this.HEIGHT = height;
        Container contentPane = getContentPane();
 
        JButton startButton = new JButton("Draw");
        final JPanel panel = new JPanel();
 
        contentPane.add(panel, BorderLayout.CENTER);
        contentPane.add(startButton, BorderLayout.SOUTH);
        setSize(WIDTH, HEIGHT);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
 
        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                Graphics g = panel.getGraphics();
                for (int i = 0; i < 20; ++i) {
                    Shape shape = ShapeFactory.getShape(getRandomShape());
                    shape.draw(g, getRandomX(), getRandomY(), getRandomWidth(),
                            getRandomHeight(), getRandomColor());
                }
            }
        });
    }
	
    private ShapeType getRandomShape() {
        return shapes[(int) (Math.random() * shapes.length)];
    }
 
    private int getRandomX() {
        return (int) (Math.random() * WIDTH);
    }
 
    private int getRandomY() {
        return (int) (Math.random() * HEIGHT);
    }
 
    private int getRandomWidth() {
        return (int) (Math.random() * (WIDTH / 10));
    }
 
    private int getRandomHeight() {
        return (int) (Math.random() * (HEIGHT / 10));
    }
 
    private Color getRandomColor() {
        return colors[(int) (Math.random() * colors.length)];
    }
 
    public static void main(String[] args) {
        DrawingClient drawing = new DrawingClient(500,600);
    }
}

 

위 클라이언트 코드에서는 Shape의 타입을 랜덤으로 생성하게끔 작성했습니다.

 

만약 실행해보신다면, Line 객체와 Oval 객체가 최초에 생성될 때에만 앞서 생성자에서 설정해두었던 sleep(2000)이 실행되고 이후에는 별도의 딜레이 없이 빠르게 객체가 생성되는 것을 확인할 수 있습니다.

 

<결과>

플라이웨이트 패턴 활용 예

플라이웨이트 패턴은 어디에서 쓰이고 있을까요?

자바의 모든 래퍼 클래스의 valueOf() 메소드가 바로 이 플라이웨이트 패턴을 사용하고 있습니다. 그래서 래퍼 클래스를 생성해야 할 때 new 키워드를 통해 인스턴스를 매번 생성하기보다는 valueOf() 메소드를 통해 생성하는 것이 더 효율적입니다.

 

또, 대표적으로 사용되는 것이 바로 Java의 String Pool 입니다. Java에서는 String Pool을 별도로 두어 같은 문자열에 대해 다시 사용될 때에 새로운 메모리를 할당하는 것이 아니라 String Pool에 있는지 검사해서 있으면 가져오고 없으면 새로 메모리를 할당하여 String Pool에 등록한 후에 사용하도록 하고 있습니다.

 

참조 원문 : https://readystory.tistory.com/137?category=822867

프록시 패턴(Proxy Pattern)

특정 객체로의 접근을 제어하는 대리인(특정 객체를 대변하는 객체)을 제공합니다.

프록시 패턴은 디자인 패턴 중 이해하기 쉬운 패턴에 속합니다.

 

프록시 패턴은 구조 패턴(Structural Pattern) 중 하나로, 어떤 다른 객체로 접근하는 것을 통제하기 위해서 그 객체의 대리자(surrogate)나 자리표시자(placeholder)의 역할을 하는 객체를 제공하는 패턴입니다.

 

 

프록시 패턴(Proxy Pattern) 이해 및 예제

프록시 패턴은 어떤 다른 객체로 접근하는 것을 통제하기 위해서 그 객체의 대리자(surrogate)나 자리표시자(placeholder)의 역할을 하는 객체를 제공하는 패턴입니다.

 

예를 들어, 우리가 시스템 명령어를 실행하는 객체를 갖고 있을 때 우리가 그 객체를 사용하는 것이라면 괜찮지만 만약 그 객체를 클라이언트에게 제공하려고 한다면 클라이언트 프로그램이 우리가 원치 않는 파일을 삭제하거나 설정을 변경하는 등의 명령을 내릴 수 있기 때문에 심각한 문제를 초래할 수 있습니다.

 

프록시 패턴은 클라이언트에게 접근에 대한 컨트롤을 제공하여 위와 같은 문제를 해결합니다.

 

예제를 살펴보겠습니다.

 

먼저 CommandExecutor 라는 명령을 실행하는 메소드를 제공하는 인터페이스를 정의하겠습니다.

 

CommandExecutor.java

package com.patterns.proxypattern;

public interface CommandExecutor {
	 
    public void runCommand(String cmd) throws Exception;
}

 

자, 이번에는 만약 프록시 패턴을 적용하지 않고 앞서 말씀드린 것처럼 그냥 클라이언트에게 명령에 대한 권한을 전부 넘겨주는 객체를 생성해보겠습니다.

 

CommandExecutorImpl.java

package com.patterns.proxypattern;

import java.io.IOException;

public class CommandExecutorImpl implements CommandExecutor {
	 
    @Override
    public void runCommand(String cmd) throws IOException {
        //some heavy implementation
        Runtime.getRuntime().exec(cmd);
        System.out.println("'" + cmd + "' command executed.");
    }
}

 

보시다시피 CommandExecutorImpl 클래스에서는 runCommand()의 파라미터로 받은 cmd 명령어를 그대로 수행하고 있습니다. 이렇게 구현할 경우에는 앞서 말씀드린 바와 같이 원치 않는 파일 삭제나 설정 변경 등에 문제가 발생할 가능성이 높아집니다.

 

그렇다면 이를 해결하기 위해 프록시 객체를 두어 관리자(Admin) 계정이 아닐 경우에는 rm 이라는 명령어에 대해 수행하지 못하도록 구현해보도록 하겠습니다.

 

CommandExecutorProxy.java

package com.patterns.proxypattern;

public class CommandExecutorProxy implements CommandExecutor {
    private boolean isAdmin;
    private CommandExecutor executor;
	
    public CommandExecutorProxy(String user, String pwd){
        if("ReadyKim".equals(user) && "correct_pwd".equals(pwd))
            isAdmin = true;
        executor = new CommandExecutorImpl();
    }
	
    @Override
    public void runCommand(String cmd) throws Exception {
        if(isAdmin){
            executor.runCommand(cmd);
        }else{
            if(cmd.trim().startsWith("rm")){
                throw new Exception("rm command is not allowed for non-admin users.");
            }else{
                executor.runCommand(cmd);
            }
        }
    }
}

 

 

자 이번에는 위에서 작성한 코드들을 테스트 해보도록 하겠습니다.

 

ProxyPatternTest.java

package com.patterns.proxypattern;

public class ProxyPatternTest {
	 
    public static void main(String[] args){
        CommandExecutor executor = new CommandExecutorProxy("ReadyKim", "wrong_pwd");
        try {
            executor.runCommand("ls -ltr");
            executor.runCommand("rm -rf abc.pdf");
        } catch (Exception e) {
            System.out.println("Exception Message::"+e.getMessage());
        }	
    }
}

 

결과에서 보다시피 Admin 계정의 ID와 Password가 틀렸기 때문에 프록시 객체가 rm 명령어에 대한 수행을 거부하였고 그 결과로 Exception을 던지게 됐습니다.

 

프록시 패턴은 이렇듯 어떤 객체에 대하여 접근할 때에 Wrapper Class를 두어 접근에 대한 통제(Control access)를 위해 사용합니다.

  Composite 패턴은 구조 패턴 중 하나로, 객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴입니다. 사용자는 이 복합체 패턴을 통해 단일 객체와 복합 객체 모두 동일하게 다룰 수 있습니다.

 

컴포지트 패턴으로 객체를 트리구조로 구성해서 부분-전체 계층구조를 구현합니다. 컴포지트 패턴을 사용하면 클라이언트에서 개별 객체와 복합 객체를 똑같은 방법으로 다룰 수 있습니다.

 

구조 패턴(Structural Pattern)이란?

구조 패턴이란 작은 클래스들을 상속과 합성을 이용하여 더 큰 클래스를 생성하는 방법을 제공하는 패턴입니다.

이 패턴을 사용하면 서로 독립적으로 개발한 클래스 라이브러리를 마치 하나인 양 사용할 수 있습니다. 또, 여러 인터페이스를 합성(Composite)하여 서로 다른 인터페이스들의 통일된 추상을 제공합니다.

 

구조 패턴의 중요한 포인트는 인터페이스나 구현을 복합하는 것이 아니라 객체를 합성하는 방법을 제공한다는 것입니다. 이는 컴파일 단계에서가 아닌 런타임 단계에서 복합 방법이나 대상을 변경할 수 있다는 점에서 유연성을 갖습니다.

 

복합체 패턴(Composite Pattern) 개념 및 예제 코드

복합체 패턴에 대해 실생활에서 예를 들어 설명해보겠습니다.

 

파워포인트에 삼각형, 사각형, 원을 각각 하나씩 만들어 놓고 삼각형과 사각형을 그룹화했다고 가정하겠습니다. 이제 우리는 모든 도형을 빨간색으로 색을 칠하려고 합니다.

이때 우리가 채우기 버튼을 누를 때 선택하는 것이 어떤 도형인지, 혹은 그룹인지에 대해서 구분하지 않아도 됩니다. 파워포인트에서는 도형 하나에 대한 채우기와 그룹 전체에 대한 채우기 버튼이 같습니다.

이처럼 복합체 패턴은 전체 도형들을 하나의 도형을 다루듯이 관리할 수 있다는 특징을 지닙니다.

쉽게 말해 "일괄적인 관리"가 가능한 것이지요.

 

복합체 패턴은 다음과 같은 오브젝트들을 갖습니다.

  • Base Component - 베이스 컴포넌트는 클라이언트가 composition 내의 오브젝트들을 다루기 위해 제공되는 인터페이스를 말합니다. 베이스 컴포넌트는 인터페이스 또는 추상 클래스로 정의되며 모든 오브젝트들에게 공통되는 메소드를 정의해야 합니다.
  • Leaf - composition 내 오브젝트들의 행동을 정의합니다. 이는 복합체를 구성하는 데 중요한 요소이며, 베이스 컴포넌트를 구현합니다. 그리고 Leaf는 다른 컴포넌트에 대해 참조를 가지면 안됩니다. 
  • Composite - Leaf 객체들로 이루어져 있으며 베이스 컴포넌트 내 명령들을 구현합니다.

 

예제를 통해 이해를 돕도록 하겠습니다.

 

1. Base Component

Base Component는 Leaf와 Composite의 공통되는 메소드들을 정의해야 합니다.

예제에서는 Shape 인터페이스 내에 각각 도형마다 색을 입히는 draw() 메소드를 정의하도록 하겠습니다.

 

Shape.java

package com.patterns.composite;

public interface Shape {
	public void draw(String fillColor);
}

 

2. Leaf Objects

Leaf 객체들은 복합체에 포함되는 요소로, Base Component를 구현해야 합니다.

예제에서는 Triangle과 Circle 클래스를 정의하도록 하겠습니다.

 

Triangle.java

package com.patterns.composite;

public class Triangle implements Shape {
	 
    @Override
    public void draw(String fillColor) {
    	
        System.out.println("Drawing Triangle with color "+fillColor);
    }
}

Circle.java

package com.patterns.composite;

public class Circle implements Shape {
	 
    @Override
    public void draw(String fillColor) {
        System.out.println("Drawing Circle with color "+fillColor);
    }
}

3. Composite Objects

Composite 객체는 Leaf 객체들을 포함하고 있으며, Base Component를 구현할 뿐만 아니라 Leaf 그룹에 대해 add와 remove를 할 수 있는 메소드들을 클라이언트에게 제공합니다.

 

Drawing.java

package com.patterns.composite;

import java.util.ArrayList;
import java.util.List;

public class Drawing implements Shape {
	 
    //collection of Shapes
    private List<Shape> shapes = new ArrayList<Shape>();
	
    @Override
    public void draw(String fillColor) {
        for(Shape sh : shapes) {
            sh.draw(fillColor);
        }
    }
	
    //adding shape to drawing
    public void add(Shape s) {
        this.shapes.add(s);
    }
	
    //removing shape from drawing
    public void remove(Shape s) {
        shapes.remove(s);
    }
	
    //removing all the shapes
    public void clear() {
        System.out.println("Clearing all the shapes from drawing");
        this.shapes.clear();
    }
}

 

여기서 중요한 것은 Composite Object 또한 Base Component를 구현해야 한다는 것입니다.

그렇게 해야만 클라이언트가 Composite 객체에 대해서 다른 Leaf들과 동일하게 취급될 수 있습니다.

 

아직까지 이해가 잘 안되더라도 괜찮습니다.

이제 우리가 만든 예제 코드를를 테스트하는 코드를 보면 확실하게 이해 가실 것입니다.

 

TestCompositePattern.java

package com.patterns.composite;

import java.util.ArrayList;
import java.util.List;

public class TestCompositePattern {
	 
    public static void main(String[] args) {
    	
        Shape tri = new Triangle();
        Shape tri1 = new Triangle();
        Shape cir = new Circle();
		
        Drawing drawing = new Drawing();
        drawing.add(tri1);
        drawing.add(tri1);
        drawing.add(cir);
		
        drawing.draw("Red");
		
        List<Shape> shapes = new ArrayList<>();
        shapes.add(drawing);
        shapes.add(new Triangle());
        shapes.add(new Circle());
        
        for(Shape shape : shapes) {
            shape.draw("Green");
        }
    }
}

 

자, 위 코드를 보시면 drawing 객체를 통해 Triangle, Circle 등의 Leaf 객체들을 Group으로 묶어서 한 번에 동작을 수행할 수 있습니다. 나아가 drawing 객체 또한 다른 도형들과 마찬가지로 Shape 인터페이스를 구현하고 있기 때문에 변수 shapes에서 살펴보는 것과 같이 drawing이 다른 도형들과 함께 취급될 수 있습니다.

Drawing Triangle with color Red
Drawing Triangle with color Red
Drawing Circle with color Red
Drawing Triangle with color Green
Drawing Triangle with color Green
Drawing Circle with color Green
Drawing Triangle with color Green
Drawing Circle with color Green

어댑터 패턴은 구조 패턴(Structural Pattern) 중 하나로, 서로 관계없는 인터페이스들을 함께 사용할 수 있게 하는 패턴입니다.

 즉 특정 틀래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환합니다. 인터페이스가 호환되지 않아 같이 쓸 수 없었던 클래스를 사용할 수 있게 도와줍니다.

 

어댑터 패턴(Adapter Pattern) 개념 및 예제 코드

어댑터가 일상생활에서 쓰이는 용도를 생각해보시면 인풋과 아웃풋이 다른, 이를테면 해외여행 갈 때 챙기는 볼트 변환기(돼지코) 같은 것들을 어댑터라고 표현하기도 합니다.

마찬가지로 어댑터 패턴은 클래스의 인터페이스를 사용자가 기대하는 인터페이스 형태로 변환시키는 패턴입니다.

어댑터 패턴은 서로 일치하지 않는 인터페이스를 갖는 클래스들을 함께 동작시킵니다.

 

위에서 말씀드린 것처럼 볼트 변환기 개념을 가지고서 예제코드를 작성해보도록 하겠습니다.

 

Volt 클래스는 단순히 volt 값을 가지고 있는 POJO 클래스입니다.

package com.patterns.adapterPattern;

public class Volt {
	
	private int volts;

	public Volt(int v) {
		this.volts = v;
	}

	public int getVolts() {
		return volts;
	}

	public void setVolts(int volts) {
		this.volts = volts;
	}
}

Socket 클래스는 단순히 120 볼트를 값으로 갖는 볼트 객체를 생성하는 클래스입니다. 

package com.patterns.adapterPattern;

public class Socket {
	
	public Volt getVolt() {
		return new Volt(120);
	}
	
}

 

이제 우리는 120볼트뿐만 아니라 3볼트와 12볼트도 추가로 생성하는 어댑터를 만들어볼 것인데, 이를 위해 각각의 볼트 객체 생성을 위한 인터페이스를 정의해주겠습니다.

package com.patterns.adapterPattern;

public interface SocketAdapter {
	
	public Volt get120Volt();
	public Volt get12Volt();
	public Volt get3Volt();
}

 

어댑터 패턴을 구현하기 위해서는 Class Adapter Object Adapter 두 가지 방법이 있습니다.

어떤 방법으로 구현하던 결과는 같습니다.

 

  • Class Adapter  - 자바의 상속(Inheritance)을 이용한 방법입니다.
  • Object Adapter - 자바의 합성(Composite)을 이용한 방법입니다.

이제 우리는 Socket 클래스와 Volt 클래스를 SocketAdapter 인터페이스에서 정의한 메소드에 맞춰서 사용할 것입니다.

그러면 Class Adpater와 Object Adapter 방식으로 각각 예제코드를 작성해보겠습니다.

 

Class Adapter 방식

package com.patterns.adapterPattern;

//Using inheritance for adapter pattern
public class SocketClassAdapterImpl extends Socket implements SocketAdapter {

	@Override
	public Volt get120Volt() {
		return getVolt();
	}

	@Override
	public Volt get12Volt() {
		Volt v = getVolt();
		return convertVolt(v, 10);
	}

	@Override
	public Volt get3Volt() {
		Volt v = getVolt();
		return convertVolt(v, 40);
	}

	private Volt convertVolt(Volt v, int i) {
		return new Volt(v.getVolts() / i);
	}

}

 

Object Adapter 방식

package com.patterns.adapterPattern;

public class SocketObjectAdapterImpl implements SocketAdapter {

	// Using Composition for adapter pattern
	private Socket sock = new Socket();

	@Override
	public Volt get120Volt() {
		return sock.getVolt();
	}

	@Override
	public Volt get12Volt() {
		Volt v = sock.getVolt();
		return convertVolt(v, 10);
	}

	@Override
	public Volt get3Volt() {
		Volt v = sock.getVolt();
		return convertVolt(v, 40);
	}

	private Volt convertVolt(Volt v, int i) {
		return new Volt(v.getVolts() / i);
	}
}

눈여겨 볼 것은 두 가지 방식 모두 SocketAdapter 인터페이스를 구현했다는 것입니다. 이 어댑터 인터페이스는 추상 클래스로 사용해도 무방합니다.

 

이제 이 어댑터 인터페이스를 사용 하여 테스트 코드를 작성해보겠습니다.

이렇게 해서 어댑터 패턴에 대해 살펴봤습니다.

 

이 어댑터 패턴은 Java의 JDK 안에서 Arrays.asList()나 InputStreamReader(InputStream), OutputStreamWriter(OutputStream) 등에서 사용되었습니다.

 

참고 사이트 : https://readystory.tistory.com/125?category=822867 

 

[구조 패턴] 어댑터 패턴(Adapter Pattern) 이해 및 예제

어댑터 패턴은 구조 패턴(Structural Pattern) 중 하나로, 서로 관계없는 인터페이스들을 함께 사용할 수 있게 하는 패턴입니다. 구조 패턴(Structural Pattern)이란? 구조 패턴이란 작은 클래스들을 상속과

readystory.tistory.com

                  https://www.hanbit.co.kr/channel/category/category_view.html?cms_code=CMS8616098823 

 

[Design pattern] 많이 쓰는 14가지 핵심 GoF 디자인 패턴의 종류

디자인 패턴을 활용하면 단지 코드만 ‘재사용’하는 것이 아니라, 더 큰 그림을 그리기 위한 디자인도 재사용할 수 있습니다. 우리가 일상적으로 접하는 문제 중 상당수는 다른 많은 이들이 접

www.hanbit.co.kr

 

프로토타입은 주로 실제 제품을 만들기에 앞서 대략적인 샘플 정도의 의미로 사용되는 단어입니다.

프로토타입 패턴은 객체를 생성하는 데 비용(시간과 자원)이 많이 들고, 비슷한 객체가 이미 있는 경우에 사용되는 생성 패턴 중 하나입니다.

프로토타입 패턴은 Original 객체를 새로운 객체에 복사하여 우리의 필요에 따라 수정하는 메커니즘을 제공합니다.

이 패턴은 복사를 위하여 Java에서 제공하는 clone()을 사용합니다.

 

프로토타입 패턴은 생성 패턴(Creational Pattern) 중 하나이다.

생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴입니다.

생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현 방법을 시스템과 분리해줍니다.

생성 패턴은 시스템이 상속(inheritance) 보다 복합(composite) 방법을 사용하는 방향으로 진화되어 가면서 더 중요해지고 있습니다.

 

생성 패턴에서는 중요한 이슈가 두 가지 있습니다.

  1. 생성 패턴은 시스템이 어떤 Concrete Class를 사용하는지에 대한 정보를 캡슐화합니다.
  2. 생성 패턴은 이들 클래스의 인스턴스들이 어떻게 만들고 어떻게 결합하는지에 대한 부분을 완전히 가려줍니다.

쉬운 말로 정리하자면, 생성 패턴을 이용하면 무엇이 생성되고, 누가 이것을 생성하며, 이것이 어떻게 생성되는지, 언제 생성할 것인지 결정하는 데 유연성을 확보할 수 있게 됩니다.

 

 

프로토타입 패턴(Prototype Pattern)의 이해 및 예제

앞서 말씀드린 것처럼 프로토타입 패턴은 객체를 생성하는 데 시간과 노력이 많이 들고, 이미 유사한 객체가 존재하는 경우에 사용됩니다. 그리고 java의 clone()을 이용하기 때문에 생성하고자 하는 객체에 clone에 대한 Override를 요구합니다. 이때 주의할 점은 반드시 생성하고자 하는 객체의 클래스에서 clone()이 정의되어야 한다는 것입니다.

 

예를 들어 DB로부터 데이터를 가져오는 객체가 존재한다고 가정해보겠습니다.

만약 DB로부터 가져온 데이터를 우리의 프로그램에서 수차례 수정을 해야하는 요구사항이 있는 경우, 매번 new 라는 키워드를 통해 객체를 생성하여 DB로부터 항상 모든 데이터를 가져오는 것은 좋은 아이디어가 아닙니다.

왜냐하면 DB로 접근해서 데이터를 가져오는 행위는 비용이 크기 때문입니다.

따라서 한 번 DB에 접근하여 데이터를 가져온 객체를 필요에 따라 새로운 객체에 복사하여 데이터 수정 작업을 하는 것이 더 좋은 방법입니다.

이때 객체의 복사를 얕은 복사(shallow copy)로 할 지, 깊은 복사(deep copy)로 할 지에 대해서는 선택적으로 행하시면 됩니다.

 

샘플 코드를 통해 이해를 돕도록 하겠습니다.

실제 DB와 연동되는 샘플 코드를 작성하는 것은 다소 복잡할 수 있으니 쉽게 직원의 명단을 갖고 있는 Employees 클래스를 통해 살펴보겠습니다.

 

package com.patterns.Prototype;

import java.util.ArrayList;
import java.util.List;

public class Employees implements Cloneable {
	
	private List<String> empList;

	public Employees() {
		empList = new ArrayList<String>();
	}

	public Employees(List<String> list) {
		this.empList = list;
	}

	public void loadData() {
		// read all employees from database and put into the list
		empList.add("Pankaj");
		empList.add("Raj");
		empList.add("David");
		empList.add("Lisa");
	}

	public List<String> getEmpList() {
		return empList;
	}

	@Override
	public Object clone() throws CloneNotSupportedException {
		
		List<String> temp = new ArrayList<String>();
		for (String s : this.empList) {
			temp.add(s);
		}
		return new Employees(temp);
	}
}

위 코드를 보시면 clone() 메소드를 재정의하기 위해 Cloneable 인터페이스를 구현한 것을 확인할 수 있습니다. 여기서 사용되는 clone()은 empList에 대하여 깊은 복사(deep copy)를 실시합니다.

 

이번에는 위에서 작성한 코드를 메인에서 테스트해보도록 하겠습니다.

package com.patterns.Prototype;

import java.util.List;

public class PrototypePatternTest {
	public static void main(String[] args) throws CloneNotSupportedException {
		Employees emps = new Employees();
		emps.loadData();

		// Use the clone method to get the Employee object
		Employees empsNew = (Employees) emps.clone();
		Employees empsNew1 = (Employees) emps.clone();
		List<String> list = empsNew.getEmpList();
		list.add("John");
		List<String> list1 = empsNew1.getEmpList();
		list1.remove("Pankaj");

		System.out.println("emps List: " + emps.getEmpList());
		System.out.println("empsNew List: " + list);
		System.out.println("empsNew1 List: " + list1);
	}
}

결과

emps List: [Pankaj, Raj, David, Lisa]
empsNew List: [Pankaj, Raj, David, Lisa, John]
empsNew1 List: [Raj, David, Lisa]

만약 Employees 클래스에서 clone()을 제공하지 않았다면, DB로부터 매번 employee 리스트를 직접 가져와야 했을 것이고, 그로 인해 상당히 큰 비용이 발생했을 것입니다.

하지만 프로토타입을 사용한다면 1회의 DB 접근을 통해 가져온 데이터를 복사하여 사용한다면 이를 해결할 수 있습니다. (객체를 복사하는 것이 네트워크 접근이나 DB 접근보다 훨씬 비용이 적습니다.)

 

참고자료(원문) : https://readystory.tistory.com/122?category=822867 

 

[생성 패턴] 프로토타입 패턴(Prototype Pattern) 이해 및 예제

프로토타입은 주로 실제 제품을 만들기에 앞서 대략적인 샘플 정도의 의미로 사용되는 단어입니다. 프로토타입 패턴은 객체를 생성하는 데 비용(시간과 자원)이 많이 들고, 비슷한 객체가 이미

readystory.tistory.com

 

빌더 패턴은 복잡한 객체를 생성하는 방법을 정의하는 클래스와 표현하는 방법을 정의하는 클래스를 별도로 분리하여, 서로 다른 표현이라도 이를 생성할 수 있는 동일한 절차를 제공하는 패턴입니다. 빌더 패턴은 생성해야 되는 객체가 Optional한 속성을 많이 가질 때 빛을 발휘합니다.

 

빌더 패턴은 생성 패턴(Creational Pattern) 중 하나이다.

생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴입니다.

생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현 방법을 시스템과 분리해줍니다.

생성 패턴은 시스템이 상속(inheritance) 보다 복합(composite) 방법을 사용하는 방향으로 진화되어 가면서 더 중요해지고 있습니다.

 

생성 패턴에서는 중요한 이슈가 두 가지 있습니다.

  1. 생성 패턴은 시스템이 어떤 Concrete Class를 사용하는지에 대한 정보를 캡슐화합니다.
  2. 생성 패턴은 이들 클래스의 인스턴스들이 어떻게 만들고 어떻게 결합하는지에 대한 부분을 완전히 가려줍니다.

쉬운 말로 정리하자면, 생성 패턴을 이용하면 무엇이 생성되고, 누가 이것을 생성하며, 이것이 어떻게 생성되는지, 언제 생성할 것인지 결정하는 데 유연성을 확보할 수 있게 됩니다.

 

빌더 패턴(Builder Pattern)의 개념 및 예제

빌더 패턴은 많은 Optional한 멤버 변수(혹은 파라미터)나 지속성 없는 상태 값들에 대해 처리해야 하는 문제들을 해결합니다.

 

예를 들어, 팩토리 패턴이나 추상 팩토리 패턴에서는 생성해야하는 클래스에 대한 속성 값이 많을 때 아래와 같은 이슈들이 있습니다.

  1.  클라이언트 프로그램으로부터 팩토리 클래스로 많은 파라미터를 넘겨줄 때 타입, 순서 등에 대한 관리가 어려워져 에러가 발생할 확률이 높아집니다.
  2. 경우에 따라 필요 없는 파라미터들에 대해서 팩토리 클래스에 일일이 null 값을 넘겨줘야 합니다.
  3. 생성해야 하는 sub class가 무거워지고 복잡해짐에 따라 팩토리 클래스 또한 복잡해집니다. 

 

빌더 패턴은 이러한 문제들을 해결하기 위해 별도의 Builder 클래스를 만들어 필수 값에 대해서는 생성자를 통해, 선택적인 값들에 대해서는 메소드를 통해 step-by-step으로 값을 입력받은 후에 build() 메소드를 통해 최종적으로 하나의 인스턴스를 리턴하는 방식입니다.

 

빌더 패턴은 굉장히 자주 사용되는 생성 패턴 중 하나로, Retrofit이나 Okhttp 등 유명 오픈소스에서도 이 빌더 패턴을 사용하고 있습니다.

 

빌더 패턴을 구현하는 방법은 아래와 같습니다.

  1. 빌더 클래스를 Static Nested Class로 생성합니다. 이때, 관례적으로 생성하고자 하는 클래스 이름 뒤에 Builder를 붙입니다. 예를 들어, Computer 클래스에 대한 빌더 클래스의 이름은 ComputerBuilder 라고 정의합니다.  (Nested :중첩)
  2. 빌더 클래스의 생성자는 public으로 하며, 필수 값들에 대해 생성자의 파라미터로 받습니다.
  3. 옵셔널한 값들에 대해서는 각각의 속성마다 메소드로 제공하며, 이때 중요한 것은 메소드의 리턴 값이 빌더 객체 자신이어야 합니다.
  4. 마지막 단계로, 빌더 클래스 내에 build() 메소드를 정의하여 클라이언트 프로그램에게 최종 생성된 결과물을 제공합니다. 이렇듯 build()를 통해서만 객체 생성을 제공하기 때문에 생성 대상이 되는 클래스의 생성자는 private으로 정의해야 합니다.

 

구현 방법만 봐서는 잘 이해가 가지 않을 수 있으니, 간단한 예제를 통해 이해를 돕도록 하겠습니다.

예제는 ComputerBuilder 클래스를 통해 Computer 클래스 객체를 생성하는 샘플 코드입니다. 

package com.patterns.builder;

public class Computer {
	
	// required parameters
	private String HDD;
	private String RAM;

	// optional parameters
	private boolean isGraphicsCardEnabled;
	private boolean isBluetoothEnabled;

	public String getHDD() {
		return HDD;
	}

	public String getRAM() {
		return RAM;
	}

	public boolean isGraphicsCardEnabled() {
		return isGraphicsCardEnabled;
	}

	public boolean isBluetoothEnabled() {
		return isBluetoothEnabled;
	}

	private Computer(ComputerBuilder builder) {
		this.HDD = builder.HDD;
		this.RAM = builder.RAM;
		this.isGraphicsCardEnabled = builder.isGraphicsCardEnabled;
		this.isBluetoothEnabled = builder.isBluetoothEnabled;
	}

	// Builder Class
	public static class ComputerBuilder {

		// required parameters
		private String HDD;
		private String RAM;

		// optional parameters
		private boolean isGraphicsCardEnabled;
		private boolean isBluetoothEnabled;

		public ComputerBuilder(String hdd, String ram) {
			this.HDD = hdd;
			this.RAM = ram;
		}

		public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) {
			this.isGraphicsCardEnabled = isGraphicsCardEnabled;
			return this;
		}

		public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
			this.isBluetoothEnabled = isBluetoothEnabled;
			return this;
		}

		public Computer build() {
			return new Computer(this);
		}

	}
}

여기서 살펴볼 것은 Computer 클래스가 setter 메소드 없이 getter 메소드만 가진다는 것과 public 생성자가 없다는 것입니다. 그렇기 때문에 Computer 객체를 얻기 위해서는 오직 ComputerBuilder 클래스를 통해서만 가능합니다.

 

이제 이렇게 작성한 예제를 클라이언트에서 사용해보도록 하겠습니다.

package com.patterns.builder;

public class TestBuilderPattern {
	 
    public static void main(String[] args) {
        Computer comp = new Computer.ComputerBuilder("500 GB", "2 GB")
                .setBluetoothEnabled(true)
                .setGraphicsCardEnabled(true)
                .build();
    }
 
}

보시는 것처럼 Computer 객체를 얻기 위해 ComputerBuilder 클래스를 사용하고 있으며 필수 값인 HDD와 RAM 속성에 대해서는 생성자로 받고 Optional한 값인 BluetoothEnabled와 GraphicsCardEnabled에 대해서는 메소드를 통해 선택적으로 입력 받고 있습니다.

즉, BluetoothEnabled 값이 필요 없는 객체라면 setBluetoothEnabled() 메소드를 사용하지 않으면 됩니다.

 

참고자료(원문) :  https://readystory.tistory.com/121?category=822867 

 

+ Recent posts