'프로그래밍/시큐어코딩' 카테고리의 글 목록 :: 잡다한 프로그래밍
반응형

1. WebFlux란?

Spring Framwork5에서 새롭게 추가된 모듈이다. webflux는 reactive 스타일의 어플리케이션 개발을 도와주는 모듈이라한다. 즉 Reactive Programming방식의 스프링이다.

 

Reactive Programming(반응 형 프로그래밍)은 비동기(Asynchronous)식 및 이벤트 기반(event-driven) Non Blocking이다. 

 

 

웹 플럭스는 기존 MVC처럼 이용할 수 있고, 기본으로 Netty를 제공한다고 함.

WebFlux는 아래와 같은 용도로 사용하는 것을 추천 한다고 합니다.

  • 비동기 - 논블록킹 리액티브 개발에 사용
  • 효율적으로 동작하는 고성능 웹어플리케이션 개발
  • 서비스간 호출이 많은 마이크로서비스 아키텍처에 적합

기존 MVC VS 웹 플럭스

 

기존 MVC 방법
Spring MVC의 경우 어플리케이션이 실행되면서 Thread Pool을 만들어 놓는다.
요청이 들어오면 그 요청을 Queue에 쌓고 이것을 Thread Pool의 Thread들이 요청을 가져가 처리한다.
하지만 많은 요청이 들어와 Pool Size를 초과하여 Queue에 계속 요청이 쌓이는 경우가 발생할 수 있다.

 

웹 플럭스

 

 

 

웹 플럭스의 2가지 방식

1. 기존 에노테이션 방식

@Controller
...{
// 애너테이션 기반 라우팅
@GetMapping("/hello")
@ResponseBody
public Mono<String> getHello() {

    return demoService.getHello();

}
}

 

2. 함수형 프로그래밍 방식

@Component

// 함수 기반 라우팅
@Bean
public RouterFunction<ServerResponse> routes(DemoHandler demoHandler) {

    return RouterFunctions
        .route(RequestPredicates.GET("/hello"), demoHandler::getHello);

}

두가지 중에 편한 방법을 통해서 사용해도 무방하다.

 

웹 플럭스의 반환형

아래와 같이 MVC에서 일반적으로 사용하던 Plain Object를 사용할 수 없고 반드시 Publisher Object로 감싸서 반환해야 한다. 이 때 Publisher는 Reactive Stream Interface중 하나인데 (Processor, Publisher, Subscriber, Subscription) Reactive Stream Interface에 관한 설명은 brunch.co.kr/@springboot/153 참고하기 바란다.

 

2. Mono & Flux란?

Publisher의 실제 구현체 즉 웹 플럭스에서 반환하는 형식이라고 생각해도 무방.

 

Flux (0-N개의 데이터)

Flux는 Publisher의 구현체로서, 0-N개의 데이터를 발행(전달, 방출)할 수 있다. 하나의 데이터를 전달할때마다 onNex 이벤트를 발생시키고, 모든 데이터 전달이 완료되면 onComplete 이벤트가 발생하며 오류가 발생할 경우 onError이벤트가 발생한다.

Mono(0-1개의 데이터)

Publisher 인터페이스의 구현체, Flux와 달리 0-1개의 데이터를 처리한다.

 

 

MVC VS WEBFLUX

m.blog.naver.com/joebak/222008524688

 

반응형
반응형

1. @Async란?

spring에서 제공하는 thread를 의미한다.

 

기존 JAVA Thread 사용방법

 

1) Thread 클래스를 상속받는 방법

class CustomThread extends Thread {
	public void run() {
		try {
			for (int i=0; i<100; ++i) {
				System.out.println("[" + getName() + "] CustomThread " + i);
				Thread.sleep(10);
			}			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 

2) Runnable 인터페이스를 상속받아 사용하는방법

class CustomRunnable implements Runnable {
	public void run() {
		try {
			for (int i=0; i<100; ++i) {
				System.out.println("[" + Thread.currentThread().getName() + "] CustomRunnable " + i);
				Thread.sleep(10);
			}			
		} catch (Exception e) {
			e.printStackTrace();
		}		
	}
}

public class ThreadPractice {
	public static void main(String[] args) {
		Thread t1 = new CustomThread();
		Thread t2 = new Thread(new CustomRunnable());
		
		t1.start();
		t2.start();
	}
}

 

@Async 이용하기

@Service
public class TestService {

	@Async
	public void customThread(int i) {
		for (int i=0; i<100; ++i) {
				System.out.println("[" + Thread.currentThread().getName() + "] CustomRunnable " + i);
				Thread.sleep(10);
			}	
	}
}

 

@Congiguration 사용하기

@Configuration
@EnableAsync
public class AsyncConfig {

	@Bean
	public Executor asyncThreadTaskExecutor() {
		ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
		threadPoolTaskExecutor.setCorePoolSize(50);
		threadPoolTaskExecutor.setMaxPoolSize(100);
		threadPoolTaskExecutor.setQueueCapacity(200);
		threadPoolTaskExecutor.setThreadNamePrefix("thread-pool");
		return threadPoolTaskExecutor;
	}

}

 

스프링을 이용하면 스레드 구성을 다음과같이 간편하게 할 수 있다

 

@Asnyc 메소드를 이용할 때 주의사항

  • public 메소드를 이용해야한다
  • 자가호출(self)을 하면 안된다

이러한 주의사항이 있는 이유는 spring AOP가 프록시 패턴의 개념을 이용하기 때문이다. 즉 프록시 클래스에서 @Async 메소드를 호출해야 하는데 private하거나, self로 호출하면 프록시클래스에 의해 호출되지 않기 때문이다.

※프록시 패턴글에 자세한 설명 있음


2. completablefuture란?

@Async같은 비동기 메소드를 사용할 때, Void형태를 사용한다면 문제가 되지 않는다. 하지만 return이 존재한다면 기존에는 Future나 ListenableFuture를 이용하여 해결했지만 JAVA 8버전 부터는 CompletableFuture를 제공하므로 이를 사용하는 방법을 정리한다.

 

다음은 Completeablefuture를 사용한 예제이다.

 

다음과 같이 @Async 메소드에서는 결과 값을 CompletableFuture객체에 담아서 반환해야하고 호출한 곳에서는  이 CompletableFuture 객체를 이용해야한다.

@Service
public class GitHubLookupService {
    
    @Async
    public CompletableFuture findUser(String user) throws InterruptedException {
	//로직
	return CompletableFuture.completedFuture(results);
    }
}

 

return을 CompletableFuture를 통해서 하는것을 알았으니 return 받은 쪽 에서는 어떻게 사용하는지 확인해보자.

 

get 함수란?

다음과 같이 .get()메소드를 통해 결과를 확인할 수 있다.

get을 하게 되면 page1의 수행이 끝날때까지 잠시 기다렸다가 결과값을 반환하게 된다.

public void run(String... args) throws Exception {
    //호출 해서 저장
    CompletableFuture page1 = gitHubLookupService.findUser("PivotalSoftware");
    
    //다른 로직
    System.out.println(page1.get());
}

 

그림으로 표현해보면 다음과 같다.

 

thenCompose 란?

하나의 CompletableFuture가 수행하고 나온 결과를 넘겨주어 다음 CompletableFuture객체가 이를 가지고 다른 메소드를 수행하는 방법.

public void run(String... args) throws Exception {
    //호출 해서 저장
    CompletableFuture<Integer> price1 = testService.getPrice(100);
    
    //다른 로직
    price1.thenCompose(result -> testService.getPrice(result)).join();
}

그림으로 표현하면 다음과 같다.

 

thenCombine 이란?

CompletableFuture를 2개 병렬 실행해서 결과를 조합할 때 사용한다. thenCompose와 달리 두개를 동시에 실행한다는 차이가 있다.

public void run(String... args) throws Exception {
    //호출 해서 저장
    CompletableFuture<Integer> price1 = testService.getPrice();
    CompletableFuture<Integer> price2 = testService.getPrice();
    
    //다른 로직
    price1.thenCombine(price2, (a, b) -> a+b);
}

 

allof 란?

thenCombine처럼 병렬 실행해서 결과를 조합할 때 사용한다. CompletableFuture를 3개이상 조합할 때 사용한다.

public void run(String... args) throws Exception {
    //호출 해서 저장
    CompletableFuture<Integer> price1 = testService.getPrice();
    CompletableFuture<Integer> price2 = testService.getPrice();
    CompletableFuture<Integer> price3 = testService.getPrice();

    //다른 로직
    CompletableFuture.allOf(price1,price2,price3).join();
}

 

allof를 지정하여 사용하고싶지않을 때

리스트에 CompletableFuture객체를 add하고 add한 리스트를 allof에 넣어서 사용할 수 있다.

List<CompletableFuture<Test>> completableFutures = new ArrayList<>();
		for (int j = 0; j <= 23; j++) {
          completableFutures.add(Dao.getCount());
		}
    
List<Test> combinedFuture = CompletableFuture
				.allOf(completableFutures.toArray(new CompletableFuture[completableFutures.size()]))
				.thenApply(result -> completableFutures.stream().map(future -> future.join()).collect(Collectors.toList()))
				.join();
반응형
반응형

1. 템플릿 메소드 패턴의 정의

메소드의 알고리즘의 공격을 정의한다. 알고리즘의 여러 단계중 일부는 서브클래스에서 구현할 수 있고, 이를 이용하면 알고리즘 구조는 그대로 이용하면서 특정단계만 재정의할 수있다.

상속받은 클래스에 기본 틀(추상클래스)이 정해져있고 세부적으로 구현하고싶은 메소드는 서브 클래스에서 구현하게하여 사용하는 방식


2. 예시

비슷한 기능을하는 커피와 티가 있다. 두개는 중복되는 부분이 너무 많은 클래스로서 템플릿 패턴을 적용하면 다음과 같다.

public class Coffee {
    
    void prepareRecipe() {
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addSugarAndMilk();
    }
    
    public void boilWater() {
        System.out.println("물 끓이는 중");
    }
    
    public void brewCoffeeGrinds() {
        System.out.println("필터를 통해서 커피를 우려내는 중");
    }
    
    public void pourInCup() {
        System.out.println("컵에 따르는 중");
    }
   
    public void addSugarAndMilk() {
        System.out.println("설탕과 우유를 추가하는 중");
    }
}
public class Tea {
    
    void prepareRecipe() {
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }
    
    public void boilWater() {
        System.out.println("물 끓이는 중");
    }
    
    public void steepTeaBag() {
        System.out.println("차를 우려내는 중");
    }
    
    public void pourInCup() {
        System.out.println("컵에 따르는 중");
    }
    
    public void addLemon() {
        System.out.println("레몬을 추가하는 중");
    }
}

 

다음은 커피와 티가 상속받을 추상클래스이다.

abstract class CaffeineBeverage {
    void final prepareRecipe(){
    	boilWater();
        brew();
        pourInCup();
        addCondiments();
    }
    abstract void brew();
    abstract void addCondiments();
    
    void boilWater() {
        System.out.println("물 끓이는 중");
    }
    void pourInCap() {
        System.out.println("컵에 따르는 중");
    }
}

 

상속받은 커피 클래스

public class Coffee extends CaffeineBeverage {
	public void brew() {
    	System.out.println("필터로 커피 우려냄");
    }
    public void addCondiments() {
    	System.out.println("설탕과 커피를 추가함");
    }
}

 

템플릿 메소드에서의 후크

정해진 알고리즘에서의 if문을 통해 과정을 실행시킬지 말지 선택하도록 하는부분을 후크라고 한다.

void prepareRecipe() {
    boilWater();
    brew();
    pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
}

    boolean customerWantsCondiments() {
        return true;
    }

 

사용자는 다음과 같이 customerWantsCondiments() 메소드를 재정의 하여 과정을 컨트롤 할 수 있다.

public class CoffeeWithHook extends CaffeineBeverageWithHook {
    
    public void brew() {
        // implements
    }
    
    public void addCondiments() {
        // implements
    }
    
    public boolean customerWantsCondiments() {
        String answer = getUserInput();
        
        if (answer.toLowerCase().startsWith("y")) {
            return true;
        }
        
        return false;
    }
    
    private String getUserInput() {
        String answer = null;
        
        System.out.println("커피에 우유와 설탕을 넣어 드릴까요?");
        
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        
        try {
            answer = in.nextLine();
        } catch (IOException ex) {
            System.err.println("IO 오류");
        }
        
        if (answer == null) {
            return "no";
        }
        
        return answer;
    }
}

3. 템플릿 메소드 패턴의 예시

spring에서 디스패쳐 서블릿의 doService라는 함수가 템플릿 메소드로 구성되어있다. 아래와 같이 FrameworkServlet을 상속받아 doService메소드를 디스패쳐 서블릿에서 구체화하여 사용하고 있고 이는 템플릿 메소드패턴의 적절한 예시이다.

public class DispatcherServlet extends FrameworkServlet {

    ...

    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        logRequest(request);

        ...중략...

    }

    ...
}
반응형
반응형

1. 빌더 패턴이란?

선택적인 파라미터가 많을수록 파라미터의 제공 상태를 일관성있게 해주고(어떤 파라미터를 제공하는지, null인지) 최종적으로 생성된 객체를 return하는 패턴

 

2. 예시

 

만약 다음과 같은 UserInfo 클래스가 있다고 할때 생성자를 통해 객체를 생성하게 될때 파라미터의 개수가 많거나 Null이거나 하는경우 어떠한 파라미터를 제공하는지 알아보기도 힘들고 객체 생성이 용이하지않음.

public class UserInfo {
	private String name;
	private int age;
    
	public UserInfo(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public void getUserInfo() {
		System.out.println(name, age);
	}
}

 

따라서 빌더패턴을 적용해보면 다음과 같다. 아래와 같이 set을 통해 변수값을 지정하고 build를 통해 객체를 생성하는 방식이다. 사용자는 set을 통해 파라미터값을 정하기 때문에 생성자 방식처럼 순서에 맞게 작성하지 않아도 된다는 장점이 있고 예시는 클래스를 따로 나눴지만 내부 클래스를 이용해 하나의 클래스처럼 이용할 수 있다.

public class UserInfoBuilder {
	private String name;
	private int age;

	public UserInfoBuilder setName(String name) {
		this.name = name;
		return this;
	}

	public UserInfoBuilder setAge(int age) {
		this.age = age;
		return this;
	}

	public UserInfo build() {
		return new UserInfo(name, age);
	}
}
UserInfoBuilder userInfoBuilder = new UserInfoBuilder();
UserInfo userInfo3 = userInfoBuilder.setName("테스터3").setAge(26).build();

 

롬복을 이용하게 되면 @Builder 어노테이션으로 같은 기능을 수행하여 코드량을 줄일 수 있다.


3. 추상 팩토리 패턴과 빌더패턴의 차이

앞서 나온 추상팩토리 패턴과 빌더패턴 모두 객체를 생성하는 관점에서 비슷하게 느껴질 수 있으나, 다음과 같은 차이가 있다.

추상팩토리는 제품의 최종단계가 아닌 원재료 즉 전체를 위한 하위객체를 바로 리턴하고,

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

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

빌더 패턴은 정보가 합쳐진 최종 객체를 반환한다는 특징이 있다.

 

쉽게 생각해서 빌더패턴은 최종 결과물을 사용자가 받아보게 되고, 팩토리 패턴은 최종 결과물에 필요한 객체를 받아보게 된다.

반응형
반응형

1. 퍼사드 패턴의 정의

어떤 시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공한다. 즉 여러 클래스의 복잡한 처리과정을 하나의 인터페이스로 묶어서 사용한다.


2. 예시

영화를 보기 위해 일련의 과정 1. 팝콘 기계를 키고 튀긴다 2. 전등을 조절한다 3. 스크린을 내리고 프로젝트를 켠다 .... 같은과정을 하나의 통합 인터페이스로 묶어 실행하는 과정

 

HomeTheaterFacade 클래스

public class HomeTheaterFacade {
 Amplifier amp;
 Tuner tuner;
 DvdPlayer dvd;
 CdPlayer cd;
 Projector projector;
 TheaterLights lights;
 Screen screen;
 PopcornPopper popper;
 
 public HomeTheaterFacade(Amplifier amp, 
     Tuner tuner, 
     DvdPlayer dvd, 
     CdPlayer cd, 
     Projector projector, 
     Screen screen,
     TheaterLights lights,
     PopcornPopper popper) {
 
  this.amp = amp;
  this.tuner = tuner;
  this.dvd = dvd;
  this.cd = cd;
  this.projector = projector;
  this.screen = screen;
  this.lights = lights;
  this.popper = popper;
 }
 
 public void watchMovie(String movie) {
  System.out.println("Get ready to watch a movie...");
  popper.on();
  popper.pop();
  lights.dim(10);
  screen.down();
  projector.on();
  projector.wideScreenMode();
  amp.on();
  amp.setDvd(dvd);
  amp.setSurroundSound();
  amp.setVolume(5);
  dvd.on();
  dvd.play(movie);
 }
//기타 메소드
}

 

퍼사드 패턴을 이용하지 않았다면 Movie를 볼때의 과정을 전부 직접 호출했어야하고, 다른 과정 음악듣기 같은경우 Movie와 다른 과정으로 직접 실행해야한다 하지만 퍼사드 패턴을 이용하면 다음과 같이 실행할 수 있다.

HomeTheaterFacade homeTheater = 
    new HomeTheaterFacade(amp, tuner, dvd, cd, 
      projector, screen, lights, popper);
 
  homeTheater.watchMovie("Raiders of the Lost Ark");
반응형
반응형

1. 어댑터 패턴의 정의

한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환하는 역할.

아래 그림에서 어탭터 클래스를 만드는것을 의미함


2. 예시

칠면조 클래스를 오리 클래스로 바꾸는 과정

 

 

Duck 인터페이스

public interface Duck {
	public void quack();
}

 

MallardDuck  클래스

public class MallardDuck implements Duck {
	public void quack() {
    	System.out.println("오리!");
    }
}

 

Turkey 인터페이스

public interface Turkey {
	public void gobble();
}

 

WildTurkey 클래스

public class WildTurkey implements Turkey {
	public void gobble {
    	System.out.println("칠면조!");
    }
}

 

어댑터 클래스

public class TurkeyAdapter implements Duck {
	Turkey turkey;
    
    public TurkeyAdapter(Turkey turkey) {
    	this.turkey = turkey;
    }
    
    public void quack() {
    	turkey.gobble();
    }
}

 

Test클래스

public class Test {
	public static void main(String[] args) {
		MallardDuck duck = new MallardDuck();
        
        WildTurkey turkey = new WildTurkey();
        Duck turkeyAdapter = new TurkeyAdapter(turkey);
        
        System.out.println(duck.quack());
        System.out.println(turkeyAdapter.quack());

 

즉 칠면조 클래스를 덕인터페이스에 맞게 변환하여 오리처럼 사용할 수 있게 해주는 패턴이 어댑터 패턴이다.

반응형
반응형

1. 커맨드 패턴의 정의

커맨드 패턴은 객체의 행위(메서드)를 클래스로 만들어 캡슐화 하는 패턴입니다.

 

즉, 어떤 객체(A)에서 다른 객체(B)의 메서드를 실행하려면 그 객체(B)를 참조하고 있어야 하는 의존성이 발생합니다.

그러나 커맨드 패턴을 적용하면 의존성을 제거할 수 있습니다.

 

즉 내가 B라는 객체의 메서드를 실행 시킬때 B를 실행시키는걸 모른채로 메서드를 실행하게 만든다.


2. 예시

다음과 같이 Lamp를 on 하는 클래스가 주어지고, 이를 사용할 Remote 클래스를 다음과 같이 만든다.

 

Lamp 클래스

public class Lamp {
    public void On(){
        System.out.println("Lamp on");
    }
}

 

Remote 클래스

public class Remote {
    private Lamp lamp;

    public Remote(Lamp lamp){
        this.lamp = lamp;
    }

    public void execute(){
        lamp.on();
    }
}

 

Client 클래스

 

public class Client {
    public static void main(String args[]){
        Lamp lamp = new Lamp();
        Remote remote = new Remote(lamp);
        remote.execute();
    }
}

만약 다음과 같이 Remote코드를 작성했을 경우 Lamp ON 이외에 다른 클래스(Streo On)가 주어질 경우 Remote가 수정되어야 하는 문제가 생긴다. 따라서 커맨드 패턴을 적용하면 다음과 같다.

 

Command 클래스

public interface Command {
          public void execute();
 }

 

LightOnCommand 클래스

public class LightOnCommand implements Command {

          Light light;   //이 Light 객체는 실제 불키는 방법을 알고있는 리시버 객체

          public LightOnCommand(Light light) {
                   this.light = light;
          }

          public void execute() {
                   light.on();
          }
 }

 

SimpleRemoteControl 클래스 (인보커)

public class SimpleRemoteControl {
          Command slot;

          public SimpleRemotecontrol() { }
         
          public void setCommand(Command command) {
                   slot = command;
          }

          public void buttonWasPressed() {
                   slot.execute();
          }
 }

 

RemoteControlTest 클래스 (클라이언트)

public class RemoteControlTest {
          public static void main(String[] args) {
          
                   SimpleRemoteControl remote = new SimpleRemoteControl();
                   Light light = new Light();
                   LightOnCommand lightOn = new LightOnCommand(light);

                   remote.setCommand(lightOn);
                   remote.buttonWasPressed();
          }           
 }

 

즉 커맨드 패턴은 다음과 같은 구조를 띄게 된다. 따라서 Lamp 이외의 클래스를 사용하려면 LightOnCommand와 같은 XXXCommand 클래스를 만들고 적용할 수 있기 때문에 확장에 용이하다.


3. 커맨드 패턴의 적용 예시

java의 Thread가 커맨드 패턴이 적용된 예시이다.

메인 클래스의 Thread 객체에서 start를 통해 Command의 run 메소드를 실행시키지만 Thread 입장에서는 어떤것을 실행시키는지 알 수 없다.

따라서 run = execute를 의미, start = buttonWasPressed를 의미한다.

public class Main {
  public static void main(String[] args) {
    Thread t = new Thread(new Command());
    t.start();  /* run을 실행하지만 Command가 무엇을 하는지는 모른다 */
  }
}

class Command implements Runnable {
  @Override
  public void run() {
    System.out.println("RUN!");
  }
}
반응형
반응형

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 도우, 치즈, 버섯 = 뉴욕 공장)
반응형

+ Recent posts