안녕하세요, SSAFYcial 신산하입니다 : )
2월 기획기사인 CS 디자인 패턴편으로 찾아왔습니다.
많은 문서를 참고하여 6가지 디자인 패턴을 총정리해보았습니다 !
대체 왜 디자인 패턴을 배워야 하는 것일까?
디자인 패턴을 쓰는 이유는 [이해하기 쉬운 좋은 코드를 쓰기 위함]입니다.
그렇다고 모든 코드들을 디자인 패턴이라는 틀에 갇혀 써내려가는 정답은 아닙니다.
현재는 현업에서 자주 일어나는 대표적인 23가지 디자인 패턴이 많이 알려져 있지만,
23가지 외에도 내가 더 쉽게 풀어나갈 방법이 있다면,
힘들게 디자인 패턴을 적용하려고 돌아가지 않아도 됩니다.
하지만 현업에서 자주 쓰는 디자인 패턴을 알고 있으면,
팀원들과 협업을 할 때도, 나의 업무를 할 때도 도움이 될 것입니다 : )
[23가지 디자인 패턴]
Creational Pattern (생성 패턴 5가지)
1) Factory Method
2) Abstract Factory
3) Builder
4) Prototype
5) Singleton
Structure Pattern (구조 패턴 7가지)
1) Adapter
2) Bridge
3) Composite
4) Decorator
5) Facade
6) Flyweight
7) Proxy
Behavioral Pattern (행동 패턴 11가지)
1) Chain of Responsibility
2) Command
3) Iterator
4) Mediator
5) Memento
6) Observer
7) State
8) Strategy
9) Template Method
10) Visitor
11) Interpreter
번외
1) MVC 패턴
2) MVVM 패턴
오늘은 하이라이트 처리된 패턴들에 대해 이야기 풀어나가도록 하겠습니다.
1. 싱글톤 패턴 (Singleton Pattern)
이론
자바 수업시간에도 면접 단골 질문이라고 불렸던 싱글톤 패턴을 첫 타자로 다뤄보겠습니다! 싱글톤 패턴은 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴입니다. 어떤 소프트웨어를 만들다보면 어떤 클래스의 객체가, 해당 프로세스에서 딱 하나만 만들어져 있어야 할 때가 있습니다.
예시 : 다크모드
다크모드가 대표적인 예시입니다. 사용자가 앱을 사용할 때 세팅에서 다크모드를 설정해놓으면 다른 페이지로 이동하더라도 다크모드가 그대로 유지되어 있어야 하기 때문에 어떤 페이지에 있든 이 세팅을 관리하는 객체는 반드시 같은 것을 사용해야 합니다. 싱글톤을 사용하지 않고, 각각 다른 객체를 쓴다면 넘어가는 페이지마다 다크모드가 설정되어 있지 않은 모습을 확인할 수 있습니다.
[다크모드 & 폰트 설정 클래스]
package singleton.before;
public class Settings {
private boolean darkMode = false;
private int fontSize = 13;
//getter : 현재 다크모드 및 폰사이즈를 얻어올 수 있다.
public boolean getDarkMode() {return darkMode;}
public int getFontSize() {return fontSize;}
//setter : 다크모드 여부와 폰트사이즈를 다시 setting 할 수 있다.
public void setDarkMode(boolean _darkMode) {darkMode=_darkMode;}
public void setFontSize(int _fontSize) {fontSize=_fontSize;}
}
현재 기본 설정값은 다크모드가 되어있지 않은 상태이고, 폰트 사이즈는 13입니다. 해당 클래스에는 현재 다크모드 및 폰트 상태를 불러오는 getter 함수와 새로운 폰트 값과 다크모드를 주고 싶을 때 변경할 수 있는 setter 함수가 적혀있습니다.
[첫번째 페이지 클래스]
package singleton.before;
public class FirstPage {
private Settings settings = new Settings();
//첫번째 페이지의 셋팅값을 다크모드&폰트 15로 바꿔준다.
public void setAndPrintSettings() {
settings.setDarkMode(true);
settings.setFontSize(15);
System.out.println(settings.getDarkMode()+" "+settings.getFontSize());
}
}
사용자가 볼 첫 번째 페이지는 Settings 클래스의 객체를 만들어 setter 함수를 이용해 다크모드 설정과 폰트를 13에서 15로 변경했습니다.
[두번째 페이지 클래스]
package singleton.before;
public class SecondPage {
private Settings settings = new Settings();
//첫번째 페이지와 셋팅값이 다르게 나온다.
public void PrintSettings() {
System.out.println(settings.getDarkMode()+" "+settings.getFontSize());
}
}
사용자가 볼 두 번째 페이지는 Settings 클래스의 객체를 만들어 별다른 셋팅값을 바꿔주지 않고, 현재 셋팅 값을 출력하는 함수만 두었습니다.
[테스트 클래스]
package singleton.before;
public class MyProgram {
public static void main(String[] args) {
//첫 번째 페이지의 다크모드 & 15포인트 셋팅 및 프린트 함수 출력
new FirstPage().setAndPrintSettings();
//두 번째 페이지도 첫 번째 페이지와 연동될까 확인하는 프린트 함수 출력
new SecondPage().PrintSettings();
}
}
출력 결과
true 15
false 13
출력 결과를 통해 내가 셋팅한 결과가 다른 페이지로 이어지지 않는다는 것을 알 수 있습니다.
내가 만약 네이버 메인화면(첫 번째 화면)에서 다크모드 및 폰트 사이즈를 조정한 채로 보고 있어도, 다른 네이버 뉴스 페이지라던가, 검색창을 이용해 다른 페이지로 이동하게 되면 다크모드 셋팅이 사라지고, 폰트 사이즈도 원래 상태로 돌아옵니다.
이를 해결하기 위해 우리는 싱글톤을 사용합니다!
[다크모드 & 폰트 설정 클래스]
package singleton.before;
public class Settings {
//1. private로 생성자를 만든다.
//그럼 다른 클래스에선 Settings 객체를 만들지 못한다.
private Settings() {}
//2. 자기 자신의 클래스의 객체를 null로 만든다.(instance를 담을 공간)
private static Settings settings = null;
//3. Settings 인스턴스에 접근할 수 있는 유일한 통로 getSettings를 만든다.
//만약 생성된 인스턴스가 없다면, getSettings 호출 시 만든 후 반환하고,
//만약 생성된 인스턴스가 있다면, 기존에 있던 인스턴스를 반환해줘!
public static Settings getSettings() {
if(settings == null) settings = new Settings();
return settings;
}
private boolean darkMode = false;
private int fontSize = 13;
//getter : 현재 다크모드 및 폰사이즈를 얻어올 수 있다.
public boolean getDarkMode() {return darkMode;}
public int getFontSize() {return fontSize;}
//setter : 다크모드 여부와 폰트사이즈를 다시 setting 할 수 있다.
public void setDarkMode(boolean _darkMode) {darkMode=_darkMode;}
public void setFontSize(int _fontSize) {fontSize=_fontSize;}
}
셋팅 클래스를 싱글톤으로 만들어 해결하는 것입니다.
1) private로 생성자를 만들어 줍니다. 그럼 다른 클래스에선 Settings 객체를 만들지 못합니다.
2) 자기 자신의 클래스의 객체를 null로 만듭니다.(instance를 담을 공간입니다.)
3) Settings 인스턴스에 접근할 수 있는 유일한 통로 getSettings를 만들어 줍니다.
- 만약 생성된 인스턴스가 없다면, getSettings 호출 시 만든 후 반환하고,
- 만약 생성된 인스턴스가 있다면, 기존에 있던 인스턴스를 반환해줍니다.
[첫번째 페이지 클래스]
package singleton.before;
public class FirstPage {
//셋팅 클래스의 유일한 인스턴스를 클래스 이름으로 가져온다.
//private Settings settings = new Settings();
private Settings settings = Settings.getSettings();
//첫번째 페이지의 셋팅값을 다크모드&폰트 15로 바꿔준다.
public void setAndPrintSettings() {
settings.setDarkMode(true);
settings.setFontSize(15);
System.out.println(settings.getDarkMode()+" "+settings.getFontSize());
}
}
4) 그 후 첫 번째 페이지 클래스와 두 번째 페이지 클래스의 객체 생성 방식을 인스턴스 호출 방식으로 변경합니다.
출력 결과
true 15
true 15
해당 결과를 통해 싱글톤으로 만들어주면, Settings 클래스의 단 하나의 인스턴스 객체 즉 매니저가 변경 상태를 모든 화면에 홀로 공유하여 다크모드 변경 결과가 다른 페이지에도 적용되는 것을 볼 수 있습니다.
장점
- 한 번의 new로 인스턴스를 생성해 다른 thread와 공유하며 사용하기 때문에 메모리 낭비를 방지할 수 있습니다.
단점
- 싱글톤 패턴을 이용하면 여러 thread(화면)에서 동시에 접근하기 때문에 동시성 문제(Thread Safe)가 발생한다. thread는 순차적으로 실행되는 것이 아닌 동시에 실행되기 때문에 발생하는 문제이다.
private static Settings settings = new Settings();
간단한 방법으로는 static 특징을 이용해 클래스 로더가 초기화하는 시점에 인스턴스 메모리에 등록하는 방법이다. stataic 변수 선언과 동시에 초기화를 해주면 동시성 문제를 해결할 수 있다.
2. 팩토리 패턴 (Factory Pattern)
이론
팩토리 패턴은 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이자 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴입니다.
즉 인터페이스를 생각하면 편합니다. 인터페이스에서는 어떤 메서드를 만들지 뼈대만 작성하고, 그 인테페이스를 상속받은 하위 클래스의 경우 메서드에 관한 구체적인 내용(강아지 speak(), 고양이 speak() 등)을 구체적으로 작성하면 됩니다.
예시
로봇을 만드는 공장으로 팩토리 메소드 패턴을 이해해보도록 하겠습니다.
[로봇 abstract 클래스]
package pattern.factory;
public abstract class Robot {
public abstract String getName();
}
로봇 이름을 얻어오는 abstract 메소드를 만들어줍니다.
[SuperRobot 클래스 & PowerRobot 클래스]
package pattern.factory;
public class SuperRobot extends Robot {
@Override
public String getName() {
return "SuperRobot";
}
}
package pattern.factory;
public class PowerRobot extends Robot {
@Override
public String getName() {
return "PowerRobot";
}
}
슈퍼로봇과 파워로봇은 Robot abstract 클래스를 상속받아 getName() 메서드를 구현합니다.
[로봇 팩토리 클래스]
package pattern.factory;
public abstract class RobotFactory {
abstract Robot createRobot(String name);
}
로봇 팩토리 추상 클래스에서는 로봇을 만듭니다.
package pattern.factory;
public class SuperRobotFactory extends RobotFactory {
@Override
Robot createRobot(String name) {
switch( name ){
case "super": return new SuperRobot();
case "power": return new PowerRobot();
}
return null;
}
}
위 추상 클래스를 상속받아 슈퍼로봇 혹은 파워로봇에 따라 슈퍼로봇 클래스 혹은 파워로봇 클래스 객체를 생성해서 반환합니다.
[테스트 클래스]
package pattern.factory;
public class FactoryMain {
public static void main(String[] args) {
RobotFactory rf = new SuperRobotFactory();
Robot r = rf.createRobot("super");
Robot r2 = rf.createRobot("power");
System.out.println(r.getName());
System.out.println(r2.getName());
}
}
실행결과
SuperRobot
PowerRobot
메인 프로그램에서 new 키워드가 없다는 것을 확인할 수 있습니다. 객체 생성을 팩토리 클래스에 위임한 결과입니다. 또한 메인 프로그램은 어떤 객체가 생성 되었는지 신경 쓰지 않고 단지 반환된 객체를 사용만 하면 됩니다.
장점
- 상위 클래스와 하위 클래스가 분리되기 때문에 느슨한 결합을 가지며 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없기 때문에 더 많은 유연성을 갖게 됩니다.
- 객체 생성 로직이 따로 떼어져 있기 때문에 코드를 리팩터링하더라도 한 곳만 고칠 수 있게 되니 유지 보수성이 증가합니다.
단점
- 간단한 기능을 사용할 때보다 많은 클래스를 정의해야 하기 때문에 코드량이 증가합니다.
3. 프록시 패턴 (Proxy Pattern)
이론
대리인 패턴이라고도 불리는 프록시 패턴은 대상 객체에 접근하기 전 그 접근에 대한 흐름을 가로채 대상 객체 앞단의 인터페이스 역할을 하는 디자인 패턴입니다. 객체의 속성, 변환 등을 보완하며 보안, 데이터검증, 캐싱, 로깅에 사용합니다.
프록시 패턴은 비서라는 직업을 생각하면 이해하기 쉽습니다. 어느 정도 규모가 있는 회사 대표의 스케쥴을 조율하거나 메세지를 남기거나 하는 등 어지간한 일은 비서, 대리인을 통해 처리가 됩니다. 회사 대표가 몸소 등장해야 하는 일은 그만큼 중요도가 있고 권한이 필요한 일일 것입니다.
프로그래밍에서 사용되는 클래스들 중에서도 인터넷에서 받아와야 해서 시간이 걸리거나 메모리를 많이 차지하거나 하는 등의 이유로 객체로 여럿 생성하기가 부담이 되는 것들이 있습니다. 그럴 때는 그 클래스에 프록시 패턴을 쓰면 됩니다.
예시 : 유튜브 프리뷰
유튜브의 썸네일을 보면 마우스를 그 위로 가져가면 영상의 프리뷰가 실행이 됩니다. 제목을 화면에 나타내는 것은 가벼운 작업이지만 프리뷰를 보여주기 위해선 영상 데이터를 받아와야 합니다. 마치 싸피 비전공 자바반 PJT로 했던 운동 영상 데이터를 받아왔던 것처럼요...^^ (참 힘들었다.)
썸네일을 담당하는 객체는 제목과 프리뷰를 두 메서드를 통해 각각 보여주도록 하되 썸네일이 처음 화면에 나타날 때는 일단 제목만 보여줄 수 있는 프록시로 생성되게 하고, 프리뷰를 보여주는 무거운 작업은 실제 클래스가 담당하도록 해서 프록시 객체로 생성된 썸네일에 커서를 올릴 때 실제 클래스가 호출돼서 프리뷰를 보여주게 하는 방식입니다.
[실제 클래스 & 프록시 클래스]
package proxy;
interface Thumbnail {
public void showTitle(); //제목을 보여주는 메소드
public void showPreview();//프리뷰를 재생하는 메소드
}
// 실제 클래스
// 영상을 받아오는 시간이 걸리는 작업을 수행한다.
// 실제 영상 데이터를 받아와 갖고 있기 때문에 제목을 보여주는 showTitle() 메서드와
// 프리뷰를 재생하는 showPreview() 메서드도 실제로 실행할 수 있다.
class RealThumbnail implements Thumbnail{
private String title;
private String movieUrl;
public RealThumbnail(String _title, String _movieUrl) {
title=_title;
movieUrl=_movieUrl;
//URL로부터 영상을 다운받는 작업 - 시간 소모
System.out.println(movieUrl + "로 부터 "+title+"의 영상 데이터 다운");
}
@Override
public void showTitle() {
System.out.println("제목: "+title);
}
@Override
public void showPreview() {
System.out.println(title+"의 프리뷰 재생");
}
}
// 대리인 클래스
// 영상을 받아오지 않기 때문에 생성하는데 시간이 걸리지 않고 객체도 가볍다.
// 실제 클래스와 같은 인터페이스를 적용했기 때문에 showPreview() 메서드는 가지고 있다. 다만 이걸 직접 실행하지는 않는다.
// showPreview() 메서드 구현은 실제 영상인 RealThumbnail 클래스 객체를 생성해 실제 썸네일 객체를 통해 영상을 실행한다.
// like 대표님 불러오는 비서
class ProxyThumbnail implements Thumbnail{
private String title;
private String movieUrl;
private RealThumbnail realThumbnail;
@Override
public void showTitle() {
System.out.println("제목: "+title);
}
@Override
public void showPreview() {
if(realThumbnail == null) {
realThumbnail = new RealThumbnail(title, movieUrl);
}
realThumbnail.showPreview();
}
}
실제 클래스와 대리인 클래스는 같은 인터페이스를 상속받습니다.
다만 실제 클래스의 경우 영상을 받아오는 시간이 걸리는 작업을 수행하며, 실제 영상 데이터를 받아와 갖고 있기 때문에 제목을 보여주는 showTitle() 메서드와 프리뷰를 재생하는 showPreview() 메서드 둘 모두 실제로 실행할 수 있습니다.
대리인 클래스 , 프록시 클래스의 경우 영상을 받아오지 않기 때문에 생성하는데 시간이 걸리지 않고 객체도 가볍습니다.
실제 클래스와 같은 인터페이스를 적용했기 때문에 프리뷰를 실행하는 showPreview() 메서드는 가지고 있지만, 이걸 직접 실행하지는 않습니다. 프록시 클래스에서 showPreview() 메서드 구현은 실제 영상인 RealThumbnail 클래스 객체를 생성해 실제 썸네일 객체를 통해 영상을 실행합니다. 마치 대표님 불러오는 비서와 같죠 : )
장점
- 사이즈가 큰 객체가 로딩되기 전에도 프록시를 통해 참조를 할 수 있습니다.
- 원래 객체에 접근에 대해 사전처리를 할 수 있습니다.
단점
- 객체를 생성할 때 한 단계를 거치게 되므로, 빈번한 객체 생성이 필요한 경우 성능이 저하될 수 있습니다.
4. 옵저버 패턴 (Observer Pattern)
이론
옵저버 패턴은 주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴입니다.
만약 옵저버 패턴을 가지지 않는다면, 이벤트를 체크해야하는 오브젝트들은 이벤트가 일어났는지 확인하기 위해서 매 1초, 매 1분, 매 1시간 마다 계속해서 변화를 확인해야 합니다. 이러한 방법을 polling이라고 부릅니다. polling은 필요없는 리소스 낭비가 생기기도 하고, 또한 만약 매 1시간 주기로 본다고 가정하면, polling interval 즉, 1시간 내 이벤트가 생겼다 사라지면 이벤트가 일어났었는지 알 수도 없습니다.
이를 해결하기 위해 옵저버 패턴을 사용합니다. 마치 싸피에서 열심히 강의를 1시간 듣고 "10분 쉬다가 오겠습니다"라는 말을 들으면 싸피 구성원들이 화장실, 산책, 공부 등 자신의 일을 하는 것처럼 옵저버 패턴은 어떤 이벤트가 발생했을 때 옵저버들이 바로 반응을 하는 패턴입니다.
예시 : 강아지 고양이를 키우는 주인
이번에는 간단한 예시로 옵저버 패턴을 이해해보겠습니다.
강아지와 고양이를 키우는 주인이 update라는 메서드로 신호를 보내면, 옵저버 (강아지 / 고양이)가 바로 반응하는 모습입니다.
[옵저버 클래스]
package observer;
interface Observer {
public void update();
}
class Cat implements Observer{
@Override
public void update() {
System.out.println("MEOW");
}
}
class Dog implements Observer{
@Override
public void update() {
System.out.println("bark!");
}
}
update() 메서드를 가지고 있는 인터페이스를 Cat과 Dog 클래스가 상속받고, update() 신호 별 행동을 각각 정의합니다.
[주인 클래스]
package observer;
import java.util.ArrayList;
public class Owner {
ArrayList<Observer> animals = new ArrayList<>();
public void register(Observer animal) {
animals.add(animal);
}
public void _notify() {
for(Observer animal : animals) {
animal.update();
}
}
}
옵저버들의 주인은 애완동물 리스트로 자신의 애완동물을 등록 및 관리하며, _notify() 메서드로 신호를 보냅니다.
[테스트 클래스]
package observer;
public class Test {
public static void main(String[] args) {
Owner owner = new Owner();
Observer cat = new Cat();
Observer dog = new Dog();
//애완동물 등록
owner.register(cat);
owner.register(dog);
//오너가 신호를 보내면 강아지 고양이들이 반응한다.
owner._notify();
}
}
실행결과
MEOW
bark!
이렇듯 옵저버 디자인 패턴은 주체(주인)가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드(_notify()) 등을 통해 옵저버 목록에 있는 옵저버들(강아지, 고양이)에게 변화를 알려줍니다.
장점
- 실시간으로 한 객체의 변경사항을 다른 객체에 전파할 수 있습니다.
- 느슨한 결합으로 객체 사이의 상호 의존성을 최소화 할 수 있어 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있습니다. (느슨한 결합이란 두 객체가 상호작용을 하지만, 서로에 대해 잘 모른다는 점을 의미)
단점
- Observer에게 알림이 가는 순서를 보장할 수 없습니다.
5. 전략 패턴 (Strategy Pattern)
이론
정책 패턴(Policy pattern)이라고도 불리는 전략 패턴은 객체의 행위를 바꾸고 싶은 경우 '직접' 수정하지 않고 전략으로 부르는 '캡슐화한 알고리즘'을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴입니다.
전략 패턴은 모드 마다의 동작 하나하나를 모듈로 따로 분리해서 각 버튼을 누를 때마다 검색버튼을 누를 때 실행될 검색 모듈을 갈아끼워주는 방식으로 코드를 짜는 것입니다. 이렇게 옵션들마다의 행동들을 모듈화해서 독립적이고 상호 교체 가능하게 만드는 것이 전략 패턴입니다.
전체/이미지/뉴스/지도 버튼 아래 검색창이 있다고하면 버튼 중 1개를 눌러 모드를 설정하고, 선택된 모드에 따라 검색 버튼을 눌렀을 때 실행되는 검색의 방식이 결정되도록 해야한다. 즉 프로그램 실행 중 모드가 바뀔 때마다 검색이 이뤄지는 방식, 즉 전략이 수정된다는 것입니다.
예시 : 모드 별 검색창
모드 별 검색 창이란 검색 바 위에 "ALL / IMAGE / NEWS / MAP" 버튼이 있어서 버튼 별 다른 검색 결과를 도출하는 시스템입니다.
예를 들어 이미지버튼을 누르고 "아이유"를 검색하면, 아이유 이미지만 도출되게 되는 것입니다.
[검색 모드 기술한 enum]
package strategy.before;
public enum Mode {
ALL,IMAGE,NEWS,MAP
}
검색 창 위에 4가지 버튼을 enum으로 기술했습니다.
[모드 셋팅 클래스]
package strategy.before;
public class MyProgram {
private SearchButton searchButton = new SearchButton(this);
//enum에서 ALL,IMAGE,NEWS,MAP 메뉴 값을 모두 가져온다.
public Mode mode = Mode.ALL;
//프로그램 셋팅에 맞게 원하는 모드로 설정한다.
public void setModeAll() {mode = Mode.ALL;}
public void setModeIMAGE() {mode = Mode.IMAGE;}
public void setModeNEWS() {mode = Mode.NEWS;}
public void setModeMAP() {mode = Mode.MAP;}
}
enum에 작성한 메뉴 값을 불러와 각 메뉴로 셋팅할 수 있는 setter를 만들었습니다. 또한 서치버튼 클래스의 객체를 만들어 해당 객체를 전달합니다.
[버튼 눌리는 동작 클래스]
package strategy.before;
public class SearchButton {
private MyProgram myprogram;
public SearchButton(MyProgram _myProgram) {
myprogram=_myProgram;
}
public void onClick() {
if(myprogram.mode == Mode.ALL) {
System.out.println("SEARCH ALL");
//전체 검색 코드 알고리즘 생략
}
else if(myprogram.mode == Mode.IMAGE) {
System.out.println("SEARCH IMAGE");
//이미지 검색 코드 알고리즘 생략
}
else if(myprogram.mode == Mode.NEWS) {
System.out.println("SEARCH NEWS");
//뉴스 검색 코드 알고리즘 생략
}
else if(myprogram.mode == Mode.MAP) {
System.out.println("SEARCH MAP");
//지도 검색 코드 알고리즘 생략
}
}
}
MyProgram에서 객체가 전달되면 해당 객체가 원하는 모드를 실행합니다.
만약 각각의 버튼에 수정사항이 생기거나 새로운 기능들이 추가되거나 하면 SearchButton에 onclick 메서드를 그때그때 다시 수정해줘야 합니다. 소프트웨어가 커지고 복잡해질수록 코드가 줄줄이 길어질 뿐만 아니라 코드를 분석하고 관리하기가 어려워질 것입니다.
이를 해결하기 위해 우리는 전략 패턴을 사용합니다!
모드 마다의 동작 하나하나를 모듈로 따로 분리해서 이 버튼들을 누를 때마다 실행될 검색 모듈을 갈아끼워주는 방식으로 코드를 짜는 것입니다.
[검색 전략 인터페이스 및 상속 클래스들]
package strategy.before;
//1. SearchStrategy 인터페이스를 만들어 그것을 상속받아 4가지 모드 버튼을 구현한다.
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 SearchStrategyNews implements SearchStrategy{
public void search() {
System.out.println("SEARCH News");
// 뉴스 검색하는 코드
}
}
class SearchStrategyMAP implements SearchStrategy{
public void search() {
System.out.println("SEARCH MAP");
// 뉴스 검색하는 코드
}
}
기존에는 SearchButton 클래스에 onclick 메서드로 버튼 클릭 시 해당 모드를 실행하게 짰다면,
팩토리 패턴에서는 인터페이스를 이용해 그것을 상속받아 각 버튼을 구현하는 방식으로 코드를 짭니다.
여기에 새로운 버튼이 생긴다면, 해당 인터페이스를 상속 받아 만들어주기만 하면 됩니다. 즉, 기존 코드는 건들 필요 없습니다.
[버튼 눌리는 동작 클래스]
package strategy.before;
public class SearchButton {
private MyProgram myprogram;
public SearchButton(MyProgram _myProgram) {
myprogram=_myProgram;
}
//1. SearchStrategy 인터페이스 객체를 만들어 전체 검색으로 초기화한다.
private SearchStrategy searchStrategy = new SearchStrategyAll();
//2. setter를 이용해 언제든 SearchStrategy 인터페이스를 입은 다른 검색 전략으로 갈아끼울 수 있다.
// 검색 전략은 MyProgram setModeImage() 메서드가 그것이다.
public void setSearchStrategy (SearchStrategy _searchStrategy) {
searchStrategy = _searchStrategy;
}
// public void onClick() {
// if(myprogram.mode == Mode.ALL) {
// System.out.println("SEARCH ALL");
// //전체 검색 코드 알고리즘 생략
// }
//
// else if(myprogram.mode == Mode.IMAGE) {
// System.out.println("SEARCH IMAGE");
// //이미지 검색 코드 알고리즘 생략
// }
//
// else if(myprogram.mode == Mode.NEWS) {
// System.out.println("SEARCH NEWS");
// //뉴스 검색 코드 알고리즘 생략
// }
//
// else if(myprogram.mode == Mode.MAP) {
// System.out.println("SEARCH MAP");
// //지도 검색 코드 알고리즘 생략
// }
// }
}
위 인터페이스로 각 모드의 메서드가 구현되었기 때문에 SearchButton의 onclick은 지우고 SearchStrategy 인터페이스 객체를 만들어 전체 검색으로 모드를 초기화합니다. 모드는 setModeImage() 메서드를 이용해 언제든 SearchStrategy 인터페이스를 입은 다른 검색 전략으로 갈아끼울 수 있습니다.
[모드 셋팅 클래스]
package strategy.before;
public class MyProgram {
private SearchButton searchButton = new SearchButton(this);
//enum에서 ALL,IMAGE,NEWS,MAP 메뉴 값을 모두 가져온다.
public Mode mode = Mode.ALL;
public void setModeAll() {
searchButton.setSearchStrategy(new SearchStrategyAll());
}
public void setModeImage() {
searchButton.setSearchStrategy(new SearchStrategyIMAGE());
}
public void setModeNews() {
searchButton.setSearchStrategy(new SearchStrategyNews());
}
public void setModeMAP() {
searchButton.setSearchStrategy(new SearchStrategyMAP());
}
//프로그램 셋팅에 맞게 원하는 모드로 설정한다.
// public void setModeAll() {mode = Mode.ALL;}
// public void setModeIMAGE() {mode = Mode.IMAGE;}
// public void setModeNEWS() {mode = Mode.NEWS;}
// public void setModeMAP() {mode = Mode.MAP;}
}
그럼 모드 셋팅 클래스는 SearchButton 클래스 객체를 이용해 모드에 맞는 SearchStrategy 인터페이스를 상속받은 클래스를 건내주면 됩니다.
이처럼 옵션들마다의 행동들을 모듈화해서 독립적이고 상호 교체 가능하게 만드는 것이 전략 패턴입니다.
장점
- 요구사항이 변경되었을 때 기존의 코드를 변경하지 않아도 된다는 것이 전략패턴의 장점이며 새로운 전략에 대해서는 새로운 클래스를 통해 관리하기 때문에 OCP의 원칙을 준수할 수 있는 패턴입니다.
단점
- 클래스가 많이 늘어나서 복잡도가 증가합니다.
6. MVC 패턴
정의
MVC패턴은 모델(Model), 뷰(View), 컨트롤러(Controller)로 이루어진 디자인 패턴입니다.
모델(Model)
모델은 앱이 포함해야할 데이터가 무엇인지 정의합니다. 데이터의 상태가 변경되면 모델을 일반적으로 뷰에게 알림으로써 필요한대로 화면을 변경할 수 있습니다. 가끔은 업데이트된 뷰를 제거하기 위해 다른 로직이 필요한 경우 컨트롤러에게 알리기도 합니다.
뷰(View)
뷰는 inputbox, checkbox, textarea 등 사용자 인터페이스 요소를 나타냅니다. 즉, 모델을 기반으로 사용자가 볼 수 있는 화면을 뜻합니다. 모델이 가지고 있는 정보를 따로 저장하지 않아야 하며 단순히 사각형 모양 등 화면에 표시하는 정보만 가지고 있어야 합니다. 또한 변경이 일어나면 이를 컨트롤러에 전달해야 합니다.
컨트롤러(Controller)
하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할을 하며 이벤트 등 메인 로직을 담당합니다. 사용자가 데이터 입력 시 그 입력에 대한 응답으로 모델/뷰를 업데이트하는 로직입니다. 또한 모델과 뷰의 생명주기도 관리하며, 모델이나 뷰의 변경 통지를 받으면 이를 해석하여 각각의 구성 요소에 해당 내용에 대해 알려줍니다.
예시 : 파이썬 / django
MVC 패턴의 예로 파이썬과 django가 있습니다. django는 파이썬 언어와 MVC 패턴을 이용해 웹을 구현할 수 있습니다.
장점
- 비교적 간단한 패턴으로 구조파악과 확장을 쉽게 할 수 있습니다.
단점
- 뷰와 모델의 완벽한 분리가 어렵고 앱이 커지면 컨트롤러의 코드량이 커져 유지보수 하기가 힘듭니다.
긴 글 정성스레 읽어주셔서 감사합니다 : )
처음하는 CS 공부이고, 다 적고 싶어서 적다보니 길어졌습니다 ㅠㅠ
여러분도 시간되시면 디자인 패턴 23가지 한 번씩 둘러보시면 좋을 것 같습니다.
마지막으로 2월 한 달도 너무 고생많으셨습니다!
저희 3월도 화이팅해요!!!
추천영상
참고문서
- OVERVIEW
1. github : tech-interview
2. 티스토리 블로그 : 느긋한 개발자
- SINGLETON
1. 티스토리 블로그 : 모르는 게 많은 개발자 Awdsd
- FACTORY
1. 티스토리 블로그 : 뱀귤 블로그
2. JDM's Blog
- PROXY
1. VELOG : newtownboy
- OBSERVER
1. 티스토리 블로그 : 서기의 개발집
- Strategy
1. 쓰리디핏 공작소
- MVC 패턴
1. MDN
2. 티스토리 블로그 : Hongz의 개발공부
'대외활동 > SSAFYicial' 카테고리의 다른 글
[CS 정리는 내가 할게, 면접은 누가볼래? - 운영체제편] 쓰레드(Thread) 이름만 들어보신 분? (0) | 2023.05.13 |
---|---|
[CS 정리는 내가 할게, 면접은 누가볼래? - Github편] Github 어디까지 해봤니? 소스트리 설정부터 브랜치 관리까지 총정리! (5) | 2023.04.18 |
[CS 정리는 내가 할게, 면접은 누가볼래? - 데이터베이스편] 실제 프로젝트를 통해 알아보는 데이터베이스 기초! ENTITY TITI FRAGILE X 2~ (2) | 2023.03.12 |
[TMI] 내가 퇴사하고, SSAFY한 이유, "난 아직 젊다!" (6) | 2023.03.11 |
[스몰 인터뷰] SSAFY 첫 월급, "첫 월급 받으면 뭐할거야?" (0) | 2023.02.18 |