<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>WOOK LAB;</title>
    <link>https://wook-lab.tistory.com/</link>
    <description>Welcome to wook-lab</description>
    <language>ko</language>
    <pubDate>Thu, 9 Jul 2026 07:53:10 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>wook-lab</managingEditor>
    <image>
      <title>WOOK LAB;</title>
      <url>https://tistory1.daumcdn.net/tistory/4434965/attach/2a673d2b4239462b99912a1bf93896cc</url>
      <link>https://wook-lab.tistory.com</link>
    </image>
    <item>
      <title>Chapter 03 역할, 책임, 협력</title>
      <link>https://wook-lab.tistory.com/22</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향 패러다임의 관점에서 핵심은 &lt;b&gt;역할(role), 책임(responsibility) 협력(collaboration)&lt;/b&gt;이다. &lt;br /&gt;(2장에서 다루었던 클래스, 상속, 지연 바인딩은 구현측면에서 중요하지만 본질과는 거리가 있다.)&lt;br /&gt;&lt;br /&gt;객체지향의&amp;nbsp;본질은&amp;nbsp;협력하는&amp;nbsp;객체들의&amp;nbsp;공동체를 창조하는 것이다. &lt;br /&gt;객체지향&amp;nbsp;설계의 핵심은&amp;nbsp;협력을 구성하기 위해&amp;nbsp;적절한 객체를 찾고&amp;nbsp;적절한 책임을 할당하는 과정에서 드러난다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;01 협력&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영화 예매 시스템 돌아보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영화 예매라는 기능을 완성하기 위해 협력하는 객체들의 상호작용을 표현한 그림&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1712&quot; data-origin-height=&quot;1041&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbTiIy/btrBT6bTq2I/MeD2bic1umgPZw02fClcYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbTiIy/btrBT6bTq2I/MeD2bic1umgPZw02fClcYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbTiIy/btrBT6bTq2I/MeD2bic1umgPZw02fClcYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbTiIy%2FbtrBT6bTq2I%2FMeD2bic1umgPZw02fClcYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1712&quot; height=&quot;1041&quot; data-origin-width=&quot;1712&quot; data-origin-height=&quot;1041&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체지향 원칙을 따르는 애플리케이션의 제어 흐름은 다향한 객체들 사이에 균형 있게 분배되는 것이다.&lt;/li&gt;
&lt;li&gt;다양한 객체들이 영화 예매라는 기능을 구현하기 위해 메시지를 주고받으면서 상호 작용한다.&lt;/li&gt;
&lt;li&gt;협력 - 객체들이 애플리케이션의 기능을 구현하기 위해 수행하는 &lt;b&gt;상호작용&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;책임 - 객체가 협력에 참여하기 위해 수행하는 &lt;b&gt;로직&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;역할 - 객체들이 협력 안에서 수행하는 객체들이 모여, &lt;b&gt;객체가 수행하는 것&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;협력&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체지향 시스템 -&amp;gt; 자율적인 객체들의 공동체&lt;/li&gt;
&lt;li&gt;객체 -&amp;gt; 시스템의 기능이라는 더 큰 목표를 달성하기 위해 다른 객체와 협력하는 사회적인 존재&lt;/li&gt;
&lt;li&gt;협력 -&amp;gt; 객체를 구현할 수 있는 유일한 방법, 어떤 객체가 다른 객체에게 무언가를 요청하는 것 (또는 위임)&lt;/li&gt;
&lt;li&gt;메시지 전송(message sending) - &amp;gt; 객체들이 협력을 위해 사용할 수 있는 유일한 커뮤니케이션 수단&lt;/li&gt;
&lt;li&gt;메서드 -&amp;gt; 메시지를 수신한 객체가 응답을 위해 실행하는 프로세스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부의 객체는 오직 메시지만 전송할 수 있다.&lt;/li&gt;
&lt;li&gt;메시지를 어떻게 처리할지는 메시지를 수신한 객체가 스스로 결정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예매 요금을 계산하기 위한 Screening과 Movie의 협력&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zTBDy/btrBS1a467M/QR4jPhr1irKDwnCTTtOXk0/img.png&quot; width=&quot;770&quot; height=&quot;213&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/zTBDy/btrBS1a467M/QR4jPhr1irKDwnCTTtOXk0/img.png&quot; data-origin-width=&quot;1755&quot; data-origin-height=&quot;485&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요금을 계산하는 작업을 Screening이 수행한다면 Movie의 인스턴스 변수인 fee와 discountPolicy에 직접 접근해야만 한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Screening은 Movie의 내부 구현에 결합&lt;/li&gt;
&lt;li&gt;Movie의 자율성이 훼손&lt;/li&gt;
&lt;li&gt;Movie는 수동적인 존재로 전락&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Movie가 자율적인 존재가 되기 위해서는 자신이 알고 있는 정보를 이용해 직접 요금을 계산해야 한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Screen이 Movie에게 요금을 계산하도록 위임하는 이유&lt;/li&gt;
&lt;li&gt;협력에 참여하는 객체들의 전체적인 자율성을 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체를 자율적으로 만드는 가장 기본적인 방법 -&amp;gt; 내부 구현을 캡슐화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;자율적인 객체는 자신에게 할당된 책임을 수행하던 중에 필요한 정보를 알지 못하거나 외부의 도움이 필요한 경우 적절한 객체에게 메시지를 전송해서 협력을 요청한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 수신한 객체 역시 메시지를 처리하던 중에 직접 처리할 수 없는 정보나 행동이 필요한 경우 또 다른 객체에게 도움을 요청한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;협력이 설계를 위한 문맥을 결정한다&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class Movie {
    private Money fee;
    private DiscountPolicy discountPolicy;

    public Money calculateMovieFee(Screening screening) {
        return fee.minus(discountPolicy.calculateDiscountAmount(screening));
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체의 행동을 결정하는 것은 객체가 참여하고 있는 협력이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;협력은 객체가 필요한 이유와 객체가 수행하는 행동의 동기를 제공&lt;/li&gt;
&lt;li&gt;Movie라는 객체가 play라는 행동이 아닌 calculateMovieFee라는 행동만 있는 이유도 영화를 예매하기 위한 협력에 참여하고 있고, 요금을 계산하는 책임을 지고 있기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체가 참여하는 협력이 객체를 구성하는 행동과 상태 모두를 결정한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태: 객체가 행동하는 데 필요한 정보에 의해 결정&lt;/li&gt;
&lt;li&gt;행동: 협력 안에서 객체가 처리할 메시지로 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;따라서 협력은 객체를 설계하는 데 필요한 일종의 문맥(context)를 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;02 책임&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;책임이란 무엇인가&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;책임
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;협력에 참여하기 위해 객체가 수행하는 행동&lt;/li&gt;
&lt;li&gt;객체에 의해 정의되는 응집도 있는 행위의 집합&lt;/li&gt;
&lt;li&gt;객체가 유지해야 하는 정보와 수행할 수 있는 행동에 대해 개략적으로 서술한 문장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체의 책임
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체가 &amp;lsquo;무엇을 알고 있는가&amp;rsquo;&lt;/li&gt;
&lt;li&gt;객체가 &amp;lsquo;무엇을 할 수 있는가&amp;rsquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;크레이그 라만(Craig Larman)의 책임
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lsquo;아는 것(knowing)&amp;rsquo;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사적인 정보에 관해 하는 것&lt;/li&gt;
&lt;li&gt;관련된 객체에 대해 아는 것&lt;/li&gt;
&lt;li&gt;자신이 유도하거나 계산할 수 있는 것에 관해 아는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;lsquo;하는 것(doing)&amp;rsquo;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체를 생성하거나 계산을 수행하는 등의 스스로 하는 것&lt;/li&gt;
&lt;li&gt;다른 객체들의 행동을 시작시키는 것&lt;/li&gt;
&lt;li&gt;다른 객체의 활동을 제어하고 조절하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;영화 예매 시스템에서의 책임
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Screening: 영화를 예매하는 것
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자신이 상영할 영화를 알고 있어야 한다.&lt;/li&gt;
&lt;li&gt;영화를 예매 할 수 있어야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Movie: 요금을 계산하는 것
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가격과 적용된 할인 정책에 대해 알고 있어야 한다&lt;/li&gt;
&lt;li&gt;예매 가격을 계산한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;어떤 책임을 수행하기 위해서는 그 책임을 수행하는 데 필요한 정보도 함께 알아야 할 책임이 있는 것이다&lt;/li&gt;
&lt;li&gt;사실 협력이 중요한 이유는 객체에게 할당할 책임을 결정할 수 있는 문맥을 제공하기 때문이다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;적절한 협력이 적절한 책임을 제공하고&lt;/li&gt;
&lt;li&gt;적절한 책임을 적절한 객체에게 전달해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;책임 할당&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;협력에 필요한 지식과 방법을 가장 잘 알고 있는 객체에게 도움을 요청하게 되는데, 요청에 응답하기 위해 필요한 이 행동이 객체가 수행할 책임으로 이어지는 것이다.&lt;/li&gt;
&lt;li&gt;책임을 할당하는 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체가 책임을 수행하게 하는 유일한 방법은 메시지를 전송하는 것&lt;/li&gt;
&lt;li&gt;책임을 할당한다는 것은 메시지의 이름을 결정하는 것과 같다.&lt;/li&gt;
&lt;li&gt;메시지를 선택했으면 메시지를 처리할 적절한 객체를 선택해야 한다.(정보를 가장 많이 알고 있는 객체에게 책임을 할당하는 것이 바람직)&lt;/li&gt;
&lt;li&gt;책임을 수행하기에 필요한 정보를 충분히 알고 있지 않다면, 외부 객체에게 요청을 해야 한다는 것을 의미한다. (새로운 메시지가 필요)&lt;/li&gt;
&lt;li&gt;새로운 메시지가 필요하다는 사실을 알게 됐다면 메시지를 처리할 적절한 객체를 선택해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이처럼 객체지향 설계는 협력에 필요한 메시지를 찾고 메시지에 적절한 객체를 선택하는 반복적인 과정을 통해 이뤄진다.&lt;/li&gt;
&lt;li&gt;이런 메시지가 메시지를 수신할 객체의 책임을 결정한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지가 객체의 퍼블릭 인터페이스를 구성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;책임 주도 설계(Responsibility-Driven Design, RDD)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;책임을 찾고 책임을 수행할 적절한 객체를 찾아 책임을 할당하는 방식으로 협력을 설계하는 방법&lt;/li&gt;
&lt;li&gt;책임 주도 설계 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템이 사용자에게 제공해야 하는 기능인 시스템 책임을 파악한다.&lt;/li&gt;
&lt;li&gt;시스템 책임을 더 작은 책임으로 분할한다.&lt;/li&gt;
&lt;li&gt;분할 된 책임을 수행할 수 있는 적절한 객체 또는 역할을 찾아 책임을 할당한다.&lt;/li&gt;
&lt;li&gt;객체가 책임을 수행하는 도중 다른 객체의 도움이 필요한 경우 이를 책임질 적절한 객체 또는 역할을 찾는다.&lt;/li&gt;
&lt;li&gt;해당 객체 또는 역할에게 책임을 할당함으로써 두 객체가 협력하게 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;책임을 할당할 때 고려해야 하는 두 가지 요소
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지가 객체를 결정한다&lt;/li&gt;
&lt;li&gt;행동이 상태를 결정한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메시지가 객체를 결정한다&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지를 먼저 식별하고 메시지를 처리할 객체를 나중에 선택했다는 것이 중요(메시지가 객체를 선택하게 했다)&lt;/li&gt;
&lt;li&gt;그 이유는?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체가 최소한의 인터페이스(minimal interface)를 가질 수 있게 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;꼭 필요한 크기의 퍼블릭 인터페이스를 가질 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체는 충분히 추상적인 인터페이스(abstract interface)를 가질 수 있게 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체의 인터페이스는 무엇(what)을 하는지는 표현해야 하지만 어떻게(how) 수행하는지를 노출해서는 안 된다.&lt;/li&gt;
&lt;li&gt;즉 (어떻게-구현이 아닌) 무엇을 수행할지에 초점&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;행동이 상태를 결정한다&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체를 객체답게 만드는 것은 객체의 상태가 아니라 객체가 다른 객체에게 제공하는 행동이다.&lt;/li&gt;
&lt;li&gt;객체지향 패러다임 초심자의 경우 객체의 행동이 아니라 상태에 초점을 맞추는 것이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저 객체에 필요한 상태가 무엇인지 결정하게 되면 객체의 내부 구현이 객체의 퍼블릭 인터페이스에 노출되도록 만들게 되고 결국 캡슐화를 저해한다.&lt;/li&gt;
&lt;li&gt;객체의 내부 구현을 변경하면 퍼블릭 인터페이스도 함께 변경되고, 결국 객체에 의존하는 클라이언트로 변경의 영향이 전파된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그렇다면 어떻게?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캡슐화를 위반하지 않도록 &lt;b&gt;구현에 대한 결정을 뒤로 미루면서&lt;/b&gt; 객체의 행위를 고려하기 위해서는 항상 협력이라는 문맥 안에서 객체를 생각해야 한다.&lt;/li&gt;
&lt;li&gt;협력 관계 속에서 다른 객체에게 무엇을 제공해야 하고 다른 객체로부터 무엇을 얻어야 하는지를 고민해야만 훌륭한 책임을 수확할 수 잇다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;협력이 객체의 행동을 결정하고 행동이 상태를 결정한다. 그리고 그 행동이 바로 객체의 책임이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;03 역할&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;역할과 협력&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할: 객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(실제로 협력을 모델링할 때는 역할에게 책임을 할당한다고 생각하는게 좋다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;예매하라&lt;/span&gt;는 메시지를 처리하기 위해 수행되는 단계&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영화를 예매할 수 있는 적절한 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;역할이 무엇인가&lt;/span&gt;를 찾고&lt;/li&gt;
&lt;li&gt;그 다음 역할을 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;수행할 객체&lt;/span&gt;를 선택해야.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유연하고 재사용 가능한 협력&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할이 중요한 이유는
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할을 통해 유연하고 재사용 가능한 협력을 얻을 수 있기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;역할이라는 개념을 고려하지 않고 객체에게 책임을 할당한다면&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GCyum/btrBUGD0ASP/yKLKiGKSMj1CLNsvjxWK20/img.png&quot; width=&quot;740&quot; height=&quot;571&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/GCyum/btrBUGD0ASP/yKLKiGKSMj1CLNsvjxWK20/img.png&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;1035&quot; /&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과적으로 대부분의 코드가 중복되고 말 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;할인 요금 계산이라는 동일한 책임을 수행 -&amp;gt; 객체라는 존재를 지우고 할인 요금을 계산하는 대표 즉 역할&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blUvCr/btrBT5D1Rdx/DWQLT5r7jL5CVGXVkLJAm0/img.png&quot; width=&quot;734&quot; height=&quot;669&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/blUvCr/btrBT5D1Rdx/DWQLT5r7jL5CVGXVkLJAm0/img.png&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;1049&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;책임과 역할을 중심으로 협력을 바라보는 것이 바로 변경과 확장이 용이한 유연한 설계로 나아가는 첫걸음이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;역할의 구현
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추상클래스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공유하는 상태와 행동의기본 구현이 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인터페이스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;책임의 목록만 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체 대 역할&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할은 객체가 참여할 수 있는 일종의 슬롯이다.&lt;/li&gt;
&lt;li&gt;여러 종류의 객체들이 참여할 수 있다면 역할이지만, 협력에 적합한 책임을 수행하는 대상이 한 종류라면 간단하게 객체로 간주한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;역할과 추상화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;역할은 공통의 책임을 바탕으로 객체의 종류를 숨기기 때문에 이런 관점에서 역할을 객체의 추상화로 볼 수 있다.&lt;/li&gt;
&lt;li&gt;추상화의 장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세부 사항에 억눌리지 않고도 상위 수준의 정책을 쉽고 간단하게 표현할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;협력이라는 관점에서는 세부적인 사항을 무시하고 추상화에 집중하는 것이 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;설계를 유연하게 만들 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;협력 안에서 역할이라는 추상화를 이용하면 기존 코드를 수정하지 않고도 새로운 행동을 추가할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배우와 배역&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연극에 참여하는 그 순간만큼은 배우들은 사라지고 배역만 남는다. 그러나 무대의 막이 내리면 배역을 사라지고 다시 본래의 연극 배우로 돌아오게 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일한 배역을 여러 명의 배우들이 연기 할 수 있다.&lt;/li&gt;
&lt;li&gt;연극 안에서 배역을 연기하는 배우 -&amp;gt; 협력안에서 역할을 수행하는 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;역할은 특정한 객체의 종류를 캡슐화하기 때문에 동일한 역할을 수행하고 계약을 준수하는 대체 가능한 객체들은 다형적이다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍/객체지향</category>
      <category>객체지향</category>
      <category>오브젝트</category>
      <author>wook-lab</author>
      <guid isPermaLink="true">https://wook-lab.tistory.com/22</guid>
      <comments>https://wook-lab.tistory.com/22#entry22comment</comments>
      <pubDate>Thu, 12 May 2022 00:18:18 +0900</pubDate>
    </item>
    <item>
      <title>Mac OS에서 Java(temurin JDK) 설치하기</title>
      <link>https://wook-lab.tistory.com/21</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;temurin JDK가 뭐야?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle이 Java 소유권을 가지면서 Java 유료화에 대한 논란이 있었다.&lt;br /&gt;(Java 유료화에 대한 논란은 구글링 해보면 알수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논란과 함께 Open JDK 사용률이 증가하게 되었고 그러면서 등장된 여러 배포버전(구현체)이 존재하게 되었다.&lt;br /&gt;(배급사별 배포버전으로 Azul Zulu, AdoptOpenJDK, Amazon Corretto 등이 흔히들 쓰이는 것 같다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 위 배포버전에서 AdoptOpenJDK를 선택했다.&lt;br /&gt;(AdoptOpenJDK는 여러 기업의 후원을 받아 개발자들이 운영하는 커뮤니티로 추후에도 유료화가 되지 않고 사용할 수 있을 거 같았다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당 AdoptOpenJDK는 Eclipse에서 Adoptium 프로젝트를 진행함에 따라 Deprecated되었고 새로운 이름으로 pre-built 된 open jdk를 제공한다. 그 이름이 바로 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;temurin&lt;/span&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mac에서 JDK 설치하는 방법?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mac OS에서 Java를 설치하는 방법은 다양하다.&lt;br /&gt;&lt;a href=&quot;https://brew.sh/index_ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Homebrew&lt;/a&gt;를 통해 패키지를 직접 설치를 하거나, &lt;a href=&quot;https://sdkman.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SDKMAN&lt;/a&gt;&amp;nbsp;을 통해 설치를 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 둘의 설치 방법에는 차이가 있다.&lt;br /&gt;Homebrew를 통해 설치하는 경우 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;/Library/Java/JavaVirtualMachines&lt;/span&gt; 경로에 설치가 되고 여러 자바 버전을 사용하기 까다롭다.&lt;br /&gt;SDKMAN을 통해 설치하는 경우 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;/home/{User}/.sdkman&lt;/span&gt;&amp;nbsp;경로에 설치가 되고 여러 자바버전을 사용하기에 용이하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 여러 프로젝트를 개발하다 보면 Java 버전이 다를때가 있는데 이때문에 SDKMAN을 선택했었다.&lt;br /&gt;(.sdkman 폴더를 삭제를 통해 간단하게 제거할 수 있다는 장점도!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 JVM기반으로 수행되는 프로그램을 사용하려 하자 자바를 인식하지 못했다.&lt;br /&gt;(아마도 JAVA_HOME을 바라보지 않는 것 같다)&lt;br /&gt;필자가 사용한 프로그램은 APM 툴인 &lt;a href=&quot;https://github.com/scouter-project/scouter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Scouter&lt;/a&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 결국 SDKMAN을 사용을 멈추게 되었고, Mac에서의 기본 경로에 설치가 되면서 여러자바 버전을 사용하기에 용이한 방법이 없을까 찾아보았다. 그래서 알게 된 것이 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;jenv&lt;/span&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;jenv로 temurin 설치하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Homebrew 설치하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mac에서 jenv를 사용하기 위해서는 기본적으로 brew가 필요하다. (이미 설치된 사람은 패스!!)&lt;/p&gt;
&lt;pre id=&quot;code_1650468117338&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/usr/bin/ruby -e &amp;ldquo;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&amp;rdquo;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. jenv 설치하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 통해 jenv를 설치하자.&lt;/p&gt;
&lt;pre id=&quot;code_1650468179336&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install jenv&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 `~/.bashrc` 또는 `~/.bash_profile` 최근 Mac을 사용한다면 `~/.zsh`에 아래 내용을 추가하자.&lt;/p&gt;
&lt;pre id=&quot;code_1650468283404&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# jenv config
export PATH=&quot;$HOME/.jenv/bin:$PATH&quot;
eval &quot;$(jenv init -)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. cask 설치하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cask는 brew를 통해 프로그램을 설치할 수 있도록 도와주는 일종의 유틸이라고 한다. 아래 명령어를 통해 cask를 설치하자.&lt;/p&gt;
&lt;pre id=&quot;code_1650468372004&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install cask&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. temurin jdk 설치하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 통해 설치해 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1650468641456&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 버전별 설치를 위해 먼저 실행
brew tap homebrew/cask-versions

# 특정 버전 설치
brew install --cask temurin8
brew install &amp;mdash;cask temurin11
brew install --cask temurin17

# 최신 버전만 설치하고 싶다면
brew install --cask temurin&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. jenv에 등록하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 통해 설치된 JDK를 등록해보자&lt;/p&gt;
&lt;pre id=&quot;code_1650468700873&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# JDK 경로를 jenv에 등록
jenv add /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home
jenv add /Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home
jenv add /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home

# jenv로 컨트롤 가능한 버전 확인
jenv versions

# 위 명령어를 통해 확인한 자바 버전중에 하나를 default로 설정할 수 있다
jenv global {version}


# maven을 사용한다면 아래 명령어를 통해 maven 의 java 버전이 jenv 설정대로 변경되도록 할 수 있다.
jenv enable-plugin maven
jenv enable-plugin export&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. jenv 삭제하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 통해 삭제할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1650469052722&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew uninstall jenv
rm -rf ~/.jenv&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 그 외 참고할 만한 명령어&lt;/h3&gt;
&lt;pre id=&quot;code_1650469128600&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 최신 temurin 버전 확인하기
brew info &amp;mdash;cask temurin

# Mac에 설치된 자바 버전 모두 보기
/usr/libexec/java_home -V&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프로그래밍/Java</category>
      <category>adoptjdk</category>
      <category>openjdk</category>
      <category>temurin</category>
      <author>wook-lab</author>
      <guid isPermaLink="true">https://wook-lab.tistory.com/21</guid>
      <comments>https://wook-lab.tistory.com/21#entry21comment</comments>
      <pubDate>Thu, 21 Apr 2022 00:39:28 +0900</pubDate>
    </item>
    <item>
      <title>[pyspark] 스파크 완벽 가이드 - 파케이(parquet)파일</title>
      <link>https://wook-lab.tistory.com/20</link>
      <description>&lt;h2 id=&quot;id-파케이(parquet)파일-데이터소스&quot; data-ke-size=&quot;size26&quot;&gt;데이터 소스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스파크는 여섯 가지 핵심 데이터 소스와 커뮤니티에서 만든 수백 가지 외부 데이터 소스가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;id-파케이(parquet)파일-핵심데이터소스&quot; data-ke-size=&quot;size23&quot;&gt;핵심 데이터 소스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CSV&lt;/li&gt;
&lt;li&gt;JSON&lt;/li&gt;
&lt;li&gt;파케이&lt;/li&gt;
&lt;li&gt;ORC&lt;/li&gt;
&lt;li&gt;JDBC/ODBC 연결&lt;/li&gt;
&lt;li&gt;일반 텍스트 파일&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;id-파케이(parquet)파일-외부데이터소스&quot; data-ke-size=&quot;size23&quot;&gt;외부 데이터 소스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카산드라&lt;/li&gt;
&lt;li&gt;HBase&lt;/li&gt;
&lt;li&gt;몽고디비&lt;/li&gt;
&lt;li&gt;AWS Redshift (Amazon Redshift는 클라우드에서 완벽하게 관리되는 페타바이트급 데이터 웨어하우스 서비스입니다.)&lt;/li&gt;
&lt;li&gt;XML&lt;/li&gt;
&lt;li&gt;기타 수많은 데이터소스&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이중에서도 가장 먼저 알아두어야 할것은 스파크의 기본 파일 포맷인 파케이(parquet)이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;id-파케이(parquet)파일-파케이파일&quot; data-ke-size=&quot;size26&quot;&gt;파케이 파일&lt;/h2&gt;
&lt;h3 id=&quot;id-파케이(parquet)파일-파케이란&quot; data-ke-size=&quot;size23&quot;&gt;파케이란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 스토리지 최적화 기술을 제공하는 오픈소스로 만들어진&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;컬럼 기반&lt;/u&gt;의 데이터 저장 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;id-파케이(parquet)파일-파케이특징및장점&quot; data-ke-size=&quot;size23&quot;&gt;파케이 특징 및 장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분석 워크로드에 최적화&lt;/li&gt;
&lt;li&gt;저장소 공간 절약&lt;/li&gt;
&lt;li&gt;전체파일 대신 개별 컬럼을 읽을 수 있음&lt;/li&gt;
&lt;li&gt;컬럼 기반의 압축기능 제공&lt;/li&gt;
&lt;li&gt;스파크의 기본 파일 포맷&lt;/li&gt;
&lt;li&gt;읽기 연산 시 JSON이나 CSV보다 훨씬 효율적으로 동작&lt;br /&gt;(따라서 장기 저장용 데이터는 파케이 포맷으로 저장하는 것이 좋음)&lt;/li&gt;
&lt;li&gt;복합 데이터 타입을 지원&lt;/li&gt;
&lt;li&gt;컬럼이 배열, 맵, 구조체 타입이라 해도 문제없이 읽고 쓰기 가능&lt;br /&gt;(단, CSV에서는 배열을 사용할 수 없음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;id-파케이(parquet)파일-파케이파일읽기&quot; data-ke-size=&quot;size23&quot;&gt;파케이 파일 읽기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파케이를 읽기 포맷으로 지정하는 방법:&amp;nbsp;spark.read.format(&quot;parquet&quot;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파케이는 자체 스키마를 사용해 데이터를 저장하기 때문에 별도 옵션이 거의 없음(정확한 스키마가 필요한 경우에만 스키마를 설정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파케이 파일은 스키마가 파일 자체에 내장되어 있으므로 추정이 필요 없음(이 방법이 더 효과적)&lt;/p&gt;
&lt;pre id=&quot;code_1647410150547&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spark.read.format(&quot;parquet&quot;)\
    .load(&quot;/dat/flight-data/parquet/2010-summary.parquet&quot;).show(5)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파케이&amp;nbsp;옵션&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 92px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 20px;&quot;&gt;읽기/쓰기&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 20px;&quot;&gt;키&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 20px;&quot;&gt;사용 가능한 값&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 20px;&quot;&gt;기본 값&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 20px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 54px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 54px;&quot;&gt;ALL&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 54px;&quot;&gt;compression 또는 codec&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 54px;&quot;&gt;none,&amp;nbsp;uncompressed,&amp;nbsp;bzip2.&amp;nbsp;deflate,&amp;nbsp;gzip,&amp;nbsp;lz4,&amp;nbsp;snappy&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 54px;&quot;&gt;none&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 54px;&quot;&gt;스파크가&amp;nbsp;파일을&amp;nbsp;읽고&amp;nbsp;쓸&amp;nbsp;때&amp;nbsp;사용하는&amp;nbsp;압축&amp;nbsp;코덱을&amp;nbsp;정의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 18px;&quot;&gt;READ&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 18px;&quot;&gt;mergeSchema&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 18px;&quot;&gt;true, false&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 18px;&quot;&gt;spark.sql.parquet.mergeSchema&amp;nbsp;속성의&amp;nbsp;설정&amp;nbsp;값&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 18px;&quot;&gt;동일한&amp;nbsp;테이블이나&amp;nbsp;폴더에&amp;nbsp;신규&amp;nbsp;추가된&amp;nbsp;파케이&amp;nbsp;파일에&amp;nbsp;컬럼을&amp;nbsp;점진적으로&amp;nbsp;추가가능한데,&lt;br /&gt;이러한&amp;nbsp;기능을&amp;nbsp;활성화하거나&amp;nbsp;비활성화&amp;nbsp;하기&amp;nbsp;위해&amp;nbsp;이&amp;nbsp;옵션을&amp;nbsp;사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;2개 옵션만 존재할 정도로 잘 만들어져 있지만 호환되지 않는 파케이 파일을 다룰 때 문제가 발생할 수 있음.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파케이 파일 쓰기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 경로만 명시하면 되며, 분할 규칙은 다른 포맷과 동일하게 적용&lt;/p&gt;
&lt;pre id=&quot;code_1647410298713&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cvsFile.write.format(&quot;parquet&quot;).mode(&quot;overwrite&quot;)\
    save(&quot;/tmp/my-parquet-file.parquet&quot;)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프로그래밍/Spark</category>
      <category>Spark</category>
      <author>wook-lab</author>
      <guid isPermaLink="true">https://wook-lab.tistory.com/20</guid>
      <comments>https://wook-lab.tistory.com/20#entry20comment</comments>
      <pubDate>Wed, 16 Mar 2022 14:58:28 +0900</pubDate>
    </item>
    <item>
      <title>[pyspark] 스파크 완벽 가이드 - 다양한 데이터 타입 다루기</title>
      <link>https://wook-lab.tistory.com/19</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;표현식을 만드는 방법과 다양한 데이터 타입을 다루는 방법&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DataFrame을&amp;nbsp;먼저&amp;nbsp;하나&amp;nbsp;생성하자&lt;/h2&gt;
&lt;pre id=&quot;code_1647408518619&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df = spark.read.format(&quot;csv&quot;)\
    .option(&quot;header&quot;, &quot;true&quot;)\
    .option(&quot;inferSchema&quot;, &quot;true&quot;)\
    .load(&quot;/data/retail-data/by-day/2010-12-01.csv&quot;)
df.printSchema()
df.show(5, False)
df.createOrReplaceTempView(&quot;dfTable&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스파크&amp;nbsp;데이터&amp;nbsp;아비을&amp;nbsp;변환하기&amp;nbsp;위한&amp;nbsp;lit&amp;nbsp;함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스파크&amp;nbsp;데이터&amp;nbsp;타입으로&amp;nbsp;변환하는&amp;nbsp;방법은&amp;nbsp;반드시&amp;nbsp;알아두어야&amp;nbsp;한다.&amp;nbsp;바로&amp;nbsp;lit&amp;nbsp;함수&lt;/p&gt;
&lt;pre id=&quot;code_1647408561618&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import lit
 
df.select(lit(5), lit(&quot;five&quot;), lit(5.0))
 
 
# 결과
# DataFrame[5: int, five: string, 5.0: double]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;불리언(Boolean)&amp;nbsp;데이터&amp;nbsp;타입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불리언&amp;nbsp;구문은&amp;nbsp;and,&amp;nbsp;or,&amp;nbsp;true,&amp;nbsp;false로&amp;nbsp;구성되며&amp;nbsp;구문을&amp;nbsp;사용해&amp;nbsp;true&amp;nbsp;또는&amp;nbsp;false로&amp;nbsp;평가되는&amp;nbsp;논리&amp;nbsp;문법을&amp;nbsp;만듦.&lt;br /&gt;또한 데이터 로우를 필터링 할 때도 사용&lt;/p&gt;
&lt;pre id=&quot;code_1647408606369&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import col
 
df.where(col(&quot;InvoiceNo&quot;) != 536365)\
  .select(&quot;InvoiceNo&quot;, &quot;Description&quot;)\
  .show(5, False)
   
# 가장 명확한 방법, 문자열 표현식에 조건절 명시
df.where(&quot;InvoiceNo = 536365&quot;)\
  .show(5, False)
   
df.where(&quot;InvoiceNo &amp;lt;&amp;gt; 536365&quot;)\ # &amp;lt;&amp;gt;: 일치하지 않음
  .show(5, False)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불리언 표현식을 사용하는 경우 항상 모든 표현식을 and 메서드로 묶어 차례대로 필터를 적용해야 함.&lt;br /&gt;스파크는 내부적으로 모든 필터를 하나의 문장으로 변환하고 그 다음 동시에 모든 필터를 처리&lt;br /&gt;반면 or 구문을 사용할 때는 반드시 동일한 구문에 조건을 정의해야 함&lt;/p&gt;
&lt;pre id=&quot;code_1647408638003&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import instr
 
priceFilter = col(&quot;UnitPrice&quot;) &amp;gt; 600
descriptFilter = instr(df.Description, &quot;POSTAGE&quot;) &amp;gt;= 1
 
df.where(df.StockCode.isin(&quot;DOT&quot;))\
  .where(priceFilter | descriptFilter)\
  .show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불리언&amp;nbsp;컬럼을&amp;nbsp;사용해&amp;nbsp;DataFrame을&amp;nbsp;필터링&lt;/p&gt;
&lt;pre id=&quot;code_1647408669287&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import instr
 
# Boolean컬럼을 사용해 필터링
DOTCodeFilter = col(&quot;StockCode&quot;) == &quot;DOT&quot;
priceFilter = col(&quot;UnitPrice&quot;) &amp;gt; 600
descriptFilter = instr(col(&quot;Description&quot;), &quot;POSTAGE&quot;) &amp;gt;= 1
df.withColumn(&quot;isExpensive&quot;, DOTCodeFilter &amp;amp; (priceFilter | descriptFilter))\         # withColumn을 이용해 불리언 컬럼 생성
  .where(&quot;isExpensive&quot;)\          # 불리언 컬럼으로 필터링
  .select(&quot;unitPrice&quot;, &quot;isExpensive&quot;).show(5)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;eqNullSafe():&amp;nbsp;null&amp;nbsp;값에&amp;nbsp;안전한(null-safe)&amp;nbsp;동치(equivalence)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;수치형&amp;nbsp;데이터&amp;nbsp;타입&amp;nbsp;다루기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;카운트(Count)&amp;nbsp;-&amp;nbsp;지수승(pow),&amp;nbsp;반올림(round),&amp;nbsp;내림(bround)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카운트는 빅데이터 처리에서 필터링 다음으로 많이 수행하는 작업&lt;br /&gt;대부분은 수치형 데이터 타입을 사용해 연산 방식을 정의&lt;/p&gt;
&lt;pre id=&quot;code_1647408770811&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import expr, pow
 
# pow 지수승 e.g. pow(값, 지수)
fabricatedQuantity = pow(col(&quot;Quantity&quot;) * col(&quot;UnitPrice&quot;), 2) + 5
df.select(expr(&quot;CustomerId&quot;), fabricatedQuantity.alias(&quot;realQuantity&quot;)).show(2)
 
# 곱셈
df.selectExpr(
    &quot;CustomerId&quot;,
    &quot;(POWER((Quantity * UnitPrice), 2.0) + 5) as realQuantity&quot;
).show(2)
 
# 소수점 첫째자리에서 반올림
from pyspark.sql.functions import round
 
df.select(expr(&quot;CustomerId&quot;), round(fabricatedQuantity, 4).alias(&quot;realQuantity&quot;)).show(2)
 
# 반올림 round, 내림 bround
from pyspark.sql.functions import bround
df.select(round(lit(&quot;2.5&quot;)), bround(lit(&quot;2.5&quot;))).show(2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;피어슨&amp;nbsp;상관계수:&amp;nbsp;두&amp;nbsp;컬럼&amp;nbsp;사이의&amp;nbsp;상관관계를&amp;nbsp;계산&lt;/h3&gt;
&lt;pre id=&quot;code_1647408797156&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import corr
 
# 피어슨 상관계수
df.stat.corr(&quot;Quantity&quot;, &quot;UnitPrice&quot;)  # 피어슨 상관계수 값
df.select(corr(&quot;Quantity&quot;, &quot;UnitPrice&quot;)).show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;describe메서드:&amp;nbsp;요약&amp;nbsp;통계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련&amp;nbsp;컬럼에&amp;nbsp;대한&amp;nbsp;집계,&amp;nbsp;평균,&amp;nbsp;표준편차,&amp;nbsp;최솟값,&amp;nbsp;최댓값을&amp;nbsp;계산&lt;br /&gt;그러나 통계 스키마는 변경 될 수 있으므로 콘솔 확인용으로만 사용&lt;/p&gt;
&lt;pre id=&quot;code_1647408847056&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 요약 통계 콘솔 확인용
df.describe().show()
 
 
# 결과
+-------+-----------------+------------------+--------------------+------------------+------------------+------------------+--------------+
|summary|        InvoiceNo|         StockCode|         Description|          Quantity|         UnitPrice|        CustomerID|       Country|
+-------+-----------------+------------------+--------------------+------------------+------------------+------------------+--------------+
|  count|             3108|              3108|                3098|              3108|              3108|              1968|          3108|
|   mean| 536516.684944841|27834.304044117645|                null| 8.627413127413128| 4.151946589446603|15661.388719512195|          null|
| stddev|72.89447869788873|17407.897548583845|                null|26.371821677029203|15.638659854603892|1854.4496996893627|          null|
|    min|           536365|             10002| 4 PURPLE FLOCK D...|               -24|               0.0|           12431.0|     Australia|
|    max|          C536548|              POST|ZINC WILLIE WINKI...|               600|            607.49|           18229.0|United Kingdom|
+-------+-----------------+------------------+--------------------+------------------+------------------+------------------+--------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한&amp;nbsp;집계가&amp;nbsp;필요하다면&amp;nbsp;함수를&amp;nbsp;직접&amp;nbsp;임포트하고&amp;nbsp;해당&amp;nbsp;컬럼에&amp;nbsp;적용할&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;from&amp;nbsp;pyspark.sql.functions&amp;nbsp;import&amp;nbsp;count,&amp;nbsp;mean,&amp;nbsp;stddev_pop,&amp;nbsp;min,&amp;nbsp;max&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;StatFunction패키지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한&amp;nbsp;통계&amp;nbsp;함수를&amp;nbsp;제공하며,&amp;nbsp;stat&amp;nbsp;속성을&amp;nbsp;사용해&amp;nbsp;접근하며&amp;nbsp;다양한&amp;nbsp;통계값을&amp;nbsp;계산할&amp;nbsp;때&amp;nbsp;사용하는&amp;nbsp;DataFrame&amp;nbsp;메서드.&lt;br /&gt;approxQuantile: 백분위수 정확하게 계산하거나 근사치를 계산&lt;br /&gt;crosstab: 교차표(cross-tabulation)&lt;br /&gt;freqItems: 자주 사용하는 항목 쌍을 확인&lt;br /&gt;monotonically_increasing_id:&amp;nbsp;모든&amp;nbsp;로우에&amp;nbsp;고유&amp;nbsp;ID값을&amp;nbsp;추가&lt;/p&gt;
&lt;pre id=&quot;code_1647408910407&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;olName = &quot;UnitPrice&quot;
quantileProbs = [0.5]
relError = 0.05
 
# approxQuantile():
df.stat.approxQuantile(&quot;UnitPrice&quot;, quantileProbs, relError) # 2.51
 
# crosstab: 교차표(cross-tabulation) 확인
df.stat.crosstab(&quot;StockCode&quot;, &quot;Quantity&quot;).show()
 
# freqItems: 자주사용하는 항목 쌍 확인
df.stat.freqItems([&quot;StockCode&quot;, &quot;Quantity&quot;]).show();
 
 
# monotonically_increasing_id
from pyspark.sql.functions import monotonically_increasing_id
df.select(monotonically_increasing_id()).show(2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문자열&amp;nbsp;데이터&amp;nbsp;타입&amp;nbsp;다루기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대/소문자&amp;nbsp;변환&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;initcap:&amp;nbsp;공백으로 나눠 모든 단어의 첫글자를 대문자로 변환&lt;/li&gt;
&lt;li&gt;lower: 문자열 전체를 소문자로 변환&lt;/li&gt;
&lt;li&gt;upper: 문자열 전체를 대문자로 변환&lt;/li&gt;
&lt;li&gt;ltrim: 왼쪽의 공백 문자열 자름&lt;/li&gt;
&lt;li&gt;rtrim:오른쪽의 공백 문자열 자름&lt;/li&gt;
&lt;li&gt;trim: 양쪽의 공백 문자열 자름&lt;/li&gt;
&lt;li&gt;lpad: 왼쪽에 입력 된 수 만큼 문자열 값으로 채움&lt;/li&gt;
&lt;li&gt;rpad: 오른쪽에 입력 된 수 만큼 문자열 값으로 채움&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단 pad의 경우 기준 문자열보다 작은 수를 입력하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;문자열의 우측부터 제거&lt;/u&gt;됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647408948624&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# initcap() 공백으로 나눠 모든 단어의 첫글자 대문자
from pyspark.sql.functions import initcap
df.select(initcap(col(&quot;Description&quot;))).show(2)
 
 
# lower: 문자열 전체를 소문자
# upper: 문자열 전체를 대문자
from pyspark.sql.functions import lower, upper
df.select(col(&quot;Description&quot;),
          lower(col(&quot;Description&quot;)),
          upper(lower(col(&quot;Description&quot;)))).show(2, False)
 
 
# lpad, ltrim, rpad, rtrim, trim
from pyspark.sql.functions import lit, ltrim, rtrim, rpad, lpad, trim
df.select(
    ltrim(lit(&quot;   HELLO   &quot;)).alias(&quot;ltrim&quot;),
    rtrim(lit(&quot;   HELLO   &quot;)).alias(&quot;rtrim&quot;),
    trim(lit(&quot;   HELLO   &quot;)).alias(&quot;trim&quot;),
    lpad(lit(&quot;HELLO&quot;), 3, &quot; &quot;).alias(&quot;lpad&quot;),  # 문자열 길이보다 작은 숫자를 넣으면 문자열의 오른쪽부터 제거됨.
    lpad(lit(&quot;HELLO&quot;), 10, &quot; &quot;).alias(&quot;lpad&quot;),
    rpad(lit(&quot;HELLO&quot;), 3, &quot; &quot;).alias(&quot;rpad&quot;),
    rpad(lit(&quot;HELLO&quot;), 10, &quot; &quot;).alias(&quot;rpad&quot;)
).show(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정규표현식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스파크는 자바 정규 표현식이 가진 강력한 기능을 활용.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;regexp_extract: 해당 패턴을 만족하는 문자열을 추출&lt;/li&gt;
&lt;li&gt;regexp_replace: 입력된 문자열을 치환 문자열로 변환&lt;/li&gt;
&lt;li&gt;translate: 기준 문자열과 치환 문자열을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;각각&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;변환&lt;/li&gt;
&lt;li&gt;instr: 문자열의 시작위치를 알려줌
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순히 문자열이 존재하는지 확인할 때 씀 (위치는 1부터시작, 때문에 0보다 크면 존재하는 것으로 간주할 수 있다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;locate: 문자열의 위치를 정수로 반환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;instr과 동일하게 위치는 1부터 시작&lt;/li&gt;
&lt;li&gt;instr과 다른점은 세번째 인수로 시작 위치를 지정할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647408978324&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# regexp_replace
from pyspark.sql.functions import regexp_replace
 
regex_string = &quot;BLACK|WHITE|RED|GREEN|BLUE&quot;
df.select(regexp_replace(col(&quot;Description&quot;), regex_string, &quot;COLOR&quot;).alias(&quot;color_clean&quot;),
          col(&quot;Description&quot;))\
  .where(instr(col(&quot;color_clean&quot;), &quot;COLOR&quot;) == 1)\
  .show(2, False)
 
 
# translate 문자열 각각을 치환
from pyspark.sql.functions import translate
 
df.select(translate(col(&quot;Description&quot;), &quot;LET&quot;, &quot;137&quot;), col(&quot;Description&quot;)).show(2, False) # L-&amp;gt;1, E-&amp;gt;3, T-&amp;gt;7
 
 
# instr 단순히 값 존재 여부 확인
from pyspark.sql.functions import instr
 
containsBlack = instr(col(&quot;Description&quot;), &quot;BLACK&quot;) &amp;gt;= 1
containsWhite = instr(col(&quot;Description&quot;), &quot;WHITE&quot;) &amp;gt;= 1
df.withColumn(&quot;hasSimpleColor&quot;, containsBlack | containsWhite)\
  .where(&quot;hasSimpleColor&quot;)\
  .select(&quot;Description&quot;).show(3, False)
 
   
# locate 문자열의 위치를 정수로 반환하는 함수(1부터 시작)
from pyspark.sql.functions import expr, locate
 
simpleColors = [&quot;black&quot;, &quot;white&quot;, &quot;red&quot;, &quot;green&quot;, &quot;blue&quot;]
 
def color_locator(column, color_string):
    return locate(color_string.upper(), column)\
        .cast(&quot;boolean&quot;)\
        .alias(&quot;is_&quot; + color_string)
 
selectedColumns = [color_locator(df.Description, c) for c in simpleColors]
selectedColumns.append(expr(&quot;*&quot;)) # Column 타입이어야 함
 
df.select(*selectedColumns).where(expr(&quot;is_white OR is_red&quot;))\
  .select(&quot;Description&quot;).show(3, False)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;날짜와&amp;nbsp;타임스탬프&amp;nbsp;데이터&amp;nbsp;타입&amp;nbsp;다루기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스파크는 두 가지 종류의 시간 관련 정보만 집중적으로 관리합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;date: 달력 형태의 날짜&lt;/li&gt;
&lt;li&gt;timestamp: 날짜와 시간 정보를 모두 가짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스파크는 특정 날짜 포맷을 명시하지 않아도 자체적으로 식별해 데이터를 읽을 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;날짜나 시간을 문자열로 저장하고, 런타임에 날짜 타입으로 변경하는 경우가 많아 문자열을 다루는 작업에 많이 사용됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간대 설정이 필요하다면 스파크 SQL 설정의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;spark.conf.se ssionLocalTimeZone&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성을 로컬 시간대로 지정해 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TimestampType 클래스는 초 단위 정밀도까지만 지원 (밀리세컨드나 마이크로세컨드 단위를 다룬다면 Long 데이터 타입으로 데이터를 변환해 처리하는 우회 정책 사용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스파크는 자바의 날짜와 타임스탬프를 사용해서 표준 체계를 따름.&lt;/p&gt;
&lt;pre id=&quot;code_1647409017242&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#오늘 날짜와 현재 타임스탬프값 구하기
from pyspark.sql.functions import current_date, current_timestamp
 
dateDF = spark.range(10)\
    .withColumn(&quot;today&quot;, current_date())\
    .withColumn(&quot;now&quot;, current_timestamp())
dateDF.createOrReplaceTempView(&quot;dateTable&quot;)
dateDF.printSchema()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오늘을&amp;nbsp;기준으로&amp;nbsp;5일&amp;nbsp;전후의&amp;nbsp;날짜&amp;nbsp;구하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;date_sub: 첫번째 인자로 입력된 날짜로부터 두번째 인자로 입력된 일수 만큼 뺌&lt;/li&gt;
&lt;li&gt;date_add: 첫번째 인자로 입력된 날짜로부터 두번째 인자로 입력된 일수 만큼 더함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647409038775&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import date_add, date_sub, col
 
dateDF.select(date_sub(col(&quot;today&quot;), 5), date_add(col(&quot;today&quot;), 5)).show(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;두&amp;nbsp;날짜의&amp;nbsp;차이를&amp;nbsp;구하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;datediff: 입력된 두 인자 사이의 날짜를 구함&lt;/li&gt;
&lt;li&gt;months_between: 입력된 두 인자 사이의 월수를 계산&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647409062443&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import datediff, months_between, to_date, lit
 
dateDF.withColumn(&quot;week_ago&quot;, date_sub(col(&quot;today&quot;), 7))\
      .select(datediff(col(&quot;week_ago&quot;), col(&quot;today&quot;))).show(1)
 
# 출력결과
+-------------------------+
|datediff(week_ago, today)|
+-------------------------+
|                       -7|
+-------------------------+
only showing top 1 row
 
 
dateDF.select(
    to_date(lit(&quot;2016-01-01&quot;)).alias(&quot;start&quot;),
    to_date(lit(&quot;2017-05-22&quot;)).alias(&quot;end&quot;)
).select(months_between(col(&quot;start&quot;), col(&quot;end&quot;))).show(1)
 
# 출력 결과
+--------------------------+
|months_between(start, end)|
+--------------------------+
|              -16.67741935|
+--------------------------+
only showing top 1 row&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;id-다양한데이터타입다루기-문자열을날짜로변환하기&quot; data-ke-size=&quot;size23&quot;&gt;문자열을 날짜로 변환하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;to_date: 문자열을 날짜로 변환, 필요에 따라 날짜 포맷을 지정&lt;/li&gt;
&lt;li&gt;to_timestamp: 문자열을 날짜로 변환, 반드시 날짜 포맷을 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647409089009&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# to_date
from pyspark.sql.functions import to_date
 
dateFormat = &quot;yyy-dd-MM&quot;
cleanDateDF = spark.range(1).select(
    to_date(lit(&quot;2017-12-11&quot;), dateFormat).alias(&quot;date&quot;),
    to_date(lit(&quot;2017-20-12&quot;), dateFormat).alias(&quot;date2&quot;))
 
cleanDateDF.createOrReplaceTempView(&quot;dateTable2&quot;)
cleanDateDF.show()
 
 
 
 
# to_timestamp
from pyspark.sql.functions import to_timestamp
 
cleanDateDF.select(to_timestamp(col(&quot;date&quot;), dateFormat)).show()
 
 
 
 
# 올바른 포맷과 타입의 날짜나 타임스탬프를 사용한다면 매우 쉽게 비교할 수 있음.
cleanDateDF.filter(col(&quot;date2&quot;) &amp;gt; lit(&quot;2017-12-12&quot;)).show()   # lit 함수를 통해 스파크 값으로 변경 후 비교
cleanDateDF.filter(col(&quot;date2&quot;) &amp;gt; &quot;'2017-12-12'&quot;).show()       # 문자열 자체로 비교&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;null&amp;nbsp;값&amp;nbsp;다루기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataFrame에서 빠져 있거나 비어 있는 데이터를 표현할 때는 항상 null 값을 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스파크에서는 빈 문자열이나 대체 값 대신 null 값을 사용해야 최적화를 수행할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;coalesce&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;스파크의 coalesce 함수는 인수로 지정한 여러 컬럼 중 null이 아닌 첫번 째 값을 반환&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647409128712&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import coalesce
 
df.select(coalesce(col(&quot;Description&quot;), col(&quot;CustomerId&quot;))).show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ifnull,&amp;nbsp;nullif,&amp;nbsp;nvl,&amp;nbsp;nvl2(coalesce와&amp;nbsp;유사한&amp;nbsp;결과를&amp;nbsp;얻을&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;SQL&amp;nbsp;함수)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ifnull: 첫 번째 값이 null 이면 두 번째 값을 반환&lt;/li&gt;
&lt;li&gt;nullif: 두 값이 같으면 null을 반환, 두 값이 다르면 첫번 째 값을 반환&lt;/li&gt;
&lt;li&gt;nvl: 첫 번째 값이 null 이면 두 번째 값을 반환, 첫번째 값이 null 이 아니면 두번째 값을 반환&lt;/li&gt;
&lt;li&gt;nvl2: 첫 번째 값이 null이 아니면 두번 째 값을 반환, 첫 번째 값이 null이면 세 번째 인수로 지정된 값을 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;drop&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 null 값을 가진 모든 로우를 제거&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;drop: null 값을 가진 모든 로우를 제거&lt;/li&gt;
&lt;li&gt;drop(&quot;any&quot;): 로우의 컬럼값 중 하나라도 null을 가지면 해당 로우를 제거&lt;/li&gt;
&lt;li&gt;drop(&quot;all&quot;): 모든 컬럼의 값이 null이거나 NaN인 경우 해당 로우를 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647409171314&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.na.drop()
df.na.drop(&quot;any&quot;)
df.na.drop(&quot;all&quot;)
df.na.drop(&quot;all&quot;, subset=[&quot;StockCode&quot;, &quot;InvoiceNo&quot;]) # drop 메서드에 배열 형태의 컬럼을 인수로 전달해 적용할 수 있음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;fill&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나 이상의 컬럼을 특정 값으로 채울 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채워 넣을 값과 컬럼 집합으로 구성된 맵을 인수로 사용&lt;/p&gt;
&lt;pre id=&quot;code_1647409195033&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# String 데이터 타입의 컬럼에 존재하는 null 값을 입력 문자열로 채움
df.na.fill(&quot;All Null values become this string&quot;)
 
# subset에 지정한 컬럼에 존재하는 null 값을 입력 문자열로 채움
df.na.fill(&quot;all&quot;, subset=[&quot;StockCode&quot;, &quot;InvoiceNo&quot;])
 
# key-value 형태로 key에는 컬럼, value에는 대체한 값을 넣어 해당 컬럼에 존재하는 null 값을 입력한 값으로 채움
fill_cols_vals = {&quot;StockCode&quot;: 5, &quot;Description&quot;: &quot;No Value&quot;}
df.na.fill(fill_cols_vals).show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;replace&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null 값을 유연하게 대처할 방법 중의 하나로 조건에 따라 다른 값으로 대체 할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;replace 메서드를 사용하려면 변경하고자 하는 값과 원래 값의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;데이터 타입이 같아야 함&lt;/b&gt;.&lt;/p&gt;
&lt;pre id=&quot;code_1647409218184&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# replace({변경할 문자}, {대체할 문자}, {대상 컬럼})
df.na.replace([&quot;&quot;], [&quot;UNKNOWN&quot;], &quot;Description&quot;).show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;복합&amp;nbsp;데이터&amp;nbsp;타입&amp;nbsp;다루기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 데이터 타입&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구조체(Struct)&lt;/li&gt;
&lt;li&gt;배열(Array)&lt;/li&gt;
&lt;li&gt;맵(Map)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구조체&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;구조체는 DataFrame 내부의 DataFrame으로 보면 됨. (쿼리문에서 다수의 컬럼을 괄호로 묶어 구조체를 만들 수 있음)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647409288204&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import struct
 
# Description 컬럼과 InvoiceNo 컬럼이 묶여서 하나의 구조체가 됨
complexDF = df.select(struct(&quot;Description&quot;, &quot;InvoiceNo&quot;).alias(&quot;complex&quot;))
complexDF.createOrReplaceTempView(&quot;complexDF&quot;)
complexDF.show(5, False)
 
 
# 출력결과
+---------------------------------------------+
|complex                                      |
+---------------------------------------------+
|[WHITE HANGING HEART T-LIGHT HOLDER, 536365] |
|[WHITE METAL LANTERN, 536365]                |
|[CREAM CUPID HEARTS COAT HANGER, 536365]     |
|[KNITTED UNION FLAG HOT WATER BOTTLE, 536365]|
|[RED WOOLLY HOTTIE WHITE HEART., 536365]     |
+---------------------------------------------+
only showing top 5 rows&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;복합 데이터 타입을 가진&amp;nbsp; DataFrame의 경우 기존 DataFrame과 다르게 점(.)을 사용하거나 getField 메서드를 사용할 수 있음.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647409304314&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;complexDF.select(&quot;complex.Description&quot;).show(1)
complexDF.select(col(&quot;complex&quot;).getField(&quot;Description&quot;)).show(1)
 
 
# 출력결과
+--------------------+
|         Description|
+--------------------+
|WHITE HANGING HEA...|
+--------------------+
only showing top 1 row
 
+--------------------+
| complex.Description|
+--------------------+
|WHITE HANGING HEA...|
+--------------------+
only showing top 1 row&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;split함수에 구분자(delimiter)를 인수로 전달해 배열로 변환&lt;/p&gt;
&lt;div data-hasbody=&quot;true&quot; data-macro-name=&quot;code&quot;&gt;
&lt;pre id=&quot;code_1647409331737&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import split
# split을 통해 공백 문자열을 구분자로 배열 생성
df.select(col(&quot;Description&quot;), split(col(&quot;Description&quot;), &quot; &quot;)).show(2, False)
 
 
# 출력 결과
+----------------------------------+----------------------------------------+
|Description                       |split(Description,  )                   |
+----------------------------------+----------------------------------------+
|WHITE HANGING HEART T-LIGHT HOLDER|[WHITE, HANGING, HEART, T-LIGHT, HOLDER]|
|WHITE METAL LANTERN               |[WHITE, METAL, LANTERN]                 |
+----------------------------------+----------------------------------------+
only showing top 2 rows
 
 
 
# 배열 참조하기
df.select(split(col(&quot;Description&quot;), &quot; &quot;).alias(&quot;array_col&quot;))\
  .selectExpr(&quot;array_col[0]&quot;).show(2, False)
 
 
# 출력 결과
+------------+
|array_col[0]|
+------------+
|WHITE       |
|WHITE       |
+------------+
only showing top 2 rows&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열의&amp;nbsp;길이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의&amp;nbsp;크기인&amp;nbsp;size로&amp;nbsp;조회&lt;/p&gt;
&lt;pre id=&quot;code_1647409351414&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import size
 
df.select(size(split(&quot;Description&quot;, &quot; &quot;))).show(2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;array_contains&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;배열에 특정 값이 존재하는지 확인&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647409382455&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import array_contains
 
df.select(array_contains(split(col(&quot;Description&quot;), &quot; &quot;), &quot;WHITE&quot;)).show(2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;explode&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;explode함수는 배열 타입의 컬럼을 입력으로 받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력된 컬럼의 배열 값에 포함된 모든 값을 로우로 변환&lt;/p&gt;
&lt;pre id=&quot;code_1647409406915&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import split, explode
 
df.withColumn(&quot;splitted&quot;, split(col(&quot;Description&quot;), &quot; &quot;))\
  .withColumn(&quot;exploded&quot;, explode(col(&quot;splitted&quot;)))\
  .select(&quot;Description&quot;, &quot;InvoiceNo&quot;, &quot;exploded&quot;, &quot;splitted&quot;).show(5, False)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;맵&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map 함수와 컬럼의 키-값 쌍을 이용하여 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열과&amp;nbsp;동일한&amp;nbsp;방법으로&amp;nbsp;값을&amp;nbsp;선택할&amp;nbsp;수&amp;nbsp;도&amp;nbsp;있음&lt;/p&gt;
&lt;pre id=&quot;code_1647409435317&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import create_map
 
complexMapDF = df.select(create_map(col(&quot;Description&quot;), col(&quot;InvoiceNo&quot;)).alias(&quot;complex_map&quot;))
complexMapDF.show(5, False)
 
# 키로 데이터 조회가 가능(일치하는 데이터가 없다면 null 값을 반환)
complexMapDF.selectExpr(&quot;complex_map['WHITE METAL LANTERN']&quot;).show()
 
 
# explode를 이용하여 map 타입을 분해하여 컬럼으로 변환
complexMapDF.selectExpr(&quot;explode(complex_map)&quot;).show(5)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JSON&amp;nbsp;다루기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 데이터를 다루기 위한 고유 기능 지원&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열 형태의 JSON을 직접 조작&lt;/li&gt;
&lt;li&gt;JSON 파싱&lt;/li&gt;
&lt;li&gt;JSON 객체로 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;get_json_object,&amp;nbsp;json_tuple&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;get_json_object: JSON 객체(딕셔너리나 배열)를 인라인 쿼리로 조회할 수 있음&lt;/li&gt;
&lt;li&gt;json_tuple: 중첩이 없는 단일 수준의 JSON 객체 조회가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647409483429&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# JSON 컬럼 생성
jsonDF = spark.range(1).selectExpr(&quot;&quot;&quot;
    '{&quot;myJSONKey&quot; : {&quot;myJSONValue&quot;: [1, 2, 3]}}' as jsonString
&quot;&quot;&quot;)
jsonDF.show()
 
jsonDF.show(1, False)
 
 
#출력결과
+------------------------------------------+
|jsonString                                |
+------------------------------------------+
|{&quot;myJSONKey&quot; : {&quot;myJSONValue&quot;: [1, 2, 3]}}|
+------------------------------------------+
 
 
 
 
 
 
from pyspark.sql.functions import get_json_object, json_tuple
 
 
jsonDF.select(
    get_json_object(col(&quot;jsonString&quot;), &quot;$.myJSONKey.myJSONValue[1]&quot;).alias(&quot;column&quot;),   # get_json_object 사용하면 인라인 쿼리로 조회 가능
    json_tuple(col(&quot;jsonString&quot;), &quot;myJSONKey&quot;)  # json_tuple
).show(2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;to_json&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StructType을 JSON 문자열로 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 데이터소스와 동일한 형태의 딕셔너리(맵)를 파라미터로 사용&lt;/p&gt;
&lt;pre id=&quot;code_1647409510634&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import to_json
 
df.selectExpr(&quot;(InvoiceNo, Description) as myStruct&quot;)\
  .select(to_json(col(&quot;myStruct&quot;))).show(2, False)
 
 
#출력결과
+-------------------------------------------------------------------------+
|structstojson(myStruct)                                                  |
+-------------------------------------------------------------------------+
|{&quot;InvoiceNo&quot;:&quot;536365&quot;,&quot;Description&quot;:&quot;WHITE HANGING HEART T-LIGHT HOLDER&quot;}|
|{&quot;InvoiceNo&quot;:&quot;536365&quot;,&quot;Description&quot;:&quot;WHITE METAL LANTERN&quot;}               |
+-------------------------------------------------------------------------+
only showing top 2 rows&lt;/code&gt;&lt;/pre&gt;
&lt;div data-hasbody=&quot;true&quot; data-macro-name=&quot;code&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;from_json&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON문자열을 객체로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;반드시 스키마를 지정&lt;/u&gt;해야 함.&lt;/p&gt;
&lt;pre id=&quot;code_1647409539333&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql.functions import from_json
from pyspark.sql.types import *
 
parseSchema = StructType((
    StructField(&quot;InvoiceNo&quot;, StringType(), True),
    StructField(&quot;Description&quot;, StringType(), True)
))
df.selectExpr(&quot;(InvoiceNo, Description) as myStruct&quot;)\
  .select(to_json(col(&quot;myStruct&quot;)).alias(&quot;newJSON&quot;))\
  .select(from_json(col(&quot;newJSON&quot;), parseSchema), col(&quot;newJSON&quot;)).show(2, False) # parseSchema 스키마 지정 필수!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용자&amp;nbsp;정의&amp;nbsp;함수(UDF)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 정의 함수(UDF; User Defined Function)는 하나 이상의 컬럼을 입력으로 받고, 반환할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 프로그래밍 언어로 개발 가능(스칼라, 파이썬, 자바), 그러나 언어별로 성능에 영향이 다름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레코드별로 데이터를 처리하는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 특정 SparkSession이나 Context에서 사용할 수 있도록 임시 함수 형태로 등록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수&amp;nbsp;정의하기&lt;/h3&gt;
&lt;pre id=&quot;code_1647409582751&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def power3(double_value):
    return double_value ** 3
 
power3(2.0) # 테스트하기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스파크에&amp;nbsp;등록하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스파크는 드라이버에서 함수를 직렬화하고 네트워크를 통해 모든 익스큐터 프로세스로 전달(언어와 상관없이 발생)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 언어에 따라 근본적으로 동작하는 방식은 다른데&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스칼라, 자바:&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JVM 환경에서만 사용&lt;/li&gt;
&lt;li&gt;따라서 스파크 내장 함수가 제공하는 코드 생성 기능의 장점을 활용할 수 없어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;성능저하&lt;/u&gt;발생&lt;/li&gt;
&lt;li&gt;많은 객체를 생성하거나 사용해도 성능 문제 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;파이썬:&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스파크는 워커 노드에 파이썬 프로세스를 실행하고 파이썬이 이해할 수 있는 포맷으로 모든 데이터를 직렬화함(앞서 JVM 사용 언어에도 존재했던 부분)&lt;/li&gt;
&lt;li&gt;파이썬 프로세스에 있는 데이터의 로우마다 함수를 실행하고 마지막으로 JVM과 스파크에 처리 결과를 반환&lt;/li&gt;
&lt;li&gt;스파크에서 워커 메모리를 관리할 수 없으므로 JVM과 파이썬이 동일한 머신에서 메모리 경합을 하면 자원에 제약이 생겨 우커가 비정상 종료가 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;자바나 스칼라로 사용자 정의 함수로 개발하는 것을 권장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파이썬에서도 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 정의 함수 등록이 완료 되면 DataFrame에서 사용 가능(문자열 표현식에서는 아직 사용 불가)&lt;/p&gt;
&lt;pre id=&quot;code_1647409611501&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 등록
from pyspark.sql.functions import udf
 
power3udf = udf(power3)
 
 
# 사용
udfExampleDF = spark.range(5).toDF(&quot;num&quot;)
udfExampleDF.select(col(&quot;num&quot;), power3udf(col(&quot;num&quot;))).show(3)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 정의 함수를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;스파크 SQL 함수로 등록&lt;/b&gt;하면 모든 프로그래밍 언어와 SQL에서 사용자 정의 함수를 사용 할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스칼라로 등록한 함수를 파이썬에서도 사용 할 수 있게됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 올바르게 동작할 수 있도록 반환되는 데이터 타입을 지정하는 것이 좋음.&lt;/p&gt;
&lt;div data-hasbody=&quot;true&quot; data-macro-name=&quot;code&quot;&gt;
&lt;pre id=&quot;code_1647409636301&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 스칼라로 사용자정의 함수(UDF)를 SQL 함수로 등록
import org.apache.spark.sql.functions.udf
 
def power3(number:Double):Double = number * number * number
spark.udf.register(&quot;power3&quot;, power3(_:Double):Double)
 
 
 
 
# 파이썬으로 사용자정의 함수(UDF)를 SQL함수로 등록
def power3Python(double_value):
    return double_value ** 3
spark.udf.register(&quot;power3Python&quot;, power3Python, &quot;Double&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/Spark</category>
      <category>Spark</category>
      <author>wook-lab</author>
      <guid isPermaLink="true">https://wook-lab.tistory.com/19</guid>
      <comments>https://wook-lab.tistory.com/19#entry19comment</comments>
      <pubDate>Wed, 16 Mar 2022 14:47:35 +0900</pubDate>
    </item>
    <item>
      <title>[pyspark] 스파크 완벽 가이드 - 구조적 API 기본 연산</title>
      <link>https://wook-lab.tistory.com/18</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 용어 개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;레코드(record) = row&lt;br /&gt;컬럼(column) = 스프레드시트의 column과 유사&lt;br /&gt;스키마(schema) = 컬럼과 데이터 타입 정의&lt;br /&gt;파티셔닝(partitioning) = 물리적으로 배치되는 형태&lt;br /&gt;파티셔닝 스키마 = 파티션을 배치하는 방법 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스키마&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DataFrame의 컬럼명과 데이터 타입을 정의&lt;br /&gt;데이터를 읽기 전에 스키마를 정의해야 하는지 여부는 상황에 따라 달라짐&lt;br /&gt;스키마는 여러개의 StructField 타입 필드로 구성된 StructType 객체&lt;/p&gt;
&lt;pre id=&quot;code_1647407589516&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.printSchema()
# 실행결과
# root
# |-- DEST_COUNTRY_NAME: string (nullable = true)
# |-- ORIGIN_COUNTRY_NAME: string (nullable = true)
# |-- count: long (nullable = true)


df.schema
# 실행결과
# StructType(List(StructField(DEST_COUNTRY_NAME,StringType,true),StructField(ORIGIN_COUNTRY_NAME,StringType,true),StructField(count,LongType,true)))


# 스키마 정의
myManualSchema = StructType([
  StructField(&quot;DEST_COUNTRY_NAME&quot;, StringType(), True),
  StructField(&quot;ORIGIN_COUNTRY_NAME&quot;, StringType(), True),
  StructField(&quot;count&quot;, LongType(), False)
])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컬럼과 표현식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사용자는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;표현식&lt;/b&gt;으로 DataFrame의 컬럼을 선택, 조작, 제거할 수 있음&lt;br /&gt;스파크의 컬럼은 표현식을 사용해 레코드 단위로 계산한 값을 단순하게 나타내는 논리적인 구조&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬럼&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;컬럼을 생성하는 간단한 방법: col(), column()&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DataFrame의 컬럼은 col 메서드로 참조&lt;/p&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;e.g. df.col(&quot;count&quot;)&amp;nbsp;&amp;nbsp;&amp;rarr; 데이터 프레임의 count 컬럼을 명시적으로 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DataFrame 컬럼에 접근하기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;df.columns&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;표현식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataFrame 레코드의 여러 값에 대한 트랜스포메이션 집합을 의미&lt;br /&gt;여러 컬럼명을 입력으로 받아 식별하고, '단일 값'을 만들기 위해 다양한 표현식을 각 레코드에 적용하는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표현식을 사용하는 방법: expr()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataFrame의 컬럼 참조&lt;/p&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;expr(&quot;someCol&quot;)&amp;nbsp;&amp;rarr; col(&quot;someCol&quot;)과 동일하게 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스파크가 연산 순서를 지정하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;논리적 트리&lt;/b&gt;로 컴파일 하기 때문에 아래는 같은 트랜스포메이션 과정을 거침&lt;/p&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;expr(&quot;someCol - 5&quot;)&lt;/li&gt;
&lt;li&gt;col(&quot;someCol&quot;) - 5&lt;/li&gt;
&lt;li&gt;expr(&quot;someCol&quot;) -5&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬럼은 단지 표현식일 뿐이며, 논리적 실행 계획으로 컴파일 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;레코드와 로우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DataFrame에서 각 로우는 하나의 레코드&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647407727294&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 첫번째 로우 출력
df.first()


# 로우 생성하기
from pyspark.sql import Row
myRow = Row(&quot;Hello', None, 1, False)


# 로우 데이터 접근하기
myRow[0]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DataFrame의 트랜스포메이션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DataFrame은 트랜스포메이션으로 아래와 같은 작업을 수행할 수 있음&lt;/p&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로우나 컬럼 추가&lt;/li&gt;
&lt;li&gt;로우나 컬럼 제거&lt;/li&gt;
&lt;li&gt;로우를 컬럼으로 변환하건, 그 반대&lt;/li&gt;
&lt;li&gt;컬럼값을 기준으로 로우 순서 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DataFrame생성하기&lt;/h3&gt;
&lt;pre id=&quot;code_1647407764033&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 파일 읽어서 DataFrame 생성하기
df = spark.read.format(&quot;json&quot;).load(&quot;{file_path}&quot;) # 파일을 읽어 DataFrame으로 변환하여 반환



# row와 createDataFrame으로 DataFrame 생성하기
from pyspark.sql import Row
from pyspark.sql.types import StructField, StructType, StringType, LongType


mySchema = StructType([
  StructField(&quot;some&quot;, StringType(), True),
  StructField(&quot;col&quot;, StringType(), True),
  StructField(&quot;names&quot;, LongType(), False)
])


myRow = Row(&quot;Hello&quot;, None, 1)
myDf = spark.createDataFrame([myRow], mySchema)
myDf.show()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;select와 selectExpr&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;select&lt;/b&gt;는 문자열 컬럼을 인수로받는 메서드&lt;/p&gt;
&lt;pre id=&quot;code_1647407798375&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 여러 컬럼 선택하여 출력하기
df.select(&quot;DEST_COUNTRY_NAME&quot;, &quot;ORIGIN_COUNTRY_NAME&quot;).show(2)
# 컬럼 참조 방식 혼합하여 사용하기
df.select(
	expr(&quot;DEST_COUNTRY_NAME&quot;),
	col(&quot;DEST_COUNTRY_NAME&quot;),
	column(&quot;DEST_COUNTRY_NAME&quot;)
).show(2)


# 컬럼 참조 방식과 문자열 참조 방식을 섞어 쓸순 없다 !!
df.select(
	col(&quot;DEST_COUNTRY_NAME&quot;),
	&quot;DEST_COUNTRY_NAME&quot;
)


# 컬럼명 변경하기
df.select(expr(&quot;DEST_COUNTRY_NAME AS destination&quot;)).show(2) # 컬럼명: destination
df.select(expr(&quot;DEST_COUNTRY_NAME AS destination&quot;).alias(&quot;DEST_COUNTRY_NAME&quot;).show(2) # 컬럼명: DEST_COUNTRY_NAME&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;selectExpr&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드는 새로운 DataFrame을 생성하는 복잡한 표현식을 간단하게 만드는 도구&lt;/p&gt;
&lt;pre id=&quot;code_1647407821160&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 새로운 컬럼(withinCountry) 추가하기
df.selectExpr(
	&quot;*&quot;,	# 모든 원본 컬럼 포함
	&quot;(DEST_COUNTRY_NAME = ORIGIN_COUNTRY_NAME) as withinCountry&quot;
).show(2)

# DataFrame 컬럼에 대한 누적집계
df.selectExpr(
	&quot;avg(count)&quot;,
	&quot;count(distinct(DEST_COUTNRY_NAME))&quot;
).show(2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스파크 데이터 타입으로 변환하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;리터럴(Literal)은 프로그래밍 언어의 리터럴값을 스파크가 이해할 수 있는 값으로 변환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1647407848593&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 리터럴 메서드 lit()
from pyspark.sql.functions import lit
df.selectExpr(
	expr(&quot;*&quot;),
	lit(1).alias(&quot;One&quot;) 	# One이라는 컬럼에 모든 값이 1로 채워짐
).show(2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬럼 추가하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;withColumn 메서드를 사용하여 DataFrame에 신규 컬럼을 추가하기&lt;/p&gt;
&lt;pre id=&quot;code_1647407865801&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 신규 컬럼 추가 메서드 withColumn()
df.withColumn(&quot;numberOne&quot;, lit(1)).show(2) 	# DataFrame의 기존 컬럼과 함께 추가된 &quot;numberOne&quot; 컬럼이 보이고, numberOne 컬럼에는 1로 채워짐
# withColumn({컬럼명}, {값 생성 표현식})
df.withColumn(
	&quot;withinCountry&quot;, expr(&quot;ORIGIN_COUNTRY_NAME == DEST_COUNTRY_NAME&quot;) # 해당 표현식에 의해 두 컬럼의 값이 일치하는지 여부에 따라 true/false 리턴
).show(2)
# 결과
# +-----------------+-------------------+-----+-------------+
# |DEST_COUNTRY_NAME|ORIGIN_COUNTRY_NAME|count|withinCountry|
# +-----------------+-------------------+-----+-------------+
# |    United States|            Romania|   15|        false|
# |    United States|            Croatia|    1|        false|
# +-----------------+-------------------+-----+-------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬럼명 변경하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;withColumnRenamed 메서드로 컬럼명 변경&lt;/p&gt;
&lt;pre id=&quot;code_1647407917719&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# withColumnRenamed()
df.withColumnRenamed(&quot;DEST_COUNTRY_NAME&quot;, &quot;dest&quot;).columns&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예약 문자와 키워드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예약 문자를 컬럼명에 사용하려면 백틱(`) 문자를 이용해 이스케이핑(escaping)해야 한다.&lt;br /&gt;(문자열 참조의 경우 문자열로 지정이 되어 있어 이스케이핑 처리 불필요)&lt;/p&gt;
&lt;pre id=&quot;code_1647407945586&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 이스케이핑 불필요
dfWithLongColName = df.withColumn(
	&quot;This Long Column-Name&quot;,
	expr(&quot;ORIGIN_COUNTRY_NAME&quot;)
) # 기존 DataFrame에 &quot;This Long Column-Name&quot; 컬럼을 추가하고 해당 컬럼의 값으로 기존 DataFrame의 &quot;ORIGIN_COUNTRY_NAME&quot; 필드 값을 대입한다.


# show(2) 결과
# +-----------------+-------------------+-----+---------------------+
# |DEST_COUNTRY_NAME|ORIGIN_COUNTRY_NAME|count|This Long Column-Name|
# +-----------------+-------------------+-----+---------------------+
#|    United States|            Romania|   15|              Romania|
# |    United States|            Croatia|    1|              Croatia|
# +-----------------+-------------------+-----+---------------------+
# only showing top 2 rows




# 이스케이핑 필요
dfWithLongColName.selectExpr(
	&quot;`This Long Column-Name`&quot;,                      # &quot;This Long Column-Name&quot; 필드 출력
	&quot;`This Long Column-Name` as `new col`&quot;   # &quot;This Long Column-Name&quot; 필드를 &quot;new col&quot; 컬럼 변경하여 출력
).show(2)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대소문자 구분&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;기본적으로 스파크는 대소문자를 가리지 않음. 따라서 설정을 통해 대소문자를 구분하게 할 수 있음&lt;/p&gt;
&lt;pre id=&quot;code_1647407970354&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;set spark.sql.caseSensitive true&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬럼 제거하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;select로 컬럼을 제거할 수 있지만, 컬럼을 제거하는 메서드 drop도 사용&lt;/p&gt;
&lt;pre id=&quot;code_1647407995022&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# drop 메서드를 통해 여러 컬럼을 한꺼번에 제거할 수 있음
df.drop(&quot;ORIGIN_COUNTRY_NAME&quot;, &quot;count&quot;).columns&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬럼의 데이터 타입 변경하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;특정 데이터 타입을 다른 데이터 타입으로 형변환이 필요할 경우 cast 메서드 사용&lt;/p&gt;
&lt;pre id=&quot;code_1647408012788&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Integer to String
df.withColumn(&quot;count2&quot;, col(&quot;count&quot;).cast(&quot;string&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로우 필터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;row를 필터링하려면 참과 거짓을 판별하는 표현식을 만들어야 함.&lt;br /&gt;DataFrame의 가장 일반적인 방법은 문자열 표현식이나 컬럼을 다루는 기능을 이용하는 것.&lt;br /&gt;DataFrame의 필터링 메서드: where, filter&lt;/p&gt;
&lt;pre id=&quot;code_1647408049558&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#filter, where 둘다 표현식과 문자열을 인수로 받을 수 있음
# filter
df.filter(col(&quot;count&quot;) &amp;lt; 2).show(2)


# where
df.where(&quot;count &amp;lt; 2&quot;).show(2)




#여러 필터를 적용할 경우
df.where(col(&quot;count&quot;) &amp;lt; 2)\ 
  .where(col(&quot;ORIGIN_COUNTRY_NAME&quot;) =!= &quot;Croatia&quot;)\
  .show()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;고유한 로우 얻기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DataFrame에서 고유한 값이나 중복되지 않는 값을 얻는 연산 distinct 메서드&lt;/p&gt;
&lt;pre id=&quot;code_1647408070387&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# distinct()
df.select(&quot;ORIGIN_COUNTRY_NAME&quot;, &quot;DEST_COUNTRY_NAME&quot;).distinct().count()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무작위 샘플 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;sample 메서드를 통해 DataFrame에서 표본 데이터 추출 비율을 지정하거나 복원 추출이나 비복원 추출의 사용 여부를 지정할 수 있음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복원 추출(sample with replacement)&lt;/li&gt;
&lt;li&gt;비복원 추출(sample without replacement)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647408096088&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;seed = 5
withReplacement = False
fraction = 0.5
df.sample(withReplacement, fraction, seed).count()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;임의 분할하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;원본 DataFrame을 임의 크기로 '분할'할 때 유용하게 사용(머신러닝 알고리즘 학습셋, 검증셋, 텍스트셋을 만들 때 주로 사용)&lt;/p&gt;
&lt;pre id=&quot;code_1647408119289&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dataFrames = df.randomSplit([0.25, 0.75], seed)
# dataFrames[1].show()
dataFrames[0].count() &amp;gt; dataFrames[1].count()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로우 합치기와 추가하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DataFrame은 불변성을 가지기 때문에 변경하는 작업은 불가능&lt;br /&gt;따라서 원본 DataFrame을 새로운 DataFrame과 통합(union)해야 함.&lt;/p&gt;
&lt;pre id=&quot;code_1647408148056&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql import Row
from pyspark.sql.functions import col

schema = df.schema
newRows = [
    Row(&quot;New Country&quot;, &quot;Other Country&quot;, 5L),
    Row(&quot;New Country 2&quot;, &quot;Other Country 3&quot;, 1L)
]
parallelizedRows = spark.sparkContext.parallelize(newRows)
newDf = spark.createDataFrame(parallelizedRows, schema)    # createDataFrame: 새로운 DataFrame 생성

df.union(newDf)\        # union 메서드로 통합
  .where(&quot;count = 1&quot;)\
  .where(col(&quot;ORIGIN_COUNTRY_NAME&quot;) != &quot;United States&quot;)\
  .show()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로우 정렬하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;sort와 orderBy 메서드를 사용해 정렬할 수 있음&lt;br /&gt;두 메서드 모두 컬럼 표현식과 문자열을 사용할 수 있으며 다수의 컬럼을 지정할 수 있음&lt;/p&gt;
&lt;pre id=&quot;code_1647408171972&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# sort(), orderyBy
df.sort(&quot;count&quot;).show(5)
df.orderBy(&quot;count&quot;, &quot;DEST_COUNTRY_NAME&quot;).show(5)
df.orderBy(col(&quot;count&quot;), col(&quot;DEST_COUNTRY_NAME&quot;)).show(5)


# asc, desc
from pyspark.sql.functions import expr
from pyspark.sql.functions import desc, asc

df.orderBy(expr(&quot;count desc&quot;)).show(2)
df.orderBy(col(&quot;count&quot;).desc(), col(&quot;DEST_COUNTRY_NAME&quot;).asc()).show(2)

# 파티션별 정렬 sortWithinPartitions 함수 사용
spark.read.format(&quot;json&quot;).load(&quot;/data/flight-data/json/*-summary.json&quot;).sortWithinPartitions(&quot;count&quot;).show(5)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로우 수 제한하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DataFrame에서 추출할 row 수를 제한해야 할 때 limit 메서드 사용&lt;/p&gt;
&lt;pre id=&quot;code_1647408190605&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.limit(5).show()
df.orderBy(expr(&quot;count desc&quot;)).limit(6).show()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;repartition과 coalesce(콜레스)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;최적화 기법 중 하나로 자주 필터링하는 컬럼을 기준으로 데이터를 분할하는 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;repartition메서드
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무조건 전체 데이터 셔플&lt;/li&gt;
&lt;li&gt;향후 사용할 파티션 수가 현재보다 많을 경우 사용&lt;/li&gt;
&lt;li&gt;컬럼을 기준으로 파티션을 만들 경우 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;coalesce메서드
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파티션을 병합하려는 경우에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647408225702&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# repartition
df.rdd.getNumPartitions()
df.repartition(5)
df.repartition(col(&quot;DEST_COUNTRY_NAME&quot;))
df.repartition(5, col(&quot;DEST_COUNTRY_NAME&quot;))

# coalesce
df.repartition(5, col(&quot;DEST_COUNTRY_NAME&quot;)).coalesce(2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;드라이버로 로우 데이터 수집하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스파크는 드라이버에서 클러스터 상태 정보를 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드라이버로 데이터를 수집하는 연산&lt;/p&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;collect: 전체 DataFrame의 모든 데이터를 수집&lt;/li&gt;
&lt;li&gt;take: 상위 N개의 로우를 반환 (정수만 인수로 받음)&lt;/li&gt;
&lt;li&gt;show: 여러 로우를 보기좋게 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647408263297&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;collectDF = df.limit(10)
collectDF.take(5) # take는 정수형 값만 인수로 사용
collectDF.show()
collectDF.show(5, False)
collectDF.collect()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;드라이버로 모든 데이터 컬렉션을 수집하는 작업은 매우 큰 비용(CPU, 메모리, 네트워크 등)이 발생&lt;/b&gt;&lt;/p&gt;</description>
      <category>프로그래밍/Spark</category>
      <category>Spark</category>
      <author>wook-lab</author>
      <guid isPermaLink="true">https://wook-lab.tistory.com/18</guid>
      <comments>https://wook-lab.tistory.com/18#entry18comment</comments>
      <pubDate>Wed, 16 Mar 2022 14:24:26 +0900</pubDate>
    </item>
    <item>
      <title>[pyspark] 스파크 완벽 가이드 - 구조적  API 기본개념과 용어</title>
      <link>https://wook-lab.tistory.com/17</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;구조적 API란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;비정형 로그 파일부터 반정형 CSV 파일, 매우 정형적인 파케이(Parquet) 파일까지 다양한 데이터를 처리할 수 있다.&lt;br /&gt;배치(batch)와 스트리밍(streaming) 처리에서 구조적 API를 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구조적 API의 세 가지 분산 컬렉션 API&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Dataset&lt;/li&gt;
&lt;li&gt;DataFrame&lt;/li&gt;
&lt;li&gt;SQL 테이블과 뷰&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DataFrame과 Dataset&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;공통점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DataFrame과 Dataset은 잘 정의된 로우와 컬럼을 가지는 분산 테이블 형태의 컬렉션&lt;/li&gt;
&lt;li&gt;결과를 생성하기 위해 어떤 데이터에 어떤 연산을 적용해야 하는지 정의하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;지연 연산의 실행 계획&lt;/b&gt;이며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;불변성&lt;/b&gt;을 가짐&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;차이점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DataFrame = 비타입형
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 타입이 존재하지 않는 것은 아님&lt;/li&gt;
&lt;li&gt;스키마에 명시된 데이터 타입의 일치 여부를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;런타임&lt;/b&gt;에 확인&lt;/li&gt;
&lt;li&gt;Row타입으로 구성된 Dataset
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Row타입은 연산에 최적화된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;인메모리 포맷&lt;/u&gt;의 내부적인 표현방식&lt;/li&gt;
&lt;li&gt;Row타입은 JVM 데이터 타입 대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;자체 데이터 포맷&lt;/u&gt;을 사용 -&amp;gt; 매울 효울적인 연산가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ff0000;&quot;&gt;DataFrame을 사용하면 스파크의 최적화 된 내부 포맷 사용이 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Dataset = 타입형&lt;br /&gt;
&lt;ul style=&quot;list-style-type: square;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스키마에 명시된 데이터 타입의 일치 여부를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컴파일 타임&lt;/b&gt;에 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스키마(Schema)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;DataFrame의 컬럼명과 데이터 타입을 정의&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컬럼(Column)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;단순 데이터 타입(정수형, 문자열), 복합 데이터 타입(배열, 맵, null)을 표현&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로우(Row)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;데이터 레코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/Spark</category>
      <category>Spark</category>
      <author>wook-lab</author>
      <guid isPermaLink="true">https://wook-lab.tistory.com/17</guid>
      <comments>https://wook-lab.tistory.com/17#entry17comment</comments>
      <pubDate>Wed, 16 Mar 2022 14:11:06 +0900</pubDate>
    </item>
    <item>
      <title>VO와 DTO 는 다르게 부르는 같은 말?</title>
      <link>https://wook-lab.tistory.com/16</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;VO vs. DTO&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #37352f;&quot;&gt;DTO와 VO는 분명히 다른 개념과 목적을 가지고 사용하는 객체 이지만 그것에 대한 구분 없이 혼동하여 사용하는 경우가 많다. 그의 원인으로는 일반적으로 ⌜Core J2EE Patterns: Best Practices and Design Strategies⌟ 책의 초판에서 데이터 전송용 객체를 VO로 표기를 했었고 2판에서는 해당 객체를 TO로 정정하여 표기를 하였다고 한다. &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #37352f;&quot;&gt;이로인하여 DTO와 VO를 혼동하게 된 것으로 보고 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #37352f;&quot;&gt;개인적으론 실질적으로 개발자들간에 협업이나 커뮤니케이션을 할때 단편적인 개념으로 이야기를 하다보니 각각  다르게 이해를 하고 있어 더욱이 혼동하고 혼용하여 사용하게 된 것 같다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;개념 정리하기&lt;/b&gt;&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DTO(Data Transfer Object)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수하게 값을 담아 전송하는 객체로 Layer간 데이터 전송을 위해 사용하는 클래스다.&lt;br /&gt;getter/setter 메서드만 가지고 있으며, 그 외의 행동(method)은 갖지 않는다. (즉 비즈니스 로직을 포함하지 않는다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(setter메서드를 제공하면 가변객체로 사용할 수 있지만, 생성자를 통해 필드변수를 초기화하여 불변객체로 사용한다면 임의로 값의 변경을 차단하므로 비즈니스 로직 처리 전까지의 값의 원자성을 보장받을 수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAO(Data Access Object)에서 DB로부터 조회한 데이터를 저장할때 사용하거나 ViewLayer에서 View를 위한 클래스로 보통 사용한다. 즉 계층간에 데이터를 넘겨줄 때 사용하게 된다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;// 가변 DTO
@Getter
@Setter
public class User {
    private String name;
    private int age;
}
 
 
// 불변 DTO
@Getter
@RequiredArgsConstructor
public class User {
    private final String name;
    private final int age;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;VO(Value Object)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수하게 값을 표현하는 객체로 특정한 비즈니스의 값을 담는 클래스다.&lt;br /&gt;(getter 외의 행동(method)을 가질 수 있어 비즈니스 로직 포함이 가능하다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 자체를 표현하기 때문에 불변객체로 생성해야 하고, 같은 값을 담고 있다면 같은 객체로 봐야하기 때문에 hashCode()와 equals()를 오버라이드하여 반드시 재정의 해야 한다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Getter
@RequiredArgsConstructor
public class User {
    private final String name;
    private final int age;
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User that = (User) o;
        return age == that.age &amp;amp;&amp;amp; Objects.equals(name, that.name);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;불변객체는 VO다!?&lt;br /&gt;&lt;br /&gt;단편적으로 본다면 틀린말은 아니지만, 사전 지식이나 정확한 개념이 없는 경우에는 'DTO는 불변객체가 아닌가?' 라고 생각할 수 있다. DTO도 setter를 제거하여 불변객체로 사용할 수 있으며, VO의 경우 반드시 불변객체로 사용해야 한다.&lt;br /&gt;&lt;br /&gt;DTO와 VO 둘다 불변객체로 사용이 가능하기 때문에 해당 DTO와 VO에 대한 구분은 객체에 비즈니스로직을 포함하느냐 그리고 같은 객체로 보기위한 hashCode()와 equals()가 재정의 되어 있느냐에 따라 구분할 수 있다.&lt;br /&gt;&lt;br /&gt;불변객체를 특정 객체의 정의로 보지말고 특징 또는 사용 방법 중 하나로 봐야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;비슷한 성격의 객체&lt;/b&gt;&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Entity(Domain Model로 표현하기도 함)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 DB테이블과 매칭되는 클래스로 ID를 가지며 테이블과 링크될 클래스다.&lt;br /&gt;도메인 로직만 가질 수 있다. 여기서 도메인 로직이란 순수하게 특정 영역/분야의 도메인에서 문제 해결을 위해 수행해야할 로직을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경이 많이 발생하는 Presentation(ViewLayer)을 위한 필드나 로직을 갖지 않도록 하고, Persistent 만을 위해 사용하도록 경계를 분리해야 의존성을 줄일 수 있어 테스트코드 작성이 용이하고 유지보수와 리팩터링에도 이점이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Model&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DomainLayer에서 비즈니스 로직(문제해결)을 처리하기 위해 값(상태)을 저장하고 책임에 맞는 메서드(행동)을 가지는 클래스다.&lt;br /&gt;오브젝트에서 말하는 객체(객체는 상태와 행동을 갖는다)에 포함되는 것 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Model객체?&lt;br /&gt;&lt;br /&gt;현재 우리팀에선 데이터를 단순 저장하는 개념으로 보기도 한다. (일반적인 DTO와 구분하기 위한 정도로 생각) 일반적인 트랜잭션 처리가 필요한 DB로 사용하기 보다 검색을 위한 데이터를 가져오는 용도로 사용하다보니 JPA와 Entity를 활용하기에 적합하지 않다. 때문에 일반적인 DTO와 구분하고 DB로부터 조회한 값을 저장하고 있는 클래스로 사용하고 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 90px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;DTO&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;VO&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;Entity&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;Model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;용도&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;Layer간 데이터 전달&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;의미있는 값 표현&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;DB 테이블과 매핑되는 클래스&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;도메인의 문제 해결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;가변/불변&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;setter 존재 시 가변&lt;br /&gt;getter만 있다면 불변&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;불변&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;가변&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;기본적으로 불변객체&lt;br /&gt;필요에 따라 가변&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;로직(행동)&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;로직을 포함하지 않음&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;로직을 포함할수 있음&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;로직을 포함할수 있음&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;로직을 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;그 외 특징&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;객체의 필드 값이 모두 같으면 같은 객체&lt;br /&gt;hashCode()/equals() 재정의 필요&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 18px;&quot;&gt;값(상태)과 메서드(행동)을 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/Java</category>
      <category>DTO</category>
      <category>entity</category>
      <category>model</category>
      <category>OOP</category>
      <category>VO</category>
      <category>객체지향</category>
      <category>자바</category>
      <author>wook-lab</author>
      <guid isPermaLink="true">https://wook-lab.tistory.com/16</guid>
      <comments>https://wook-lab.tistory.com/16#entry16comment</comments>
      <pubDate>Thu, 27 Jan 2022 14:10:41 +0900</pubDate>
    </item>
    <item>
      <title>오브젝트:코드로 이해하는 객체지향 설계 Ch.02</title>
      <link>https://wook-lab.tistory.com/15</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;01 (온라인) 영화 예매 시스템&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;영화&lt;/b&gt;: 영화에 대한 기본 정보를 표현 (제목, 상영시간, 가격 정보)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상영&lt;/b&gt;: 실제로 관객들이 영화를 관람하는 사건 (상영 일자, 시간, 순번 등)&lt;/li&gt;
&lt;li&gt;두 용어의 차이의 중요성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 실제로 예매하는 대상은 영화가 아니라 &lt;b&gt;상영&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;사람들은 영화를 예매한다고 표현하지만 실제로 특정 시간에 상영되는 영화를 관람할 수 있는 권리를 구매하기 위해 돈을 지불하는 것&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;1442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPNWaH/btrrxmB5KKV/kk4Pt9ZNk1Z7T5SuVdiAB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPNWaH/btrrxmB5KKV/kk4Pt9ZNk1Z7T5SuVdiAB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPNWaH/btrrxmB5KKV/kk4Pt9ZNk1Z7T5SuVdiAB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPNWaH%2FbtrrxmB5KKV%2Fkk4Pt9ZNk1Z7T5SuVdiAB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1540&quot; height=&quot;1442&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;1442&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;2057&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AWQvv/btrrr51WcX7/U5gP5K1msgjvwa1gtXDFF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AWQvv/btrrr51WcX7/U5gP5K1msgjvwa1gtXDFF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AWQvv/btrrr51WcX7/U5gP5K1msgjvwa1gtXDFF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAWQvv%2Fbtrrr51WcX7%2FU5gP5K1msgjvwa1gtXDFF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1540&quot; height=&quot;2057&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;2057&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;1414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tluIl/btrrqlLetsC/shvIQDZys0XH5kXY145jHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tluIl/btrrqlLetsC/shvIQDZys0XH5kXY145jHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tluIl/btrrqlLetsC/shvIQDZys0XH5kXY145jHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtluIl%2FbtrrqlLetsC%2FshvIQDZys0XH5kXY145jHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1540&quot; height=&quot;1414&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;1414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특정한 조건을 만족하는 예매자는 요금을 할인받을 수 있음&lt;/li&gt;
&lt;li&gt;할인액을 결정하는 두 가지 규칙
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;할인 조건(discount condition): 가격의 할인 여부를 결정하며 &amp;lsquo;순서 조건&amp;rsquo;과 &amp;lsquo;기간조건&amp;rsquo; 두 종류로 나눌 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순서 조건(sequence condition): 상영 순번을 이용해 할인 여부를 결정하는 규칙&lt;/li&gt;
&lt;li&gt;기간 조건(period condition): 영화 상영 시작 시간을 이용해 할인 여부 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;할인 정책(discount policy): 할인 요금을 결정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;금액 할인 정책(amount discount policy): 일정 금액을 할인해 주는 방식&lt;/li&gt;
&lt;li&gt;비율 할인 정책(percent discount policy): 정가에서 일정 비율의 요금을 할인해 주는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;영화별로 하나의 할인 정책만 할당 (할인 정책을 지정하지 않는 것도 가능)&lt;/li&gt;
&lt;li&gt;할인 조건은 다수의 할인 조건을 함께 지정하고, 순서 조건과 기간 조건을섞는 것도 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사용자가 예매를 완료하면 예매 정보를 생성 (제목, 상영정보, 인원, 정가, 결제금액)01 (온라인) 영화 예매 시스템&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;02 객체지향 프로그래밍을 향해&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;협력, 객체, 클래스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향 프로그램을 작성할 때 가장 먼저 고려하는 것은 무엇일까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;어떤 클래스(class)가 필요한지 고민&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;클래스가 아닌 객체에 초점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;어떤 객체들&lt;/b&gt;이 필요한지 그리고 어떤 객체들이 어떤 &lt;b&gt;상태&lt;/b&gt;와 &lt;b&gt;행동&lt;/b&gt;을 가지는지를 먼저 고민&lt;/li&gt;
&lt;li&gt;객체를 독립적인 존재가 아니라 &lt;b&gt;기능을 구현하기 위해 협력&lt;/b&gt;한는 공동체의 일원으로 바라보는 시각
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공통된 특성과 상태를 가진 객체들의 타입으로 분류하고&lt;/li&gt;
&lt;li&gt;이 타입을 기반으로 클래스를 구현하라&lt;/li&gt;
&lt;li&gt;훌륭한 협력 -&amp;gt; 훌륭한 객체 -&amp;gt; 훌륭한 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도메인의 구조를 따르는 프로그램 구조&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인(domain): 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야를 도메인이라고 부른다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체지향 패러다임은 강력하다. 왜?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항을 분석하는 &lt;b&gt;초기&lt;/b&gt; 단계부터 프로그램을 구현하는 &lt;b&gt;마지막&lt;/b&gt; 단계까지 &lt;b&gt;객체라는 동일한 추상화 기법을 사용&lt;/b&gt;할 수 있기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;요구사항 == 프로그램 == 객체
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;요구사항&lt;/b&gt;과 &lt;b&gt;프로그램&lt;/b&gt;을 &lt;b&gt;객체&lt;/b&gt;라는 동일한 관점으로 바라볼 수 있기 때문에, 도메인을 구성하는 개념들이 프로그램의 객체와 클래스로 매끄럽게 연결될 수 있다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMRCq3/btrrpEqsUAy/5bIIx0Q7ShQYyX9K2UT0g0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMRCq3/btrrpEqsUAy/5bIIx0Q7ShQYyX9K2UT0g0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMRCq3/btrrpEqsUAy/5bIIx0Q7ShQYyX9K2UT0g0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMRCq3%2FbtrrpEqsUAy%2F5bIIx0Q7ShQYyX9K2UT0g0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1340&quot; height=&quot;401&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프로그램의 구조를 이해하고 예상하기 쉽게 만들어야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스 이름: 대응되는 도메인 개념의 이름과 동일하거나 유사하게&lt;/li&gt;
&lt;li&gt;클래스 사이의 관계: 최대한 도메인 개념 사이에 맺어진 관계와 유사하게&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6Nume/btrroMbtYxg/7LledQmzbtBd8Z2XmxspoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6Nume/btrroMbtYxg/7LledQmzbtBd8Z2XmxspoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6Nume/btrroMbtYxg/7LledQmzbtBd8Z2XmxspoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6Nume%2FbtrroMbtYxg%2F7LledQmzbtBd8Z2XmxspoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1340&quot; height=&quot;349&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클래스 구현하기&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;/**
 * 상영
 */
public class Screening {
    private Movie movie;
    private int sequence;
    private LocalDateTime whenScreened;	// 인스턴스 변수의 가시성은 private

    public Screening(Movie movie, int sequence, LocalDateTime whenScreened) {
        this.movie = movie;
        this.sequence = sequence;
        this.whenScreened = whenScreened;
    }

    public LocalDateTime getStartTime() {
        return whenScreened;
    }

    public boolean isSequence(int sequence) {
        return this.sequence == sequence;
    }

    public Money getMovieFee() {	// 메서드의 가시성은 public
        return movie.getFee();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;훌륭한 클래스를 설계하기 위한 핵심
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 부분을 외부에 공개하고 어떤 부분을 감출지 &lt;b&gt;클래스의 경계를 구분&lt;/b&gt; 짓는 것&lt;/li&gt;
&lt;li&gt;구분해야 하는 이유는?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경계의 명확성 -&amp;gt; 객체의 &lt;b&gt;자율성&lt;/b&gt; 보장&lt;/li&gt;
&lt;li&gt;프로그래머에게 &lt;b&gt;구현의 자유&lt;/b&gt;를 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자율적인 객체&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체가 상태(state)와 행동(behavior)을 함께 가진는 복합적인 존재&lt;/li&gt;
&lt;li&gt;객체가 스스로 판단하고 행동하는 자율적인 존재&lt;/li&gt;
&lt;li&gt;객체지향 이전, 데이터와 기능이라는 독립적인 존재로 프로그램 구성&lt;/li&gt;
&lt;li&gt;반면 객체지향은 객체라는 단위 안에 데이터와 기능을 한 덩이로 묶음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캡슐화: 데이터와 기능을 객체 내부로 함께 묶는 것 (단순 DTO의 인스턴스 변수를 private 처리 한것을 뜻하는게 아님)&lt;/li&gt;
&lt;li&gt;Java에서는 접근 수정자(access modifier)를 통해 접근 제어(access control) 메커니즘 제공&lt;/li&gt;
&lt;li&gt;외부의 간섭을 최소화 하기 위해 객체 내부에 대한 접근을 통제 -&amp;gt; 객체를 자율적인 존재로 만듬.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;훌륭한 객체지향 프로그램을 만들기 위한 핵심 원칙: 인터페이스와 구현의 분리(separation of interface and implementation)원칙
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;퍼블릭 인터페이스(public interface): 외부에서 접근 가능한 부분
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바의 키워드인 interface가 아님&lt;/li&gt;
&lt;li&gt;public으로 지정된 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;구현(implementation): 오직 내부에서만 접근 가능한 부분
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;private, protected 로 지정된 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체의 상태는 숨기고 행동만 외부에 공개&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로그래머의 자유&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그래머의 두 역할
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스 작성자(class creator)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 데이터 타입을 프로그램에 추가&lt;/li&gt;
&lt;li&gt;클라이언트 프로그래머에게 필요한 부분만 공개하고 나머지는 숨김&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클라이언트 프로그래머(client programmer)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스 작성자가 추가한 데이터 타입을 사용&lt;/li&gt;
&lt;li&gt;필요한 클래스들을 엮어서 애플리케이션을 빠르고 안정적으로 구축&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구현 은닉(implementation hiding)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;숨겨 놓은 부분에 마음대로 접근할 수 없도록 방지함으로써 내부 구현을 마음대로 변경할 수 있음&lt;/li&gt;
&lt;li&gt;클래스 작성자가 내부 구현을 은닉 할 수 있게 해줌으로써 클라이언트 프로그래머가 실수로 숨겨진 부분을 접근하는 것을 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체체의 외부와 내부를 구분하면
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알아야할 지식의 양이 줄어들고&lt;/li&gt;
&lt;li&gt;클래스 작성자가 자유롭게 구현을 변경할 수 있는 폭이 넓어진다&lt;/li&gt;
&lt;li&gt;따라서 클래스를 개발할 때마다 인터페이스와 구현을 깔끔하게 분리하기 위해 노력해야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;설계가 필요한 이유는 변경을 관리하기 위해서이다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;협력하는 객체들의 공동체&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영화를 예매하는 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642928945520&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Reservation reserve(Customer customer, int audienceCount) {
    return new Reservation(customer, this, calculateFee(audienceCount), audienceCount);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요금을 계산하는 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642928958404&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private Money calculateFee(int audienceCount) {
    return movie.calculateMovieFee(this).times(audienceCount);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;금액과 관련된 다양한 계산을 구현하는 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642928997657&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Money {
    public static final Money ZERO = Money.wons(0);

    private final BigDecimal amount;

    public static Money wons(long amount) {
        return new Money(BigDecimal.valueOf(amount));
    }

    public static Money wons(double amount) {
        return new Money(BigDecimal.valueOf(amount));
    }

    Money(BigDecimal amount) {
        this.amount = amount;
    }

    public Money plus(Money amount) {
        return new Money(this.amount.add(amount.amount));
    }

    public Money minus(Money amount) {
        return new Money(this.amount.subtract(amount.amount));
    }

    public Money times(double percent) {
        return new Money(this.amount.multiply(BigDecimal.valueOf(percent)));
    }

    public boolean isLessThan(Money other) {
        return amount.compareTo(other.amount) &amp;lt; 0;
    }

    public boolean isGreaterThanOrEqual(Money other) {
        return amount.compareTo(other.amount) &amp;gt;= 0;
    }

    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }

        if (!(object instanceof Money)) {
            return false;
        }

        Money other = (Money)object;
        return Objects.equals(amount.doubleValue(), other.amount.doubleValue());
    }

    public int hashCode() {
        return Objects.hashCode(amount);
    }

    public String toString() {
        return amount.toString() + &quot;원&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의미를 좀 더 명시적이고 분명하게 표현할 수 있다면, 그 개념이 비록 하나의 인스턴스 변수만 포함하더라도 개념을 명시적으로 표현하는 것은 전체적인 설계의 명확성과 유연성을 높이는 첫걸음이다.&lt;/li&gt;
&lt;li&gt;예약 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642929017675&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Reservation {
    private Customer customer;
    private Screening Screening;
    private Money fee;
    private int audienceCount;

    public Reservation(Customer customer, Screening Screening, Money fee, int audienceCount) {
        this.customer = customer;
        this.Screening = Screening;
        this.fee = fee;
        this.audienceCount = audienceCount;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영화를 예매하기 위해 Screening, Movie, Reservation 인스턴스들은 서로의 메서드를 호출하며 상호 작용하게 되는데 이를 &lt;b&gt;협력(Collaboration)&lt;/b&gt;이라 부른다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;555&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EY33s/btrroWSTJln/CDqEESS0Ky825jvGOcQlyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EY33s/btrroWSTJln/CDqEESS0Ky825jvGOcQlyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EY33s/btrroWSTJln/CDqEESS0Ky825jvGOcQlyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEY33s%2FbtrroWSTJln%2FCDqEESS0Ky825jvGOcQlyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1630&quot; height=&quot;555&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;555&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1647310482638&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Reservation reserve(Customer customer, int audienceCount) {
    return new Reservation(customer, this, calculateFee(audienceCount), audienceCount);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1647310477088&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Reservation reserve(Customer customer, int audienceCount) {
    return new Reservation(customer, this, calculateFee(audienceCount), audienceCount);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;협력에 관한 짧은 이야기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지와 메서드를 구분 (다형성 개념의 출발)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스에 공개된 행동을 수행하도록 요청할 때 상호작용할 수 있는 유일한 방법은 &lt;b&gt;메시지&lt;/b&gt;를 전송(send a message)하는 것 뿐&lt;/li&gt;
&lt;li&gt;메시지를 수신한 객체는 스스로의 결정에 따라 자율적으로 메시지를 처리할 방법을 결정하는데
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수신된 메시지를 처리하기 위한 자신만의 방법을 &lt;b&gt;메서드&lt;/b&gt;(method)라고 부른다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;03 할인 요금 구하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;할인 요금 계산을 위한 협력 시작하기&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Movie {
    private String title;
    private Duration runningTime;
    private Money fee;
    private DiscountPolicy discountPolicy;

    public Movie(String title, Duration runningTime, Money fee, DiscountPolicy discountPolicy) {
        this.title = title;
        this.runningTime = runningTime;
        this.fee = fee;
        this.discountPolicy = discountPolicy;
    }

    public Money getFee() {
        return fee;
    }

    public Money calculateMovieFee(Screening screening) {
        return fee.minus(discountPolicy.calculateDiscountAmount(screening));	// discountPolicy에게 calculateDiscountAmount 메시지 전송
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 할인 정책을 사용할 것인지 결정하는 코드가 어디에도 존재하지 않는다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단지 discountPolicy에게 메시지를 전송할 뿐이다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체지향에서 중요한 개념
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상속(inheritance)&lt;/li&gt;
&lt;li&gt;다형성(polymorphism)&lt;/li&gt;
&lt;li&gt;그 기반에는 추상화(abstraction)라는 원리가 숨겨져 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;할인 정책과 할인 조건&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;할인 정책 추상 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642929146428&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class DiscountPolicy {
    private List&amp;lt;DiscountCondition&amp;gt; conditions = new ArrayList&amp;lt;&amp;gt;();

    public DiscountPolicy(DiscountCondition ... conditions) {
        this.conditions = Arrays.asList(conditions);
    }

    public Money calculateDiscountAmount(Screening screening) {
        for(DiscountCondition each : conditions) {
            if (each.isSatisfiedBy(screening)) {
                return getDiscountAmount(screening);
            }
        }

        return Money.ZERO;
    }

    abstract protected Money getDiscountAmount(Screening Screening);
}​&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 클래스인 DiscountPolicy 안에 중복 코드를 두고 AmountDiscountPolicy와 PercentDiscountPolicy가 이 클래스를 상속
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 클래스에 기본적인 알고리즘의 흐름을 구현하고 중간에 필요한 처리르 자식 클래스에게 위임하는 디자인 패턴을 TemplateMethod 패턴이라 부른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;할인 조건 인터페이스&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642929167197&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface DiscountCondition {
    boolean isSatisfiedBy(Screening screening);
}​&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순서 조건&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642929186334&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SequenceCondition implements DiscountCondition {
    private int sequence;

    public SequenceCondition(int sequence) {
        this.sequence = sequence;
    }

    public boolean isSatisfiedBy(Screening screening) {
        return screening.isSequence(sequence);
    }
}​&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기간 조건&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642929209754&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PeriodCondition implements DiscountCondition {
    private DayOfWeek dayOfWeek;
    private LocalTime startTime;
    private LocalTime endTime;

    public PeriodCondition(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) {
        this.dayOfWeek = dayOfWeek;
        this.startTime = startTime;
        this.endTime = endTime;
    }

    public boolean isSatisfiedBy(Screening screening) {
        return screening.getStartTime().getDayOfWeek().equals(dayOfWeek) &amp;amp;&amp;amp;
                startTime.compareTo(screening.getStartTime().toLocalTime()) &amp;lt;= 0 &amp;amp;&amp;amp;
                endTime.compareTo(screening.getStartTime().toLocalTime()) &amp;gt;= 0;
    }
    // 1. 변수가 적을 땐 값을 직접 넘김 -&amp;gt; 의존성 X
    // 2. 너무 많으면 객체를 넘김 (근데 새로 만들어서 받을때도..?) -&amp;gt; 의존성 O
}​&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;금액 할인 정책&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642929220988&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AmountDiscountPolicy extends DiscountPolicy {
    private Money discountAmount;

    public AmountDiscountPolicy(Money discountAmount, DiscountCondition... conditions) {
        super(conditions);
        this.discountAmount = discountAmount;
    }

    @Override
    protected Money getDiscountAmount(Screening screening) {
        return discountAmount;
    }
}​&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비율 할인 정책&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642929243308&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PercentDiscountPolicy extends DiscountPolicy {
    private double percent;

    public PercentDiscountPolicy(double percent, DiscountCondition... conditions) {
        super(conditions);
        this.percent = percent;
    }

    @Override
    protected Money getDiscountAmount(Screening screening) {
        return screening.getMovieFee().times(percent);
    }
}​&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영화 가격 계산에 참여하는 모든 클래스 사이의 관계를 다이어그램으로 표현&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c344gv/btrroMCzEcN/kvL3ETaZAHqYuCqScRoKzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c344gv/btrroMCzEcN/kvL3ETaZAHqYuCqScRoKzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c344gv/btrroMCzEcN/kvL3ETaZAHqYuCqScRoKzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc344gv%2FbtrroMCzEcN%2FkvL3ETaZAHqYuCqScRoKzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1730&quot; height=&quot;537&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;04 상속과 다형성&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Movie 내부에 할인 정책을 결정하는 조건문이 없는데도 불구하고 어떻게 영화 요금을 계산할 때 할인 정책과 비율 할인 정책을 선택할 수 있을까? -&amp;gt; 상속과 다형성&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴파일 시간 의존성과 실행 시간 의존성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 클래스가 다른 클래스에 접근할 수 있는 경로를 가지거나 해당 클래스의 객체의 메서드를 호출할 경우 두 클래스 사이에 의존성이 존재한다고 말한다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vvM4W/btrrxngGVPA/NkwRWRBZZIU6F6NcHkyjv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vvM4W/btrrxngGVPA/NkwRWRBZZIU6F6NcHkyjv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vvM4W/btrrxngGVPA/NkwRWRBZZIU6F6NcHkyjv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvvM4W%2FbtrrxngGVPA%2FNkwRWRBZZIU6F6NcHkyjv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1340&quot; height=&quot;596&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;Movie의 인스턴스는 실행 시에 AmountDiscountPolicy 또는 PercentDiscountPolicy에 의존해야 한다.&lt;/li&gt;
&lt;li&gt;하지만 코드 수준에서는 오직 추상 클래스인 DiscountPolicy에만 의존하고 있다.&lt;/li&gt;
&lt;li&gt;실행 시점에 협력 가능한 이유는 무엇일까?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Movie의 인스턴스를 생성할 때 인자로 AmountDiscountPolicy 또는 PercentDiscountPolicy의 인스턴스를 전달하면 된다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;1372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7iItE/btrrxYOJqlI/xlYAZBgQxKaVF9xZwF3HU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7iItE/btrrxYOJqlI/xlYAZBgQxKaVF9xZwF3HU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7iItE/btrrxYOJqlI/xlYAZBgQxKaVF9xZwF3HU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7iItE%2FbtrrxYOJqlI%2FxlYAZBgQxKaVF9xZwF3HU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1340&quot; height=&quot;1372&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;1372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉 &lt;b&gt;코드의 의존성&lt;/b&gt;과 &lt;b&gt;실행 시점의 의존성&lt;/b&gt;이 서로 다를 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유연하고, 쉽게 재사용할 수 있으며, 확장 가능&lt;/li&gt;
&lt;li&gt;그러나 코드를 이해하기 어려워진다는 단점&lt;/li&gt;
&lt;li&gt;의존성의 양면성(객체지향 설계를 한다는 것은 적절한 균형이 필요하고 그 균형에 따른 결과 즉 트레이드오프의 산물이다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;훌륭한 객체지향 설계자로 성장하기 위해서는 항상 &lt;b&gt;유연성&lt;/b&gt;과 &lt;b&gt;가독성&lt;/b&gt; 사이에서 고민해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무조건 유연한 설계도, 무조건 읽기 쉬운 코드도 정답이 아니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차이에 의한 프로그래밍&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스를 하나 추가하고 싶은데 그 클래스가 기존의 어떤 클래스와 매우 흡사하다고 가정해보자&lt;/li&gt;
&lt;li&gt;좋은 방법은 그 클래스의 코드를 전혀 수정하지 않고도 재사용하는 것인데 이를 가능하게 해주는 방법이 바로 상속이다.&lt;/li&gt;
&lt;li&gt;상속은 기존 클래스를 기반으로 새로운 클래스를 쉽고 빠르게 추가할 수 있는 간편한 방법을 제공한다.&lt;/li&gt;
&lt;li&gt;또한 부모 클래스의 구현은 공유하면서도 행동이 다른 자식 클래스를 쉽게 추가할 수 있다.&lt;/li&gt;
&lt;li&gt;이처럼 부모 클래스와 다른 부분만을 추가해서 새로운 클래스를 쉽고 빠르게 만드는 방법을 차이에 의한 프로그래밍(programming by difference)이라고 부른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상속과 인터페이스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상속이 가치 있는 이유는 **부모 클래스가 제공하는 모든 인터페이스(public 메서드)**를 자식 클래스가 물려받을 수 있기 때문이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분의 사람들은 상속의 목적이 메서드나 인스턴스 변수를 재사용하는 것이라고 생각한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인터페이스는 객체가 이해할 수 있는 메시지의 목록을 정의한다는 것이다.&lt;/li&gt;
&lt;li&gt;상속을 통해 자식 클래스는 &lt;b&gt;자신의 인터페이스에 부모 클래스의 인터페이스를 포함&lt;/b&gt;하게 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결과적으로 부모 클래스가 수신할 수 있는 &lt;b&gt;모든 메시지를 수신할 수 있기 때문에&lt;/b&gt; 외부 객체는 &lt;b&gt;자식 클래스를 부모클래스와 동일한 타입으로 간주&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;li&gt;이처럼 자식 클래스가 부모 클래스를 대신하는 것을 업캐스팅(upcasting)이라고 부른다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DiBZ7/btrroYcbeac/2usSkeEbFBEh1QXWYzpwe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DiBZ7/btrroYcbeac/2usSkeEbFBEh1QXWYzpwe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DiBZ7/btrroYcbeac/2usSkeEbFBEh1QXWYzpwe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDiBZ7%2FbtrroYcbeac%2F2usSkeEbFBEh1QXWYzpwe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1340&quot; height=&quot;560&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다형성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다형성은 객체지향 프로그램의 &lt;b&gt;컴파일 시간 의존성과 실행 시간 의존성이 다를 수 있다&lt;/b&gt;는 사실을 기반으로 한다.&lt;/li&gt;
&lt;li&gt;다형성이란 &lt;b&gt;동일한 메시지&lt;/b&gt;를 수신했을 때 객체의 타입에 따라 &lt;b&gt;다르게 응답&lt;/b&gt;할 수 있는 능력을 의미한다.&lt;/li&gt;
&lt;li&gt;다형성을 구현하는 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공통점: 메시지에 응답하기 위해 &lt;b&gt;실행 될 메서드를 컴파일 시점이 아닌 실행 시점에 결정&lt;/b&gt;한다&lt;/li&gt;
&lt;li&gt;지연 바인딩(lazy binding), 동적 바인딩(dynamic binding)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반대 개념: 초기 바인딩(early binding) 또는 정적 바인딩(static binding)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다형성 관점에서의 상속
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구현 상속(implementation inheritance)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브클래싱(subclassing)이라고 부름&lt;/li&gt;
&lt;li&gt;코드를 재사용하기 위한 목적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인터페이스 상속(interface inheritance)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브타이핑(subtyping)이라고 부름&lt;/li&gt;
&lt;li&gt;다형적인 협력을 위해 부모 클래스와 자식 클래스가 인터페이스를 공유할 수 있도록 하기 위한 목적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;s&gt;대부분의 사람들은 코드 재사용을 상속의 주된 목적이라고 생각하지만&lt;/s&gt; &lt;b&gt;상속은&lt;/b&gt; 구현 상속이 아니라 &lt;b&gt;인터페이스 상속을 위해 사용&lt;/b&gt;해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인터페이스와 다형성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바에서는 인터페이스라는 프로그래밍 요소를 제공한다.&lt;/li&gt;
&lt;li&gt;자바의 인터페이스란 &lt;b&gt;구현에 대한 고려 없이&lt;/b&gt; 다형적인 &lt;b&gt;협력&lt;/b&gt;에 참여하는 클래스들이 &lt;b&gt;공유 가능한 외부 인터페이스&lt;/b&gt;를 정의한 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;05 추상화와 유연성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추상화의 힘&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추상화의 계층만 따로 떼어 놓고 살펴보면 요구사항의 정책을 높은 수준에서 서술할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추상화를 이용해 상위 정책을 기술한다는 것은 기본적인 애플리케이션의 협력 흐름을 기술한다는 것을 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;추상화를 사용하면 설계가 좀 더 유연해진다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqvIPG/btrrr4PxXOy/Q6gUJcmk1u1IEnkGWwaf31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqvIPG/btrrr4PxXOy/Q6gUJcmk1u1IEnkGWwaf31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqvIPG/btrrr4PxXOy/Q6gUJcmk1u1IEnkGWwaf31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqvIPG%2Fbtrrr4PxXOy%2FQ6gUJcmk1u1IEnkGWwaf31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1630&quot; height=&quot;344&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유연한 설계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추상화를 중심으로 코드의 구조를 설계하면 유연하고 확장 가능한 설계를 만들 수 있다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1731&quot; data-origin-height=&quot;807&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wMCKv/btrrpTnR2c5/KzblcDoDvZvkX12SdyJEKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wMCKv/btrrpTnR2c5/KzblcDoDvZvkX12SdyJEKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wMCKv/btrrpTnR2c5/KzblcDoDvZvkX12SdyJEKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwMCKv%2FbtrrpTnR2c5%2FKzblcDoDvZvkX12SdyJEKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1731&quot; height=&quot;807&quot; data-origin-width=&quot;1731&quot; data-origin-height=&quot;807&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추상 클래스와 인터페이스 트레이드 오프&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DiscountPolicy에서 할인 조건이 없을 경우에는 getDiscountAmount() 메서드를 호출하지 않는데&lt;/li&gt;
&lt;li&gt;할인 조건이 없는 NoneDiscountPolicy 클래스의 getDiscountAmount() 메서드는 쓰지 않는 메서드가 된다.&lt;/li&gt;
&lt;li&gt;이것은 부모 클래스인 DiscountPolicy와 NoneDiscountPolicy를 개념적으로 결합시킨다.&lt;/li&gt;
&lt;li&gt;기존 추상 클래스였던 DiscountPolicy를 인터페이스로 바꾸고&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642929482676&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface DiscountPolicy {
    Money calculateDiscountAmount(Screening screening);
}​&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원래 DiscountPolicy를 DefaultDiscountPolicy로 변경하고 인터페이스를 구현하도록 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642929497980&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class DefaultDiscountPolicy implements DiscountPolicy {
   // .... 생략
}​&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DiscountPolicy 인터페이스를 NoneDiscountPolicy를 구현하도록 하여 개념적인 혼란과 결합을 제거할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1642929525832&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class NoneDiscountPolicy implements DiscountPolicy {
    @Override
    public Money calculateDiscountAmount(Screening screening) {
        return Money.ZERO;
    }
}​&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;807&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dyLDDt/btrrr6s1eO9/3Kg5j83pvWbKnQhON7cxa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dyLDDt/btrrr6s1eO9/3Kg5j83pvWbKnQhON7cxa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dyLDDt/btrrr6s1eO9/3Kg5j83pvWbKnQhON7cxa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdyLDDt%2Fbtrrr6s1eO9%2F3Kg5j83pvWbKnQhON7cxa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1730&quot; height=&quot;807&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;807&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경된 설계에 대한 두 가지 생각
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이상적으로는 인터페이스를 사용하도록 변경한 설계가 더 좋다.&lt;/li&gt;
&lt;li&gt;현실적으로는 NoneDiscountPolicy만을 위해 인터페이스를 추가하는 것이 과하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;구현과 관련된 모든 것들이 트레이드오프의 대상이며, 작성하는 모든 코드에는 합당한 이유가 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 재사용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체지향 설계에서는 코드 재사용을 위해서는 상속보다는 합성(composition)이 더 좋은 방법&lt;/li&gt;
&lt;li&gt;합성은 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 재사용하는 방법을 말한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상속&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드를 재사용하기 위해 널리 사용해온 상속은
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 클래스의 구현이 자식 클래스에게 노출되기 때문에 &lt;b&gt;캡슐화를 위반&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;상속은 부모 클래스와 자식 클래스 사이의 관계를 컴파일 시점에 결정하기 때문에 실행 시점에 객체의 종류를 변경할 수 없어 &lt;b&gt;설계를 유연하지 못하게 만든다&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;합성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스에 정의된 &lt;b&gt;메시지를 통해서만 코드를 재사용하는 방법&lt;/b&gt;을 합성이라고 부른다.&lt;/li&gt;
&lt;li&gt;합성은 상속이 가지는 두 가지 문제점(캡슐화 위반, 유연하지 못한 설계)을 모두 해결한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍/객체지향</category>
      <category>객체지향</category>
      <category>오브젝트</category>
      <author>wook-lab</author>
      <guid isPermaLink="true">https://wook-lab.tistory.com/15</guid>
      <comments>https://wook-lab.tistory.com/15#entry15comment</comments>
      <pubDate>Sun, 23 Jan 2022 18:19:33 +0900</pubDate>
    </item>
    <item>
      <title>오브젝트:코드로 이해하는 객체지향 설계 Ch.01</title>
      <link>https://wook-lab.tistory.com/13</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Chapter 01 객체, 설계&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소프트웨어 유지보수에 대해 중요도가 낮음&lt;/li&gt;
&lt;li&gt;소프트웨어 모듈이 가져야 하는 세가지 기능(로버트 마틴)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 중에 제대로 동작하는 것&lt;/li&gt;
&lt;li&gt;변경을 위해 존재하는 것&lt;/li&gt;
&lt;li&gt;코드를 읽는 사람과 의사소통하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변경에 취약한 코드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의존성(dependency): 어떤 객체가 변경될 때, 그 객체에 의존하는 객체도 변경될 수 있음&lt;/li&gt;
&lt;li&gt;결합도(coupling)가 높은 코드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결합도가 높다: 의존성이 강하다&lt;/li&gt;
&lt;li&gt;결합도가 낮다: 합리적인 수준으로 의존한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체지향 설계의 목표: 객체 사이의 결합도를 낮춰 변경이 용이한 설계는 만드는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변경에 유연한 코드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자율성을 높이자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캡슐화(encapsulation)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개념적이나 물리적으로 객체 내부의 세부적인 사항을 감추는 것을 캡슐화&lt;/li&gt;
&lt;li&gt;내부로의 접근을 제한하면 -&amp;gt; 객체와 객체 사이의 결합도를 낮출 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;어떻게?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자기 자신의 문제는 스스로 해결&lt;/li&gt;
&lt;li&gt;객체 내부의 상태를 캡슐화하고 객체 간에 오직 &lt;b&gt;메시지&lt;/b&gt;를 통해서만 상호작용&lt;/li&gt;
&lt;li&gt;응집도를 높임
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응집도(cohesion): 밀접하게 연관된 작업만 수행 (연관성 없는 작업은 다른 객체에게 위임)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;절차지향과 객체지향
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;절차적 프로그래밍(Procedural Programming)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스와 데이터를 별도의 모듈에 위치&lt;/li&gt;
&lt;li&gt;우리의 예상을 쉽게 벗어남&lt;/li&gt;
&lt;li&gt;책임이 한 객체에 집중&lt;/li&gt;
&lt;li&gt;변경하기 어려운 코드를 양산&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체지향 프로그래밍(Object-Oriented Programming)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터와 프로세스가 동일한 모듈 내부에 위치&lt;/li&gt;
&lt;li&gt;동일한 모듈 내부에 위치하기 때문에 예상 범위 안에 있음&lt;/li&gt;
&lt;li&gt;개별 객체에 책임 할당&lt;/li&gt;
&lt;li&gt;객체 내부의 변경이 객체 외부에 파급되지 않도록 제어할 수 있기 때무에 변경하기 수월함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체지향의 핵심
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;적절한 객체에 적절한 책임을 할당하는 것이다.&lt;/li&gt;
&lt;li&gt;따라서 &lt;s&gt;객체가 어떤 데이터를 가지느냐보다는&lt;/s&gt; 객체에 어떤 책임을 할당할 것이냐에 초점&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체지향의 세계가 우리가 세상을 바라보는 직관과 객체를 완전 일치한다?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비록 현실에서는 수동적인 존재라고 하더라도 일단 객체지향의 세계에 들어오는 모든 것이 능동적이고 자율적인 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;좋은 설계란?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오늘 요구하는 기능을 온전히 수행하면서 내일의 변경을 매끄럽게 수용할 수 있는 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍/객체지향</category>
      <author>wook-lab</author>
      <guid isPermaLink="true">https://wook-lab.tistory.com/13</guid>
      <comments>https://wook-lab.tistory.com/13#entry13comment</comments>
      <pubDate>Sat, 22 Jan 2022 22:31:22 +0900</pubDate>
    </item>
    <item>
      <title>Constant Interface</title>
      <link>https://wook-lab.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;상수를 인터페이스로 사용하는 것은 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;Anti pattern&lt;/span&gt;이다. &lt;br /&gt;인터페이스가 아닌 Final Class를 써야 한다. &lt;br /&gt;상수 변수 사용시 import를 static으로 선언하면 클래스명을 생략하여 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;/**
 * 클래스로 구현한 공통코드 상수
 */
public  final  class  CommonCode  {
 
    private  CommonCode()  {
        throw new AssertionFailure();
    }
 
    public  static  final  String ERROR_CODE_A  =  &quot;001&quot;;
    public  static  final  String ERROR_CODE_B  =  &quot;002&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import CommonCode ;

/**
 * 공통코드 상수 클래스를 호출하는 방법1
 */ 
public  class  Response {
    public  String getResponseCode() {
        return CommonCode.ERROR_CODE_A;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import static CommonCode.*;  // static으로 선언

/**
 * 공통코드 상수 클래스를 호출하는 방법2
 */ 
public  class  Response {
    public  String getResponseCode() {
        return ERROR_CODE_A;
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프로그래밍/Java</category>
      <category>java</category>
      <author>wook-lab</author>
      <guid isPermaLink="true">https://wook-lab.tistory.com/12</guid>
      <comments>https://wook-lab.tistory.com/12#entry12comment</comments>
      <pubDate>Thu, 20 Jan 2022 11:08:05 +0900</pubDate>
    </item>
  </channel>
</rss>