어제 [객체 지향 설계 SOLID]에 대해 공부하면서 5가지 원칙에 대해서 어느정도 이해는 했지만 적용하는 것이 쉽지 않았습니다. 😥
그래서 객체 지향 디자인 패턴을 보면서 적용해볼까 합니다!!
객체 지향 프로그래밍 설계를 할 때 자주 발생하는 문제들을 피하기 위해 사용되는 패턴을 의미합니다.
코드를 수정하거나 새로운 기능을 추가해야 하는데 의도치 않은 결과나 버그를 발생시키기 쉽고 성능을 최적화시키기도 어렵기 때문에 디자인 패턴을 사용하여 문제를 해결할 수 있습니다.
어떤 클래스의 객체가 해당 프로세스에서 딱 하나만 만들어져 있어야 할 때가 있습니다.
가령 어떤 페이지가 있는 같은 설정이 유지되어야하는 상황이라고 가정했을 때 싱글톤 패턴을 통해 설정해줄 수 있습니다.
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;
}
}
private
으로 만들어 new Setting();
으로 설정되지 못하도록 만듭니다.static
으로 settings
객체를 둡니다.static
: 정적 공간에 할당static
이 아닌 경우 : 동적 공간에 할당 (객체가 생성될 때마다 메모리의 공간을 새로 차지)static
함수인 getSettings()
를 통해 settings
정적 객체가 null이라면 객체를 선언해줍니다. (존재한다면 그대로 리턴해줍니다.)🤔 정적 변수를 쓰지 않고 왜 싱글톤 패턴을 사용하는 것일까?
interface의 사용이나 lazy loading 등 싱글톤 패턴으로 할 수 있는 것이 더 많다고 합니다!
- Lazy loading
웹페이지 전체를 로딩하면 메모리 및 대역폭의 낭비가 발생할 수 있습니다.
- 웹브라우저에 보이는 영역 안에 있는 이미지는 로드하고, 보이지 않는 부분은 로드하지 않습니다.
- 밑으로 스크롤하여 이미지가 있는 영역에 도달하면 그 때 이미지를 로드합니다.
이렇게 하면 불필요한 이미지 로딩으로 인한 속도 저하를 방지할 수 있습니다.
이미지, 블로그, 카페 등의 모드를 누르고 검색을 한다고 가정해봅시다.
프로그램 실행 중 모드가 바뀔 때마다 검색이 이뤄지는 방식, 즉 전략이 수정되는 경우에 사용하는 패턴입니다.
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 패턴은 Strategy 패턴과 헷갈릴 수 있으니 둘의 차이점을 비교하면서 알아봅시다.
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());
}
}
ModeStateLight
: TV ON 상태setState(new ModeStateDark())
: TV 끄기ModeStateDark
: TV OFF상태setState(new ModeStateLight())
: TV 켜기Strategy 패턴은 특정 메소드가 지정된 모듈화된 모드에 따라 다르게 실행되도록 하는 거라면,
State 패턴은 그 메소드가 실행될 때 모드도 전환되도록 하는 것이라고 합니다.
굳이 사용을 구분 하자면, 전략 패턴은 상속을 대체하려는 목적으로, 스테이트 패턴은 코드내의 조건문들을 대체하려는 목적으로 사용된다고 합니다. ([참고])
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();
}
}
space
를 입력받아 생성execute()
: space
만큼 이동합니다. moveForward()
호출direction
과 함께 생성execute()
: 해당 방향으로 회전합니다. turn()
호출execute()
: 물건을 집어올리는 pickup()
호출private ArrayList<Command> commands = ~;
for (Command command : commands) {
command.setRobot(robot);
command.execute();
}
setRobot()
을 통해 각각 알맞는 command를 할당하고, 그에 맞는 execute()
문을 호출하게 됩니다.
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()
라고 말하면 FindAlgorithm
의 find()
함수를 불러오도록 연결이 되어집니다.
클래스들 중 인터넷에서 받아와야해서 시간이 오래 걸리거나 메모리를 많이 차지하는 등의 이유로 객체로 여러개 생성하기가 부담되는 경우가 있습니다.
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
객체를 생성해 이 객체를 통해 프리뷰를 실행합니다.
디자인 패턴.. 정보처리기사 시험 준비하면서 이론을 공부하기 힘들었던 기억이 있었습니다.
솔직하게 말하면 공부하기 조금 망설여졌던 파트였는데, 내 프로젝트에 어떻게 적용해볼까 고민하다보니 생각보다 쉽고 재미있게 공부할 수 있어서 다행이네요 😀