객체지향 디자인패턴

포모·2020년 11월 28일
1

어제 [객체 지향 설계 SOLID]에 대해 공부하면서 5가지 원칙에 대해서 어느정도 이해는 했지만 적용하는 것이 쉽지 않았습니다. 😥
그래서 객체 지향 디자인 패턴을 보면서 적용해볼까 합니다!!


🎯 디자인 패턴이란?

객체 지향 프로그래밍 설계를 할 때 자주 발생하는 문제들을 피하기 위해 사용되는 패턴을 의미합니다.
코드를 수정하거나 새로운 기능을 추가해야 하는데 의도치 않은 결과나 버그를 발생시키기 쉽고 성능을 최적화시키기도 어렵기 때문에 디자인 패턴을 사용하여 문제를 해결할 수 있습니다.


🎯 Single tone pattern

어떤 클래스의 객체가 해당 프로세스에서 딱 하나만 만들어져 있어야 할 때가 있습니다.
가령 어떤 페이지가 있는 같은 설정이 유지되어야하는 상황이라고 가정했을 때 싱글톤 패턴을 통해 설정해줄 수 있습니다.


  • 예시
public class Setting {
	private Setting() {};	-- (1)
	private static Setting setting = null;	-- (2)
    
	public staic Settings getSettings() {	-- (3)
	  if (settings == null) {
		  settings = new Settings();
	  }

	  return settings;
	}
}
  1. 생성자를 private으로 만들어 new Setting();으로 설정되지 못하도록 만듭니다.
  2. static으로 settings 객체를 둡니다.
    • static : 정적 공간에 할당
    • static이 아닌 경우 : 동적 공간에 할당 (객체가 생성될 때마다 메모리의 공간을 새로 차지)
  3. static 함수인 getSettings() 를 통해 settings 정적 객체가 null이라면 객체를 선언해줍니다. (존재한다면 그대로 리턴해줍니다.)

🤔 정적 변수를 쓰지 않고 왜 싱글톤 패턴을 사용하는 것일까?
interface의 사용이나 lazy loading 등 싱글톤 패턴으로 할 수 있는 것이 더 많다고 합니다!

  • Lazy loading
    웹페이지 전체를 로딩하면 메모리 및 대역폭의 낭비가 발생할 수 있습니다.

    1. 웹브라우저에 보이는 영역 안에 있는 이미지는 로드하고, 보이지 않는 부분은 로드하지 않습니다.
    2. 밑으로 스크롤하여 이미지가 있는 영역에 도달하면 그 때 이미지를 로드합니다.


      이렇게 하면 불필요한 이미지 로딩으로 인한 속도 저하를 방지할 수 있습니다.

🎯 Strategy pattern

이미지, 블로그, 카페 등의 모드를 누르고 검색을 한다고 가정해봅시다.
프로그램 실행 중 모드가 바뀔 때마다 검색이 이뤄지는 방식, 즉 전략이 수정되는 경우에 사용하는 패턴입니다.


  • 예시
interface SearchStrategy {
	public void search();
}

class SearchStrategyAll implements SearchStrategy {
	public void search() {
    	System.out.println("SEARCH ALL");
    }
}

class SearchStrategyImage implements SearchStrategy {
	public void search() {
    	System.out.println("SEARCH IMAGE");
    }
}

class SearchStrategyBlog implements SearchStrategy {
	public void search() {
    	System.out.println("SEARCH BLOG");
    }
}

class SearchStrategyCafe implements SearchStrategy {
	public void search() {
    	System.out.println("SEARCH CAFE");
    }
}

위의 코드와 같이 전략 패턴을 적용하여 생성해주었습니다.
그렇다면 이 패턴을 어떻게 사용하면 될까요?

public class SearchButton {
    private MyProgram myProgram;
    
    public SearchButton(MyProgram _myProgram) {
    	myProgram = _myProgram;
    }
    
    private SearchStrategy searchStrategy = new SearchStrategyAll();
    
    public void setSearchStrategy (SearchStrategy _searchStrategy) {
    	searchStrategty = _searchStrategy;
    }
    
    public void onClick() {
        searchStrategy.search();
    }
}

처음 생성은 SearchStrategyAll()로 설정되어있지만,
setSearchStrategy()를 통해서 전략 패턴을 바꿔줄 수 있습니다.

public class MyProgram {
  private SearchButton searchButton = new SearchButton(this);
    
  public void setModeAll() {
      searchButton.setSearchStrategy(new SearchStrategyAll()));
  }
  
  public void setModeImage() {
      searchButton.setSearchStrategy(new SearchStrategyImage()));
  }
 
 ...
 
  public void testProgram() {
	searchButton.onClick();		// "SEARCH ALL"
	setModeImage();
	searchButton.onClick();		// "SEARCH IMAGE"
	setModeBlog();   
	searchButton.onClick();		// "SEARCH BLOG"
	setModeCAFE();
	searchButton.onClick();		// "SEARCH CAFE"
  }

이렇게 set 함수를 통해 전략 패턴을 변경하여 여러 모드를 사용할 수 있습니다.


🎯 State pattern

State 패턴은 Strategy 패턴과 헷갈릴 수 있으니 둘의 차이점을 비교하면서 알아봅시다.

  • Strategy 패턴 : 어떤 동일한 틀 안에 있는 특정 작업의 방식, 모드를 바꿔줄 때
  • State 패턴 : 상태들 자체+상태마다 실행시 할 일을 하나하나 모듈화 해서 지정해둘 때 (ex. TV OFF - 켜기 / TV ON - 끄기)

  • 예시
public interface ModeState {
	public void toggle (ModeSwitch modeSwitch);
}

class ModeStateLight implements ModeState {	-- (1)
	public void toggle (ModeSwitch modeSwitch) {
		modeSwitch.setState(new ModeStateDark());
	}
}

class ModeSateDark implements ModeState { 	-- (2)
	public void toggle (ModeSwitch modeSwitch) {
		modeSwitch.setState(new ModeStateLight());
	}
}
  1. ModeStateLight : TV ON 상태
    • setState(new ModeStateDark()) : TV 끄기
  2. ModeStateDark : TV OFF상태
    • setState(new ModeStateLight()) : TV 켜기

Strategy 패턴은 특정 메소드가 지정된 모듈화된 모드에 따라 다르게 실행되도록 하는 거라면,
State 패턴은 그 메소드가 실행될 때 모드도 전환되도록 하는 것이라고 합니다.

굳이 사용을 구분 하자면, 전략 패턴은 상속을 대체하려는 목적으로, 스테이트 패턴은 코드내의 조건문들을 대체하려는 목적으로 사용된다고 합니다. ([참고])


🎯 Command pattern

Command 패턴도 Strategy 패턴과 유사한데,
Strategy 패턴은 같은 일을 하되 알고리즘이나 방식이 갈아끼워지는 것이라면
Command 패턴은 하는 일 자체가 다르다고 합니다.


  • 예시
public class Robot {
	public enum Direction { LEFT, RIGHT }
    
	public void moveForward(int space) {
		System.out.println(space + " 칸 전진");
	}
    
	public void turn (Direction _direction) {
		System.out.println("방향 전환 ~ ");
	}
    
	public void pickup() {
		System.out.println("물건 집어들기");
	}
}

좌표계 칸을 이동하여 물건을 집어드는 로봇이 있습니다.
주어진 칸만큼 전진하는 moveForward(), 방향을 전환하는 turn(), 물건을 집어드는 pickup()
이와 같이 로봇이 할 수 있는 일들을 나타내는 메소드들이 있습니다.

abstract class Command {
	protected Robot robot;
    
	public void setRobot(Robot _robot) {
		this.robot = _robot;
	}
    
	public abstract void execute();		// 명령 실행 함수
}

추상(abstract) 클래스스스로 객체 생성이 불가하지만 상속을 받는 자식 클래스들이 상속받은 것을 그대로 또는 수정하여 사용할 수 있습니다.
cf. 인터페이스는 일반 클래스와 달리 다중 상속이 가능

class MoveForwardCommand extends Command {		--(1)
	int space;
    
	public MoveForwardCommand(int _space) {
		space = _space;
	}
    
	public void execute() {
		robot.moveForward(space);
	}
}

class TurnCommand extends Command {			--(2)
	Robot.Direction direction;
    
	public TurnCommand(Robot.Direction _direction) {
		direction = _direction;    
	}
    
	public void execute() {
		robot.turn(direction);
	}
}

class PickupCommand extends Command {			--(3)
	public void execute() {
		robot.pickup();
	}
}
  1. 전진 명령 : 이동 칸 수를 의미하는 space를 입력받아 생성
    • execute() : space만큼 이동합니다. moveForward() 호출
  2. 회전 명령 : 방향값 direction과 함께 생성
    • execute() : 해당 방향으로 회전합니다. turn() 호출
  3. 접기 명령 : 필요로 하는 변수 없이 생성
    • execute() : 물건을 집어올리는 pickup() 호출

private ArrayList<Command> commands = ~;

for (Command command : commands) {
	command.setRobot(robot);
	command.execute();
}

setRobot()을 통해 각각 알맞는 command를 할당하고, 그에 맞는 execute() 문을 호출하게 됩니다.


🎯 Adapter pattern

Adapter 패턴은 인터페이스가 서로 다른 객체들이 같은 형식 아래 작동할 수 있도록 하는 역할을 수행합니다.


interface FindAlgorithm{
	public void find(boolean global);
}

Strategy patter에서 예로 들었던 SearchStrategy 인터페이스와 위의 FindAlgorithm 인터페이스를 추가로 같이 사용하고 싶다고 가정해봅시다.

class SearchFindAdapter implements SearchStrategy {
	private FindAlgorithm findAlgorithm;
    
	public SearchFindAdapter(FindAlgorithm _findAlgorithm) {
		findAlgorithm = _findAlgorithm;
	}
    
	public void search() {
		findAlgorithm.find(true);
	}

SearchFindAdapter 클래스에 SearchStrategy을 상속하였습니다.
FindAlgorithm 객체가 멤버 변수로 생성되어있습니다.
search() 함수에서 find() 함수를 실행하도록 설정하였습니다.
즉, SearchFindAdapter 객체가 search()라고 말하면 FindAlgorithmfind() 함수를 불러오도록 연결이 되어집니다.


🎯 Proxy pattern

클래스들 중 인터넷에서 받아와야해서 시간이 오래 걸리거나 메모리를 많이 차지하는 등의 이유로 객체로 여러개 생성하기가 부담되는 경우가 있습니다.
Proxy 패턴을 두어 가벼운 일을 그 클래스의 객체가 처리하고, 무거운 작업을 수행할 때 실제 클래스를 생성해서 사용하는 방식이 Proxy 패턴입니다.

  • 예시
interface Thumbnail {
	public void showTitle();		-- (1) 	제목 표시
	public void showPreview();		-- (2)	프리뷰
}

유튜브 동영상 목록을 띄우고 마우스가 위치한 영상의 프리뷰를 띄우는 사이트를 예시로 들어보겠습니다.

class RealThumbnail implements Thumbnail {
	private String title;
	private String movieUrl;
    
	public RealThumbnail(String _title, String _movieUrl) {
		title = _title;
		movieUrl = _movieUrl;
        
		System.out.prinln(movieUrl + "로부터" + title + "의 영상 데이터 다운");
	}
    
	public void showTitle() {
		System.out.println("제목 : " + title);
	}


	public void showPreview() {
		System.out.println(title + "의 프리뷰 재생");
	}
}

실제 클래스인 RealThumbnail와 대리인 클래스인 ProxyThumbnail 모두 Thumbnail 인터페이스를 적용합니다.
RealThumbnail는 영상을 받아오는, 시간이 오래 걸리는 작업을 수행할 뿐만 아니라 제목을 보여주고(showTitle()) 프리뷰 재생을 하는 showPreview() 함수도 실제로 실행할 수 있습니다.

class ProxyThumbnail implements Thumbnail {
	private String title;
	private String movieUrl;
    
	private RealThumbnail realThumbnail;
    
   	public ProxyThumbnail(String _title, String _movieUrl) {
		title = _title;
		movieUrl = _movieUrl;
	}
    
	public void showTitle() {
		System.out.println("제목 : " + title);
	}
    
	public void showPreview() {
		if (realThumbnail == null) {
			realThumbnail = new RealThumbnail(title, movieUrl);
		}
        realThumbnail.realThumbnail();
	}
}

ProxyThumbnail은 영상을 받아오지 않기 때문에 생성하는데 시간이 걸리지도 않고 객체가 가볍습니다.
또한 Thumbnail 인터페이스를 상속했기 때문에 showPreview() 메소드를 갖고는 있습니다.
가벼운 작업인 showTitle()과 같은 항목은 RealThumbnail의 개입없이 ProxyThumbnail 자체 내에서 해결 가능합니다.
그러나 showPreview()의 경우 실제 썸네일인 RealThumbnail 객체를 생성해 이 객체를 통해 프리뷰를 실행합니다.


🛴 마무리

디자인 패턴.. 정보처리기사 시험 준비하면서 이론을 공부하기 힘들었던 기억이 있었습니다.
솔직하게 말하면 공부하기 조금 망설여졌던 파트였는데, 내 프로젝트에 어떻게 적용해볼까 고민하다보니 생각보다 쉽고 재미있게 공부할 수 있어서 다행이네요 😀


참고

0개의 댓글