'분류 전체보기' 카테고리의 글 목록 (6 Page) :: 잡다한 프로그래밍
반응형

1. 팩토리 메소드 패턴의 정의

객체를 생성하기 위한 인터페이스를 정의하고, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 한다.

 

 

단순한 팩토리 패턴?

 

다음과 같은 orderPizza 메소드가 있다고 할때 이 메소드는 pizza의 종류가 변경되면 코드가 변경되어야 한다. 따라서 변경되는 부분과 변경되지 않는 부분을 분리할 필요가 있다.

 Pizza orderPizza(String type) {
       Pizza pizza;
       
       if(type.equals("cheese")) pizza = new CheesePizza();
       else if(type.equals("greek")) pizza = new GreekPizza();
       else if(type.equals("pepperoni")) pizza = new PepperoniPizza();       

       pizza.prepare();
       pizza.bake();
       pizza.cut();
       pizza.box();

       return pizza;
 } 

 

다음과 같이 변경되는 부분을 새로운 SimplePizzaFactory라는 클래스도 분리하면 다음과 같다.

PizzaStore는 SimplePizzaFactory 를 이용하여 피자를 생성하고 이는 앞선 문제를 해결할 수 있다. (Spring 에서 Bean을 생성할 때 사용했던 방법과 매우 유사하다.)

public class SimplePizzaFactory {	

	public Pizza createPizza(String type){
		Pizza pizza = null;

		if(pizza.equals("cheese")) pizza = new CheesePizza();
		if(pizza.equals("pepper")) pizza = new PepperoniPizza();
		if(pizza.equals("clam")) pizza = new ClamPizza();
		if(pizza.equals("veggie")) pizza = new VeggiePizza();

		return pizza;

	}
 }
 public class PizzaStore{

	SimplePizzaFactory simplePizzaFactory;
	
	public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
		this.simplePizzaFactory = simplePizzaFactory;
	}
    
	public Pizza orderPizza(String type){

		Pizza pizza;

		pizza = simplePizzaFactory.createPizza(type);
        
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
        
		return pizza;

	}
 }

 

하지만 뉴욕, 시카고등 지역별로 같은 피자여도 조금씩 다르게 만들기 원하는 상황에서는 어떤방법으로 해결하면 좋을까?

 

팩토리 메소드패턴

다음과 같이 PizzaStore를 추상클래스로 선언하고 기존 팩토리 클래스로부터 create하는 부분을 추상 메소드로 선언한다.

 

PizzaStore 추상 클래스

public abstract class PizzaStore{ 추상 클래스
	public Pizza orderPizza(String type){
		Pizza pizza;
		
		pizza = createPizza(type);
		
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();

		return pizza;
	}
	abstract Pizza createPizza(String type); 추상 메소드
}

 

NYPizzaStore 클래스

public class NYPizzaStore extends PizzaStore{

	@Override
	public Pizza createPizza(String type){
		Pizza pizza = null;
		if(type.equals("cheese")) pizza = new NYStyleCheesePizza();
		if(type.equals("peper")) pizza = new NYStylePepperoniPizza();
		if(type.equals("clam")) pizza = new NYStyleClamPizza();
		if(type.equals("veggie")) pizza = new NYStyleVeggiePizza();

		return pizza;
	}
 } 

 

Test클래스

public class PizzaTestDrive {
	public static void main(String[] args) {
        
        PizzaStore nyStore = new NYPizzaStore();		
        
        Pizza nySytpePizza = nyStore.orderPizza("cheese");
        System.out.println(nySytpePizza.getname());
	}
 } 

 

전체적인 구조는 다음과 같다.

팩토리 메소드 패턴에서는 서브 클래스에서 어떤 클래스를 만들지 결정하게함으로서 (즉 추상클래스를 상속받은 NYPizzaStore에서 추상메소드를 수행함) 객체 생성을 캡슐화 한다.

 

이러한 팩토리 메소드 패턴에서 찾아볼 수 있는 객체 지향적 디자인 원칙은 의존성 뒤집 이다.

기존 PizzaStore는 각 피자가 바뀔때마다 수정되어야하는 방식의 의존성을 가지고 있었으나.

PizzaStore -> NYStyleCheesePizza

PizzaStore -> ChicagoStypeCheesePizza

PizzaStore -> NYStyleVeggiePizza

 

피자 인터페이스를 사용하여 인터페이스에만 의존적이고 나머지 실제 구현 객체는 인터페이스에 의존적인 관계가 되어 의존성 뒤집기 원칙을 가지고 있다.

PizzaStore -> Pizza

Pizza <- NYStyleCheesePizza

Pizza <- ChicagoStyleCheesePizza

Pizza <- NYStyleVeggiePizza


2. 추상 팩토리 패턴

피자를 만들기 위한 원재료를 처리하는 방법에 대해 고민해보자.

지역별 공장들을 추상화한 인터페이스를 가지는 구조로 작성한다

 

PizzaIngredientFactory 인터페이스

public interface PizzaIngredientFactory {
	public Dough createDough();
	public Sauce createSauce();
	public Cheese createCheese();
	public Veggies[] createVeggies();
	public Pepperoni createPepperoni();
	public Clams createClams();
 } 

 

NYPizzaIngredientFactory 클래스

public class NYPizzaingredientFactory implements PizzaIngredientFactory{
	@Override
	public Dough createDough() {
		return new ThinCrustdough();
	}

	@Override
	public Sauce createSauce() {
		return new MarinaraSauce();
	}

	@Override
	public Cheese createCheese() {
		return new ReggianoCheese();
	}

	@Override
	public Veggies[] createVeggies() {
		Veggies veggies[] = { new Farlic(), new Onion(), new Mushroom(), new RedPepper() };
		return veggies;
	}

	@Override
	public Pepperoni createPepperoni() {
		return new SlicedPepperoni();
	}

	@Override
	public Clams createClams() {
		return new Freshclams();
	}
 }

 

전체적인 구조는 다음과 같다


3. 팩토리 메소드 패턴과 추상 팩토리의 차이?

  1. 팩토리 메소드 패턴은 상속, 추상 팩토리는 인터페이스를 이용한다.
  2. 팩토리 메소드 패턴은 단일 메소드가 여러 객체를 생성한다.
  3. 추상 팩토리 패턴은 여러 메소드에서 여러 객체를 생성하여 하나의 제품군처럼 만들어서 사용(create 도우, 치즈, 버섯 = 뉴욕 공장)
반응형
반응형

1. 옵저버 패턴의 정의

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의함. 옵저버 패턴은 실제 구현할 수도, 자바 내장 패턴을 이용할 수도 있다.

옵저버 패턴은 상태를 저장하고 있는 주제 객체와, 상태가 변하는것을 바라보고 있는 옵저버들로 이루어져 있다.

 

 

옵저버 패턴의 구조

 

Subject interface

public interface Subject {
    public void registerObserver(Observer observer);    // 등록하기 위한 observer를 인자로 받는다.
    public void deleteObserver(Observer observer);    // 삭제하기 위한 observer를 인자로 받는다.
    public void notifyObserver();    // subject 객체의 상태(소식)이 변경되었을 때 모든 observer들에게 알리 위해 호출되는 메소드
}

 

Observer interface

public interface Observer {
    public void update(float temp, float humidity, float pressure);    // 상태가 갱신되었을 때 전달할 정보들을 인자로 받는다.
}

 

DisplayElement interface

public interface DisplayElement
	public void display();
}

 

Subject 구현 클래스

import java.util.ArrayList;
 
public class WeatherData implements Subject {
    // field    
    private float temp;                        // // 느슨한 결합(Loose Coupling)을 위해 상위형식으로 구성을 사용
    private float humidity;
    private float pressure;
    
    // constructor
    public WeatherData() {
        private ArrayList<Observer> observers = new ArrayList<>();    
    }
    
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }
 
    @Override
    public void deleteObserver(Observer observer) {
        int i = observers.indexOf(observer);
        if(i>=0) {
            observers.remove(i);
        }
        
    }
 
    // subject객체가 가진 목록 즉, 모든 observer들의 update() 호출
    @Override
    public void notifyObserver() {    
        for(int i = 0 ; i<observers.size() ; i++ ){
            Observer observer = observers.get(i);
            observer.update(this.temp, this.humidity, this.pressure);
        }
    }
    
    // subject의 상태(소식)이 변경되었을 때 호출 : observer들에게 상태를 알림
    public void measurementsChanged() {
        notifyObserver();
    }
    
    // subject 객체의 상태를 갱신
    public void setMeasurements(float temp, float humidity, float pressure) {    // 
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        
        measurementsChanged();
    }
 
}

 

Observer 구현 클래스

public class CurrentConditionDisplay implements Observer, DisplayElement {
 
    private float temp;
    private float humidity;
    private float pressure;
    
    private Subject weatherData;
   
    public CurrentConditionDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        this.weatherData.registerObserver(this);
    }
    
    
    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temp = temp;
        this.humidity = humidity;
        
        display();
    }
    
    @Override
    public void display() {
        System.out.println("Current Condition: "+this.temp+"F degrees and "+this.humidity+"% humidity and "+pressure+" pressure");
    }       
}

 

Test 클래스

public class WeatherStation {
    public static void main(String args[]) {
        WeatherData weatherData = new WeatherData(); // 서브젝트 객체 생성과 동시에 리스트 생성됨
        
        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData); //옵저버 클래스 생성하면서 등록됨.
        
        weatherData.setMeasurements(32, 65, 30); 값을 설정하게 되면 옵저버 update 메소드를 호출함
        
    }
}

 

앞서 나온 방식을 이용하게되면 3가지 정보를 모두 넘겨주지만 실제 옵저버 클래스에서는 temp와 humidity만 사용하는데 모든 정보를 넘겨받는다는 점과, 정보를 받고싶은 상황을 선택하고 싶을 경우에는 어떻게 구현해야할까?

observer.update(this.temp, this.humidity, this.pressure);

 

2. 옵저버 패턴의 push vs pull

앞서 나온 정보가 변경될 경우 주제에서 옵저버에게 변경사항을 전달해주는 방식을 push방식이라고 한다. 이와 다르게 옵저버가 필요할 때마다 정보를 요청하는 방식을 pull방법이라 한다. JAVA에서 제공하는 옵저버 클래스를 활용하여 pull방식을 구현해보자.

 

Java 내장 옵저버를 이용하면 다음과 같다. Observable은 인터페이스가 아닌 클래스이고 앞서만든 Subject interface와 같은 역할을 한다.

 

Observable 클래스를 상속받은 WeatherData

import java.util.ArrayList;
 
public class WeatherData extends Observable {

	private float temp;                        // // 느슨한 결합(Loose Coupling)을 위해 상위형식으로 구성을 사용
    private float humidity;
    private float pressure;
    
    public WeatherData() {  } // Array를 사용할 필요 X
    
    // subject의 상태(소식)이 변경되었을 때 호출 : observer들에게 상태를 알림
    public void measurementsChanged() {
    	setChanged();
        notifyObservers();
    }
    
    public void setMeasurements(float temp, float humidity, float pressure) {    // 
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        
        measurementsChanged();
    }
 	public float getTemperature(){
    	return temperature;
    }
    public float getHumuduty(){
    	return humuduty;
    }
    public float getPressure(){
    	return pressure;
    }
}

 

Observer 구현 클래스

public class GeneralDisplay implements Observer, DisplayElement {
 	
    Observable observable;
    private float temp;
    private float humidity;
       
    public CurrentConditionDisplay(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }
    
    public void update(Observable obs, Object arg) { //값을 받는것이 아닌 객체를 통해 get메소드로 원하는 정보를 가져간다.
    	if(obs instaceof WeatherData){
        	WeatherData weatherData = (WeatherData)obs;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
        	display();
        }
    }
    
    public void display() {
        System.out.println("Current Condition: "+this.temp+"F degrees and "+this.humidity+"% humidity and "+pressure+" pressure");
    }       
}

 

pull 방식은 다음과 push방법처럼 정보를 주입 받는것이 아니라 다음과 같이 객체를 받아 원하는 정보를 가져간다는 특징이 있다.

public void update(Observable obs, Object arg) { //값을 받는것이 아닌 객체를 통해 get메소드로 원하는 정보를 가져간다.

 

setChanged()는 무엇을 의미할까? Observable 슈퍼 클래스에 정의된 flag이다. 즉 변화로 인정해서 update할지 말지 의미하는것이고 내가 온도변화를 3도 이상 변했을경우만 setChange를 할수 있게 변경할 수 있다.


3. 옵저버 패턴의 예시

MVC패턴에서 옵저버 패턴을 찾아볼 수 있다. 옵저버 패턴이란 상태변화를 통지해 주는것을 의미했는데 MVC패턴은 다음과같다.

  1. 컨트롤러에 의해 Model의 상태가 변함 (Model의 값이 변함)
  2. 뷰는 모델에 값이 변할때 상태 변화를 모델로부터 직접 가져옴(Pull)
반응형
반응형

1. 프록시 패턴의 정의

Proxy는 우리말로 대리자라는 뜻입니다. 다른 누군가를 대신해서 그 역할을 수행하는 존재입니다. 즉 프록시에게 어떤 일을 대신 시키는 것.

 

구체적으로 인터페이스를 사용하고 실제 기능을 하는 타겟 클래스 대신 이를 대신할 프록시 클래스를 만들고 프록시 클래스가 타겟 클래스를 호출하는 것

이때 프록시는 흐름제어만 할 뿐 결과값을 조작하거나 변경시키면 안된다.

 

아래와 같은 구조로 예시를 들면 다음과 같다.

 

ServiceI 인터페이스

public interface ServiceI {
 
    String run();
}

 

TargetService 클래스

public class TargetService implements ServiceI{
 
    @Override
    public String run() {
        return "실제 클래스입니다.";
    }
     
}

 

ProxyService클래스

public class ProxyService implements IService{
 
    ServiceI service;
     
    @Override
    public String run() {
        System.out.println("흐름을 바꿈 결과 값은 같다.");
         
        service = new Service(); 
        return service.run();
    }
 
}

 

클라이언트

public class Main {
 
    public static void main(String[] args) {
        //직접 호출하지 않고 프록시를 호출한다.
        ServiceI proxy = new ProxyService();
        System.out.println(proxy.run());
    }
}

2. 데코레이터 패턴의 정의

 

객체에 추가적인 요건을 동적으로 첨가한다.

데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다. 즉 타깃에 부가적인 기능을 부여하는것을 의미한다

 

아래와 같은 구조로 예시를 들면 다음과 같다.

 

Beverage 클래스

public abstract class Beverage {
    
    String description = "no title"; //음료 이름
 
    public abstract int cost();
 
    public String getDescription() {
        return description;
    }
    
}

 

CaffeLatte 클래스

public class CaffeLatte extends Beverage {
 
    public CaffeLatte() {
        super();
        description = "카페라떼";
    }
 
    @Override
    public int cost() {
        return 5000;
    }
}

 

CondimentDecorator 클래스

public abstract class CondimentDecorator extends Beverage {
    
    public abstract String getDescription();
}

 

Cream클래스

public class Cream extends CondimentDecorator {
 
    Beverage beverage;

    public Cream(Beverage beverage) {
        super();
        this.beverage = beverage;
    }
 
    @Override
    public String getDescription() {
        return beverage.getDescription() + ", 크림";
    }
 
    @Override
    public int cost() {
        return beverage.cost() + 500;
    }
}

 

Main클래스

public class Test {
    public static void main(String[] args) {
        
        Beverage beverage = new CafeLatte();
        beverage = new Cream(beverage); //beverage 필드에 CaffeLatte 인스턴스 저장
        
        System.out.println("가격 : " + beverage.cost());
    }
}

3. 프록시, 데코레이터의 차이

둘다 기존 타겟 객체가 아닌 다른곳에서의 행위가 일어나므로 비슷하게 느껴지지만 프록시 패턴은 요청을 위임해주는데 의의가 있고, 데코레이터 패턴은 부가기능을 추가하는데에 있다.


4. spring AOP개념에서의 proxy는?

아래와 같이 트랜잭션을 분리할때 proxy는 디자인 패턴으로 봤을때 프록시 패턴인가? 데코레이터 패턴인가?

기존 타겟코드(Dao의 요청을 업데이트하는부분)에 트랜잭션을 적용하는 부분으로 바라보았을때는 데코레이터 패턴의 역할도 있고, 타겟클래스 전후로 요청을 위임하는 부분으로 보았을때는 프록시 패턴으로 바라볼수도 있다.

public class TransactionHandler implements InvocationHandler {
    private Object target;
    private PlatformTransactionManager transactionManager;
    private String pattern; //트랜잭션을 적용할 메소드 이름 패턴
    
    public void setTarget(Object target) {
        this.target = target;
    }
    
    public void setTransactionManager(PlatormTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
    
    public void setPattern(String pattern) {
        this.pattern = pattern;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().startsWith(pattern)) {
            return invokeInTransaction(method, args);
        } else {
            return method.invoke(target, args);
        }
    }
    
    private Object invokeInTransaction(Method method, Object[] args) throws Throwable {
        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            Object ret = method.invoke(target, args);
            this.transactionManager.commit(status); //타깃 호출후 예외가 발생하지 않으면 commit
            return ret;
        } catch (InvocationTargetException e) { //예외가 발생하면 트랜잭션 롤백
            this.transactionManager.rollback(status);
            return e.getTargetException();
        }
    }
}
반응형
반응형

0. 개요

앞서 작성한 다음과같은 트랜잭션 코드가 있다고 가정할 때, 실제 Dao에 data를 읽어오는 핵심로직과, TransactionManager부분은 분리될 필요가 있다. 왜냐하면 다른 메소드에 같은 코드가 반복될 수 있기 때문이다.

public void upgradeLevels() throws Exception {
    TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
    
    //핵심 로직
	List<User> users = userDao.getAll();
    for(User user : users) {
    	if(canUpgradeLevel(user)) {
           	upgradeLevel(user);
        }
    }
    
    this.transactionManager.commit(status);
    } catch (Exception e) {
        this.transactionManager.rollback(status);
        throw e;
    }
}

 

1. AOP란?

AOP는 애플리케이션의 핵심적인 비즈니스로직으로부터 부가적인 기능을 분리해서 애스펙트(Aspect)로 정의하고 설계하여 개발하는 방식.

 

2. AOP용어

  • Advice : 타깃에게 제공할 부가 기능을 담은 모듈을 의미한다. 즉 앞 예제에 비즈니스로직 전 후로 나오는 트랜잭션을 의미한다.
  • Joinpoint : 어드바이스가 적용될 수 있는 위치를 말한다. AOP에서 조인포인트는 메소드의 실행 단계에 적용된다.
  • pointcut : 조인포인트를 선별하는 작업 또는 그 기능을 정의한 모듈을 말한다. (조인포인트의 집합)
  • adviser : 포인트컷과 어드바이스를 하나씩 가지고 있는 오브젝트를 의미한다.
  • aspect: 한개 이상의 포인트컷과 어드바이스의 조합으로 만들어진 공통 기능 (트랜잭션, 로그 등)

3. AOP Proxy

핵심 기능과 부가기능을 나누어 사용할 수 있는 이유는 AOP가 proxy를 이용하기 때문이다.

 

proxy란?

 

클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장하여 클라이언트의 요청을 받고 대리하여 업무를 처리하는 대리자 역할.

함수호출자는 주요업무가 아닌 보조업무를 프록시에게 맡기고, 프록시는 내부적으로 보조업무를 처리.

 

  1. Proxy를 호출
  2. 보조업무처리
  3. 실제 핵심 함수 호출 및 처리
  4. Proxy로 다시 넘어오고 보조업무처리
  5. 메서드 반환

4. 예시

개요에서 작성한 트랜잭션코드를 프록시 형태로 수정하면 다음과 같다.

기존 핵심 로직은 UserDao 클래스내부에 그대로 있고 부가기능을 수행할 handler를 따로 만들어서 handler부분이 선처리 후처리를 하도록 하는 예시이다.

 

TransactionHandler 클래스(부가기능)

public class TransactionHandler implements InvocationHandler {
    private Object target;
    private PlatformTransactionManager transactionManager;
    private String pattern; //트랜잭션을 적용할 메소드 이름 패턴
    
    public void setTarget(Object target) {
        this.target = target;
    }
    
    public void setTransactionManager(PlatormTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
    
    public void setPattern(String pattern) {
        this.pattern = pattern;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().startsWith(pattern)) {
            return invokeInTransaction(method, args);
        } else {
            return method.invoke(target, args);
        }
    }
    
    private Object invokeInTransaction(Method method, Object[] args) throws Throwable {
        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            Object ret = method.invoke(target, args); //타깃 함수 호출
            this.transactionManager.commit(status); //타깃 호출후 예외가 발생하지 않으면 commit
            return ret;
        } catch (InvocationTargetException e) { //예외가 발생하면 트랜잭션 롤백
            this.transactionManager.rollback(status);
            return e.getTargetException();
        }
    }
}

 

Test클래스

TransactionHandler txHandler = new TransactionHandler();
txHandler.setTarget(testUserService);
txHandler.setTransactionManager(transactionManager);
txHandler.setPattern("upgradeLevels");
UserService txUserService = (UserService)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] {UserService.class }, txHandler);

txUserService.upgradeLevels();

5.  프록시 방식의 AOP의 이슈

1. public 클래스를 이용해야 한다.

만약 private클래스에 @Transactional 어노테이션을 활용한다고 가정해보자. @Transactional을 이용하면 스프링에서 프록시를 이용해 프록시 클래스에서 타겟클래스 메소드를 호출하게 하는데, private로 선언된 메소드는 같은 클래스 내에서 만 호출이 가능하다 즉 proxy클래스는 타겟클래스와 같은 클래스가 아니기 때문에 사용이 불가하므로 생기는 이슈

 

2. 같은 클래스의 메소드가 다른 메소드를 호출할 때 생기는 이슈

다음 그림과 같은 구조에서 update만 트랜잭션이 적용되어야 한다고 하면, 다음과 같은 문제가 발생할 것이다. 외부 클라이언트가 delete를 호출하고, delete는 바로 내부의 update를 호출하는데, 이때 프록시 클래스를 통한 호출이 아니기 때문에 트랜잭션이 적용되지 않는 이슈가 발생한다.

 

 

따라서 delete메소드에도 @Transactional을 걸면 delete 요청시 프록시 클래스로 요청이 이루어지고 이후 update를 요청하기 때문에 이슈를 해결할 수 있다.

또는 aspectj를 공식 페이지에서 권장한다.

public void delete(String name) {
	//delete 코드
    update(name);
}

@Transactional
public void update(String name) {
	//업데이트 코드
}

 

이슈가 발생한 이유는 스프링 AOP 방식을 이용하면 내부 메소드가 다른 메소드를 호출하게 되면 프록시 클래스를 거치지 않기 때문

반응형
반응형

1. 트랜잭션 적용이 필요한 시나리오

JDBC를 이용하는 환경에 맞게 스프링에서 제공하는 트랜잭션 기능을 통해 Service를 다음과 같이 구성했다고 하자. 

하지만 만약 다른 회사는 JDBC가 아닌 여러개의 DB를 이용하여 JTA를, 다른 회사는 , JPA를 사용한다고 했을 때 이 서비스는 사용자들의 환경에 따라 수정이 필요한 서비스가 되어 버린다. 따라서 스프링에서 제공하는 트랜잭션 서비스를 추상화 하는 인터페이스(PlatformTransactionManager)통해 추상화 할 수 있다.

@Autowired
private DataSource dataSource;

public void setDataSource(DataSource dataSource){
	this.dataSource = dataSource;
}

public void upgradeLevels() throws Exception {
	TransactionSynchronizationManager.initSynchronization(); //트랜잭션 동기화 관리자를 이용해 초기화
    Connection c = DataSourceUtils.getConnection(dataSource); //JDBC 커넥션을 시작함
    c.setAutoCommit(false);
    ......

 

다음과 같이 추상화 할 수 있다. set방식은 기존 DI와 같은 방식이고 추상화를 이용하면 JDBC, JPA 등 다양한 트랜잭션을 getTransaction, commit 같은 메소드로 실행할 수 있다.

public class UserService {
...
	private PlatformTransactionManager transactionManager;
    
    public void setTransactionManager(PlatformTransactionManager transactionManager) {
    	this.transactionManager = transactionManager;
    }
    
    public void upgradeLevels() {
    	TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
        ...
}
반응형
반응형

1. 전략패턴이란?

 

전략패턴이란 동적으로 알고리즘을 교체할 수 있는 구조를 말한다. 알고리즘 인터페이스를 정의하고, 각각의 알고리즘 클래스별로 교체 사용 가능하게 한다. (앞선 Connectionmaker 인터페이스구조가 이에 해당)

 

 

2. 예시

만약 다음과 같은 delete예제가 있다고 할때 전략 패턴을 적용시켜보자.

다음과 같은 코드에서 변경될 부분은 ps = c.prepareStatement("delete from users"); 부분이다.

나머지 부분은 고정되어 있어야하니 Context에 해당하고 ps 부분이 전략 부분에 해당된다.

Connection c = null;
PreparedStatement ps = null;
try{
	c = dataSource.getConnection();
    
    ps = c.prepareStatement("delete from users");
    
    ps.executeUpdate();
} catch (SQLException e) {
	throw e;
} finally {
	...
}

 

StatementStrategy인터페이스

public interface StatementStrategy {
	PreparedStatement makePreparedStatement(Connection c) throws SQLException
}

 

DeleteStrategy

public class DeleteStrategy implemetns StatementStrategy {

	public PreparedStatement makePreparedStatement(Connection c) thorws SQLException {
    preparedStatement ps = c.preparedStatement("delete from users");
    return ps;
  	}
}

 

수정된 Context 부분

public void Context(StatementStrategy stmt) throws SQLException {
  Connection c = null;
  PreparedStatement ps = null;
  try{
      c = dataSource.getConnection();

      ps = stmt.makePreparedStatement(c);

      ps.executeUpdate();
  } catch (SQLException e) {
      throw e;
  } finally {
      ...
  }
}

 

Factory부분

public void Factory() throws SQLException {
	StatementStrategy st = new DeleteStrategy();
    Context(st);
}

 

3. 정리

앞선 UserDao와 같은 패턴으로 이루어져 있고 IOC와 DI모두 이해할 수 있다.

반응형
반응형

1. 예시

만약 DB로부터 정보를 얻어오는 다음과같은 예제를 구현했다고 가정하면, 예제는 정상작동하지만, 메소드의 종류가 늘어나면 Connection 부분이 반복적으로 수행된다. 이는 DB의 정보가 바뀌면 메소드의 개수만큼 수정이 일어나야한다는 단점이 있고, Connection부분이 각 A, B회사가 다르게 구성하고 싶을때 UserDao부분의 수정이 일어나야하므로 적절한 방법으로의 수정이 필요하다.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserDao {

	public void insertUser(User user) throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/test?serverTimezone=UTC", "root", "1234");
		PreparedStatement ps = c.prepareStatement("INSERT INTO USERS(ID, NAME, PASSWORD) values(?,?,?)");
		ps.setString(1,user.getId());
		ps.setString(2,user.getName());
		ps.setString(3,user.getPassword());

		ps.executeUpdate();
		
		ps.close();
		c.close();
	}
	
	public User getUserList(String id) throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/test?serverTimezone=UTC", "root", "1234");
		PreparedStatement ps = c.prepareStatement("SELECT * FROM USERS WHERE id = ?");
		ps.setString(1,id);

		ResultSet rs = ps.executeQuery();
		rs.next();
		User user = new User();
		user.setId(rs.getString("id"));
		user.setName(rs.getString("name"));
		user.setPassword(rs.getString("password"));
		
		rs.close();
		ps.close();
		c.close();
		
		return user;
	}
}

 

따라서 제공하는 다음과 같은 구조를 이용하여 코드를 개선한다. ConnectionMaker라는 인터페이스를 통하여 다음과 같이 수정한다.

UserDao

public class UserDao {
	private ConnectionMaker connectionMaker;
    
    public UserDao(ConnectionMaker connectionMaker) {
		this.connectionMaker = connectionMaker;
    }
    
    public void insert.....
    
    public User get.....
    
}

 

ConnectionMaker interface

public interface ConnectionMaker {
	
    public Connection makeConnection() throws ClassNotFoundException, SQLException;
}

 

AConnectionMaker(A회사의 구현 클래스)

public class AConnectionMaker implements ConnectionMaker {
	...
    
    public Connection makeConnection() throws ClassNotFoundException, SQLException {
    	// A회사의 Connection을 생성하는 코드
    }
}

 

2. IOC란

IOC란 Inversion Of Control의 약자로, 제어의 역전이라는 뜻을 가지고 있다.

일반적으로 프로그램은 자신이 사용할 오브젝트를 직접 선택하고, 생성한다

new를 통해 어떤 오브젝트를 생성할건지 선택해 놓음.

private ConnectionMaker connectionMaker = new AConnectionMkaer();

앞선 예제에는 UserDao에서 new를 통해직접적으로 오브젝트를 생성하는대신, 생성자 파라미터를 통해 오브젝트를 선택하게된다. 즉 변화에 유연한 코드를 만들면서 DAO를 수동적인 상태로 변환시켰다.

또한 필요한 오브젝트를 직접 만들지않고, 이후에 나올 Factory가 만들어 준 오브젝트를 전달받게 된다.

즉 제어의 역전이란, 말 그대로 제어권을 역전 시키는 것으로써, 제 3자(Factory)에게 오브젝트에 대한 제어권을 넘겨주고, 자신은 제 3자가 선택하고 생성한 오브젝트를 받아서 사용하는 수동적인 상태가 되는 것을 말한다. 스프링은 IOC를 기반으로 개발자가 활용할 수 있게 해주는 프레임워크이다.

 

DaoFactory(A, B 회사중 선택할 수 있는 클래스)

public class DaoFactory {
	public UserDao userDao() {
    	ConnectionMaker connectionMaker = new AConnectionMaker(); //A회사 선택
        UserDao userDao = new UserDao(connectionMaker); //DI
        return userDao;
    }
}

다음과 같이 구성하게되면 더이상 UserDao는 수정될 필요없는 부분을 유지하고 수정될 여지가 있는 부분은 인터페이스를 implement한 클래스들이, 어떤 클래스를 UserDao가 사용할지는 Factory부분에서 정해주는것을 확인할 수 있다. 이렇게 interface를 이용한 부분이 IOC를 설명하고 있다.

 

3. DI / 의존성 주입

DI란 Dependency Injection의 약자로, 의존관계 주입 이라는 의미를 가지고 있다.

의존 관계란 하나의 오브젝트에서 다른 오브젝트를 사용할 때를 말한다. A라는 클래스에서 B라는 클래스를 사용할 경우, A클래스는 B클래스에 의존하고 있다 라고 표현한다. 즉 UserDao는 ConnectionMaker Interface에 의존한다. 따라서 A회사인지 B회사인지의 여부는 알지 못하는 상태이다.

 

이때 AConnectionMaker클래스를 생성해서 UserDao클래스에 넣어주는 과정, 이를 의존성 주입이라고 보면 된다.

 

즉 Factory에서 다음과 같은 부분이 DI가 일어나는 부분이다

UserDao userDao = new UserDao(connectionMaker); //DI

 

4. 정리

기존 IOC 개념 = 객체 new로 생성안하고 다른곳에서 생성해서 사용하는것 정도로 알고 있던것이 명확하게 알게되었고, 앞서 나온 Interface를 사용한 패턴이 전략패턴이라는것을 알게 되었다 전략패턴은 IOC에 기본이 되는 패턴이라 한다.

 

또한 스프링에서 Factory부분을 컨테이너(ApplicationContext)로 제공하기 때문에 Factory를 통해 의존성 주입을 하지 않아도 된다.

반응형
반응형

1. 정의

개발하며 발생하는 오류를 아무조취를 취하지 않거나 콘솔에 찍는것은 좋은 방법이 아님. 따라서 모든 예외는 적절하게 복구되던지, 작업을 중단시키고 운영자 또는 개발자에게 분명하게 통보가 되어야함.

 

2. 예외의 종류

Error

java.lang.Error 클래스의 서브 클래스로 시스템에 비정상적인 상황이 발생했을때 사용된다. 주로 VM에서 발생시키는것으로 코드상에서 에러를 찾으려고 하면 안된다.

 

Exception과 체크 예외

java.lang.Exception 

 

java.lang.Exception 클래스와 그 서브클래스로 정의되는 예외는 개발자가 만든 애플리케이션 코드 작업중에 예외상황이 발생하였을 경우에 사용한다. 이 중 체크예외는 RuntimeException 클래스를 상속하지 않은 부분을 의미한다.

 

RuntimeException과 언체크 예외

java.lang.RuntimeException 클래스를 상속한 예외들을 의미하고, 명시적인 예외처리를 강제하지 않기 때문에 언체크 예외라고 불린다. 에러와 마찬가지로 catch나 throws로 잡아주지 않아도 된다. 주로 프로그램의 오류가 있을때 발생한다.

 

3. 예외처리 방법

예외 복구

사용자가 요청한 파일을 읽으려 했으나 파일이 없거나, 읽히지 않아서 IOException이 발생한 경우, 사용자에게 상황을 알려주고 다른 파일을 이용하도록 하는 과정, 이 때 사용자에게 예외상황을 안내만하는경우는 복구라고 할 수 없다.

 

이는 네트워크 오류로인해 sql이 실패할경우 대기시간을 거쳐 여러번 시도하게하는 예시이다.

int maxretry = MAX_RETRY;
while(maxretry --> 0){
	try{
	..
    }
    catch(SomeException e){
    //로그 출력 정해진 시간만큼 대기
    }
    finally {
    //리소스반납 정리작업
    }
}
throw new RetryFailedException();

 

예외처리 회피

throw문으로 선언하여 예외가 발생하면 알아서 던져지게 하거나, catch문으로 예외를 잡고 로그를 남기고 다시 예외를 던지는 방법을 의미한다. 이때 내가 처리하지않고 회피한다는것이 catch로 예외를잡고 예외를 발생하지 않은것처럼 하는것은 아니다.

 

이는 JDBC Template에 에러를 넘기는 예시이다. 하지만 콜백이나 템플릿이 아닌 자신의 코드에서 발생하는 예외를 던지는것은 회피가 아니다.

public void add() throws SQLException {
 //JDBC API
}
public void add() throws SQLException {
	try{
    	//JDBC API
    }
    catch(SQLException e){
    	throw e;
    }
}

 

예외 전환

예외 회피와 비슷하게 예외를 복구해서 정상적인 상태로 만들 수 없기 때문에 예외를 메소드 밖으로 던지는 것 이다. 하지만 예외 회피와 달리 예외를 분명하게 의미있는 예외로 바꾸어 전달하는것을 의미한다.

예를들어 새로운 사용자를 등록하려 했을때 DB에 중복되는 아이디가 있다면 JDBC API는 SQLException을 발생시킨다 이를 DuplicateUserIdException으로 바꾸어 던지는것을 의미한다.

public void add(User user) throws DuplicateUserIdException, SQLException {
	try{
    	//JDBC를 이용해 user 정보를 DB에 추가하는 코드
    }
    catch(SQLException e){
    	//ErrorCode가 Mysql의 "Duplicate Entry(1062)" 이면 예외 전환
        if(e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY)
        	throw DuplicateUserIdException();
        else
        	throw e;
    }
}

 

반응형

+ Recent posts