출처: 클린 소프트웨어

 

정적 프록시 모델

프록시 패턴의 구현목적은 Product의 Database접근과 Business logic의 분리를 위하여 사용하는 패턴입니다.

 

그렇다고 마구 사용해서는 안되는 패턴이며, 애플리케이션과 API(혹은 네트워크, 데이터베이스)의 둘중 혹은 하나가 잦은 변화가 생길때 중간 레이어에 프록시를 두어서 사용하길 권장드리는 패턴입니다.

 

위의 그림에서는 Product DB Proxy class가 user가 요청한 data를 Database에 읽거나 직접 쓰는 역할을하며, 그 가져온 data의 Business logic을 처리하는 부분은 Product Implementation 클래스를 delegate하여 사용하게 됩니다.

 

이렇게하면 장점은 Database에서 데이터를 가져오는 부분과 Business Logic부분을 따로 분리하여 관리할 수 있는 강점을 갖게 됩니다.

 

 

예제상황

Proxy클래스 다이어그램

상품에 대한정보도있으며, 이를 주문하는 시스템이 있다고 가정해 봅시다.

위의 그림에서는 상품에 대한 데이터베이스 정보와 비즈니스 로직을 처리하는 부분을 Product 인터페이스의 하위 클래스들로 구현된 클래스 다이어그램입니다.

 

또한, 주문을 처리하는 Order인터페이스가 있으며 Order에 대한 데이터베이스 정보와 비즈니스 로직을 처리하는 부분을 Order 인터페이스의 하위 클래스로 구현된 클래스 다이어그램입니다.

 

참고로 다이어그램에서 -Data로 끝나는 클래스 이름들은 Database에서 받아오는 객체의 정보입니다.

-Proxy로끝나는 하위 클래스들은 직접 Database에 접근 하는 클래스이며, -Impl로 끝나는 클래스들은 비즈니스 로직을 처리하는 부분이라고 보면 됩니다.

 

이 그림은 프록시들간의 관계를 나타내는 좀 더 응용된 프록시 패턴의 클래스 다이어그램으로 보시면 됩니다.

 

Product가 원래 존재하였는데, Order시스템 구현으로 인해서 OrderProxy에서 Order DB정보뿐만 아니라, Product의 데이터와 비즈니스 로직이 필요하여 ProductProxy를 OrderImpl에 주입하여주고, OrderImpl과 멤버필드인 item은 Product 인터페이스들을 통하여 Order와 관련된 비즈니스 로직을 처리할때 필요한 Product정보들을 받아오며 이용하는 것이 이 클래스 다이어그램의 가장 주목해서 보셔야 합니다.

 

 

프록시 요약

프록시는 사용이 간결함과 단순하지 않습니다. 프록시는 사용하기 까다롭습니다.

프록시의 이점

프록시는 매우 까다로운 특성이있습니다. 그럼에도 불구하고 매우 강력한 이점이 한가지 있는데 그것은 '관심의 분리' 입니다.

위의 클래스 다이어그램들을 살펴보시면 비즈니스로직과, 데이터베이스는 완전히 분리되어있습니다.

업무규칙과 데이터베이스 구현부의 분리가 아주 중요한 인스턴스에서는, 프록시가 적용하기 좋은 패턴이 될 수 있습니다.

 

 

 

애플리케이션은 보통 직접적으로 API를 물고 개발되는편입니다. 다만 서드파티의 API를 바로 사용하지않고 중간 레이어를 두어서 보다 추상적으로 사용하는 편이기도합니다.

단 어떤 경우에는 애플리케이션의 종속성으로 인해 레이어와 애플리케이션 관계가 뒤집어져야 하는 경우도 있습니다.

이런 종속성 뒤집기를 할때, 프록시 패턴이 적용될 수 있습니다. 애플리케이션은 프록시에 전혀 의존하지 않게 됩니다.

 

이렇게 애플리케이션과 API 사이의 관계정보는 오직 프록시에 집중되게 됩니다.

이 정보집중은 어찌보면 프록시가 악몽이 된다는 것을 의미합니다. API가 변경될때마다, 프록시도 변경됩니다. 애플리케이션이 변경될때마다 프록시도 변경됩니다. 프록시는 매우 다루기 어려운것이 되어버릴 수 있습니다.

 

차라리 자신의 악몽이 어디에 사는지 알고있는 편이 더 나을 수도 있습니다. 프록시가 없다면, 악몽은 애플리케이션 코드 전체에 퍼질수도 있기때문입니다.

 

단 프록시가 애플리케이션과 API의 철저한 분리가 이로울 때가 있는데, 스키마와 API 둘 모두 또는 어느 한쪽에서 잦은 변화가 발생하는 아주 큰 시스템의 경우는 거의 그렇습니다. 또 다른 많은 데이터베이스 엔진이나 미들웨어 엔진 위에 얹힐 수 있는 시스템도 그렇습니다. (왼쪽에서 2번째와 3번째 그림에 해당하는 말)

 

 

결론

대부분의 애플리케이션에는 프록시가 필요없습니다. 프록시는 아주 무거운 솔루션입니다.

하지만 우리는 프록시가 필요할 것이라는 것은 매우 매력적이라 생각합니다.

그런 필요가 실제로 생기기 한참전에 말입니다.

그러나 이것은 좋은 생각이 아닙니다. 특히 프록시에 대해서는 더욱 그렇습니다.

일단 퍼사드로 시작하고, 필요하면 리팩토링할 것을 권합니다. 그렇게하면 소중한 시간을 아끼고 문제를 줄일 수 있습니다.

 

 

 

유튜브 강의

youtu.be/JEcH5DhRq6U

 

 

참고했던 싸이트들

https://jdm.kr/blog/235

https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9D%EC%8B%9C_%ED%8C%A8%ED%84%B4#%EC%9E%90%EB%B0%94

 

 

의존성 역전 원칙 (DIP, Dependency Inversion Principle)

 

모든 소프트웨어 시스템, 혹은 작은 애플리케이션이라도 Layer가 존재합니다.

어떻게 Layer를 구성하며, 각 Layer마다 의존관계는 어떻게 이루어져야하는지에 대한 원칙이 있습니다.

 

 

큰 원칙으로는

 a. 상위수준의 모듈은 하위수준의 모듈에 의존해서는 안된다. 둘 모두 추상화에 의존해야한다.

 b. 추상화는 구체적인 사항에 의존해서는안된다. 구체적인 사항은 추상화에 의존해야한다.

 

 

여기서 상위수준이란, 정책의사결정과 업무모델을 포함하는 층, 재사용하기 원하는것들, 어플리케이션의 본질을 담는층 혹은, 고객의 요구사항중 내재하는 추상화, 구체적인 것들이 변경되더라도 바뀌지않는 진실의 핵심적인 내용들이 있는 Layer를 상위수준 Layer라고 합니다.

 

이런모듈이 하위모듈을 의존하면 안된다고 합니다.

- 하위모듈은 구체적인 모듈을 의미하며, 하위수준의 구체적인 모듈에 영향을 주어야하는 것은 정책을 결정하는 상위수준 모듈이어야합니다.

- 즉 상위수준의 모듈의 변경이 생겼을때, 하위수준의 모듈이 수정, 확장이 이루어지게 되도록 Layer를 구성해야합니다.

 

 

상위수준의 모듈이 하위수준의 모듈에 독립적이라면 이 상위수준의 모듈은 아주간단히 재사용 될 수 있습니다. 이 원칙은 프레임워크 설계의 핵십입니다.

 

미숙한 Layer 나누기

위의 그림은 정책이 구체적인 Layer들에 의존하여 실패한 Layer 나누기의 예입니다.

이것을 어떻게 수정할 수 있을까요?

 

 

바로 의존성역전원칙을 사용하면 됩니다.

의존성역전원칙을 적용하실때는, 상위수준에서 하위수준에 의존할때 사용하던 메서드들을 인터페이스 뽑은다음, 그 인터페이스를 상위수준에서 의존하게끔 하게 만들면 됩니다. 그리고 그 인터페이스의 구현체는 하위수준Layer에서 구현을 해놓으면 됩니다.

 

 

역전된 레이어

위와같이 의존성이역전되게되면, Utilty Layer의 변화와 Mechanism Layer의 변화가 Policy Layer에게 변화를 안끼치게 됩니다.

 

여기서 명심하셔야 할 것은, 의존성역전의 결과로 인터페이스의 소유권이 어디있는지 주목 해보시기 바랍니다.

인터페이스는 되게 사용하는층에서 가져야 합니다.

대개 한번쯤은 유틸리티라이브러리가 그것의 고유인터페이스를 소유한것으로 생각하지만, DIP가 적용된 경우에는 클라이언트(사용하는쪽)가 추상 인터페이스를 소유하는 경향이있습니다.

 

 

 

DIP가 아직 무엇인지 이해가 안되신다면 추상화에 의존하자! 이 키워드만 꼭 기억하시기 바랍니다.

구체적인 클래스는 추상적인것에 의존할 수록 좋은것이고, 반대로 추상적인클래스가 구체적인클래스에 의존하면 설계가 실패했다고 생각하시면 됩니다.

 

 

 

 

 

이제 간단한 예제2개를 살펴보도록 하겠습니다.

 

 

먼저 Button에서 감지한것을 바탕으로 외부객체에 반응을 내보내는 시스템을 만든다고 가정해보겠습니다.

흔히들 처음 목표는 아래그림처럼 버튼눌렀을때 램프를 켜는 시스템을 만들어주세요! 라고 요청이 왔을 것입니다.

하지만 이러한 요구사항에 앞서서 한번더 상위모듈 (정책, 변하지않는 메타포, 추상화) 을 생각해봅시다.

 

미숙한 Button과 Lamp

추상화를 좀 더 들어간다면, 버튼이라는것이 눌렸을때, 어떤 대상이 되었건 동작이 발생해야한다가 추상적인 개념이며 이 시스템의 상위모듈에 위치해야합니다.

즉 아래처럼 Lamp를 의존성 역전을 시켜서 버튼의 반응이왔을때 그것이 램프든, CCTV든 Motor든 어떤 외부객체라도 호출할 수 있도록 할 수 있습니다.

역시 인터페이스를 하나 만들고, 상위모듈에 두며, 그 인터페이스의 구현체는 하위모듈에 가는 방식으로 Layer가 나뉘게 됩니다.

 

Lamp에 적용된 의존성 역전원칙

 

 

 

 

 또다른 예제를 살펴보겠습니다. 레귤레이터 시스템이 있고, 현재온도에따라 히터를 틀어주거나 끄거나 하는 시스템이 있다고 가정해봅시다. 구체적인 온도계와, 히터는 밑의 하위모듈로 내리고, 상위모듈에서는 추상적인 인터페이스들로 비즈니스로직을 구성하도록하면, 상위모듈은 하위모듈에 영향을 안받게 됩니다.

 

Layer가 잘 나뉘어진 온도시스템 조절기

 

 

 

 

유튜브 강의링크

youtu.be/_uBeQkrV-wg

 

추상팩토리 패턴은 도대체 언제 사용해야할까요?

Simple factory pattern과 차이점은 무엇일까요?

 

사실 필자가 생각하기론 Simple factory pattern과 추상팩토리패턴 (Abstarct Factory pattern)은 거의 흡사하다고 봅니다.

 

다만, 군(플랫폼, 환경, 개체군)으로 관리해야하는 상황이 생기거나 군 밑의 자식클래스(Concrete class)의 잦은 수정이 생길것으로 판단될 때, 추상 팩토리 패턴을 적용해야 미래에 변경용이성을 가져가실 수 있습니다.

 

다시한번 말씀드리지만, 군으로 관리해야하느냐 아니냐에 따라 추상팩토리를 사용하느냐 Simple facotry Pattern을 사용하느냐로 결정하시면 됩니다.

 

 

추상팩토리 패턴이란?

Product class의 생성을 책임지는 생성에 관한 패턴으로써, 군 별로 Product class 를 책임지고 생성하는 Desgin Pattern

 

 

 

예제상황

View개발자들이 있는데 이 개발자들은 자기가 담당하는 View들이 따로있다. (Button, EditText, CloseButton 등등)

그런데 Window와 Linux 에 따라 각각의 View 군들의 Product class들의 생성을 따로 해야하는 상황이다.

 

 

 

Class Diagram

 

Abstract factory class diagram

 

소스코드

파일구성

ButtonView.java
EditTextView.java
FactoryCreator.java
LinuxButtonView.java
LinuxEditTextView.java
LinuxViewFactory.java
Main.java
ViewFactory.java
WindowButtonView.java
WindowEditTextView.java
WindowViewFactory.java

소스코드 Github 

https://github.com/control-man/design_pattern_sample/tree/master/abstarct_factory

 

간략한 소스코드 소개

public class FactoryCreator {
	
	static ViewFactory createViewFactory(String OS) {
		if (OS.equals("window")) {
			return new WindowViewFactory();
		} else if (OS.equals("linux")){
			return new LinuxViewFactory();
		} else {
			return null;
		}
	}
}

public class WindowViewFactory implements ViewFactory {

	@Override
	public ButtonView createButtonView() {
		return new WindowButtonView();
	}

	@Override
	public EditTextView createEditTextView() {
		return new WindowEditTextView();
	}

}

public class LinuxViewFactory implements ViewFactory {

	@Override
	public ButtonView createButtonView() {
		return new LinuxButtonView();
	}

	@Override
	public EditTextView createEditTextView() {
		return new LinuxEditTextView();
	}

}


public class Main {

	public static void main(String[] args) {

		ViewFactory viewFactory = FactoryCreator.createViewFactory("window");
		ButtonView buttonView = viewFactory.createButtonView();
		EditTextView editTextView = viewFactory.createEditTextView();
		
		buttonView.doButtonSomething();
		editTextView.doEditTextSomething();
		
		viewFactory = FactoryCreator.createViewFactory("linux");
		buttonView = viewFactory.createButtonView();
		editTextView = viewFactory.createEditTextView();

		buttonView.doButtonSomething();
		editTextView.doEditTextSomething();
	}

}



 

실행결과

 

장점

- Product 의 하위 클래스인 Concrete Product 클래스의 수정이나 확장이 발생해도 Client Side에서는

영향을 받지 않는다.

 

- 군별로 Product를 생성하는 Factory 클래스를 완전 분리함으로써, 코드 가독성이 올라간다.

 

 

 

단점

Product의 종류가 증가되면 수정되는(영향을 받는) 클래스의 수가 많아진다.

(그러므로 개체의 종류는 최대한 미리 정해놓고 진행하는것이 좋음)

 

만약 Product 가 늘어나면 아래의 Class diagram처럼 변하게 됨 

-> ViewFactory의 인터페이스 수정으로 인해 Concrete View Factory들의 수정사항도 생기고

Client도 변경사항에 대해 영향을 받게 됨.

 

Product가 늘어나게되면 Factory 클래스들의 수정이 발생한다.

 

 

유튜브 강의 링크

 

https://www.youtube.com/watch?v=wu2SIkJpaI0

안녕하세요 2년차 개발자 진쓰입니다.

전략패턴, Strategy pattern 에 대해서 포스팅 시작하겠습니다.


- 의도

다양한 알고리즘이 있을 때, 이들을 각각의 클래스로 캡슐화하여 만들고, 알고리즘 대체가 가능하도록 하고자 할 때 사용합니다.


- 활용

알고리즘 행위가 조금씩 다를 뿐, 개념적으로 관련된 많은 클래스들이 존재할 때 활용합니다.


알고리즘 변형이 빈번하게 필요한 경우 활용합니다.



예제상황과 코드로 배워보도록 하겠습니다.


- 예제상황



많은 데이터를 검색하려고 한다.

검색을 할 때, 상황에 따라 다양한 정렬알고리즘을

바꿔가면서 적용하려고 한다.

 




- 코드리뷰


코드구성


 Sort.java

전략을 바꿀 수 있는 매개체 인터페이스입니다.

 import java.util.List;

public interface Sort {
    public void sort(List <Object> al);
}



 MergeSort.java

Merge sort 전략 클래스입니다.

 

import java.util.List;
public class MergeSort implements Sort {
    @Override
    public void sort(List<Object> al) {
       System.out.println("Merge Sort!!!");
    }
}


 QuickSort.java

Quick sort 전략 클래스입니다.

 import java.util.List;

public class QuickSort implements Sort {
    @Override
    public void sort(List<Object> al) {
       System.out.println("Quick Sort!!!");       
    }
}



 SortEngine.java

Sort sorter는 현재 전략을 가르키는 객체변수이다.

Main에서 SortEngine을 생성하고 상황에 따라 전략을 set한다.

 


import
 java.util.ArrayList;
import java.util.List;
public class SortEngine {
    private Sort sorter;    
    public SortEngine(Sort ss){
       sorter = ss;
    }
    public void setSorter(Sort ss){
       this.sorter = ss;
    }
    
    public List<Object> search(){
       //sort할, array 값을 구성.
       List list = new ArrayList<Object>();      
       
       sorter.sort(list);
       return list;
    }  
}


.



 Main.java

1. Quick sort 전략을 생성하여, SortEngine에 넣고 search() 호출합니다.

2. Merge sort 전략을 생성하고, SortEngin에 넣고 search() 호출합니다.


전략을 바꿔가며(알고리즘을 바꿔가며) serach를 수행중이다.

 


public class Main {
    public static void main(String[] args) {
       
       //엔진생성
       SortEngine se;
       //퀵소트 Strategy 생성
       Sort st = new QuickSort();       
       //엔진생성 + 퀵소스 Strategy 설정.
       se = new SortEngine(st);       
       //엔진이 퀵소트를 실행
       se.search();     
       
       //머지소트 Strategy 생성
       st = new MergeSort();
       
       //엔진에 머지소트 Strategy 설정
       se.setSorter(st);       
       //엔진 머지소트 실행
       se.search();
       
    }
}




결과화면







- UML


> UML 설명


전략(알고리즘)을 나타내는 Sort 인터페이스가 있고, QuickSort와 MergeSort각각의 전략이 있다.

SortEngine은 Sort를 has a 관계로 가지고있다.

SortEngine의 전략(알고리즘)은 Main에서 setSorter()를 통해서 바뀌게되고

변경된 전략으로 search()를 수행한다.



- 정 리

전략 패턴을 사용하면?


> 관련 알고리즘 집합을 형성할 수 있습니다.


> 알고리즘의 선택 혹은 대체가 가능합니다.


> 알고리즘 객체 수가 증가 합니다.



고생하셨습니다 좋은 하루되세요 :)


안녕하세요. 살빼고싶은 개발자 진쓰입니다. 어댑터 패턴에 대해서 포스팅 시작하겠습니다.



- 의도

이미 제공되는 기능이 형태가 달라, 필요로하는 형태로 변경하고자 할 때 사용합니다.



- 활용

기존의 클래스를 사용해야 하나 인터페이스가 수정되어야 하는경우 사용합니다.  (인터페이스 수정은 지양해야됩니다.)


기존 라이브러리 모양을 달리하여 재사용하고자 할 경우 사용합니다. (라이브러리를 직접 수정할수는 없죠.)



역시 예제상황과 코드리뷰로 배워보도록 하겠습니다.



- 예제 상황

-LED Framework를 제공되어졌다. 

LED를 끄고 RGB 색상으로 켜고 하는 메소드들을 제공받았다.(Framework)

하지만, 우리하드웨어는 켜있는 상태에서 다른 색상을 켜면 색이 섞이는 현상이 생겨서, 무조건 한번 끄고 색상을 켜는 기능을 추가로 하고싶다.


Framework자체를 수정해버리면, 하드웨어가 바뀌면 또다시 Framework를 되돌려야 하는 부담감이 있다. 




- 코드리뷰


소스코드 구성




 ILedFramework.java

개발자가 제공받을 LedFramework.java 의 api를 추출하여 인터페이스를 생성했습니다. 

 public interface ILedFramework {

    //기존메소드, 기존의기능에서 수정할 메소드 구현, 완전 새로운 메소드 구현
    public void ledOff();
    public void ledOn(String color);    
    public void ledBlink(String color1);
}



 LedFramework.java

개발자가 받은 API입니다. 문제가 있죠?(색이 섞이는 현상코드로 구현되어있음)

 //adatee

public class LedFramework {
    public int state = 0;
    public String curColor;
    
    public void ledOn(String color){
       if(state==0){
           System.out.println("LED "+color+" ON");
           curColor=color;
           state=1;
       }
       else{
           if(curColor.equals(color)){
               System.out.println("LED "+color+" ON");
               state=1;
               return;
           }
           //색이 섞인 경우발생.
           
           if(color.equals("RED")){
               curColor=curColor+" RED";
               System.out.println("LED "+curColor+" ON");        
           }
           else if(color.equals("BLUE")){
               curColor=curColor+" BLUE";
               System.out.println("LED "+curColor+" ON");
           }
           else if(color.equals("GREEN")){
               curColor=curColor+" GREEN";
               System.out.println("LED "+curColor+" ON");    
           }   
           
       }
    }
    public void ledOff(){
       System.out.println("Led Off");   
       state = 0;
       curColor="";
    }
    
}



 LedFrameworkAdpater.java

1. LedframeWork는 implements 하였습니다. 

2. LedFramework(색이 섞이는 문제가 있는부분) 를 has a 관계로 가지고있습니다. 

3. ledOn을 보시면 기존의 ledOn을 호출하기전 ledOff를 먼저 호출하도록 수정하였습니다. 

4. ledBlink라는 새로운 기능을 추가하기도 했습니다.


 public class LedFrameworkAdpater implements ILedFramework{

    //1. 확장하려는 클래스를 멤버변수로 선언.
    public LedFramework led;
    //2. 생성자에서 생성.
    public LedFrameworkAdpater(){
       led = new LedFramework();
    }
    
    //3. 기존메소드, 기존의기능에서 수정할 메소드 구현, 완전 새로운 메소드 구현
    @Override
    public void ledOff() {
       led.ledOff();
    }
    @Override
    public void ledOn(String color) {
       ledOff();
       led.ledOn(color);
    }
    @Override
    public void ledBlink(String color1) {
       for(int i=0;i<10;i++){
           ledOff();
           led.ledOn(color1);
       }
    }
    
    
}



 Main.java

led프레임워크를 그대로 사용하는 경우와, 어댑터 패턴을 적용한 객체를 생성해서 사용하는 예제를 볼 수 있다. 

 public class Main {

    public static void main(String[] args) {
       // TODO Auto-generated method stub
       LedFramework led = new LedFramework();
       led.ledOn("RED");
       led.ledOff();
       led.ledOn("RED");
       led.ledOn("BLUE");
       led.ledOn("GREEN");
       led.ledOff();
       led.ledOn("GREEN");
       
       System.out.println("LED프레임워크 -> 어댑터 패턴 적용\n\n\n\n");
    
       ILedFramework newLed = new LedFrameworkAdpater();
       newLed.ledOff();
       newLed.ledOn("RED");
       newLed.ledOn("BLUE");
       newLed.ledOn("GREEN");
       newLed.ledBlink("GREEN");
    }
}



결과화면


Adpater패턴 적용 전 


 Red를 켜고 Blue를 켜버리면, Red와 Blue가 섞이게 된다.

 Adpater패턴 적용 후


색이 안섞이는것을 볼 수있다.

 


 



.


- UML

> UML 설명

ILedFramework 인터페이스로, 기존에 제공받은 LedFrameWork api와 더불어 유저가 추가하고싶은 ledBlink()메소드도 선언하였습니다.



LedFrameworkAdpater.java 는 ILedFramework를 implements하였으며 프레임워크(LedFramework)를 has a 관계로 가지고있습니다.

ledOn은 기존의 기능에 새로 추가된 내용을 덧 붙혔으며, ledBlink는 기존 LedFramework가 없는 기능입니다.


어댑터 패턴의 장점을 보실 수 있습니다.(플랫폼에 알맞게 수정, 확장)




- 정리

지금 포스팅된 어댑터 패턴은 Object Adapter 패턴입니다. 일반적으로 많이 사용됩니다.


오브젝트 어댑터 패턴을 사용하면, Adaptee 클래스의 하위 클래스에 대해서도 소스코드 변경없이, 이를 활용하여 새로운 기능 추가가 편리해집니다.




정독해주셔서 감사합니다.

항상 정성을 다해서 포스팅 하도록 하겠습니다 :)

풀스택개발자를 지향하는 진쓰의 디자인패턴 - 상태패턴 블로그 포스팅 시작하겠습니다.


의도

 - 객체 자신의 내부상태에 따라 행위를 변경하는 효과를 주고자 할 때 사용합니다.



활용

- 객체 행위는 상태에 따라 달라질 수 있다. 객체의 상태에 따라서 프로그램이 실행 중 행위가 바뀌어야할 때 활용할 수 있습니다.


- 객체 상태에 따라 수많은 if를 갖도록 구현해야하는데 이를 피해보고자 할 때 사용합니다.



역시 개발자는 코드를 보면서 공부를 해야제맛이죠. 

예제상황 살펴보시겠습니다.


 

- Edit 프로그램을 구현하려 한다.


하나의 문서를 열 수 있으며, 

문서가 수정상태일 경우, =>          문서의 저장, 열기, 종료 기능을 처리하는 방법은,

문서가 수정상태가 아닌 경우의  => 처리 방법을 다르게 동작한다고 한다.




코드리뷰


- 구조





 State.java   // 작명을 잘못한것같애요. UML부분에 이유를 설명드리겠습니다.

 public interface State {

    public void open();
    public void close();
    public void save();
    public void edit(String text);  
}




 InitState.java


State를 implements 하여 구현하였습니다. 앞서배웠던 싱글톤 패턴으로 작성되어있는것도 살펴보실수 있습니다.


 public class InitState implements State {

    private InitState(){}
    private static InitState init;
    public static InitState getInstance(){
       if(init ==null)
           init = new InitState();
       return init;
    };
    
    @Override
    public void open() {
       //System.out.println("새로 문서가 열렸습니다.");
    }
    @Override
    public void close() {
       System.out.println("문서 수정없이 문서가 닫혔습니다.");
    }
    @Override
    public void save() {
       System.out.println("저장할 것이 없습니다.");
    }
    @Override
    public void edit(String text) {
       System.out.println("init 상태 -> modify상태 ");
        EditEngine.setState(ModifyState.getInstance());
    }
}




 ModifyState.java

 public class ModifyState implements State {

    private ModifyState(){}
    private static ModifyState modify;
    public static ModifyState getInstance(){
       if(modify ==null)
           modify = new ModifyState();
       return modify;
    };
    
    @Override
    public void open() {
       System.out.println("이미 열려있는 파일이 있습니다.파일을 저장하고 엽니다.");
       save();
    }
    @Override
    public void close() {
       System.out.println("수정된 사항이있습니다. 파일을 저장하고 종료합니다.");
       save();
    }
    @Override
    public void save() {
       System.out.println("현재내용을 저장합니다.");
        EditEngine.setState(InitState.getInstance());
    }
    @Override
    public void edit(String text) {
       System.out.println("편집 상태 -> 편집상태 (상태변화없음)");
       
    }
}




 EditEngine.java


여기서 EditEngine은 state를 가지고 있는데, public static으로 선언되어있어서, State 인터페이스를 implements한 클래스들이 접근할 수 있도록 만들어 놓았습니다.

 public class EditEngine {

    
    private String text ="";
    private static State myState = InitState.getInstance();
    
    public void setText(String text){
       this.text = text;
    }
    public String getText(){
           return text;
    }
    public static void setState(State state){
       myState = state;
    }
    
    public void open(){
       myState.open();
       System.out.println("문서가 open되었습니다.");
    }
    public void close(){
       myState.close();
       System.out.println("문서가 종료 되었습니다.");
    }
    public void edit(String text){
       myState.edit(text);
       System.out.println("문서가 편집되었습니다");
    }
    public void save(){
       myState.save();
    }
}



.


 Main.java

 public class Main {

    public static void main(String[] args) {
       
       EditEngine edit = new EditEngine();
       
       edit.open();
       edit.edit("안녕");
       edit.save();
       edit.save();
       
       System.out.println("\n\n");
       edit.open();
       edit.edit("Jinss Programming Lecture");
       edit.open();
       edit.close();
       
       System.out.println("\n\n");
       edit.open();
       edit.edit("끝내자");
       edit.close();
       
    }
}


결과화면





UML


UML 설명


- State에 문서가 가질수있는 상태 혹은 동작 들을 인터페이스로 구현하였습니다.  음... 동작이라는 표현이 더 와닿는것 같기도합니다.

어쨌든 State 인터페이스를 상속받아 InitState와 ModifyState 를 각각 두웠습니다.

덕분에 InitState일때와, ModifyState일때 open, close, save, edit 라는 행위를 할때는 State에 따라 다르게 동작하게 됩니다.


그러고보니 인터페이스인 State는 막상 State라는 의미이기보단 Behavior? 라는게 더 알맞는 작명일것 같습니다.

뭐 개발자 나름이니깐 저는 그렇게 판단이 되네요.



어쩄든 EditEngine은 State를 가지고있는 has a 관계를 형성하고있습니다.

Main에서 EditEngine을 생성하고 단지 open, close, save, edit만 하면 상태에 따라서 알아서 착착착 다르게 호출되며 돌아가게 됩니다.



그리고 EditEnngine는, setState(state: State): void 를 static method로 선언해서, InitState와 ModfityState에서 접근하여 상태를 바꿀 수 있도록 해놓았습니다.  


EditEngine은 State인터페이스 변수를 두고있고, 현재 상태를 InitState혹은 ModifyState 로 갱신해야되니 뚫어놓아야겠죠?



정독해주셔서 감사합니다 :)




안녕하세요. 풀스택개발자를 지향하는 진쓰입니다.

디자인패턴 팩토리메소드 포스팅 시작하겠습니다.




목적

- 개발자는 객체를 생성해서 사용해야하는데, 어떤 객체를 생성할지, 어떤 객체가 생성가능할지 없을 때 사용할 수 있습니다.




활용

- 생성할 객체 타입을 예측할 수 없을 때 활용합니다.


- 객체 생성의 책임을 하위 클래스에게 위임시키고, 어느 하위 클래스에게 위임했는지에 대한 정보를 은닉하고자 할 때 활용합니다.




목적과 활용으로 봐서는 감이 안오실수있습니다. 예제를 통해 살펴보고 설명하도록 하겠습니다.

예제

- 교통수단이 되는 것을 만들어내는 회사가 있다. 이회사는 택시와 자전거를 만들어내는 회사다.





코드리뷰


 - 구성




 TransProduct.java

 public abstract class TransProduct {

    abstract void useTrans();
}




 TransProduct_Taxi.java

 public class TransProduct_Taxi extends TransProduct {

    @Override
    void useTrans() {
       System.out.println("Using Taxi");    
    }
}



 TransProduct_Bicycle.java

 public class TransProduct_Bicycle extends TransProduct {

    @Override
    void useTrans() {
       System.out.println("Using Bicycle");       
    }
}



.



 TransCreator.java

 //추상 클래스.

//Caller는 Interface혹은 abstract 클래스를 통해, Callee와 연결해야된다.
public abstract class TransCreator {
    //TransProduct 생성하는 팩토리 메소드(자전거, 택시)
    public abstract TransProduct transCreate(String trans);
}




 RealTransCreator_TransCompany.java

 public class RealTransCreator_TransCompany extends TransCreator {

    @Override
    public TransProduct transCreate(String trans) {
       
       if(trans.equals("taxi")){
           return new TransProduct_Taxi();
       }
       else{
           return new TransProduct_Bicycle();
       }   
       
       
    }
}



Main.java 

 public class Main {

    public static void main(String[] args) {
       
       //교통수단을 만들어내는 생산자 선언.
       TransCreator tc = new RealTransCreator_TransCompany();
       
       //Caller(Main)은 Callee를 직접변수를 선언하지않고 생성.
       //항상 인터페이스(중간매체, 추상클래스포함)를 통해 생성.
       
       TransProduct tp;
       
       //교통수단 - 자전거 생성
       tp = tc.transCreate("bicycle");
       
       //자전거 기능 이용.
       tp.useTrans();
       
       //교통수단 - 택시 생성
       tp = tc.transCreate("taxi");
       
       //택시 기능 이용.
       tp.useTrans();
    }
}



결과화면





UML




- UML 설명

TransCreator 클래스가 부모 클래스 입니다.

RealTransCreator가 진짜 객체를 만드는 클래스 입니다

여기선 RealTransCreator가 Bicycle과 Taxi를 생성하여(TransProduct)반환한다.



*) 여기서 회사가 만들어내는 제품이 늘어난다면?

(1) TransProduct 상속받는 클래스 추가합니다.

(2) RealTransCreator_TransCompany(Product를 생성하는 클래스)에서도 transCreate 수정합니다.

(Product가 늘어났으니!,dependency 관계니깐)



 RealTransCreator_TransCompany는 TransProduct_Bicycle, TransProduct_Taxi와 dependency 관계라서 Product가 늘어났다는것은 RealTransCreator_TransCompany역시 수정되어야 한다는 의미이다.



점선화살표는 dependency 를 의미합니다.





객체지향 원칙

Caller는 Callee를 직접 만들어 호출하지 않는다.


=> 여기선, TransCreator에서 TransProdcut_Taxi와 TransProduct_Bicycle을 만들 수 있지만, 

RealTransCreator_TransComapny 라는 sub 클래스를 하나 둬서 , 실제 TransProduct를 생성하여 반환하도록 하였다.


이처럼 객체지향언어에서는 Caller는 Callee를 직접만들어 호출하지않고 한단계를 더 둔다=>결합도를 떨어트리기 위해서이다.







정독해주셔서 감사합니다 :)

안녕하세요 풀스택개발자를 꿈꾸는 진쓰입니다.

디자인패턴 - 싱글톤 패턴 포스팅 시작하겠습니다.

부족한부분은 포스팅 밑부분, 싱글톤 패턴 영상으로 보충하실수있습니다.




의도

=> 클래스를 생성하는 인스턴스가 오직하나여야 한다.


인스턴스 접근 혹은 생성 역시 한가지의 방식으로만 제공하여 오직, 

하나의 인스턴스만 생성하고 이용할수 있게된다.




활용

- 클래스 인스턴스가 오직하나여야함을 필요로 하는곳에 활용할 수 있습니다.

- 모든 클라이언트가 하나의 인스턴스만 사용해야하는 경우에 활용할 수 있습니다.


싱글톤 패턴 만드는 방법!  딱 3가지만 숙지하시면 됩니다.

1. private 생성자 선언 합니다.


2. private static 인스턴스 변수 선언 합니다.


3. public static getInstance() method 구현 해야합니다.






예제 코드로 살펴보시겠습니다.


예제상황: 안드로이드 시스템에서 스피커객체는 유일하게 하나만 존재해야 하며, 

스피커의 객체에서 attribute값 중하나인 시스템사운드 크리를 읽어 오려한다.





시스템 스피커의 클래스 선언부입니다.


 SystemSpeaker.java

 public class SystemSpeaker {

    //원래는 private, getter, setter 필요. 편의상.
    public int systemVol = 5;
    //2. private static 인스턴스 변수
    private static SystemSpeaker ss = new SystemSpeaker();
    
    //3.public static getInstance() method 구현.
    public static SystemSpeaker getInstance(){   
       //if(ss==null){
       //  ss = new SystemSpeaker();
       //}
       return ss;     
    } 
    //1.private 생성자.
    private SystemSpeaker(){    
    }
  
    //4.다양한 방법 존재.
    /*
     * 시작과 동시에 객체를 생성할것인가 혹은, 프로그램 러닝중에 객체를 생성할 것인가.
     *
     *
     */
}


위와 같이 SystemSpeaker.java 를 생성하면, 자바 가상머신이 시작할때 바로 객체가 미리 생성하고 프로그램이 시작된다.


아래와같이 SystemSpeaker.java를 생성하면 프로그램이 실행중에 객체를 1번 생성하게된다. (필요할때 생성하는 방식)


어느것을 선택할지는 개발자 몫이다.


  SystemSpeaker.java

 public class SystemSpeaker {

  
    public int systemVol = 5;  
    //2. private static 인스턴스 변수
    private static SystemSpeaker ss;
    //3.public static getInstance() method 구현.
    public static SystemSpeaker getInstance(){ 
       if(ss==null){
           ss = new SystemSpeaker();
       }
       return ss;     
    }
    
    //1.private 생성자.
    private SystemSpeaker(){
       
    }

}

.


스피커 객체를 생성하고 값을 읽어오는 메인부입니다.

 Main.java

 public class Main {

    public static void main(String[] args) {    

       //객체1생성

       //SystemSpeaker ss = new SystemSpeaker();

       SystemSpeaker ss1 = SystemSpeaker.getInstance();

       //객체2생성

       SystemSpeaker ss2 = SystemSpeaker.getInstance();

       //객체의 주소 비교.

       System.out.println(ss1);

       System.out.println(ss2);   

       //객체의 attribute 읽어오기.

       System.out.println(ss1.systemVol);

       System.out.println(ss2.systemVol);       

    }

}

 



객체의 주소를 비교해본 결과, 같은 객체임을 알수있다.






정리

- 유일하게 존재하는 인스턴스 접근을 통제하고자 할 때 사용하면 됩니다.


- 싱글톤 패턴을 사용하면 인스턴스가 오직한개이기때문에 변수영역을 줄일 수 있습니다.





정독해주셔서 감사합니다. :)



+ Recent posts