카테고리 없음

<디자인 패턴> 디자인 패턴이란? 1편

studying develop 2020. 4. 8. 11:37

디검색할때 마다 디자인 패턴 개념이 종류가 몇개 나뉘는거 같아서 일단 검색을 많이 해본다.

 

[https://namu.wiki/w/%EB%94%94%EC%9E%90%EC%9D%B8%20%ED%8C%A8%ED%84%B4

 

일단 나무위키지만 개요를 찾아봤다. 

 

객체 지향 프로그래밍 설계를 할 때 자주 발생하는 문제들을 피하기 위해 사용되는 패턴.

여러 사람이 협업해서 개발할 때 다른 사람이 작성한 코드, 기존에 존재하는 코드를 이해하는 것은 어렵다. 이런 코드를 수정하거나 새로운 기능을 추가해야 하는데 의도치 않은 결과나 버그를 발생시키기 쉽고 성능을 최적화시키기도 어렵다. 이로 인해 시간과 예산이 소모된다.

디자인 패턴은 의사소통 수단의 일종으로서 이런 문제를 해결해준다. 예를 들어 문제 해결의 제안에 있어서도 “기능마다 별도의 클래스를 만들고, 그 기능들로 해야할 일을 한번에 처리해주는 클래스를 만들자.”라고 제안하는 것보다 "Facade 패턴을 써보자."라고 제안하는 쪽이 이해하기 쉽다.

일반 프로그래머가 만나는 문제가 지구상에서 유일한 문제[1]일 확률은 거의 없다. 이미 수많은 사람들이 부딪힌 문제다. 따라서 전문가들이 기존에 해결책을 다 마련해 놓았다.

다만 과유불급. 디자인 패턴을 맹신한 나머지 모든 문제를 패턴을 써서 해결하려 드는 패턴병에 걸리지 않도록 조심하자. 디자인 패턴보다 중요한 것은 코드베이스의 간결성이다. 즉 디자인 패턴 적용이 굳이 필요가 없을 것 같은 부분은 적용하지 않는게 상책이다. 디자인 패턴은 알고리즘이 아니라 상황에 따라 자주 쓰이는 설계 방법을 정리한 코딩 방법론일 뿐이며 모든 상황의 해결책이 아니다. 디자인 패턴에 얽매이는 것보단 그 패턴이 왜 효율적인 방식인지를 이해해야 한다. 같은 이름의 패턴이 다른 언어로 구현된 모습을 보면 이에 대해 좀 더 쉽게 이해할 수 있을 것이다.

 

내가 보기에 약간 개발자로서 대화를 하려면 패턴에 대해 아는게 , 다른 분야의 용어를 아는것이랑 좀 비슷한거 같다.

 


음 일단 퍼사이드 패턴이 무엇인지 궁금해서 찾아봤다.

 

[https://lktprogrammer.tistory.com/42]

 

Facade는 "건물의 정면"을 의미하는 단어로 어떤 소프트웨어의 다른 커다란 코드 부분에 대하여 간략화된 인터페이스를 제공해주는 디자인 패턴을 의미합니다. 퍼사드 객체는 복잡한 소프트웨어 바깥쪽의 코드가 라이브러리의 안쪽 코드에 의존하는 일을 감소시켜 주고, 복잡한 소프트웨어를 사용 할 수 있게 간단한 인터페이스를 제공해줍니다.

 

public void view()
{
     Beverage beverage = new Beverage("콜라");
     Remote_Control remote= new Remote_Control();
     Movie movie = new Movie("어벤져스");
       
     beverage.Prepare();  //음료 준비
     remote.Turn_On();   //tv를 켜다
     movie.Search_Movie();  //영화를 찾다
     movie.Charge_Movie();  // 영화를 결제하다
     movie.play_Movie();   //영화를 재생하다
}

 

 

사용자 입장에서는 영화를 보기위해서는 저런 복잡한 코드를 사용하여 영화를 봐야만 합니다. 여기서 퍼사드 객체가 등장하게 되는데 퍼사드는 이런 사용자와 영화를 보기위해 사용하는 서브 클래스들 사이의 간단한 통합 인터페이스를 제공해주는 역할을 하게 됩니다.

 

facade

퍼사이드 패턴은 복잡한 동작들을 통합 관리함으로서 간단한 인터페이스를 제공하는 패턴 같다.

 

이전에 이런식으로 구현해 본적이 있는데, 패턴이라는게 결국 사실 생각해서 짜다보면 내가 붙이지 않은 이름도 같은게 있을거 같다. 


 

이건 패턴들의 종류이다. 위에서본 퍼사드 패턴이 구조 패턴에 포함되어 있다.

 


팩토리 메소드

 

객체를 만들어 반환하는 함수를 (생성자 대신) 제공하여 초기화 과정을 외부에서 보지 못하게 숨기고 반환 타입을 제어하는 방법.

크게 두 가지 방법이 있는데, 하나는 아예 다른 객체를 직접 만들어 넘겨주는 객체를 따로 만드는 것이 있고, 다른 방식으로 팩토리 기능을 하는 함수가 자기 자신에 포함되어 있고 생성자 대신 사용하는 게 있다.

 

class Map {
    Map(File mapFile) {
        while(mapFile.hasNext() == true) {
            Unit unit = UnitFactory.create(mapFile.getNext());
       }
       //유닛 초기화 이후 코드
    }
}

나무위키 예시인데, 스타크래프트에서 맵을 호출할때, 그안에서 각 유닛, 마린,파벳 객체를 생성하는 역할까지 하면 다른 유닛을 추가하려면 맵을 직접 수정해야 할것이다. 그 대신 유닛 팩토리 객체를 통해, 유닛의 생성을 관리 해주면 맵과 유닛 생성 책임을 분리할 수 있을거 같다. 

 

전에 c로 구현할때, 이런 아이디어는 클래스는 아니지만, 함수를 이용해 사용해 본적이 있다. 함수에 각 맡는 책임을 분리해서 구현한건데, 아이디어가 비슷한거 같다.

 

추가로 두번째 팩토리 패턴의 사용법인데 생성자 대신, 팩토리 패턴 방식을 사용한다는 말인데, 대충 느낌은 오는데, 완벽히 이해는 안된다.

 

두번째 방법인 생성자 대신 사용하는 함수는 왜 사용하느냐 하면, 언어 문법상 생성자를 바로 접근하지 못하도록 막아야 구현할 수 있는 문제가 몇몇 있기 때문.

예시로는

  • 상속을 막고 싶은데 final 키워드가 직접 지원되지 않는 언어 버전이라던가

  • 생성 객체의 총 수를 제한하고 싶다던가

  • 생성 도중에 C++ 예외가 터지기 때문에 생성자에 초기화 코드를 넣기 곤란한 경우라던가 [3]

  • 생성 과정에 다른 객체를 참조해서 생성 순서를 조절해야 하는 경우라던가

  • 생성 직후 반환값을 사용해서 계산하는게 주 업무인 객체라던가

  • 생성되는 객체의 구체적인 타입을 숨기거나 도중에 바꾸고 싶은 경우 (라이브러리 등)

  • 또는 객체 생성이 확실하지 않은 연산 과정의 캡슐화


등 생각보다 많다. C++에서는 'std::chrono::어쩌구저쩌구_clock::now()' class static 함수가 대표적인 팩토리 패턴이다.

 


빌더 패턴 - 생성 패턴

 

(Java 기준, 빌더 클래스의 사용)

빌더 클래스는 인스턴스를 생성자를 통해 직접 생성하지 않고, 빌더라는 내부 클래스를 통해 간접적으로 생성하게 하는 패턴이다.

 

class Something {

    private Something(int number, String name, double size) {
        //Something 클래스 초기화
    }

    public static class Builder {
        int number=0;
        String name=null;
        double size=0d;

        public Builder() {
            //Builder 초기화
        }

        public Builder setNumber(int number) {
            this.number = number;
            return this;
        }

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

        public Builder setSize(double size) {
            this.size = size;
            return this;
        }

        public Something build() {
            return new Something(number, name, size);
        }
    }
}



사용 목적에는 크게 두 가지로 나뉜다.

  • 클래스와 사용 대상의 결합도를 낮추기 위해

어떤 클래스의 사양 변경으로 인해, 생성자에 인수로 전달해야 하는 부분의 규격이 변경되었다면 어떻게 수정해야 될까?
일반적인 패턴으로는 일단 해당 클래스를 수정한 후, 해당 클래스를 생성하는 모든 부분의 코드를 일일히 다 수정해야 할 것이다.(그렇지 않으면 컴파일 오류가 난다.)
혼자 만드는 건 어찌어찌 Ctrl+F로 코드 찾아가면서 해당 클래스의 생성자를 전부 찾아가면서 변경을 하겠지만, 해당 부분이 다른 사람에게 배포하여 사용하는 Library같은 물건이라면?
Builder는 해당 문제점을 해결하기 위해 고안된 패턴이다.
대상 클래스의 생성자는 private 등의 접근 제한자로 제한하여 외부에서 임의로 접근하는 것을 막아 클래스와 사용대상의 결합도를 떨어뜨리고, 대신 Builder라는 내부 클래스를 통해 해당 클래스를 간접적으로 생성한다.


Builder는 설정되지 않은 인수에 대해서는 적절한 값으로 초기화를 하여 해당 인수가 할당되지 않더라도 일단 컴파일 자체는 가능하며, 사용자의 요청에 따라 상세한 값을 설정하는 것도 가능하다.
예를 들어, 위 Something 클래스에서 double weight라는 인수를 추가로 할당하려고 하면, 전통적인 패턴에서는 위에 언급한대로 모든 생성자마다 double weight라는 단서를 추가로 달아야겠지만, Builder 패턴에서는 대상 클래스의 private 생성자에 weight를 추가하고, Builder에 setWeight(double weight) 하나만 추가하면 끝. 기본값은 -1(설정되지 않음)으로 하면 수많은 코드들을 일일히 찾아다니지 않아도 기능 추가가 가능하다.

  • 생성자에 전달하는 인수에 의미를 부여하기 위해

예를 들어서, 위에 제시된 예시에서 빌더 패턴이 없다고 가정하고 인스턴스를 생성하려면 Something something = new Something(number, name, size); 이렇게 코드를 작성하여야 한다.
위의 예시에서는 인수가 세 개니까 그냥 저렇게 써도 큰 문제는 없지만, 생성자에 전달하는 인수의 가짓수가 열 종류 가까이 되는 클래스의 경우에는 고전적인 생성자 패턴으로는 인수를 전달하는 것이 상당히 비직관적이 된다.(인수의 종류를 외워서 써넣어야되는 것뿐만 아니라, 인수의 순서까지 고려해야 한다!)
그래서 빌더 패턴을 통해 setXXX 형식으로 인수를 전달하면 한 눈에 보기에도 이것이 무슨 인수인지를 파악하기가 쉽다.

빌더와 팩토리 패턴은 유사점이 많아 그냥 팩토리로 퉁쳐서 칭하기도 한다. 특히 자바 이외의 언어에서.

 

public void createSomething() {
    Something something = new Something.Builder().setNumber(number).setName(name).setSize(size).build();
}

 

즉 생성자를 통한 객체 생성은, 생성자를 수정하려면 너무 많은 것을 수정해야 할 수 도 있다는 것 같다. 그런데 빌더 패턴을 이용하면, 내부 클래스를 메소드를 통해 일일이 원하는 프로퍼티의 추가, 제거가 쉽다는 말, 해당 프로퍼티에 대한 메소드만 수정하면 되니까. 이 부분도 사실 내가 생성자를 사용하기 좀 꺼려졌던 부분이다. 추가해줄 인자가 있는데, 일일이 고치려니 파일 몇개를 돌아다녀야 되서 괜히 잘못 고칠까봐 걱정됐었다.

 


프로토타입 패턴 - 생성 패턴

 

원본(Prototype)을 만들어 놓고 원본 객체를 복사하여 사용하는 방식.


싱글톤 패턴 - 생성 패턴

class Singleton {

    static final Singleton instance = new Singleton();

    private Singleton() {
        //초기화
    }

    public Singleton getInstance() {
        return instance;
    }

}

(Java에서의 예시)
키보드 리더, 프린터 스풀러, 점수기록표 등 클래스의 객체를 하나만 만들어야 하는 경우 사용한다. 클래스 내에서 인스턴스가 단 하나뿐임을 보장하므로, 프로그램 전역에서 해당 클래스의 인스턴스를 바로 얻을 수 있고, 불필요한 메모리 낭비를 최소화한다.
이 패턴에서는 생성자를 클래스 자체만 사용할 수 있도록 private 등의 접근제한자를 통하여 제한하여야 한다. 생성자를 다른 곳에서도 사용할 수 있으면 그 곳에서도 인스턴스를 만들 수 있기 때문.


싱글톤 패턴을 사용하기 위해서는 반드시 접근제한자를 이용하여 외부의 접근을 막거나, final로 reference를 변경 불가능하게 설정하여야 한다. 물론 생성자에 접근제한자를 사용하면 최소한 다른 인스턴스로 레퍼런스시키지는 못하겠지만, ClassName.singleton = null; 처럼 레퍼런스 자체를 지워버릴 수 있기 때문.

 

자바에서(Java) final 은 상수를 표현하기 위한 예약어 입니다. 마지막이라는 단어의 뜻 처럼 선언한 그대로 사용하라는 의미입니다


구현 방법에는 사전 초기화, 늦 초기화 등이 있다.

  • Eager initialization(사전 초기화)

클래스 로딩시에 인스턴스를 생성하는 방법이다. 위의 예시가 사전 초기화. 멀티스레드 환경에서의 이중 객체 생성 문제가 없지만, 인스턴스를 호출하지 않아도 무조건 클래스를 초기화하기에 메모리 효율이나 연산 효율은 낮다.


Java에서는 static block initialization이라는 변종도 있다. 클래스가 로딩될 때 최초 1회만 실행되는 static block을 통해 싱글톤 인스턴스를 초기화하는 방법인데, 구조적으로는 크게 다르지 않다.

 

 

  • Lazy initialization(늦 초기화)

인스턴스를 실제로 사용할 시점에서 인스턴스를 생성하는 방법이다. 세심한 방법을 쓰지 않으면 위에 언급한 이중 객체 생성 문제가 발생할 가능성이 높으나, 인스턴스를 실제로 사용하지 않는다면 메모리와 연산량을 아낄 수 있다는 장점이 있다.

 


위에서 파사드를 다뤘지만 다른 글도 정리한다.

 

파사드 패턴 - 구조 패턴.

 

쉽게 설명하자면 복잡한 호출과정을 대신 처리해주는 wrapper 객체를 따로 만드는 것. 함수 호출 비용이 조금 들어가나 훨씬 쉽게 사용할 수 있다.

 

일단 wrapper란 무엇일까?

더보기

wrapper의 사전적 의미는 '(특히 식품) 포장지'라는 뜻입니다. 여기서 래퍼 클래스는 기본 자료형(primitive data types)에 대한 클래스 표현을 래퍼 클래스(wrapper classes)라고 합니다.

 

기본 자료형에 대해서 객체로서 인식되도록 '포장'을 했다는 뜻입니다. 객체라는 상자에 기본 자료형을 넣은 상태로 생각하면 됩니다. 필요시 컴파일러가 자동으로 수행하기 때문에 이를 오토박싱(autoboxing)이라고 합니다.

 

아래 그림으로 보여지는 숫자 자료형의 모든 래퍼 클래스는 모두 Number라는 추상 클래스를 상속 받아서 구현한 것입니다.  

 

Byte 클래스는 byte 자료형, Short는 short 자료형, Integer는 int 자료형, Long은 long 자료형, Float는 float 자료형, Double은 double 자료형에 대한 래퍼 클래스입니다.

 

 

 

 

 

 

기본 자료형 대신 래퍼 클래스를 사용하는 이유는 크게 세 가지가 있습니다.

 

1. 객체 또는 클래스가 제공하는 메소드 사용

2. 클래스가 제공하는 상수 사용(MIN_VALUE and MAX_VALUE)

3. 숫자, 문자로의 형변환 또는 진법 변환시 사용


굳이 객체를 따로 만드는 이유로는 하위 모듈을 건드릴 수 없는 경우(외부 라이브러리)나 저수준과 고수준 추상층(abstract layer) 개념 구분을 하고 싶은 경우, 크로스플랫폼 기술 구현 등의 이유가 있다.

파사드는 프랑스어 Façade에서 차용된 단어로 보통 건물의 출입구로 이용되는 정면 외벽 부분을 가리키는 말이다.
파사드 패턴은 시스템의 복잡성을 감추고, 사용자(Client)가 시스템에 접근할 수 있는 인터페이스(Interface)를 사용자(Client)에게 제공한다. 따라서 파사드 패턴은 기존의 시스템에 인터페이스를 추가함으로써, 복잡성을 감추기 위해 사용된다. 파사드 패턴은 구조적 패턴(Structural Pattern)에 포함된다.


정보기술에서의 Wrapper

 

래퍼 정보기술에서 말하는 래퍼는, 실제 데이터의 앞에서 어떤 틀을 잡아 주는 데이터 또는 다른 프로그램이 성공적으로 실행되도록 설정하는 프로그램이다. 인터넷상에서, http://와 ftp:// 등과 같이 인터넷 주소 또는 URL의 앞에 붙는 것들도 래퍼라 할 수 있다. 어떤 단어를 감싸는데 사용되는 < 또는 > 등과 같은 꺾쇠 기호들도 래퍼라고 한다. 

 

프로그래밍에서, 래퍼는 활동범위를 설정하고 좀더 중요한 다른 프로그램의 실행을 가능하게 하는 프로그램이나 스크립트를 말한다. 

 

데이터 통신에서, 래퍼는 전송 메시지의 앞이나 둘레에 놓여져 그에 관한 정보를 제공하는 데이터로서, 지정 수신자 외에는 보지 못하도록 캡슐화될 수 있다. 래퍼는 흔히 캡슐화된 데이터의 앞에 오는 헤더와, 데이터의 뒤에 따라 오는 트레일러로 구성된다. 

 

데이터베이스 기술에서, 래퍼는 감추어진 데이터를 보거나 변경하기 위해 누가 액세스해야할지를 결정하는데 사용될 수 있다



출처: https://eehoeskrap.tistory.com/177 [Enough is not enough] 

 

 

파사드 구현 방법

1단계:
인터페이스를 생성한다.

public interface Shape {
   void draw();
}

 

2단계:
그 인터페이스를 구현하기 위한 구체적인 클래스를 생성한다.

public class Rectangle implements Shape {

   @Override
   public void draw() {
      System.out.println("Rectangle::draw()");
   }
}

public class Square implements Shape {

   @Override
   public void draw() {
      System.out.println("Square::draw()");
   }
}

public class Circle implements Shape {

   @Override
   public void draw() {
      System.out.println("Circle::draw()");
   }
}

 

3단계:
파사드 클래스를 생성한다.

public class ShapeMaker {
   private Shape circle;
   private Shape rectangle;
   private Shape square;

   public ShapeMaker() {
      circle = new Circle();
      rectangle = new Rectangle();
      square = new Square();
   }

   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
   public void drawSquare(){
      square.draw();
   }
}

 

파사드 클래스가 여러 도형을 생성해주고, 포함된 도형들의 기능을 사용할 수 있게 해주는거 같다.

 

4단계:
다양한 종류의 형태를 만들기 위해 파사드를 사용한다.

public class FacadePatternDemo {
   public static void main(String[] args) {
      ShapeMaker shapeMaker = new ShapeMaker();

      shapeMaker.drawCircle();
      shapeMaker.drawRectangle();
      shapeMaker.drawSquare();		
   }
}

 

5단계:
결과값을 확인한다.

Circle::draw()
Rectangle::draw()
Square::draw()

프록시 - 구조 패턴

 

연산을 할 때 객체 스스로가 직접 처리하지 않고 중간에 다른 '숨겨진' 객체를 통해 처리하는 방법.

C++에서 다중 배열 접근은 operator[] 를 통해 이루어지는데, 만약에 배열을 내부에 캡슐화하고 var[1][2] 처럼 접근하고 싶어서 연산자 오버로딩을 동원하면 컴파일이 되지 않는다. operator[][] 는 없기 때문.

이 경우 프록시 객체를 따로 만들어서 내부 배열 첨자를 참조하는 다른 객체를 반환하게 하고(1차원, 2차원, ...) 중첩 operator[] 를 각각의 객체에 적용하면 .operator[](임시 객체.operator[](...)) 처럼 처리되어 구현할 수 있게 된다.

std::vector<bool> 클래스도 내부적으로는 1비트 단위로 접근하기 위해 비트 연산을 동원하는데, 이 과정에서 프록시 클래스를 경유한다. 물론 deprecated (사용 금지 권고) 받은 구시대 유물이니 std::bitset을 대신 사용하도록 하자.

표현식 템플릿(expression template) 이라는 고급 최적화 기법을 구현하는 방법도 프록시 클래스다.
템플릿을 통해 컴파일 타임에 게으른 평가(lazy evaluation)를 적용시켜 여러 연산자, 특히 행렬 처리를 풀어헤쳐서 임시 객체 생성을 최소화시킨다.

 


반복자 - 행위 패턴

 

객체 지향 언어에서 가장 접하기 쉬운 패턴. 당장 C#의 foreach 문은 반복자(IEnumerable) 인터페이스를 구현해야 사용 가능하다. 또한 C#의 모든 배열은 IEnumerable 인터페이스를 구현한다.

List<int> list = new List<int>();
// List에 요소 추가 //

foreach(int _value in list) {
   Console.WriteLine(_value);
}


자세히 설명하자면, 고전적인 패턴으로 자료구조에서 자료 전체를 순회할 때 List같은 구조에서는 아래와 같이 반복할 것이다.

for(int index=0; index<list.size(); index++) {
  list.get(index).doSomething();
}

 

저런 식으로 index를 하나씩 올려가며 순회하는 알고리즘은 List에는 적절하지만, 자료구조에는 List만 있는 것이 아니다. Tree, Trie, Graph, Map 등 오히려 index 접근을 못하는 구조가 더 많다.
이 경우에는 반복자(Iterator)라는 것을 사용하게 된다.
반복자는 인터페이스인데, 자바로 치면 아래와 같은 메서드를 정의해둔다.(참고)

interface Iterator<E> {
  boolean hasNext();
  E next();
  void remove();
}

이렇게 정의를 하고, 실제로 자료구조에 접근할 때에는 아래와 같이 접근하면 된다.

Iterator<Object> iterator = collection.iterator();
while(iterator.hasNext() == true) {
  Object object = iterator.next();
  object.doSomething();
}

이러면 자료구조가 Array이든 List이든 Tree Graph든, Iterable을 정의해놓기만 하면 저런 식으로 자료구조 전체를 순회하여 작업을 할 수 있게 된다.

물론 맨 위의 방법처럼 고전적인 for문을 쓸 수 있다면 그 쪽이 더 빠르긴 하다. for문은 컴퓨터 구조적인 발전으로 인해 캐시 메모리의 효과를 가장 많이 받는 구문이지만, 반복자는 그렇지 않기 때문.

C++의 경우엔 반복자를 정의하는 클래스가 begin과 end란 이름의 함수, 그리고 반복자엔 ++연산자 오버로딩, * 연산자 오버로딩을 구현해야 하도록 되어 있다. begin은 첫 자료의 반복자를 반환하고 end는 마지막 반복자를 반환하는데, 사실 빈 반복자를 반환하면 된다. 어디에 인터페이스같은게 정의되 있는 것은 아니라서 배우기는 상대적으로 어려운 편.

실제 자료구조엔 이런식으로 접근한다.

std::list<int>list;
for(std::list<int>::iterator it = list.begin(); it != list.end(); ++it){
  (*it)=3;
}

이터레이터는 늘상 사용했지만, 리스트에서 주로 사용해서 왜 사용하나 했는데, 트리에서도 사용할 수 있을 줄은 몰랐다. 그런 목적을 보니 더 와닿는거 같다.


템플릿 메소드 - 구조 패턴

 

전체적인 레이아웃을 통일시키지만 상속받은 클래스가 유연성을 가질 수 있게 만드는 패턴이다.

 


전반적인 디자인 패턴에 대해 알아 보았다. 사실 이전에 나도 모르게 사용하던것이 많던거 같다. 음 사실 구현할때, 나도 모르게 복잡해지는 부분이 있었는데, 이런걸 좀 더 고민해서 설계하면 더 좋을거 같다. 

 

예를 들면 갤러리 플젝 디테일 뷰컨에서, 디테일 뷰컨에서 보여줄 데이터를 뷰컨에서 직접 로딩 실행, 저장, 중복 데이터 체크에다 뷰에 보여주기까지 했는데, 나는 디테일 뷰컨에서만 사용하는 데이터라 그냥 디테일 뷰컨에서 처리했다. 내 생각에 그런데 다른 방법으로 로딩,저장 역할을 모델로 빼는 방법도 있을텐데, 그러면 책임은 분리되긴 할거 같다.