Skip to content

장기계획 수립을 위한 고찰

2011년 6월 28세 처음으로 스타트업 다운 회사를 시작하고 거의 동시에 결혼을 했다. 이 블로그를 작성하는 2017년 1월 그 이후 만으로 6년 가까이 지났다.

처음 회사 운영이라는 것이 뭔지 모르고 좋은 멘토 조차 없었고, 그래서 많은 시행착오를 겪었다. 결혼생활은 또 어떠랴. 누구든 결혼은 처음 겪겠지만, 회사 생활 만큼 몰랐고 역시 시행착오는 많았다. 3년간 바로풀기라는 서비스를 만들고 1년 반 연구실 생활을 하고 다시 1년 반 스위즐에서 데이터 분석 서비스를 만들었다. 어느 것 하나 완전히 실패하진 않았지만, 어느 것 하나 만족스럽도록 완성시키지 못했다. 결혼 후, 2015년 초 귀여운 주연이를 얻은 것 말고는 뭐 하나 잘한게 없기도 하다.

바람에 이리저리 흔들리는 갈대처럼 느껴졌다.

스스로 모토로 생각하는 한 가지 명언이 있다. “바뀌려면 변해야 한다.” 누구의 성공을 보고 누구의 실패를 보고 있자면, 성공하는 자는 스스로 변할 줄 알고 실패하는 자는 똑같은 행동을 반복한다고 오래도록 느꼈다. (무엇이 성공인가에 대해선 논외로 하자.) 나는 미래를 바꾸기 위해 어떻게 변해야 할까 고민했다. 그리고 여러 번 시도하고자 했지만 그럴듯한 시도조차 하지 못하고 또다시 실패했다. 그 중 가장 큰 실패는 규칙적인 삶과 끊임없는 자기 개발하는 것이다.

성공에 대해 갈망하며 꼭 필요한 덕목이라 생각했던 규칙적이고 끊임없는 자기개발을 지속적으로 달성하고자 했지만 번번히 실패했던 이유는 무엇일까. 요 최근 가장 크게 느낀 점은 장,단기 계획의 부재다. 개발 방법론 중, 프로덕트를 지속적으로 향상시키기 위해 많이 사용하는 방법이 스크럼이다. 스크럼은 매 반복 주기(스프런트)로 달성해야할 목표를 매일 정해진 목표를 완성단위(백로그)로 완성해야 한다. 그러기 위해선 철저하게 계획적인 장단기 업무 정리가 필요하고 매일같이 이 업무가 수정 및 반영 되어야 한다. 이 방법론은 개발분야에서 많은 성공사례를 만들어내며 지속적으로 발전 및 수정되도록 적용되어지고 있다.

스크럼의 심리적 성공요인은 성과에 대한 인지적 분석과 목표달성 세분화에 의한 동기부여라고 생각한다. 즉, 세분화된 작은 성공과 커뮤니케이션을 지속적으로 혹은 반 강제적으로 큰 업무에 주입함으로써, 매우 복잡한 달성과제를 일정한 속도로 최종적으로 달성하게 하는데 있다. 그리고 이 과정을 인지하기 쉬운 사람의 라이프사이클에 접목해 실제로 실행 가능하며 통제 가능하도록 한다.

개발은 현업에서 비용과 직접적인 연관이 있기에 이렇도록 발전되어저 왔지만, 개인의 삶에는 이러한 잘 완성된 방법론이 있기 어렵다. 성공을 결정하는 요인이 성향, 직업, 가족, 개인, 문화등 훨씬 다양하고 고려해야할 것이 많고, 그렇기에 개개인 별로 다르게 적용되어야 함은 부인할 수 없다.

평생의 장기계획 부터 매일같이 진행할 일일의 계획과 목표를 세부적으로 세우고 달성 방법까지 구체적으로 기술해 지치지 않고 2017년의 목표를 끝까지 달성해보자. 작게는 회사를 위해 크게는 나라를 위해 내가 해야할 일이 무엇인지 구체적으로 생각해보자.

호그댁 공략 (3) – 클래시 로얄(Clash Royale)

  1. 왜 호그댁인가
  2. 호그댁의 공격 메커니즘
  3. 호그댁 조합법
  4. 상대 댁별 상대법

이 포스트에서 제시할 조합은 몇가지 예시이며, 유사한 컨셉은 같이 설명한다.

첫번째 설명할 조합은 딜러 조합이다. 호그와 함께 딜러를 함께 조합해, 호그가 타워의 공격을 맞아주는 동안 싸고 공격력이 강한 카드를 활용해 타워에 데미지를 강력하게 넣는 조합이다.

  1. 호그 + 고블린 조합
  2. 호그 + 해골 조합
  3. 호그 + 미니언 조합

고블린과의 조합은 출시 초창기부터 매우 많이 사용해 오던 조합이나 현재(2017년 1월 1일)는 많이 쓰이진 않는다. 단일 딜러로 코스트 대비 가장 강력하다고 볼 수 있는 고블린을 사용해 딜을 넣는 조합이다. 해골과의 조합은 고블린 대신 사용할 수 있고 딜은 좀 적지만 벽타기가 가능하고 1코스트 적게 들어갈 수 있다. 미니언과 함께 할 경우는 코스트가 많이 들 수는 있지만 지상 수비병력만으로 막을 수 없기 때문에 막기위해 원거리 유닛을 사용해야 한다.

딜러 조합의 장점은 상대방보다 엘릭서가 유리할 때 타워에 강력한 데미지를 넣어 줄 수있다. 하지만 딜러가 체력이 약해 방어하는데 어렵지 않다.

들어가는 타이밍은 상대보다 엘릭서가 유리한 시점에 들어가야 하며 섣부르게 들어가다 역습맞을 수 있는 리스크가 있다.

 

두번째 설명할 조합은 어그로 조합이다. 호그에게 들어갈 데미지를 잠시 멈춰 호그가 더 많은 공격을 타워에 넣을 수 있도록 하는 조합이다.

  1. 호그 + 얼음골램
  2. 호그 + 얼음정령
  3. 호그 + 화염정령

이 조합은 순간적으로 적의 움직임을 멈추거나 느리게해 호그의 생존시간을 늘리는 조합이다. 얼음골램의 경우에는 호그가 밀고들어가 대신 탱킹을 해주면 가장 좋다. 얼음정령은 적 방어병력을 잠시 멈추게해 호그의 생존시간을 길게하며 화염정령은 적의 병력은 순간적으로 제거해 호그의 생존시간을 길게 할 수 있는 조합니다.

특히 호그 얼음골램, 얼음정령은 얼음골램,얼음정령 자체의 효능이 뛰어나 많이 사용되면서 호그와 조합으로 많이 사용되고 있다. 얼음골램이나 얼음정령은 감전과 함께 사용하면 미니언까지 처리가 가능해매우 강력한 조합이라고 볼 수 있다. 딜러 조합보다 방어하기 어렵기 때문에 방어하는 입장에서 방어타워 사용을 강제하기도 한다.

 

세번째 설명할 조합은 위 조합에 마법을 함께 섞은 조합이다.

일단 호그가 혼자 들어가거나 위에 먼저 언급한 카드와 조합되어 들어갈때 같이 사용할 수 있는 마법 들이다.

  1. 호그 + 파이어볼
  2. 호그 + 독
  3. 호그 + 감전
  4. 호그 + 통나무
  5. 호그 + 번개
  6. 호그 + 얼음
  7. 호그 + 분노

사실 호그의 강력함은 마법과 함께할 때 빛을 발한다. 호그를 막기위해 방어병력을 배치하는 시점이 가장 좋은 마법 사용 시점이라 볼 수 있다. 호그를 막기위해 자주사용하는 바바리안의 경우 파이어볼을 사용하면 순간적으로 분산되어 호그가 공격할 수 있는 상황을 만들어준다. 번개의 경우는 적의 방어타워를 무력화 시키며 일방적인 타워 공격이 가능하게끔 해줄 수 있다.

마법은 상대 방어를 예측해 사용할 때 가장 효과적이며, 이 부분이 호그댁을 잘 사용하는지 아닌지 판가름나는 부분이라 볼 수 있다.

호그댁 공략 (2) – 클래시 로얄(Clash Royale)

  1. 왜 호그댁인가
  2. 호그댁의 공격 메커니즘
  3. 호그댁 조합법
  4. 상대 댁별 상대법

호그댁의 공격 메커니즘의 기본은 호그의 사용 타이밍이다. 호그는 다양한 상황에서 사용이 가능한데, 모든 카드의 사용 타이밍과 위치는 호그의 사용 타이밍을 고려해 사용한다.

예를 들면 호그가 오른쪽으로 들어갈 타이밍을 노리고 있을때, 상대방이 인페르노 타워를 가지고 있다면, 미리 오른쪽 중간 방향으로 메가 미니언을 넣어 호그가 들어갈때 어그로를 끌 수 있도록 해주는 방법이 있다.

호그의 사용 타이밍을 분류해 보면 다음과 같다.

  1. 초반 찌르기
  2. 저 코스트로 방어 성공 후 사용
  3. 비슷한 코스트일 때 조합을 갖춰 사용 (벽타기 이용)
  4. 어그로 끌기 및 역습용으로 사용

크게 네가지 정도로 나눌 수 있다.

1. 초반 찌르기의 경우 게임이 시작하자마자 첫카드로 호그를 사용하는 것이다. 이 방법은 첫카드로 사용하는 다른 카드에 비해 장점이 많은데, 그 이유는 타워에 데미지를 입지 않기 위해서 상대방이 방어타워를 쓰거나 호그와 비슷한 코스트를 사용해 수비 병력을 빼야 하기 때문이다.

방어타워의 경우 가장 효율적으로 막을 수 있는 카드는 대포이지만, 대포를 사용해 막는다 해도, 1코스트 손해밖게 입지 않고 또한 방타로 역습을 올 수는 없기 때문에 큰 손해는 아니다. 만일 다른 방타의 경우에는 호그와 코스트가 같거나 높기 때문에 상대의 코스트 손해를 가져온다. 만일 수비 병력으로 막을 경우 가장 효율적인 것은 해골군대 정도일 것이다. 만일 막기위해 바바리안을 사용한다면 바바리안으로 상대에게 역습을 가하기 어렵기 때문에 방어 측면에서 막기 어렵지 않다. 그 외 메가 미니언이나 아처를 뺀다 해도, 크게 손해가 아니며 막기 어렵지 않다.

2. 저 코스트로 방어 성공 후 사용의 경우 상대가 빅댁일 때 효과적이다. 예를 들어 상대가 라바로 들어올 경우 라바댁은 메가미니언 만으로 적은 코스트 방어가 가능하다. 이 때 순간적으로 상대보다 코스트가 유리해지는 시점이 있는데, 이때 적의 코스트 부족 타이밍을 노려 사용하는 것이다. 상대 조합을 적은 코스트로 방어하는 방법은 따로 정리해 보겠다.

3. 비슷한 코스트일 때 조합을 갖춰 사용의 경우 호그를 사용해, 고블린, 해골, 얼음정령이 벽타기 조합을 이루기 좋은 카드들이며 얼음골램은 밀고 올라가기 좋은 카드이다. 고블린과 해골의 경우 놔두면 상당히 타워가 피해를 많이 당할 것이며 얼음정령, 얼음 골램 또한 호그가 공격할 시간을 더 벌어 상대 타워에 많은 피해를 줄 수 있다.

벽타기를 사용해야 하는 이유는 방타의 위치를 중간에 사용하지 못하도록 하기 위함이다. 방타의 위치가 중간일 경우 크라운 타워 두개에 동시에 공격 당하기 때문에 호그가 좀더 빨리 죽기 때문이다.

4. 어그로 끌기  및 역습용으로 사용의 경우 많이 숙달 되었을 때 사용하기 좋은데, 가장 어그로가 잘 끌리는 카드들이 있다. 예를 들언 미니 페카의 경우 호그의 점프를 통해 페카가 호그를 때리지 못하고 계속 따라가게 만들 수 있다. 그동한 타워가 미니페카를 쳐서 적은 코스트로 수비가 가능해지며, 호그로 역습까지 가능해지는 것이다. 단 이속이 느린 적 카드의 경우 조금 따라가다가 다시 공격으로 전환할 수 있으니 여러번 연습을 통해 상대 조합에 맞게 사용해야 할 것이다. 물론 상황이 자주 발생하지는 않는다.

 

호그댁의 공격 메커니즘 중 중요한 것은 방어 이다. 호그는 기본적으로 적 카드를 공격할 수 없기 때문에 방어시에 활용하기 어렵다. 따라서 적은 코스트로 효율적인 수비가 가능한 카드들과 함께 조합해 방어를 항상 고려해야 한다. 공격은 약하지만 방어에 효율적인 아처, 얼음법사, 프린세스, 미니언, 메가미니언 등 적은 코스트지만 조합을 통해 상대 공격을 무마할 수 있는 카드가 필요하며, 통나무, 감전과 같이 저코스트로 방어를 해낼 수 있는 카드들도 함께 조합을 하는 것이 좋다.

아처의 경우 3코스트이지만, 원거리 유닛으로써 공격력이 상당하기 때문에 상대의 느린 유닛을 잡는데 효율적이다. 얼음법사는 공격력은 높지 않지만 상대방을 느리게 만들어 공격을 무마시키며, 체력도 높은 편이다. 프린세스는 반대편에 소환함으로써 피가 적은 카드를 효율적으로 방어하며 이후 상대방의 코스트 낭비도 불러올 수 있다. 미니언, 메가 미니언 등은 높은 공격력으로 공중 공격이 불가한 근거리 유닛을 막는데 효율적이다. 또한 해골, 고블린, 창고블린, 가드등은 상대가 높은 코스트로 단일 공격 유닛을 막는데 효과적이다.

구체적인 방어 방법은 상대 댁별 상대법에서 다시 정리해 보겠다.

 

마지막으로 호그댁의 공격 메커니즘 중 중요한 것이 공격용 마법 사용이다. 호그댁은 파이어볼, 번개, 독 등과 함께 조합되어 상대방의 방어타워를 무마시키거나 상대 수비병력을 무력화해 타워에 대미지를 주는 방법이 많이 사용된다. 호그를 막기위해 상대방이 사용할 카드를 예측해 미리 마법을 쓸 수 있다면 큰 피해를 상대방에게 줄 수 있을 것이다.

파이어볼은 상대방의 방타를 미리 예측해 쓰거나, 방어를 위해 사용한 바바리안에게 피해를 주기위해 사용할 수 있으며, 또한 생산기지나 앨릭서 정제소 견제용으로 사용가능하다. 번개는 상대방이 머스킷, 메가미니언, 방어타워 등 적은 유닛수로 방어할 경우 매우 효율적으로 사용할 수 있다.단 상대가 해골군대, 메가미니언 등을 사용할 수 있기 때문에 이러한 유닛을 견제할 수 있는 마법이나 유닛도 함께 준비해야 할 것이다.

호그댁 공략 (1) – 클래시 로얄(Clash Royale)

  1. 왜 호그댁인가
  2. 호그댁의 공격 메커니즘
  3. 호그댁 조합법
  4. 상대 댁별 상대법

클래시 로얄의 기본 게임 방법은 제한시간 내에 상대 타워를 먼저 깨는 것이다. 이 기본 게임 방법에 가장 적합하게 설계된 카드이다. 먼저 아래 표를 참조해 보자.

2016년 12월 26일 토너먼트 레벨(1-4-7-9) 기준
카드명 속도 초당 피해량 체력 코스트
호그 라이더 매우 빠름 176 1408 4
자이언트 느림  140  3344 5
로얄 자이언트 느림  93  2544 6
골렘 느림  103(데쓰-259)  4256 8
해골 비행선 보통  266 (데쓰-272)  1396 5
라바 하운드 느림  34 (펍-45)  3000 7

위 카드들은 타워나 건물을 먼저 부수는 카드들이다. 클래시 로얄의 룰이 상대 타워를 먼저 부수는 것이기 때문에 위 건물 공격 카드가 중심이 되어 운영 방법들이 나올 가능성이 높다.

물론 석궁, 박격포 같이 저격댁이나, 광부를 활용한 짤짤이 댁, 생산 건물을 활용한 물량댁도 있지만, 주류 매타가 되긴 쉽지 않다. 싸움은 잘할 수 있어도, 결국 상대를 이기려면 타워를 부숴야 하는데, 위와 같은 덱은 상대의 운영 여하에 따라 타워 공략에 실패할 가능성이 높다.

저 건물을 부수는 댁 중에 적은 코스트로 빠르게 건물에 접근에 많은 데미지를 주는 것에 특화된 카드가 바로 호그라이더이다. 이런 특징 때문에 강력한 카운터 카드가 없으며, 다양한 운영 방법과 조합이 탄생할 수 있는 좋은 카드가 될 수 있다.

호그는 다양한 역할 수행이 가능하다. 역할을 나누어 보자면 다음과 같다.

  1. 기습 – 상대가 엘릭서가 없을때 빠르게 찌르기
  2. 딜러 – 타워에 직접적인 데미지를 줌
  3. 낭비 유도 – 상대방이 호그를 막게해 엘릭서를 낭비시킴
  4. 탱커 – 상대 타워의 공격을 맞아줌
  5. 유인 – 어그로에 취약한 카드를 유인함
  6. 낚시 – 호그로 공격하는 척 하면서 반대편으로 딜러를 진입시킴
  7. 방타 무시 – 벽타기를 이용한 방어 타워 지나치기

위 카드중엔 특정 역할을 강력하게 담당할 수는 있지만, 이 모든 역할을 수행 할 수 있는 카드는 호그라이더 밖에 없다.

특히 호그라이더를 사용하면서 상대방이 호그라이더를 막기위한 카드 사용을 예측해 미리 마법을 사용하며 큰 이득을 취하는 운영은 호그라이더를 더욱 강력하게 만들어 준다. 그럼 호그라이더에 대해 더 자세히 알아보자.

파사드(facade) – 디자인패턴 이야기 (Story of design patterns)

드워프의 금광 캐기

금광을 캐기 위해선 3가지 일을 해야한다. 먼저 금광 터널을 뚫어야 하며, 금을 캐야하고 마지막으로 금을 날라야 한다. 이 세가지 작업을 드워프 혼자서 하려면 3단계 업무가 필요하다

  1. 굴파기
  2. 금캐기
  3. 나르기

 

하지만 드워프 1명이서는 하루에 한가지 작업만 할 수 있는데, 하루 일과는 이렇다.

  1. 일어나기
  2. 광산으로 이동
  3. 일하기
  4. 집에가기
  5. 잠자기

 

파사드는 드워프들의 고용주이며, 귀차니즘이 심해서 하루의 시작과 끝을 알리는 일과 금을 캐라라는 3가지 명령만 하려고 한다.

  1. 하루 시작
  2. 일해
  3. 하루 끝

 

파사드는 3명의 드워프를 고용해 각각의 역할을 부여했다. 각자 역할을 아는 드워프들은 일하기시간에 알아서 자신의 일을 할 수 있다. 파사드는 일꾼들에게 사전 교육을 진행한다(-makeActions). 하루 시작을 알리면 1 일어나기, 2 광산으로 이동 을 하도록 한다. 일해라고 명령하면 3 일하기 업무에 따라 각자 자신의 역할에 맞는 일을 시작한다. 하루가 끝나면 4 집에가기, 5 잠자기를 하도록 한다.

이제 파사드는 3가지 명령만으로 3 종류의 각기 다른 작업을 5 단계에 하루 일과에 맞춰 진행할 수 있게 되었다. 파사드 만세!

 

facade.png

https://github.com/iluwatar/java-design-patterns/tree/master/facade

 

왓슨API, 노드로 구현하기 (Implement Watson API by Node.js)

왓슨API는 다양한 기능의 API를 지원하는데, 개인적으로 유사 API 중 가장 뛰어나다고 생각하는 것이 자연어 프로세싱 관련 API 이다. 특이하게 왓슨에서는 자연어 처리를 위한 API중 AlchemyAPI 라는 것이 있는데, 해당 기능은 IBM에서 AlchemyAPI를 인수하면서 이름을 그대로 사용했기 때문이다.

텍스트 데이터 분석을 위해 AlchemyAPI를 사용하는 방법 중에, 호감도 분석(Sentiment Analysis)과 감정 분석(Emotion Analysis)은 쉽게 사용 가능하다. 입력 텍스트만 있다면 단순한 웹 요청으로 결과값을 받아올 수 있기 때문이다.

본 포스트에서는 Node.js 라이브러리인 watson-developer-cloud 를 활용해 호감도 분석과 감정 분석을 할 수 있는 방법을 소개한다.

먼저 왓슨API를 사용하기 위해서는 Bluemix에 가입해 사용할 API의 서비스와 인증 키를 생성해야 한다.

https://console.ng.bluemix.net/ 로 들어가면 가입을 할 수 있다. 간단한 절차를 통해 가입을 완료하고 로그인을 하면 다음과 같은 화면을 볼 수 있다.

%ec%ba%a1%ec%b2%981

우리가 사용할 메뉴는 왼쪽 텝에 서비스 항목이다. 서비스를 누르고 대시보드를 누르면 서비스를 등록할 수 있는 “서비스 작성” 버튼을 볼 수 있다. “서비스 작성”을 누르고 우리가 사용할 AlchemyAPI를 선택한다. AlchemyAPI는 Watson의 하위 항목에 첫번째에서 찾을 수 있다.

누르면 하단에 “작성” 버튼을 누른다. 따로 설정을 변경하지 않았다면 무료 플랜으로 설정되어 있을 것이다. 생성된 서비스로 들어가면 3개의 탭을 볼 수 있는데,  “서비스 신임 정보” 텝으로 들어가 새 신임정보를 추가한다.

%ec%ba%a1%ec%b2%982

신입정보에 있는 API Key는 API를 활용하는데 사용할 파라메터이다. 이제 이 API Key를 사용해 WatsonAPI에 접속해 보자.

먼저 Node.js의 waton-developer-cloud 라이브러리를 설치한다. Node.js가 설치되어 있다면 npm으로 간단하게 설치할 수 있다.

sh> npm install watson-developer-cloud

설치가 완료되었다면 샘플 코드를 작성해 보자.

var AlchemyLanguageV1 = require('watson-developer-cloud/alchemy-language/v1');
var alchemy_language = new AlchemyLanguageV1({ api_key: 'API_KEY'});
var params = { text: 'IBM Watson won the Jeopardy television show hosted by Alex Trebek'};
alchemy_language.sentiment(params, function (err, response) { 
  if (err)
    console.log('error:', err); 
  else 
    console.log(JSON.stringify(response, null, 2));
});

위처럼 작성하면 text 입력값에 따른 결과값을 받아올 수 있다. sentiment 함수는 입력 텍스트의 호감도를 분석한 결과값을 반환하는 함수이다. sentiment 이외에도 emotion과 같은 다양한 API를 지원한다. (https://www.ibm.com/watson/developercloud/alchemy-language.html)

왓슨API는 개발자가 처음에 사용하기는 상당히 쉽게 설계되어 있다. 하지만, 적은 무료 사용량조차 한달이라는 기간 동안만 사용가능하며 계속 사용하려면 과금계정을 생성해야 한다. 이때문에 무료 사용기간에 서비스에 접목시켜 보는 것은 조금 힘들 수 있다. 그럼에도 불구하고 왓슨의 강력한 기술력을 API 사용료만으로 사용할 수 있다는 사실은 상당히 큰 장점이기 때문에, 데이터 기술이 필요한 많은 업체들의 요구를 충족시켜 줄 것으로 기대한다.

 

Spring Data’s Page Object Converting using Collector (feat. Java8)

Java8 에서 stream Collection 타입을 활용해 리스트 내부 오브젝트를 바꾸는 코드를 효율적으로 작성할 수 있다.

스프링 데이터에 쓰이는 Page 오브젝트는 List Wrapper 클래스중에 하나이다. 이 클래스도 마찬가지로 stream map 함수를 사용하는 것처럼 쉽게 사용하기 위해 커스텀 Collector 클래스를 하나 작성해 보았다.

기존에 5줄 정도 사용하던 java6 용 코드를 한줄에 넣을 수 있게 되었다.

최종 활용코드는 다음과 같다.

Page<DTO> dtos = PageCollector.convert(repository.findAll(pageable), DTO:new);

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Stream.concat;

/**
 * Created by Swizzle on 2016-11-17.
 */
public class PageCollector implements Collector<T, List, Page> {

    private final Pageable pageable;
    private final long total;

    public PageCollector(Pageable pageable, long total) {
        this.pageable = pageable;
        this.total = total;
    }

    @Override
    public Supplier supplier() {
        return ArrayList::new;
    }

    @Override
    public BiConsumer<List, T> accumulator() {
        return List::add;
    }

    @Override
    public BinaryOperator combiner() {
        return (left, right) -> concat(left.stream(), right.stream()).collect(toList());
    }

    @Override
    public Function<List, Page> finisher() {
        return builder -> new PageImpl<>(builder, pageable, total);
    }

    @Override
    public Set characteristics() {
        return Collections.unmodifiableSet(Collections.EMPTY_SET);
    }

    public static  Collector<T, ?, Page> toPage(Pageable pageable, long total) {
        return new PageCollector<>(pageable, total);
    }

    public static <O, T> Page convert(Page page, Function<O, T> convertFunc) {
        return page.getContent().stream()
                .map(convertFunc)
                .collect(toPage(
                        new PageRequest(page.getNumber(), page.getSize(), page.getSort()),
                        page.getTotalElements()));
    }
}