<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>SW</title>
    <link>https://sw-ko.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 7 Apr 2026 14:16:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>SWKo</managingEditor>
    <image>
      <title>SW</title>
      <url>https://tistory1.daumcdn.net/tistory/3570564/attach/5ae37bde19cf49c88e4301b3bba5f6da</url>
      <link>https://sw-ko.tistory.com</link>
    </image>
    <item>
      <title>[개발서적] 헤드퍼스트 디자인 패턴 Ch12. 복합 패턴</title>
      <link>https://sw-ko.tistory.com/476</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 복합 패턴&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;p data-ke-size=&quot;size16&quot;&gt;오리 시뮬레이션 게임을 처음부터 다시 만들면서 몇 가지 기능을 추가해 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 하나의 문제를 해결할 때 여러 패턴이 어떻게 공존하고 협력하는지 볼 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Quackable 인터페이스 정의&lt;/h4&gt;
&lt;pre id=&quot;code_1729778035201&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Quackable {
    public void quack();
}&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;Quackable 인터페이스는 quack() 메서드를 선언합니다. 이 메서드는 오리가 소리를 낼 때 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Quackable 구현 클래스들&lt;/h4&gt;
&lt;pre id=&quot;code_1729778064540&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MallardDuck implements Quackable {
    public void quack() {
        System.out.println(&quot;꽥꽥&quot;);
    }
}

public class RedheadDuck implements Quackable {
    public void quack() {
        System.out.println(&quot;꽥꽥&quot;);
    }
}

public class DuckCall implements Quackable {
    public void quack() {
        System.out.println(&quot;꽉꽉&quot;);
    }
}

public class RubberDuck implements Quackable {
    public void quack() {
        System.out.println(&quot;삑삑&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 오리 클래스는 Quackable 인터페이스를 구현하여 자신의 고유한 quack() 메서드를 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. DuckSimulator 클래스&lt;/h4&gt;
&lt;pre id=&quot;code_1729778096104&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DuckSimulator {
    public static void main(String[] args) {
        DuckSimulator simulator = new DuckSimulator();
        simulator.simulate();
    }

    void simulate() {
        Quackable mallardDuck = new MallardDuck();
        Quackable redheadDuck = new RedheadDuck();
        Quackable duckCall = new DuckCall();
        Quackable rubberDuck = new RubberDuck();

        System.out.println(&quot;\n오리 시뮬레이션 게임&quot;);

        simulate(mallardDuck);
        simulate(redheadDuck);
        simulate(duckCall);
        simulate(rubberDuck);
    }

    void simulate(Quackable duck) {
        duck.quack();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DuckSimulator 클래스는 시뮬레이션을 실행하는 메인 클래스로, 다양한 오리 객체를 생성한 후 각 오리의 quack() 메서드를 호출하여 소리를 냅니다.&lt;/li&gt;
&lt;li&gt;simulate() 메서드는 Quackable 객체를 받아서 quack() 메서드를 실행합니다. 이로 인해 다형성을 통해 각 오리의 소리를 동일한 방식으로 처리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 거위 추가&lt;/h4&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1729778205693&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Goose {
    public void honk() {
        System.out.println(&quot;끽끽&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Goose 클래스는 honk() 메서드를 가지고 있으며, 거위가 소리를 낼 때 &quot;끽끽&quot; 소리를 냅니다. 그러나 Goose는 Quackable 인터페이스를 구현하지 않으므로 바로 시뮬레이션에 사용할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 어댑터 패턴 사용하기 (GooseAdapter)&lt;/h4&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1729778241929&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GooseAdapter implements Quackable {
    Goose goose;

    public GooseAdapter(Goose goose) {
        this.goose = goose;
    }

    public void quack() {
        goose.honk();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GooseAdapter는 어댑터 패턴을 사용하여 Goose 객체를 Quackable로 감싸줍니다.&lt;/li&gt;
&lt;li&gt;quack() 메서드가 호출되면, 내부적으로 거위의 honk() 메서드가 호출되어 &quot;끽끽&quot; 소리가 나도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. 시뮬레이터에 거위 추가&lt;/h4&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1729778332401&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DuckSimulator {
    public static void main(String[] args) {
        DuckSimulator simulator = new DuckSimulator();
        simulator.simulate();
    }

    void simulate() {
        Quackable mallardDuck = new MallardDuck();
        Quackable redheadDuck = new RedheadDuck();
        Quackable duckCall = new DuckCall();
        Quackable rubberDuck = new RubberDuck();
        Quackable gooseDuck = new GooseAdapter(new Goose());

        System.out.println(&quot;\n오리 시뮬레이션 게임 (거위 어댑터)&quot;);

        simulate(mallardDuck);
        simulate(redheadDuck);
        simulate(duckCall);
        simulate(rubberDuck);
        simulate(gooseDuck);
    }

    void simulate(Quackable duck) {
        duck.quack();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시뮬레이터에 거위를 추가할 때 GooseAdapter를 사용하여 Goose를 Quackable로 변환합니다.&lt;/li&gt;
&lt;li&gt;이제 거위도 다른 오리들과 마찬가지로 simulate() 메서드에서 quack()을 호출할 수 있습니다. 이때 거위는 &quot;끽끽&quot; 소리를 냅니다.&lt;/li&gt;
&lt;li&gt;Goose는 원래 Quackable을 구현하지 않지만, GooseAdapter를 통해 Quackable처럼 동작할 수 있도록 변환되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-24 오후 11.16.16.png&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ujJb9/btsKjrEV48G/wfDekQB2aKwykOS5asPbc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ujJb9/btsKjrEV48G/wfDekQB2aKwykOS5asPbc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ujJb9/btsKjrEV48G/wfDekQB2aKwykOS5asPbc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FujJb9%2FbtsKjrEV48G%2FwfDekQB2aKwykOS5asPbc0%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;400&quot; height=&quot;195&quot; data-filename=&quot;스크린샷 2024-10-24 오후 11.16.16.png&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7. Quack 소리 횟수 세기 (QuackCounter)&lt;/h4&gt;
&lt;pre id=&quot;code_1729779413638&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class QuackCounter implements Quackable {
    Quackable duck;
    static int numberOfQuacks;

    public QuackCounter(Quackable duck) {
        this.duck = duck;
    }

    public void quack() {
        duck.quack();
        numberOfQuacks++;
    }

    public static int getQuacks() {
        return numberOfQuacks;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;QuackCounter(데코레이터) 클래스&lt;/b&gt;는 &lt;b&gt;데코레이터 패턴&lt;/b&gt;을 사용하여 Quackable을 구현한 객체에 소리 횟수를 세는 기능을 추가합니다.
&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;기존의 Quackable 객체들에 직접 수정 없이&lt;/b&gt; 소리 횟수를 세는 기능 추가 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;quack() 메서드가 호출될 때, 내부의 duck.quack()이 호출된 후, numberOfQuacks가 증가합니다.&lt;/li&gt;
&lt;li&gt;getQuacks() 메서드는 지금까지 소리가 난 횟수를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;8. 시뮬레이터에서 모든 오리에 데코레이터 적용&lt;/h4&gt;
&lt;pre id=&quot;code_1729779529419&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DuckSimulator {
    public static void main(String[] args) {
        DuckSimulator simulator = new DuckSimulator();
        simulator.simulate();
    }

    void simulate() {
        Quackable mallardDuck = new QuackCounter(new MallardDuck());
        Quackable redheadDuck = new QuackCounter(new RedheadDuck());
        Quackable duckCall = new QuackCounter(new DuckCall());
        Quackable rubberDuck = new QuackCounter(new RubberDuck());
        Quackable gooseDuck = new GooseAdapter(new Goose());

        System.out.println(&quot;\n오리 시뮬레이션 게임 (데코레이터)&quot;);

        simulate(mallardDuck);
        simulate(redheadDuck);
        simulate(duckCall);
        simulate(rubberDuck);
        simulate(gooseDuck);

        System.out.println(&quot;오리가 소리 낸 횟수: &quot; + QuackCounter.getQuacks() + &quot; 번&quot;);
    }

    void simulate(Quackable duck) {
        duck.quack();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;QuackCounter 데코레이터&lt;/b&gt;는 모든 오리 객체에 적용되어 소리 낸 횟수를 기록합니다.&lt;/li&gt;
&lt;li&gt;시뮬레이션이 끝난 후, QuackCounter.getQuacks()를 통해 총 몇 번 오리들이 소리를 냈는지 출력합니다.&lt;/li&gt;
&lt;li&gt;이 코드에서는 &lt;b&gt;GooseAdapter&lt;/b&gt;를 사용하여 거위도 포함되며, 각 오리가 소리를 낼 때마다 그 횟수가 기록됩니다.&lt;/li&gt;
&lt;li&gt;&quot;오리가 소리 낸 횟수: 4 번&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;9. 팩토리 패턴을 통한 오리 생성&lt;/h4&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1729779639912&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class AbstractDuckFactory {
    public abstract Quackable createMallardDuck();
    public abstract Quackable createRedheadDuck();
    public abstract Quackable createDuckCall();
    public abstract Quackable createRubberDuck();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;AbstractDuckFactory는 각 오리 클래스를 생성하는 메서드를 선언하고, 이를 구체화한 팩토리 클래스에서 해당 메서드들을 구현하여 오리 객체를 생성할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1729781995594&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CountingDuckFactory extends AbstractDuckFactory {
    public Quackable createMallardDuck() {
        return new QuackCounter(new MallardDuck());
    }
    public Quackable createRedheadDuck() {
        return new QuackCounter(new RedheadDuck());
    }
    public Quackable createDuckCall() {
        return new QuackCounter(new DuckCall());
    }
    public Quackable createRubberDuck() {
        return new QuackCounter(new RubberDuck());
    }
}&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;CountingDuckFactory는 QuackCounter 데코레이터를 사용해 각 오리의 quack() 메서드가 호출될 때마다 소리 횟수를 세도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1729782034332&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DuckSimulator {
    public static void main(String[] args) {
        DuckSimulator simulator = new DuckSimulator();
        AbstractDuckFactory duckFactory = new CountingDuckFactory();
        simulator.simulate(duckFactory);
    }

    void simulate(AbstractDuckFactory duckFactory) {
        Quackable mallardDuck = duckFactory.createMallardDuck();
        Quackable redheadDuck = duckFactory.createRedheadDuck();
        Quackable duckCall = duckFactory.createDuckCall();
        Quackable rubberDuck = duckFactory.createRubberDuck();
        Quackable gooseDuck = new GooseAdapter(new Goose());

        System.out.println(&quot;\n오리 시뮬레이션 게임 (데코레이터)&quot;);

        simulate(mallardDuck);
        simulate(redheadDuck);
        simulate(duckCall);
        simulate(rubberDuck);
        simulate(gooseDuck);

        System.out.println(&quot;오리가 소리 낸 횟수: &quot; + QuackCounter.getQuacks() + &quot; 번&quot;);
    }

    void simulate(Quackable duck) {
        duck.quack();
    }
}&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;QuackCounter를 사용한 오리들의 소리 횟수를 기록할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;10. Flock 패턴 (무리 패턴)&lt;/h4&gt;
&lt;pre id=&quot;code_1729781838644&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Flock implements Quackable {
    List&amp;lt;Quackable&amp;gt; quackers = new ArrayList&amp;lt;&amp;gt;();

    public void add(Quackable quacker) {
        quackers.add(quacker);
    }

    public void quack() {
        Iterator&amp;lt;Quackable&amp;gt; iterator = quackers.iterator();
        while (iterator.hasNext()) {
            Quackable quacker = iterator.next();
            quacker.quack();
        }
    }
}&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;b&gt;Flock&lt;/b&gt; 클래스는 Quackable 객체들의 리스트를 관리하고, 모든 객체의 quack() 메서드를 호출할 수 있습니다.&lt;/li&gt;
&lt;li&gt;여러 오리 객체를 한 번에 관리할 수 있는 구조를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;11. Flock을 시뮬레이션에 적용&lt;/h4&gt;
&lt;pre id=&quot;code_1729782094534&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void simulate(AbstractDuckFactory duckFactory) {
    Flock flockOfDucks = new Flock();

    Quackable redheadDuck = duckFactory.createRedheadDuck();
    Quackable duckCall = duckFactory.createDuckCall();
    Quackable rubberDuck = duckFactory.createRubberDuck();
    Quackable gooseDuck = new GooseAdapter(new Goose());

    flockOfDucks.add(redheadDuck);
    flockOfDucks.add(duckCall);
    flockOfDucks.add(rubberDuck);
    flockOfDucks.add(gooseDuck);

    System.out.println(&quot;\n오리 시뮬레이션 게임: 무리&quot;);

    simulate(flockOfDucks);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Flock&lt;/b&gt; 패턴을 사용하여 오리의 무리를 만들고, 무리 내 모든 오리의 quack() 메서드를 호출합니다.&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;b&gt;Flock 패턴&lt;/b&gt;은 오리 무리를 관리하고, 모든 오리가 동일한 행동을 하도록 쉽게 설정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;12. 옵저버 패턴 도입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵저버 패턴은 객체의 행동을 관찰하고 추적하려는 시나리오에 적합한 패턴입니다. 여기서는 오리들이 소리를 낼 때 이를 추적하고자 옵저버 패턴을 적용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729782680396&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface QuackObservable {
    public void registerObserver(Observer observer);
    public void notifyObservers();
}&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;QuackObservable은 옵저버를 등록하고, 소리가 날 때 이를 옵저버에게 알리는 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Quackable 인터페이스&lt;/h4&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1729782774088&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Quackable은 QuackObservable을 확장하며, 오리가 소리를 낼 때 이를 옵저버에게 알릴 수 있도록 합니다.
public interface Quackable extends QuackObservable {
    public void quack();
}

// Observable 클래스는 옵저버를 등록하고, 오리가 소리를 낼 때마다 등록된 옵저버들에게 알립니다.
public class Observable implements QuackObservable {
    List&amp;lt;Observer&amp;gt; observers = new ArrayList&amp;lt;&amp;gt;();
    QuackObservable duck;

    public Observable(QuackObservable duck) {
        this.duck = duck;
    }

    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(duck);
        }
    }
}

// MallardDuck 클래스는 Observable 객체를 가지고 있으며, 소리가 날 때 옵저버들에게 알립니다.
public class MallardDuck implements Quackable {
    Observable observable;

    public MallardDuck() {
        observable = new Observable(this);
    }

    public void quack() {
        System.out.println(&quot;꽥꽥&quot;);
        notifyObservers();
    }

    public void registerObserver(Observer observer) {
        observable.registerObserver(observer);
    }

    public void notifyObservers() {
        observable.notifyObservers();
    }
}

// Observer 인터페이스는 옵저버가 업데이트될 때 호출되는 메서드를 정의합니다.
public interface Observer {
    public void update(QuackObservable duck);
}

// Quackologist는 Observer를 구현하여 오리가 소리를 낼 때 이를 출력합니다.
public class Quackologist implements Observer {
    public void update(QuackObservable duck) {
        System.out.println(&quot;꽥꽥학자: &quot; + duck + &quot;가 방금 소리냈다.&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1729782790405&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DuckSimulator {
    public static void main(String[] args) {
        DuckSimulator simulator = new DuckSimulator();
        AbstractDuckFactory duckFactory = new CountingDuckFactory();
        simulator.simulate(duckFactory);
    }

    void simulate(AbstractDuckFactory duckFactory) {
        Flock flockOfDucks = new Flock();
        Quackologist quackologist = new Quackologist();
        flockOfDucks.registerObserver(quackologist);

        simulate(flockOfDucks);
        System.out.println(&quot;오리가 소리 낸 횟수: &quot; + QuackCounter.getQuacks() + &quot; 번&quot;);
    }

    void simulate(Quackable duck) {
        duck.quack();
    }
}&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;옵저버인 Quackologist를 무리에 등록하고, 오리들이 소리를 낼 때마다 옵저버가 그 행동을 추적하도록 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;옵저버 패턴&lt;/b&gt;을 통해 오리들이 소리를 낼 때 이를 추적할 수 있습니다.&lt;/li&gt;
&lt;li&gt;옵저버는 여러 객체의 상태 변화를 실시간으로 반영하며, 이 패턴을 통해 객체 간의 의존성을 줄일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 정리&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-message-model-slug=&quot;gpt-4o&quot; data-message-id=&quot;165609be-003d-4afd-894b-ca81e67bbbb2&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지에 있는 내용은 &lt;b&gt;옵저버 패턴&lt;/b&gt;, &lt;b&gt;데코레이터 패턴&lt;/b&gt;, &lt;b&gt;반복자 패턴&lt;/b&gt;을 활용해 변경된 설계에 대한 설명입니다. 주요 내용을 요약하면 다음과 같습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Quackable의 확장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 Quackable 객체들은 기본적인 quack() 메서드를 구현했습니다. 하지만 거위가 등장하면서 거위도 Quackable이 되고 싶어 했고, 어댑터 패턴을 사용하여 거위를 Quackable로 만들었습니다. 이제 거위는 quack()을 호출하면 자동으로 honk()를 실행하게 되었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 데코레이터 패턴으로 소리 횟수 측정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팩퀵학자들이 오리의 소리 횟수를 측정하고 싶어했습니다. 이를 위해 데코레이터 패턴을 적용해 QuackCounter 데코레이터를 추가했고, 이로 인해 각 quack() 호출 시 소리 횟수를 셀 수 있게 되었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 팩토리 패턴을 이용한 객체 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데코레이터가 적용되지 않은 Quackable 객체들이 생길까 걱정한 팩퀵학자들은 팩토리 패턴을 사용해 항상 데코레이터가 적용된 Quackable 객체가 생성되도록 했습니다. 이를 통해 오리 객체의 생성이 중앙에서 관리되었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Flock 패턴(무리 패턴)과 반복자 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 오리와 거위를 관리하는 것이 어려워졌습니다. 이를 해결하기 위해 Flock 패턴을 도입해 오리들을 무리 단위로 관리하게 되었습니다. 이 과정에서 반복자 패턴을 사용해 각 오리들의 quack()을 순차적으로 호출할 수 있게 했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 옵저버 패턴 도입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팩퀵학자들은 오리의 행동을 관찰하고 싶어했습니다. 옵저버 패턴을 적용해 Quackologist가 Quackable 객체의 옵저버로 등록되었고, 오리가 소리를 낼 때마다 옵저버에게 알릴 수 있었습니다.&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;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. MVC 패턴&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오전 12.32.30.png&quot; data-origin-width=&quot;1684&quot; data-origin-height=&quot;1212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bliO1i/btsKjGV9itW/4JNZxFoANWxfXOvAKDzlLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bliO1i/btsKjGV9itW/4JNZxFoANWxfXOvAKDzlLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bliO1i/btsKjGV9itW/4JNZxFoANWxfXOvAKDzlLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbliO1i%2FbtsKjGV9itW%2F4JNZxFoANWxfXOvAKDzlLk%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;600&quot; height=&quot;432&quot; data-filename=&quot;스크린샷 2024-10-25 오전 12.32.30.png&quot; data-origin-width=&quot;1684&quot; data-origin-height=&quot;1212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div data-message-model-slug=&quot;gpt-4o&quot; data-message-id=&quot;682ce89a-8a6c-4d76-834a-3b825a7b3312&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 사용자와 뷰(View)의 상호작용&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;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 컨트롤러가 모델에게 상태 변경 요청&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;b&gt;모델&lt;/b&gt;에 상태 변경을 요청합니다.&lt;/li&gt;
&lt;li&gt;예를 들어, 사용자가 버튼을 클릭하면 컨트롤러가 그 입력을 받아 모델에 처리 방법을 지시합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 컨트롤러가 뷰(View)에게 요청&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;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 모델이 뷰에게 상태 변경 알림&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;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 뷰가 모델에게 상태 요청&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;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오전 12.41.23.png&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;1262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vPgBw/btsKjnW2yLI/5iTgkMWIehdV1GnYgGYkKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vPgBw/btsKjnW2yLI/5iTgkMWIehdV1GnYgGYkKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vPgBw/btsKjnW2yLI/5iTgkMWIehdV1GnYgGYkKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvPgBw%2FbtsKjnW2yLI%2F5iTgkMWIehdV1GnYgGYkKK%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;600&quot; height=&quot;508&quot; data-filename=&quot;스크린샷 2024-10-25 오전 12.41.23.png&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;1262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-25 오전 12.41.58.png&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;1834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QGFlb/btsKjKda9Lv/7s0VUvSiZc1ByvJKdKszMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QGFlb/btsKjKda9Lv/7s0VUvSiZc1ByvJKdKszMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QGFlb/btsKjKda9Lv/7s0VUvSiZc1ByvJKdKszMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQGFlb%2FbtsKjKda9Lv%2F7s0VUvSiZc1ByvJKdKszMK%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;600&quot; height=&quot;714&quot; data-filename=&quot;스크린샷 2024-10-25 오전 12.41.58.png&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;1834&quot;/&gt;&lt;/span&gt;&lt;/figure&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;</description>
      <category>개발서적/헤드퍼스트 디자인패턴</category>
      <author>SWKo</author>
      <guid isPermaLink="true">https://sw-ko.tistory.com/476</guid>
      <comments>https://sw-ko.tistory.com/476#entry476comment</comments>
      <pubDate>Thu, 24 Oct 2024 09:47:05 +0900</pubDate>
    </item>
    <item>
      <title>[개발서적] 헤드퍼스트 디자인 패턴 Ch11. 프록시 패턴</title>
      <link>https://sw-ko.tistory.com/475</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 모니터링 코드 만들기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbK7XI/btsJZadB5gK/RyHVSa9XzswR4rtqrDSUok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbK7XI/btsJZadB5gK/RyHVSa9XzswR4rtqrDSUok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbK7XI/btsJZadB5gK/RyHVSa9XzswR4rtqrDSUok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbK7XI%2FbtsJZadB5gK%2FRyHVSa9XzswR4rtqrDSUok%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;600&quot; height=&quot;351&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CEO는 원격에 있는 모든 뽑기 기계를 모니터링 하기를 원합니다.&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;뽑기 기계 코드의 알맹이의 개수를 알려주는 메소드와 기계의 현재 상태를 알려주는 메소드는 현재 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서, 모든 뽑기 기계의 재고와 현재 상태를 알려 주는 기능을 추가하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 GumballMachine 클래스에 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;뽑기 기계의 현재 위치를 알려 주는 기능을 추가&lt;/span&gt;해봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728486375055&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GumballMachine {
    String location;  // 기계의 위치
    int count;        // 알맹이의 개수
    String state;     // 기계의 현재 상태
    
    // 생성자: 기계의 위치와 재고를 초기화
    public GumballMachine(String location, int count) {
        this.location = location;
        this.count = count;
        this.state = &quot;대기 중&quot;; // 초기 상태 설정
    }
    
    // 기계의 위치를 반환하는 메소드
    public String getLocation() {
        return location;
    }

    // 재고 수를 반환하는 메소드 (이미 구현된 것)
    public int getCount() {
        return count;
    }

    // 상태를 반환하는 메소드 (이미 구현된 것)
    public String getState() {
        return state;
    }
}&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;뽑기 기계의 위치, 재고, 현재 상태를 가져와서 보고서를 출력해주는 GumballMonitor 클래스를 만들어봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728486428730&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GumballMonitor {
    GumballMachine machine;  // 모니터링할 GumballMachine 객체

    // 생성자: GumballMachine 객체를 전달받아 초기화
    public GumballMonitor(GumballMachine machine) {
        this.machine = machine;
    }

    // 보고서 출력 메소드
    public void report() {
        System.out.println(&quot;뽑기 기계 위치: &quot; + machine.getLocation());
        System.out.println(&quot;현재 재고: &quot; + machine.getCount() + &quot; 개&quot;);
        System.out.println(&quot;현재 상태: &quot; + machine.getState());
    }
}&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;/p&gt;
&lt;pre id=&quot;code_1728486898965&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GumballMachineTestDrive {
    public static void main(String[] args) {
        int count = 0;

        // 입력 값 유효성 확인
        if (args.length &amp;lt; 2) {
            System.out.println(&quot;GumballMachine &amp;lt;location&amp;gt; &amp;lt;inventory&amp;gt;&quot;);
            System.exit(1);  // 인수가 부족할 경우 프로그램 종료
        }

        // 명령줄 인수에서 위치와 초기 재고 개수 가져오기
        String location = args[0];  // 기계 위치
        count = Integer.parseInt(args[1]);  // 초기 재고 수

        // GumballMachine 객체 생성
        GumballMachine gumballMachine = new GumballMachine(location, count);

        // GumballMonitor 객체 생성
        GumballMonitor monitor = new GumballMonitor(gumballMachine);

        // 보고서 출력
        monitor.report();
    }
}&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;830&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clkdlO/btsJZ6an0m3/MDuQzm4RZIAJEdYAjXpgdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clkdlO/btsJZ6an0m3/MDuQzm4RZIAJEdYAjXpgdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clkdlO/btsJZ6an0m3/MDuQzm4RZIAJEdYAjXpgdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclkdlO%2FbtsJZ6an0m3%2FMDuQzm4RZIAJEdYAjXpgdK%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;400&quot; height=&quot;130&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;하지만, 요구사항은 원격 모니터링입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 GumballMonitor는 로컬에서만 Gumball Machine을 모니터링할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;원격 프록시를 통해 해결이 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;진짜 객체&lt;/b&gt;처럼 동작&lt;/span&gt;하지만, 실제 객체에 접근하기 전 이를 제어하거나 간접적으로 접근하게 만드는 디자인 패턴입니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;원격 프록시(Remote Proxy)&lt;/b&gt;: 원격 서버에 있는 객체에 접근하기 위해, 원격 통신을 관리하고 제어할 수 있는 &lt;b&gt;대리 객체&lt;/b&gt;입니다.&lt;/span&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;: GumballMonitor는 &lt;b&gt;진짜 Gumball Machine&lt;/b&gt; 대신 원격 프록시 객체에 접근합니다. 프록시는 원격으로 실제 기계의 상태와 재고를 조회하고, 이를 모니터에 전달합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 원격 프록시의 역할&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 프록시는 원격 객체의 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;로컬 대변자 역할&lt;/span&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;로컬 대변자 = 어떤 메소드를 호출하면, 다른 원격 객체에게 그 메소드 호출을 전달해 주는 객체&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbK7XI/btsJZadB5gK/RyHVSa9XzswR4rtqrDSUok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbK7XI/btsJZadB5gK/RyHVSa9XzswR4rtqrDSUok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbK7XI/btsJZadB5gK/RyHVSa9XzswR4rtqrDSUok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbK7XI%2FbtsJZadB5gK%2FRyHVSa9XzswR4rtqrDSUok%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;600&quot; height=&quot;351&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;/figure&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;클라이언트 객체(GumballMonitor)는 원격 객체의 메소드 호출을 하는 것처럼 행동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로는 로컬 힙에 들어있는 'Proxy' 객체의 메소드를 호출하고 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 통신과 관련된 저수준 작업은 이 'Proxy' 객체에서 처리해줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 모니터링 코드에 원격 프록시 추가하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 JVM에 들어있는 객체의 메소드를 호출하는 프록시를 만들어봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 방법으로는 다른 힙에 들어있는 객체 레퍼런스를 가져올 수는 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728488881186&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Duck d = &amp;lt;다른 힙에 있는 객체&amp;gt;&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;변수 d가 어떤 객체를 참조하든, 그 객체는 선언문이 있는 코드와 같은 힙 공간에 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 이제 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;Java의 원격 메소드 호출(RMI, Remote Method Invocation)&lt;/span&gt;이 쓰입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RMI를 사용하면 원격 JVM에 있는 객체를 찾아서 그 메소드를 호출할 수 있습니다.&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;4. 원격 메소드의 기초&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eI06ls/btsJX8U9Vai/K2umukuCBy4bXJzcnyIYG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eI06ls/btsJX8U9Vai/K2umukuCBy4bXJzcnyIYG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eI06ls/btsJX8U9Vai/K2umukuCBy4bXJzcnyIYG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeI06ls%2FbtsJX8U9Vai%2FK2umukuCBy4bXJzcnyIYG1%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;700&quot; height=&quot;330&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;702&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;b&gt;클라이언트 객체&lt;/b&gt;: 메소드를 호출하는 원본 객체.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 보조 객체(Proxy)&lt;/b&gt;: 원격 메소드 호출을 포장하고 전달하는 프록시 객체.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서비스 보조 객체(Remote Skeleton)&lt;/b&gt;: 클라이언트의 호출 정보를 해석하고, 서비스 객체의 메소드를 호출하는 역할.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서비스 객체&lt;/b&gt;: 실제 비즈니스 로직을 수행하고, 작업을 처리하는 원본 객체.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1728489222634&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. Client &amp;rarr; [Client Proxy] : doBigThing()
2. [Client Proxy] &amp;rarr; (네트워크) &amp;rarr; [Remote Skeleton] : doBigThing() 호출 정보 전달
3. [Remote Skeleton] &amp;rarr; [Service Object] : 메소드 해석 후, 서비스 객체 메소드 호출
4. [Service Object] : 비즈니스 로직 처리 후, 결과 반환
5. [Remote Skeleton] &amp;rarr; (네트워크) &amp;rarr; [Client Proxy] : 메소드 호출 결과 전달
6. [Client Proxy] &amp;rarr; Client : 메소드 결과 리턴&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;1130&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HfTgV/btsJZdaknPh/ew9KVSV8JYti1ANEvhi5OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HfTgV/btsJZdaknPh/ew9KVSV8JYti1ANEvhi5OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HfTgV/btsJZdaknPh/ew9KVSV8JYti1ANEvhi5OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHfTgV%2FbtsJZdaknPh%2Few9KVSV8JYti1ANEvhi5OK%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;400&quot; height=&quot;209&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&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;5. 자바 RMI(Remote Method Invocation)로 원격 서비스 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;RMI는 클라이언트와 서비스 보조 객체를 만들어 줍니다.&lt;/span&gt; 보조 객체에는 원격 서비스와 똑같은 메소드가 들어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 로컬 JVM에 있는 메소드를 호출하듯 원격 메소드(진짜 서비스 객체에 있는 메소드)를 호출할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 클라이언트가 원격 객체를 찾아서 접근할 때 쓸 수 있는 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;룩업(lookup) 서비스도 RMI에서 제공합니다&lt;/span&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;RMI와 로컬 메소드의 차이점 : RMI는 클라이언트 보조 객체가 네트워크로 호출을 전송해야 하므로 네트워킹 및 입출력 기능이 반드시 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;RMI(Remote Method Invocation)를 사용한 원격 서비스 구현 4단계&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div data-message-model-slug=&quot;gpt-4o&quot; data-message-id=&quot;48baca28-37df-4ac1-abb6-239511402eb9&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 원격 인터페이스 만들기 (Remote Interface)&lt;/b&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;b&gt;목표&lt;/b&gt;: 클라이언트가 원격 객체에서 호출할 메소드를 정의하는 인터페이스를 생성합니다.&lt;/li&gt;
&lt;li&gt;이 인터페이스는 &lt;b&gt;java.rmi.Remote&lt;/b&gt; 인터페이스를 상속하고, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;예외 처리용으로 &lt;b&gt;RemoteException&lt;/b&gt;을 선언&lt;/span&gt;해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;클라이언트&lt;/b&gt;와 &lt;b&gt;서버&lt;/b&gt; 모두 이 인터페이스를 사용하여 통신하게 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;이 인터페이스는 원격 객체의 메소드 호출 방식을 클라이언트와 서버 간에 &lt;b&gt;표준화&lt;/b&gt;해 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1728558408837&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.rmi.Remote;
import java.rmi.RemoteException;

public interface MyService extends Remote {
    // 클라이언트가 원격으로 호출할 메소드 정의
    public String getServiceMessage() throws RemoteException;
}&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;b&gt;2. 서비스 구현 클래스 만들기 (Service Implementation)&lt;/b&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;b&gt;목표&lt;/b&gt;: 원격 인터페이스(MyService)를 구현한 &lt;b&gt;서비스 클래스&lt;/b&gt;를 작성하여, 원격 메소드의 실제 동작을 정의합니다.&lt;/li&gt;
&lt;li&gt;이 클래스는 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;UnicastRemoteObject&lt;/b&gt;를 상속받고, 원격 메소드를 구현&lt;/span&gt;해야 합니다.&lt;/li&gt;
&lt;li&gt;실제 작업을 수행하는 메소드의 구체적인 로직을 이 클래스에 작성합니다.&lt;/li&gt;
&lt;li&gt;예를 들어, Gumball Machine의 getState(), getCount() 같은 메소드를 정의할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1728558452657&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

// MyService 인터페이스 구현 클래스
public class MyServiceImpl extends UnicastRemoteObject implements MyService {

    // 생성자: RemoteException을 던져야 함. 어떤 클래스가 생성될 때 그 슈퍼클래스의 생성자도 반드시
    // 호출되므로 슈퍼클래스 생성자가 어떤 예외를 던진다면 서브 클래스의 생성자도 그 예외를 선언해야 함.
    public MyServiceImpl() throws RemoteException {
        super();
    }

    // 원격으로 호출할 메소드 구현
    @Override
    public String getServiceMessage() throws RemoteException {
        return &quot;원격 서비스에 접근 성공!&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;&lt;b&gt;3. RMI 레지스트리 실행하기 (rmiregistry)&lt;/b&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;b&gt;목표&lt;/b&gt;: &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;RMI 레지스트리&lt;/b&gt;는 원격 객체를 클라이언트가 찾을 수 있도록 &lt;b&gt;&quot;전화번호부&quot;&lt;/b&gt; 역할을 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;레지스트리에는 각 원격 객체의 &lt;b&gt;레퍼런스&lt;/b&gt;가 등록되어 있으며, 클라이언트는 이 레지스트리를 통해 객체를 찾고 접근할 수 있습니다.&lt;/li&gt;
&lt;li&gt;레지스트리를 실행하면, RMI 시스템이 원격 객체의 프록시(Stub)를 생성하고 이를 레지스트리에 등록합니다.&lt;/li&gt;
&lt;li&gt;RMI 레지스트리는 별도의 터미널 창에서 다음 명령어를 사용해 실행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1728558479057&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# RMI 레지스트리 실행 명령어
rmiregistry&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;948&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG40yB/btsJ0DmIC4b/uzRxYiN9OXNq2KHIdPy6vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG40yB/btsJ0DmIC4b/uzRxYiN9OXNq2KHIdPy6vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG40yB/btsJ0DmIC4b/uzRxYiN9OXNq2KHIdPy6vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG40yB%2FbtsJ0DmIC4b%2FuzRxYiN9OXNq2KHIdPy6vk%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;500&quot; height=&quot;328&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;b&gt;4. 원격 서비스 실행 및 등록하기&lt;/b&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;b&gt;목표&lt;/b&gt;: 원격 객체의 인스턴스를 생성하고, 이를 RMI 레지스트리에 &lt;b&gt;등록&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;이 과정에서 서비스 객체의 이름을 RMI 레지스트리에 바인딩하여, 클라이언트가 원격 객체에 접근할 수 있게 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;스텁(Stub)과 스켈레톤(Skeleton)은 &lt;b&gt;RMI 시스템이 자동으로 생성&lt;/b&gt;하므로, 개발자가 직접 작성할 필요가 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;클라이언트는 레지스트리에서 이 이름을 사용하여 원격 객체의 레퍼런스를 가져와 메소드를 호출하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1728558590565&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class MyServiceServer {
    public static void main(String[] args) {
        try {
            // RMI 레지스트리 시작
            LocateRegistry.createRegistry(1099);
            
            // 원격 서비스 객체 생성
            MyService service = new MyServiceImpl();

            // RMI 레지스트리에 원격 객체를 바인딩
            Naming.rebind(&quot;rmi://localhost/MyService&quot;, service);
            
            System.out.println(&quot;원격 서비스가 시작되었습니다.&quot;);
        } catch (RemoteException | java.net.MalformedURLException e) {
            e.printStackTrace();
        }
    }
}&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;클라이언트가 RMI 레지스트리를 사용하여 원격 객체의 스텁(Stub) 객체를 가져와 메소드를 호출하는 과정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728559391837&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.rmi.Naming;

public class MyServiceClient {
    public static void main(String[] args) {
        new MyServiceClient().go();
    }

    // 원격 객체 호출을 수행하는 메소드
    public void go() {
        try {
            // 1. RMI 레지스트리에서 원격 객체 조회 (Object 타입이므로 명시적 캐스팅 필요)
            MyService service = (MyService) Naming.lookup(&quot;rmi://localhost/MyService&quot;);

            // 2. 스텁 객체의 메소드 호출 (원격 메소드 실행)
            String response = service.getServiceMessage();

            // 3. 결과 출력
            System.out.println(&quot;원격 서비스의 응답: &quot; + response);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}&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;6. 뽑기 기계용 원격 프록시&lt;/h2&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-filename=&quot;스크린샷 2024-10-10 오후 8.28.13.png&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOg3B2/btsJ1D0rlzy/VLZQHAWV1fH7wjtFupO6N0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOg3B2/btsJ1D0rlzy/VLZQHAWV1fH7wjtFupO6N0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOg3B2/btsJ1D0rlzy/VLZQHAWV1fH7wjtFupO6N0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOg3B2%2FbtsJ1D0rlzy%2FVLZQHAWV1fH7wjtFupO6N0%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;600&quot; height=&quot;315&quot; data-filename=&quot;스크린샷 2024-10-10 오후 8.28.13.png&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;766&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 프록시를 쓸 수 있도록 가장 먼저 GumballMachine 클래스를 클라이언트로부터 전달된 원격 요청을 처리하도록 바꿉니다.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 원격 인터페이스 정의 (GumballMachineRemote)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GumballMachine의 원격 메소드를 정의하는 인터페이스를 생성합니다. 이 인터페이스는 Remote 인터페이스를 상속받고, &lt;b&gt;원격 호출할 수 있는 메소드들&lt;/b&gt;을 포함합니다. 각 메소드는 RemoteException을 던져야 하며, 리턴 타입은 직렬화 가능해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728559829675&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.rmi.Remote;
import java.rmi.RemoteException;

// GumballMachine의 원격 인터페이스
public interface GumballMachineRemote extends Remote {
    public int getCount() throws RemoteException;  // 알맹이의 개수를 반환
    public String getLocation() throws RemoteException;  // 기계의 위치 반환
    public State getState() throws RemoteException;  // 기계의 현재 상태 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 직렬화 가능한 State 인터페이스&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GumballMachine의 메소드 중 getState()는 State 객체를 반환합니다. State 클래스가 원격 호출에서 사용되려면 &lt;b&gt;Serializable 인터페이스&lt;/b&gt;를 구현해야 합니다. 따라서 State 인터페이스를 다음과 같이 변경합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728559898896&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.Serializable;

// State 인터페이스에 Serializable 추가
public interface State extends Serializable {
    public void insertQuarter();
    public void ejectQuarter();
    public void turnCrank();
    public void dispense();
}&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: #9feec3;&quot;&gt;이렇게 State가 &lt;b&gt;직렬화&lt;/b&gt; 가능해지면, RMI 시스템이 State 객체를 네트워크를 통해 안전하게 전송할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. GumballMachine 클래스에서 원격 인터페이스 구현&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 기존 GumballMachine 클래스를 &lt;b&gt;원격 서비스 객체&lt;/b&gt;로 변경하여 GumballMachineRemote 인터페이스를 구현합니다. 또한, UnicastRemoteObject를 상속받아 RMI 시스템에서 사용할 수 있는 원격 객체로 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728572442827&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

// GumballMachine 클래스가 GumballMachineRemote를 구현하도록 변경
public class GumballMachine extends UnicastRemoteObject implements GumballMachineRemote {
    String location;
    int count;
    State state;

    // 생성자: RemoteException을 던져야 함
    public GumballMachine(String location, int count) throws RemoteException {
        this.location = location;
        this.count = count;
        // 초기 상태 설정 (State 객체는 직렬화 가능해야 함)
        this.state = new NoQuarterState(this);  // 예: NoQuarterState는 State의 직렬화 가능 클래스
    }

    // 원격 메소드 구현
    @Override
    public int getCount() throws RemoteException {
        return count;
    }

    @Override
    public String getLocation() throws RemoteException {
        return location;
    }

    @Override
    public State getState() throws RemoteException {
        return state;
    }

    // 기타 GumballMachine 클래스의 메소드들...
}&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: #9feec3;&quot;&gt;GumballMachine 클래스가 &lt;b&gt;원격 서비스&lt;/b&gt;로 동작하며, RMI 시스템에서 클라이언트가 호출할 수 있는 원격 메소드를 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. GumballMachine 객체를 RMI 레지스트리에 등록&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;GumballMachine을 RMI 레지스트리에 등록하고, 클라이언트가 접근할 수 있도록 설정합니다.&lt;/span&gt; 기존 MyServiceServer 코드와 유사한 방식으로 RMI 레지스트리를 초기화하고, GumballMachine 객체를 등록할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728572657902&quot; class=&quot;actionscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class GumballMachineServer {
    public static void main(String[] args) {
        try {
            // 1. RMI 레지스트리 시작
            LocateRegistry.createRegistry(1099);

            // 2. 원격 GumballMachine 객체 생성
            GumballMachine gumballMachine = new GumballMachine(&quot;Seattle&quot;, 100);

            // 3. RMI 레지스트리에 원격 GumballMachine 객체 바인딩
            Naming.rebind(&quot;rmi://localhost/GumballMachine&quot;, gumballMachine);

            System.out.println(&quot;Gumball Machine 원격 서비스가 시작되었습니다.&quot;);
        } catch (RemoteException | java.net.MalformedURLException e) {
            e.printStackTrace();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 클라이언트 코드 (GumballMonitor)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 클라이언트가 RMI 레지스트리에서 GumballMachineRemote 객체를 가져와서 원격 메소드를 호출할 수 있도록 클라이언트 코드를 작성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728572709545&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.rmi.Naming;

public class GumballMonitorTest {
    public static void main(String[] args) {
        try {
            // RMI 레지스트리에서 GumballMachine 원격 객체 가져오기
            GumballMachineRemote machine = (GumballMachineRemote) Naming.lookup(&quot;rmi://localhost/GumballMachine&quot;);

            // 원격 메소드 호출
            System.out.println(&quot;Gumball Machine 위치: &quot; + machine.getLocation());
            System.out.println(&quot;현재 재고: &quot; + machine.getCount());
            System.out.println(&quot;현재 상태: &quot; + machine.getState());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;GumballMachineRemote&lt;/b&gt; 원격 인터페이스 생성.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;State&lt;/b&gt; 클래스 직렬화 가능하도록 수정.&lt;/li&gt;
&lt;li&gt;GumballMachine 클래스를 원격 서비스로 변환.&lt;/li&gt;
&lt;li&gt;RMI 레지스트리에 등록하여 원격 서비스 설정.&lt;/li&gt;
&lt;li&gt;클라이언트 코드(GumballMonitorTest)를 통해 원격 메소드 호출.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 GumballMachine을 원격 서비스로 변환하여 클라이언트가 네트워크를 통해 접근할 수 있게 만들 수 있습니다.&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;7. 새로운 모니터링 기능 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 CEO가 만들고 싶어했던, 수많은 뽑기 기계를 모니터링 할 수 있는 코드를 작성해봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728573032361&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.rmi.Naming;

public class GumballMonitorTestDrive {
    public static void main(String[] args) {
        // 1. 여러 대의 Gumball Machine이 위치한 RMI URL 배열
        String[] location = {
            &quot;rmi://santafe.mightygumball.com/gumballmachine&quot;,
            &quot;rmi://boulder.mightygumball.com/gumballmachine&quot;,
            &quot;rmi://austin.mightygumball.com/gumballmachine&quot;
        };

        // 2. GumballMonitor 배열 생성 (각 Gumball Machine을 모니터링할 객체)
        GumballMonitor[] monitor = new GumballMonitor[location.length];

        // 3. RMI 레지스트리에서 각 원격 GumballMachine 객체를 가져와 모니터링 객체 생성
        for (int i = 0; i &amp;lt; location.length; i++) {
            try {
                // 3-1. RMI 레지스트리에서 GumballMachine 원격 객체 가져오기
                GumballMachineRemote machine = (GumballMachineRemote) Naming.lookup(location[i]);

                // 3-2. 각 GumballMachineRemote 객체에 대한 GumballMonitor 생성
                monitor[i] = new GumballMonitor(machine);
                System.out.println(&quot;모니터링할 Gumball Machine: &quot; + location[i]);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 4. 각 Gumball Machine의 보고서 출력
        for (int i = 0; i &amp;lt; monitor.length; i++) {
            monitor[i].report();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-11 오전 12.37.30.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ot4SJ/btsJ1duebfS/3k5YarDIuWR2eKCkMM9Jo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ot4SJ/btsJ1duebfS/3k5YarDIuWR2eKCkMM9Jo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ot4SJ/btsJ1duebfS/3k5YarDIuWR2eKCkMM9Jo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fot4SJ%2FbtsJ1duebfS%2F3k5YarDIuWR2eKCkMM9Jo0%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;500&quot; height=&quot;313&quot; data-filename=&quot;스크린샷 2024-10-11 오전 12.37.30.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 프록시 패턴의 정의&lt;/h2&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;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;프록시 패턴&lt;/b&gt;은 &lt;b&gt;특정 객체에 대한 접근을 제어&lt;/b&gt;하는 &lt;b&gt;대리 객체&lt;/b&gt;를 제공하는 패턴입니다.&lt;/span&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;b&gt;실제 객체&lt;/b&gt;의 직접 접근을 제한하거나, 특정 상황에서 &lt;b&gt;추가적인 기능&lt;/b&gt;을 제공하기 위해 사용됩니다. 클라이언트가 직접 접근하지 않고, 프록시를 통해 간접적으로 접근함으로써 &lt;b&gt;접근 권한&lt;/b&gt;을 제어하고, &lt;b&gt;부가적인 로직&lt;/b&gt;을 추가하거나, &lt;b&gt;네트워크 호출&lt;/b&gt; 등을 투명하게 처리할 수 있습니다. =&amp;gt; 다양한 목적으로 쓰인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프록시 패턴의 주요 변형&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 패턴에는 여러 변형이 있으며, 각 변형은 특정한 접근 제어 방법을 사용하여 &lt;b&gt;프록시 객체&lt;/b&gt;의 역할을 다르게 정의합니다. 다음은 대표적인 프록시 패턴의 변형입니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;원격 프록시(Remote Proxy)&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;span style=&quot;background-color: #9feec3;&quot;&gt;원격 프록시는 &lt;b&gt;네트워크 통신&lt;/b&gt;을 통해 &lt;b&gt;원격 서버에 있는 객체&lt;/b&gt;와 클라이언트 사이의 &lt;b&gt;프록시 역할&lt;/b&gt;&lt;/span&gt;을 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예&lt;/b&gt;: GumballMachineRemote를 사용하여 원격 Gumball Machine 객체와 클라이언트 간의 통신을 관리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가상 프록시(Virtual Proxy)&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;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;생성 비용이 높은 객체&lt;/b&gt;를 &lt;b&gt;필요할 때만&lt;/b&gt; 생성하도록 제어합니다.&lt;/span&gt; 객체의 &lt;b&gt;지연 초기화(Lazy Initialization)&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보호 프록시(Protection Proxy)&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;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;접근 권한&lt;/b&gt;을 제어하여 특정 클라이언트만 실제 객체에 접근할 수 있도록 합니다.&lt;/span&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;b&gt;읽기/쓰기 권한&lt;/b&gt;을 다르게 설정하여, 권한이 없는 사용자가 데이터에 접근할 수 없도록 보호.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLyuOi/btsJ0geb7V4/BkWLloydQSYdRDfz2tTq3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLyuOi/btsJ0geb7V4/BkWLloydQSYdRDfz2tTq3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLyuOi/btsJ0geb7V4/BkWLloydQSYdRDfz2tTq3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLyuOi%2FbtsJ0geb7V4%2FBkWLloydQSYdRDfz2tTq3k%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;700&quot; height=&quot;412&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Subject&lt;/b&gt;: &lt;b&gt;공통 인터페이스&lt;/b&gt;로, 실제 객체와 프록시가 &lt;b&gt;같은 메소드&lt;/b&gt;를 가지고 있어야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RealSubject&lt;/b&gt;: 실제 동작을 수행하는 &lt;b&gt;진짜 객체&lt;/b&gt;로, 프록시를 통해 접근하는 대상 객체입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Proxy&lt;/b&gt;: Subject 인터페이스를 구현한 &lt;b&gt;대리자 객체&lt;/b&gt;로, RealSubject와 동일한 메소드를 제공합니다. 프록시는 클라이언트의 요청을 받아 이를 &lt;b&gt;실제 객체&lt;/b&gt;에게 전달하고, 결과를 다시 클라이언트에게 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 원격 프록시와 가상 프록시 비교하기&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;원격 프록시&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 프록시는 다른 JVM에 들어있는 객체의 대리인에 해당하는 로컬 객체입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v4cr8/btsJ0uco1Ma/kDbUAsplJQoQ1JN0UeyjK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v4cr8/btsJ0uco1Ma/kDbUAsplJQoQ1JN0UeyjK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v4cr8/btsJ0uco1Ma/kDbUAsplJQoQ1JN0UeyjK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv4cr8%2FbtsJ0uco1Ma%2FkDbUAsplJQoQ1JN0UeyjK0%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;500&quot; height=&quot;219&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;가상 프록시&lt;/b&gt;&lt;/h4&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;객체 생성 전이나 객체 생성 도중에 객체를 대신하기도 합니다. 객체 생성이 끝나면 그냥 RealSubject에 직접 요청을 전달합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vOPPw/btsJZVIhuI4/ogKgCRMk738dsKbtKDWwFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vOPPw/btsJZVIhuI4/ogKgCRMk738dsKbtKDWwFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vOPPw/btsJZVIhuI4/ogKgCRMk738dsKbtKDWwFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvOPPw%2FbtsJZVIhuI4%2FogKgCRMk738dsKbtKDWwFK%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;500&quot; height=&quot;325&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;가상 프록시를 사용하는 프레임워크 예&lt;/b&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;b&gt;React와 Vue의 Lazy Loading&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동적 import를 통해 &lt;b&gt;필요한 컴포넌트만&lt;/b&gt; 로드하여, 초기 렌더링을 최적화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IntersectionObserver API&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 가상 프록시로 앨범 커버 뷰어 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가상 프록시(Virtual Proxy)&lt;/b&gt;를 사용하여 &lt;b&gt;앨범 커버 뷰어&lt;/b&gt;를 만드는 방식은, 네트워크로부터 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;이미지를 로드하는 동안 사용자에게 &lt;b&gt;로딩 상태를 표시&lt;/b&gt;하고, &lt;b&gt;실제 이미지가 로드될 때&lt;/b&gt; 이를 화면에 보여줌으로써 사용자 경험을 개선하는 디자인 패턴&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tP94w/btsJZ7u3hgI/MLWMbzmlx2cZfb4Jpd4rKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tP94w/btsJZ7u3hgI/MLWMbzmlx2cZfb4Jpd4rKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tP94w/btsJZ7u3hgI/MLWMbzmlx2cZfb4Jpd4rKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtP94w%2FbtsJZ7u3hgI%2FMLWMbzmlx2cZfb4Jpd4rKk%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;700&quot; height=&quot;367&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Icon&lt;/b&gt; (인터페이스):
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;paintIcon(), getIconWidth(), getIconHeight() 등의 메소드를 정의하여 &lt;b&gt;이미지를 화면에 그리는 표준 인터페이스&lt;/b&gt; 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1728578801890&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.awt.Component;
import java.awt.Graphics;

// 공통 인터페이스: 아이콘을 그리기 위한 표준 메소드 정의
public interface Icon {
    void paintIcon(Component c, Graphics g, int x, int y);
    int getIconWidth();
    int getIconHeight();
}&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;b&gt;RealImageIcon&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;Icon 인터페이스를 구현하여 &lt;b&gt;실제 이미지 데이터를 화면에 렌더링&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1728578815641&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.awt.*;
import javax.swing.*;
import java.net.*;

public class RealImageIcon implements Icon {
    private ImageIcon imageIcon;  // 실제 이미지 아이콘
    private URL imageUrl;

    // 생성자: 네트워크 URL에서 이미지를 가져옴
    public RealImageIcon(URL url) {
        this.imageUrl = url;
        try {
            this.imageIcon = new ImageIcon(imageUrl);  // 실제 이미지 로딩
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 이미지를 그리는 메소드
    @Override
    public void paintIcon(Component c, Graphics g, int x, int y) {
        if (imageIcon != null) {
            imageIcon.paintIcon(c, g, x, y);
        }
    }

    @Override
    public int getIconWidth() {
        return (imageIcon != null) ? imageIcon.getIconWidth() : 0;
    }

    @Override
    public int getIconHeight() {
        return (imageIcon != null) ? imageIcon.getIconHeight() : 0;
    }
}&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;b&gt;ImageProxy&lt;/b&gt; (가상 프록시):
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Icon 인터페이스를 구현하지만, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;실제 이미지가 로드되기 전까지 &lt;b&gt;대체 메시지&lt;/b&gt;를 표시합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;이미지가 로드되면 RealImageIcon 객체로 &lt;b&gt;모든 작업을 위임&lt;/b&gt;하여 &lt;b&gt;실제 이미지를 화면에 표시&lt;/b&gt;합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1728578828493&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.awt.*;
import javax.swing.*;
import java.net.*;

public class ImageProxy implements Icon {
    private RealImageIcon realImageIcon;  // 실제 이미지 객체
    private URL imageUrl;
    private boolean retrieving = false;  // 이미지 로드 상태

    public ImageProxy(URL url) {
        this.imageUrl = url;
    }

    @Override
    public void paintIcon(Component c, Graphics g, int x, int y) {
        if (realImageIcon != null) {
            realImageIcon.paintIcon(c, g, x, y);  // 실제 이미지가 있으면 그리기
        } else {
            g.drawString(&quot;앨범 커버를 불러오는 중입니다... 잠시만 기다려 주세요.&quot;, x + 50, y + 50);
            if (!retrieving) {
                retrieving = true;
                new Thread(() -&amp;gt; {  // 비동기 로딩
                    realImageIcon = new RealImageIcon(imageUrl);
                    c.repaint();  // 이미지 로딩 후 화면 갱신
                }).start();
            }
        }
    }

    @Override
    public int getIconWidth() {
        return (realImageIcon != null) ? realImageIcon.getIconWidth() : 800;  // 기본 너비 설정
    }

    @Override
    public int getIconHeight() {
        return (realImageIcon != null) ? realImageIcon.getIconHeight() : 600;  // 기본 높이 설정
    }
}&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;11. 보호 프록시 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;동적 프록시(Dynamic Proxy)&lt;/b&gt;는 자바의 java.lang.reflect 패키지를 사용하여 &lt;b&gt;런타임&lt;/b&gt;에 생성되는 &lt;b&gt;프록시 객체&lt;/b&gt;입니다.&lt;/span&gt; 자바의 &lt;b&gt;동적 프록시&lt;/b&gt;를 사용하면 &lt;b&gt;인터페이스 기반&lt;/b&gt;으로 프록시를 생성하고, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;메소드 호출을 제어&lt;/b&gt;하거나 &lt;b&gt;특정 로직을 동적으로 추가&lt;/b&gt;&lt;/span&gt;할 수 있습니다. 이번에는 동적 프록시를 사용하여 &lt;b&gt;보호 프록시(Protection Proxy)&lt;/b&gt;를 만들어 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;보호 프록시는 주로 &lt;b&gt;권한 제어&lt;/b&gt;를 통해 &lt;b&gt;클라이언트가 특정 메소드에 접근하지 못하게 막는 역할&lt;/b&gt;을 수행합니다.&lt;/span&gt; 예를 들어, 사용자가 자신의 정보를 수정할 수는 있지만, &lt;b&gt;다른 사용자의 정보&lt;/b&gt;를 수정할 수는 없도록 제한하는 로직을 포함할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-11 오전 2.12.39.png&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXhLof/btsJ0d2T8FV/gILa9ifWnY0j4mFpWI3Wfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXhLof/btsJ0d2T8FV/gILa9ifWnY0j4mFpWI3Wfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXhLof/btsJ0d2T8FV/gILa9ifWnY0j4mFpWI3Wfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXhLof%2FbtsJ0d2T8FV%2FgILa9ifWnY0j4mFpWI3Wfk%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;700&quot; height=&quot;387&quot; data-filename=&quot;스크린샷 2024-10-11 오전 2.12.39.png&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인터페이스(Subject)&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;/li&gt;
&lt;li&gt;java.lang.reflect.Proxy는 반드시 &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;b&gt;실제 객체(Real Subject)&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프록시 객체(Proxy)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;java.lang.reflect.Proxy와 InvocationHandler를 사용하여 &lt;b&gt;동적으로 생성&lt;/b&gt;됩니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;InvocationHandler를 구현하여, &lt;b&gt;모든 메소드 호출을 가로채고&lt;/b&gt; 권한을 확인한 후 &lt;b&gt;실제 객체로 메소드를 전달&lt;/b&gt;하거나, 접근이 허용되지 않은 경우 &lt;b&gt;예외를 발생&lt;/b&gt;시킵니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;InvocationHandler&lt;/b&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: #9feec3;&quot;&gt;&lt;b&gt;모든 메소드 호출을 가로채는 역할&lt;/b&gt;을 하며, 메소드 호출 시 &lt;b&gt;접근 제어 로직&lt;/b&gt;을 추가하여 &lt;b&gt;권한이 있는지 확인&lt;/b&gt;합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;invoke() 메소드를 통해 모든 메소드 호출을 처리합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. 객체마을 데이팅 서비스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체마을의 데이팅 서비스에서는 Person 인터페이스를 사용하여 사용자의 &lt;b&gt;이름&lt;/b&gt;, &lt;b&gt;성별&lt;/b&gt;, &lt;b&gt;관심사&lt;/b&gt; 및 &lt;b&gt;괴짜 지수(Geek Rating)&lt;/b&gt;를 설정하고 조회할 수 있습니다. 이 인터페이스를 구현한 PersonImpl 클래스를 통해 사용자의 &lt;b&gt;데이터를 저장&lt;/b&gt;하고, get 및 set 메소드로 데이터를 조작할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 여기서 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;보호 프록시(Protection Proxy)를 사용하여 &lt;b&gt;권한에 따라 접근을 제어&lt;/b&gt;하고, &lt;b&gt;사용자가 자신의 프로필을 수정&lt;/b&gt;할 때와 &lt;b&gt;다른 사용자의 프로필을 평가&lt;/b&gt;할 때 &lt;b&gt;동작을 다르게 설정&lt;/b&gt;할 수 있습니다.&lt;/span&gt; 예를 들어, 사용자가 자신의 프로필을 수정할 수는 있지만, 다른 사용자의 프로필을 수정할 수는 없도록 제한할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 객체마을 데이팅 서비스의 보호 프록시를 사용하여 &lt;b&gt;사용자의 접근 권한을 제어하는 방법&lt;/b&gt;을 구현해 보겠습니다.&lt;/p&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;서비스 시나리오&lt;/b&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;b&gt;데이팅 서비스에는 두 가지 유형의 사용자&lt;/b&gt;가 있습니다:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;소유자(Owner)&lt;/b&gt;: 자신의 정보를 수정하고, &lt;b&gt;이름, 성별, 관심사, 괴짜 지수&lt;/b&gt;를 설정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;게스트(Guest)&lt;/b&gt;: 다른 사람의 프로필을 볼 수 있지만, &lt;b&gt;평가(괴짜 지수 설정)만&lt;/b&gt; 가능합니다. &lt;b&gt;이름, 성별, 관심사&lt;/b&gt;는 변경할 수 없습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 인터페이스 설계&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Person 인터페이스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Person 인터페이스는 사용자의 정보를 설정하고 가져오는 메소드를 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728581132654&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Person {
    String getName();
    void setName(String name);

    String getGender();
    void setGender(String gender);

    String getInterests();
    void setInterests(String interests);

    int getGeekRating();
    void setGeekRating(int rating);
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;PersonImpl 클래스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Person 인터페이스를 구현한 &lt;b&gt;구체 클래스&lt;/b&gt;입니다. 사용자 정보를 저장하고 관리하는 역할을 수행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728581159461&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PersonImpl implements Person {
    private String name;
    private String gender;
    private String interests;
    private int rating;
    private int ratingCount = 0;

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getGender() {
        return gender;
    }

    @Override
    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String getInterests() {
        return interests;
    }

    @Override
    public void setInterests(String interests) {
        this.interests = interests;
    }

    @Override
    public int getGeekRating() {
        if (ratingCount == 0) return 0;
        return (rating / ratingCount);
    }

    @Override
    public void setGeekRating(int rating) {
        this.rating += rating;
        ratingCount++;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 보호 프록시 설계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;InvocationHandler 구현&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 접근 제어 방식이 필요합니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;OwnerInvocationHandler&lt;/b&gt;: &lt;b&gt;소유자&lt;/b&gt;가 자신의 프로필을 &lt;b&gt;전체 수정&lt;/b&gt;할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;NonOwnerInvocationHandler&lt;/b&gt;: &lt;b&gt;게스트&lt;/b&gt;는 &lt;b&gt;프로필 평가(괴짜 지수 설정)&lt;/b&gt;만 가능하고, &lt;b&gt;다른 필드&lt;/b&gt;는 수정할 수 없습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1728581198940&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.lang.reflect.*;

public class OwnerInvocationHandler implements InvocationHandler {
    private Person person;

    public OwnerInvocationHandler(Person person) {
        this.person = person;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
        try {
            if (method.getName().startsWith(&quot;get&quot;)) {
                return method.invoke(person, args);
            } else if (method.getName().equals(&quot;setGeekRating&quot;)) {
                throw new IllegalAccessException(&quot;소유자는 자기 자신의 괴짜 지수를 설정할 수 없습니다.&quot;);
            } else if (method.getName().startsWith(&quot;set&quot;)) {
                return method.invoke(person, args);
            }
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
}

public class NonOwnerInvocationHandler implements InvocationHandler {
    private Person person;

    public NonOwnerInvocationHandler(Person person) {
        this.person = person;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
        try {
            if (method.getName().startsWith(&quot;get&quot;)) {
                return method.invoke(person, args);
            } else if (method.getName().equals(&quot;setGeekRating&quot;)) {
                return method.invoke(person, args);
            } else if (method.getName().startsWith(&quot;set&quot;)) {
                throw new IllegalAccessException(&quot;게스트는 프로필을 수정할 수 없습니다.&quot;);
            }
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;프록시 생성기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Proxy 클래스를 사용하여, Person 객체의 &lt;b&gt;프록시 객체&lt;/b&gt;를 생성합니다. OwnerInvocationHandler와 NonOwnerInvocationHandler에 따라 &lt;b&gt;소유자&lt;/b&gt;와 &lt;b&gt;게스트&lt;/b&gt; 권한을 다르게 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728581226096&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ProxyFactory {
    public static Person getOwnerProxy(Person person) {
        return (Person) Proxy.newProxyInstance(
                person.getClass().getClassLoader(),
                person.getClass().getInterfaces(),
                new OwnerInvocationHandler(person)
        );
    }

    public static Person getNonOwnerProxy(Person person) {
        return (Person) Proxy.newProxyInstance(
                person.getClass().getClassLoader(),
                person.getClass().getInterfaces(),
                new NonOwnerInvocationHandler(person)
        );
    }
}&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;3. 테스트 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;소유자와 게스트의 권한을 테스트하여, 보호 프록시가 권한에 따라 동작하는지 확인합니다.&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1728581244828&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DatingServiceTest {
    public static void main(String[] args) {
        Person joe = new PersonImpl();
        joe.setName(&quot;Joe Javabean&quot;);
        joe.setGender(&quot;Male&quot;);
        joe.setInterests(&quot;Programming, Tennis, Movies&quot;);

        // 1. 소유자 프록시 생성
        Person ownerProxy = ProxyFactory.getOwnerProxy(joe);
        System.out.println(&quot;이름: &quot; + ownerProxy.getName());
        ownerProxy.setInterests(&quot;Programming and playing games&quot;);
        System.out.println(&quot;관심사: &quot; + ownerProxy.getInterests());

        try {
            ownerProxy.setGeekRating(10);  // 소유자가 자신의 괴짜 지수를 설정하려고 하면 예외 발생
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        // 2. 게스트 프록시 생성
        Person nonOwnerProxy = ProxyFactory.getNonOwnerProxy(joe);
        System.out.println(&quot;\n게스트 권한 테스트:&quot;);
        System.out.println(&quot;이름: &quot; + nonOwnerProxy.getName());
        try {
            nonOwnerProxy.setInterests(&quot;Watching movies&quot;);  // 게스트가 관심사를 설정하려고 하면 예외 발생
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        nonOwnerProxy.setGeekRating(5);  // 게스트는 괴짜 지수를 설정할 수 있음
        System.out.println(&quot;괴짜 지수: &quot; + nonOwnerProxy.getGeekRating());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;결과&lt;/b&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;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;/li&gt;
&lt;/ul&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구현하면 &lt;b&gt;보호 프록시(Protection Proxy)&lt;/b&gt;를 통해 &lt;b&gt;접근 권한을 제어&lt;/b&gt;하고, &lt;b&gt;사용자 권한에 따라 메소드 접근을 제한&lt;/b&gt;할 수 있습니다. 이 패턴을 통해 &lt;b&gt;사용자의 잘못된 접근을 방지&lt;/b&gt;하고, &lt;b&gt;안정적인 시스템 제어&lt;/b&gt;를 할 수 있습니다.&lt;/p&gt;</description>
      <category>개발서적/헤드퍼스트 디자인패턴</category>
      <author>SWKo</author>
      <guid isPermaLink="true">https://sw-ko.tistory.com/475</guid>
      <comments>https://sw-ko.tistory.com/475#entry475comment</comments>
      <pubDate>Wed, 9 Oct 2024 23:50:40 +0900</pubDate>
    </item>
    <item>
      <title>[개발서적] 헤드퍼스트 디자인 패턴 Ch10. 상태 패턴</title>
      <link>https://sw-ko.tistory.com/474</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 뽑기 기계 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뽑기 기계 회사는 최근 기술 발달에 맞춰 뽑기 기계에 CPU를 탑재하여 매출을 늘리고, 네트워크 연결을 통해 재고 관리 및 고객 만족도 집계를 하려는 목표를 가지고 있습니다.&lt;/p&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;1404&quot; data-origin-height=&quot;1042&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNaxsz/btsJHzSaegV/a6YbIxYK6DDCVViHyydmB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNaxsz/btsJHzSaegV/a6YbIxYK6DDCVViHyydmB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNaxsz/btsJHzSaegV/a6YbIxYK6DDCVViHyydmB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNaxsz%2FbtsJHzSaegV%2Fa6YbIxYK6DDCVViHyydmB1%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;600&quot; height=&quot;445&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;1042&quot;/&gt;&lt;/span&gt;&lt;/figure&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;1. 상태 정의 및 상태 변수 설정&lt;/p&gt;
&lt;pre id=&quot;code_1726995463268&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 상태 정의
final static int SOLD_OUT = 0;   // 매진 상태
final static int NO_QUARTER = 1; // 동전 없음 상태
final static int HAS_QUARTER = 2; // 동전 있음 상태
final static int SOLD = 3;       // 알맹이 판매 상태

// 현재 상태를 저장하는 변수
int state = SOLD_OUT;  // 처음에는 매진 상태로 시작&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;2. 행동 정의&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 행동을 상태에 따라 구현&lt;/p&gt;
&lt;pre id=&quot;code_1727017996787&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void insertQuarter() {
    if (state == HAS_QUARTER) {
        System.out.println(&quot;동전은 한 개만 넣어주세요.&quot;);
    } else if (state == NO_QUARTER) {
        state = HAS_QUARTER;  // 상태 변경: 동전 있음
        System.out.println(&quot;동전이 투입되었습니다.&quot;);
    } else if (state == SOLD_OUT) {
        System.out.println(&quot;매진되었습니다. 다음 기회에 이용해주세요.&quot;);
    } else if (state == SOLD) {
        System.out.println(&quot;알맹이를 내보내고 있습니다. 잠시만 기다려주세요.&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;&lt;b&gt;뽑기 기계 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727018113477&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GumballMachine {
    // 상태 정의
    final static int SOLD_OUT = 0;
    final static int NO_QUARTER = 1;
    final static int HAS_QUARTER = 2;
    final static int SOLD = 3;
    
    // 현재 상태를 저장하는 변수
    int state = SOLD_OUT;
    // 알맹이 개수를 저장하는 변수
    int count = 0;
    
    // 생성자: 알맹이 개수를 초기화
    public GumballMachine(int count) {
        this.count = count;
        if (count &amp;gt; 0) {
            state = NO_QUARTER;  // 알맹이가 있으면 동전 대기 상태로
        }
    }

    // 동전 투입
    public void insertQuarter() {
        if (state == HAS_QUARTER) {
            System.out.println(&quot;동전은 한 개만 넣어 주세요.&quot;);
        } else if (state == NO_QUARTER) {
            state = HAS_QUARTER;
            System.out.println(&quot;동전을 넣으셨습니다.&quot;);
        } else if (state == SOLD_OUT) {
            System.out.println(&quot;매진되었습니다. 다음 기회에 이용해 주세요.&quot;);
        } else if (state == SOLD) {
            System.out.println(&quot;알맹이를 내보내고 있습니다.&quot;);
        }
    }

    // 동전 반환
    public void ejectQuarter() {
        if (state == HAS_QUARTER) {
            System.out.println(&quot;동전이 반환됩니다.&quot;);
            state = NO_QUARTER;
        } else if (state == NO_QUARTER) {
            System.out.println(&quot;동전을 넣어 주세요.&quot;);
        } else if (state == SOLD) {
            System.out.println(&quot;이미 알맹이를 뽑으셨습니다.&quot;);
        } else if (state == SOLD_OUT) {
            System.out.println(&quot;동전을 넣지 않으셨습니다. 동전이 반환되지 않습니다.&quot;);
        }
    }

    // 손잡이 돌림
    public void turnCrank() {
        if (state == SOLD) {
            System.out.println(&quot;손잡이는 한 번만 돌려 주세요.&quot;);
        } else if (state == NO_QUARTER) {
            System.out.println(&quot;동전을 넣어 주세요.&quot;);
        } else if (state == SOLD_OUT) {
            System.out.println(&quot;매진되었습니다.&quot;);
        } else if (state == HAS_QUARTER) {
            System.out.println(&quot;손잡이를 돌리셨습니다.&quot;);
            state = SOLD;
            dispense();
        }
    }

    // 알맹이 내보내기
    public void dispense() {
        if (state == SOLD) {
            System.out.println(&quot;알맹이를 내보내고 있습니다.&quot;);
            count = count - 1;
            if (count == 0) {
                System.out.println(&quot;더 이상 알맹이가 없습니다.&quot;);
                state = SOLD_OUT;
            } else {
                state = NO_QUARTER;
            }
        } else if (state == NO_QUARTER) {
            System.out.println(&quot;동전을 넣어 주세요.&quot;);
        } else if (state == SOLD_OUT) {
            System.out.println(&quot;알맹이가 없습니다.&quot;);
        } else if (state == HAS_QUARTER) {
            System.out.println(&quot;알맹이를 내보낼 수 없습니다.&quot;);
        }
    }

    // 기타 메소드: 기계 상태 출력
    @Override
    public String toString() {
        return &quot;남은 알맹이 개수: &quot; + count + &quot;, 현재 상태: &quot; + getStateString();
    }

    // 상태 문자열 반환
    private String getStateString() {
        switch (state) {
            case SOLD_OUT: return &quot;매진&quot;;
            case NO_QUARTER: return &quot;동전 대기&quot;;
            case HAS_QUARTER: return &quot;동전 있음&quot;;
            case SOLD: return &quot;알맹이 판매 중&quot;;
            default: return &quot;알 수 없음&quot;;
        }
    }
    
    // 재고를 채우는 메소드
    public void refill(int numGumballs) {
        this.count = numGumballs;
        state = NO_QUARTER;
        System.out.println(&quot;재고가 보충되었습니다. 현재 알맹이 개수: &quot; + count);
    }
}&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;b&gt;사용 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727018133707&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GumballMachineTestDrive {
    public static void main(String[] args) {
        GumballMachine gumballMachine = new GumballMachine(5);

        System.out.println(gumballMachine);

        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();

        System.out.println(gumballMachine);

        gumballMachine.insertQuarter();
        gumballMachine.ejectQuarter();
        gumballMachine.turnCrank();

        System.out.println(gumballMachine);
    }
}&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;b&gt;출력 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727018153740&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;남은 알맹이 개수: 5, 현재 상태: 동전 대기
동전을 넣으셨습니다.
손잡이를 돌리셨습니다.
알맹이를 내보내고 있습니다.
남은 알맹이 개수: 4, 현재 상태: 동전 대기
동전을 넣으셨습니다.
동전이 반환됩니다.
동전을 넣어 주세요.
남은 알맹이 개수: 4, 현재 상태: 동전 대기&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뽑기에 게임 기능을 추가하려면 WINNER 상태를 추가해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 새로 추가된 WINNER 상태를 확인하는 조건문을 모든 메서드에 추가해줘야 하는 문제점이 생겼습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 새로운 디자인 구상하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코드를 활용하는 대신 상태 객체를 별도 코드에 넣고, 어떤 행동이 일어나면 현재 상태 객체에서 필요한 작업을 처리하게 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;뽑기 기계 행동에 관한 메서드가 들어있는 State 인터페이스를 정의해야 합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;기계의 모든 상태를 대상으로 상태 클래스를 구현해야 합니다.&lt;/li&gt;
&lt;li&gt;조건문 코드를 모두 없애고, 상태 클래스에 모든 작업을 위임합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 상태 클래스에서 구현할 State Interface를 정의해봅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zgrOx/btsJI0Vgqf4/GGKhUcE3noHT5sKyOe1shk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zgrOx/btsJI0Vgqf4/GGKhUcE3noHT5sKyOe1shk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zgrOx/btsJI0Vgqf4/GGKhUcE3noHT5sKyOe1shk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzgrOx%2FbtsJI0Vgqf4%2FGGKhUcE3noHT5sKyOe1shk%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;400&quot; height=&quot;417&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;852&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;b&gt;NoQuarterState&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727019173797&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class NoQuarterState implements State {
    GumballMachine gumballMachine;

    // GumballMachine 인스턴스를 전달받는 생성자
    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    // 동전이 투입되었을 때 처리
    public void insertQuarter() {
        System.out.println(&quot;동전을 넣으셨습니다.&quot;);
        // 상태를 HasQuarterState로 전환
        gumballMachine.setState(gumballMachine.getHasQuarterState());
    }

    // 동전을 반환할 때 처리
    public void ejectQuarter() {
        System.out.println(&quot;동전을 넣지 않았습니다.&quot;);
    }

    // 손잡이를 돌렸을 때 처리
    public void turnCrank() {
        System.out.println(&quot;동전을 넣어 주세요.&quot;);
    }

    // 알맹이를 내보낼 때 처리
    public void dispense() {
        System.out.println(&quot;동전을 넣어 주세요.&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;&lt;b&gt;HasQuarterState&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727019973136&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class HasQuarterState implements State {
    GumballMachine gumballMachine;

    public HasQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    // 동전이 이미 들어 있는 상태에서 동전을 다시 넣으려 할 때
    public void insertQuarter() {
        System.out.println(&quot;동전은 한 개만 넣어 주세요.&quot;);
    }

    // 동전 반환 요청 시, 동전을 반환하고 상태를 동전 없음 상태로 전환
    public void ejectQuarter() {
        System.out.println(&quot;동전이 반환됩니다.&quot;);
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    // 손잡이를 돌리면 알맹이를 내보낼 준비가 되고 상태를 판매 상태로 전환
    public void turnCrank() {
        System.out.println(&quot;손잡이를 돌리셨습니다.&quot;);
        gumballMachine.setState(gumballMachine.getSoldState());
    }

    // 아직 손잡이를 돌리지 않았으므로 알맹이를 내보낼 수 없음
    public void dispense() {
        System.out.println(&quot;알맹이를 내보낼 수 없습니다.&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;&lt;b&gt;SoldState&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727019995850&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SoldState implements State {
    GumballMachine gumballMachine;

    // 생성자: GumballMachine 인스턴스를 받음
    public SoldState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    // 동전 투입: 이미 알맹이를 내보내는 중이므로 부적절한 행동
    public void insertQuarter() {
        System.out.println(&quot;알맹이를 내보내고 있습니다.&quot;);
    }

    // 동전 반환: 이미 알맹이가 나갔기 때문에 동전을 반환할 수 없음
    public void ejectQuarter() {
        System.out.println(&quot;이미 알맹이를 뽑으셨습니다.&quot;);
    }

    // 손잡이 돌림: 이미 돌렸으므로 다시 돌리는 것은 부적절함
    public void turnCrank() {
        System.out.println(&quot;손잡이는 한 번만 돌려 주세요.&quot;);
    }

    // 알맹이 내보내기
    public void dispense() {
        // 알맹이를 내보내는 작업
        gumballMachine.releaseBall();

        // 남은 알맹이가 있는지 확인
        if (gumballMachine.getCount() &amp;gt; 0) {
            // 알맹이가 남아 있다면 상태를 NoQuarterState로 전환
            gumballMachine.setState(gumballMachine.getNoQuarterState());
        } else {
            // 알맹이가 없다면 SoldOutState로 전환
            System.out.println(&quot;Oops, out of gumballs!&quot;);
            gumballMachine.setState(gumballMachine.getSoldOutState());
        }
    }
}&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;b&gt;SoldoutState&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727020014123&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SoldOutState implements State {
    GumballMachine gumballMachine;

    // 생성자: GumballMachine 인스턴스를 받음
    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    // 동전 투입: 매진 상태이므로 동전을 받을 수 없음
    public void insertQuarter() {
        System.out.println(&quot;매진되었습니다. 동전을 넣을 수 없습니다.&quot;);
    }

    // 동전 반환: 동전을 넣을 수 없으므로 반환할 동전도 없음
    public void ejectQuarter() {
        System.out.println(&quot;동전을 넣지 않으셨습니다. 반환할 동전이 없습니다.&quot;);
    }

    // 손잡이 돌림: 알맹이가 없으므로 손잡이를 돌려도 소용없음
    public void turnCrank() {
        System.out.println(&quot;매진되었습니다.&quot;);
    }

    // 알맹이 내보내기: 알맹이가 없으므로 내보낼 수 없음
    public void dispense() {
        System.out.println(&quot;알맹이가 없습니다.&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;뽑기 기계 코드를 수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727019730445&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GumballMachine {
    // 상태 객체 선언
    State soldOutState;
    State noQuarterState;
    State hasQuarterState;
    State soldState;

    // 현재 상태를 저장하는 변수
    State state;
    // 알맹이 개수를 저장하는 변수
    int count = 0;

    // 생성자: 알맹이 개수를 설정하고 상태 객체들을 초기화
    public GumballMachine(int numberGumballs) {
        // 상태 객체 생성
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldState = new SoldState(this);
        
        // 알맹이 개수 설정
        this.count = numberGumballs;

        // 알맹이가 있으면 noQuarterState로, 없으면 soldOutState로 초기화
        if (numberGumballs &amp;gt; 0) {
            state = noQuarterState;
        } else {
            state = soldOutState;
        }
    }

    // 동전 투입
    public void insertQuarter() {
        state.insertQuarter();
    }

    // 동전 반환
    public void ejectQuarter() {
        state.ejectQuarter();
    }

    // 손잡이 돌리기
    public void turnCrank() {
        state.turnCrank();
        state.dispense();  // 알맹이 내보내기
    }

    // 상태 전환
    void setState(State state) {
        this.state = state;
    }

    // 알맹이 내보내기 (보조 메소드)
    void releaseBall() {
        System.out.println(&quot;알맹이를 내보내고 있습니다.&quot;);
        if (count &amp;gt; 0) {
            count = count - 1;
        }
    }

    // 현재 남은 알맹이 개수를 반환
    public int getCount() {
        return count;
    }

    // 상태 객체 반환 메소드들
    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }

    // 재고를 보충하는 메소드
    public void refill(int numGumballs) {
        this.count += numGumballs;
        System.out.println(&quot;재고가 보충되었습니다. 현재 알맹이 개수: &quot; + this.count);
        if (count &amp;gt; 0) {
            state = noQuarterState;
        }
    }

    @Override
    public String toString() {
        return &quot;GumballMachine{알맹이 개수: &quot; + count + &quot;, 현재 상태: &quot; + state.getClass().getSimpleName() + &quot;}&quot;;
    }
}&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;3. 뽑기 기계 구조 다시 살펴보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 구현한 &lt;b&gt;GumballMachine&lt;/b&gt; 클래스 구조는 &lt;b&gt;상태 패턴&lt;/b&gt;을 사용하여 개선되었으며, 초기의 조건문 기반 구조와는 큰 차이가 있습니다. 하지만, 기능적으로는 완전히 동일한 동작을 수행합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상태의 행동을 별도의 클래스로 분리&lt;br /&gt;상태 국지화, 모듈화&lt;/li&gt;
&lt;li&gt;복잡한 조건문 제거&lt;/li&gt;
&lt;li&gt;OCP(Open-Closed Principle) 적용&lt;br /&gt;GumballMachine은 새로운 상태를 추가할 때 기존 코드를 수정할 필요 없이(Closed) 새로운 상태 클래스를 추가하면 됩니다.(Open)&lt;/li&gt;
&lt;li&gt;더 직관적이고 이해하기 쉬운 구조&lt;br /&gt;상태마다 독립적인 클래스가 있으므로, 모드를 읽는 사람이 상태별로 어떤 일이 벌어지는지 쉽게 알 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 상태 패턴의 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;상태 패턴(State Pattern)을 사용하면 객체의 내부 상태가 바뀜에 따라 객체의 행동을 바꿀 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있습니다.&lt;/span&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;&lt;b&gt;1. &quot;객체의 내부 상태가 바뀜에 따라 객체의 행동을 바꿀 수 있습니다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 패턴에서는 &lt;b&gt;상태를 별도의 클래스로 캡슐화&lt;/b&gt;하고, 객체는 현재 상태를 나타내는 객체에게 &lt;b&gt;행동을 위임&lt;/b&gt;합니다. 이로 인해 &lt;b&gt;내부 상태가 바뀔 때&lt;/b&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;b&gt;NoQuarterState&lt;/b&gt;일 때: 동전을 넣으면 기계가 동전을 받아들임.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HasQuarterState&lt;/b&gt;일 때: 이미 동전이 들어 있으므로, 다시 동전을 넣으면 기계가 동전을 받아들이지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 상태가 달라짐에 따라 &lt;b&gt;동일한 메소드 호출이 다른 결과&lt;/b&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;&lt;b&gt;2. &quot;클래스가 바뀌는 것 같은 결과&quot;&lt;/b&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;예를 들어, 사용자는 뽑기 기계가 &lt;b&gt;NoQuarterState&lt;/b&gt;와 &lt;b&gt;HasQuarterState&lt;/b&gt;에서 완전히 다른 방식으로 동작하는 것을 보고, 마치 기계가 &lt;b&gt;다른 클래스&lt;/b&gt;로 변신한 것처럼 느낄 수 있습니다. 이는 상태 객체를 바꿔서 처리하는 것이므로, 객체가 실제로 다른 클래스로 변신하지는 않지만 &lt;b&gt;다른 클래스로 바뀐 것 같은&lt;/b&gt; 효과를 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-26 오전 9.48.48.png&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JMQJZ/btsJLuw1WUD/5TnkRRJLQWP1EoJoV2r9g0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JMQJZ/btsJLuw1WUD/5TnkRRJLQWP1EoJoV2r9g0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JMQJZ/btsJLuw1WUD/5TnkRRJLQWP1EoJoV2r9g0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJMQJZ%2FbtsJLuw1WUD%2F5TnkRRJLQWP1EoJoV2r9g0%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;700&quot; height=&quot;349&quot; data-filename=&quot;스크린샷 2024-09-26 오전 9.48.48.png&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Context(문맥, 예: GumballMachine)&lt;/b&gt;: 상태 객체를 관리하고, 상태에 따른 행동을 위임합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;State(상태 인터페이스)&lt;/b&gt;: 상태가 수행해야 할 행동을 정의합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ConcreteState(구체적인 상태, 예: NoQuarterState, HasQuarterState)&lt;/b&gt;: 상태 인터페이스를 구현하며, 상태별로 구체적인 동작을 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 전략 패턴 vs 상태 패턴&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;b&gt;별도의 클래스&lt;/b&gt;로 캡슐화하고, &lt;b&gt;Context 객체&lt;/b&gt;에서 그 행동을 &lt;b&gt;위임&lt;/b&gt;받아 수행합니다.&lt;/li&gt;
&lt;li&gt;다이어그램 상으로는 &lt;b&gt;Context&lt;/b&gt;와 &lt;b&gt;State/Strategy&lt;/b&gt; 객체들이 존재하며, &lt;b&gt;Context&lt;/b&gt; 객체는 행동의 변화를 그 객체에 위임하여 처리한다는 공통점을 가지고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차이점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 상태 패턴&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;b&gt;다른 상태 객체&lt;/b&gt;로 전환됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동작 방식&lt;/b&gt;: &lt;b&gt;Context&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;: 뽑기 기계에서 동전이 없을 때(상태 1), 동전이 있을 때(상태 2), 매진일 때(상태 3) 등 상태에 따라 행동이 달라집니다. 상태 전환은 내부적으로 이루어집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 전략 패턴&lt;/h4&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;b&gt;Context&lt;/b&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;b&gt;클라이언트가 지정&lt;/b&gt;합니다. 각 오리에게 날 수 있는 전략이나 날지 못하는 전략을 선택해서 적용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 핵심 차이점&lt;/h4&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;Context 객체가 스스로 변화&lt;/b&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;b&gt;적절한 전략을 직접 설정&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 보너스 알맹이 당첨 기능 추가하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10번에 1번 꼴로 알맹이를 하나 더 주는 기능을 추가해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GumballMachine 클래스에 먼저 상태를 추가해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727312703702&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GumballMachine {
    State soldOutState;
    State noQuarterState;
    State hasQuarterState;
    State soldState;
    State winnerState;  // WinnerState 추가

    State state;
    int count = 0;

    public GumballMachine(int numberGumballs) {
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldState = new SoldState(this);
        winnerState = new WinnerState(this);  // WinnerState 초기화

        this.count = numberGumballs;
        if (numberGumballs &amp;gt; 0) {
            state = noQuarterState;
        } else {
            state = soldOutState;
        }
    }

    // 상태 전환 메소드
    public void setState(State state) {
        this.state = state;
    }

    public void releaseBall() {
        System.out.println(&quot;알맹이가 나왔습니다.&quot;);
        if (count &amp;gt; 0) {
            count--;
        }
    }

    // 알맹이 개수 반환 메소드
    public int getCount() {
        return count;
    }

    // 각 상태 객체에 대한 게터 메소드들
    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }

    public State getWinnerState() {
        return winnerState;  // WinnerState 게터
    }
}&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;b&gt;WinnerState 클래스 구현&lt;/b&gt; - 당첨되었을 때 알맹이를 2개 배출하는 역할, 당첨 시에 첫 번째 알맹이를 배출 후, 기계에 알맹이가 남아있으면 추가로 한 개를 더 배출합합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727312768423&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class WinnerState implements State {
    GumballMachine gumballMachine;

    // 생성자
    public WinnerState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    // 동전 투입 시 오류 메시지
    public void insertQuarter() {
        System.out.println(&quot;알맹이가 나오는 중입니다. 잠시만 기다려 주세요.&quot;);
    }

    // 동전 반환 시 오류 메시지
    public void ejectQuarter() {
        System.out.println(&quot;이미 알맹이를 뽑으셨습니다.&quot;);
    }

    // 손잡이 돌림 시 오류 메시지
    public void turnCrank() {
        System.out.println(&quot;손잡이는 한 번만 돌려 주세요.&quot;);
    }

    // 알맹이 배출
    public void dispense() {
        // 첫 번째 알맹이 배출
        gumballMachine.releaseBall();
        if (gumballMachine.getCount() == 0) {
            gumballMachine.setState(gumballMachine.getSoldOutState());
        } else {
            // 두 번째 알맹이 배출
            System.out.println(&quot;축하드립니다! 알맹이를 하나 더 받으실 수 있습니다.&quot;);
            gumballMachine.releaseBall();
            if (gumballMachine.getCount() &amp;gt; 0) {
                gumballMachine.setState(gumballMachine.getNoQuarterState());
            } else {
                gumballMachine.setState(gumballMachine.getSoldOutState());
            }
        }
    }
}&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GumballMachine에서 WinnerState로 상태 전환하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727312852160&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class HasQuarterState implements State {
    GumballMachine gumballMachine;

    public HasQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    public void insertQuarter() {
        System.out.println(&quot;동전은 한 개만 넣어 주세요.&quot;);
    }

    public void ejectQuarter() {
        System.out.println(&quot;동전이 반환됩니다.&quot;);
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    public void turnCrank() {
        System.out.println(&quot;손잡이를 돌리셨습니다.&quot;);
        // 10번에 1번 꼴로 WinnerState로 전환
        if (Math.random() &amp;lt; 0.1 &amp;amp;&amp;amp; gumballMachine.getCount() &amp;gt; 1) {
            gumballMachine.setState(gumballMachine.getWinnerState());
        } else {
            gumballMachine.setState(gumballMachine.getSoldState());
        }
    }

    public void dispense() {
        System.out.println(&quot;알맹이를 내보낼 수 없습니다.&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;&lt;b&gt;테스트 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1727312925115&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GumballMachineTestDrive {
    public static void main(String[] args) {
        // 뽑기 기계에 5개의 알맹이를 넣고 생성
        GumballMachine gumballMachine = new GumballMachine(5);

        // 현재 기계 상태 출력
        System.out.println(gumballMachine);

        // 동전 투입 및 손잡이 돌리기
        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();

        // 현재 상태 출력
        System.out.println(gumballMachine);

        // 다시 동전 투입 및 손잡이 돌리기
        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();

        // 동전 투입 및 손잡이 돌리기
        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();

        // 마지막 상태 출력
        System.out.println(gumballMachine);
    }
}&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;860&quot; data-origin-height=&quot;1156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6prPe/btsJMxzgIqo/cCDrOG3Sdon3VgCN8zLIU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6prPe/btsJMxzgIqo/cCDrOG3Sdon3VgCN8zLIU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6prPe/btsJMxzgIqo/cCDrOG3Sdon3VgCN8zLIU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6prPe%2FbtsJMxzgIqo%2FcCDrOG3Sdon3VgCN8zLIU1%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;400&quot; height=&quot;538&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;1156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 정상성&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;점검(sanity check)하기&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. &lt;b&gt;SoldState와 WinnerState의 중복 코드&lt;/b&gt;&lt;/h4&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;: SoldState와 WinnerState에서 알맹이를 내보내는 동작이 중복되어 있습니다. 비슷한 로직이 반복되므로 &lt;b&gt;코드 중복&lt;/b&gt; 문제가 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결책&lt;/b&gt;: 중복된 코드를 해결하려면, &lt;b&gt;State를 추상 클래스로 만들어&lt;/b&gt; 공통 기능을 상속받도록 하면 됩니다. dispense()와 같은 공통 메소드를 추상 클래스에서 정의하고, 상태별로 필요한 부분만 오버라이드하면 됩니다. 각 상태 클래스는 &lt;b&gt;공통 기능&lt;/b&gt;을 상속받고, 상태 전환 로직만 다르게 구현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1727313089671&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class AbstractState implements State {
    GumballMachine gumballMachine;

    public AbstractState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    public void insertQuarter() {
        System.out.println(&quot;동전은 한 개만 넣어 주세요.&quot;);
    }

    public void ejectQuarter() {
        System.out.println(&quot;이미 알맹이를 뽑으셨습니다.&quot;);
    }

    public void turnCrank() {
        System.out.println(&quot;손잡이는 한 번만 돌려 주세요.&quot;);
    }

    // 공통 dispense() 메소드
    public void dispense() {
        System.out.println(&quot;알맹이를 내보낼 수 없습니다.&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. &lt;b&gt;dispense() 메소드 호출 문제&lt;/b&gt;&lt;/h4&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;: dispense() 메소드가 동전 없이 손잡이를 돌렸을 때도 호출됩니다. 잘못된 상태에서 호출되더라도 알맹이는 배출되지 않지만, 비효율적인 호출이 발생할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결책 1. &lt;b&gt;불리언 값을 리턴하는 방식&lt;/b&gt;&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;turnCrank() 메소드가 성공적으로 동작했는지를 불리언 값으로 리턴하여, 그 값에 따라 dispense()를 호출할지 말지를 결정할 수 있습니다.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1727313273546&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public boolean turnCrank() {
    if (state == hasQuarterState || state == winnerState) {
        state.turnCrank();
        state.dispense();
        return true;
    }
    return false;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;해결책 2. 예외 처리 방식&lt;/b&gt;&lt;br /&gt;예외 처리를 도입하여, 잘못된 상태에서 호출될 때 예외를 던지게 할 수 있습니다. 이는 잘못된 상태에서의 호출을 명확하게 처리할 수 있지만, 예외 처리가 복잡해질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1727313491454&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void dispense() throws IllegalStateException {
    if (state != hasQuarterState &amp;amp;&amp;amp; state != winnerState) {
        throw new IllegalStateException(&quot;잘못된 상태에서 dispense 호출&quot;);
    }
    state.dispense();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. &lt;b&gt;상태 전환 정보를 상태 클래스에 넣는 문제&lt;/b&gt;&lt;/h4&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;&lt;b&gt;해결책&lt;/b&gt;: 상태 전환 로직을 &lt;b&gt;GumballMachine&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;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;h4 data-ke-size=&quot;size20&quot;&gt;4. &lt;b&gt;상태 인스턴스를 정적 변수로 만들어 공유하는 문제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;: 여러 개의 GumballMachine 인스턴스를 만들 때 &lt;b&gt;각 기계가 상태 인스턴스를 공유할 수 있도록&lt;/b&gt; 개선할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결책&lt;/b&gt;: 상태 인스턴스를 &lt;b&gt;정적(static) 변수&lt;/b&gt;로 만들어, 모든 기계가 상태 인스턴스를 공유하도록 설계할 수 있습니다. 이를 통해 상태 인스턴스의 재사용이 가능해져 &lt;b&gt;메모리 효율&lt;/b&gt;을 높일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1727313703729&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GumballMachine {
    private static final State soldOutState = new SoldOutState();
    private static final State noQuarterState = new NoQuarterState();
    private static final State hasQuarterState = new HasQuarterState();
    private static final State soldState = new SoldState();
    private static final State winnerState = new WinnerState();

    // 상태 전환 로직
}&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;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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;FE 상태 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드에서도 **상태 패턴(State Pattern)**을 적용할 수 있습니다. 특히 상태에 따라 UI가 변화해야 하거나 사용자 인터랙션에 따라 여러 동작이 다르게 수행되는 경우에 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;b&gt;React&lt;/b&gt;에서 상태 패턴을 적용하여 버튼의 상태에 따라 다른 동작을 수행하는 간단한 예시입니다. 여기서 버튼의 상태는 &quot;idle&quot;, &quot;loading&quot;, &quot;success&quot;, &quot;error&quot; 등으로 변하고, 각 상태에 따라 버튼의 UI와 동작이 달라집니다.&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;1. 상태 인터페이스 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 상태 패턴을 적용하려면, 각 상태의 행동을 정의하는 인터페이스처럼 사용할 수 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1727421499990&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ButtonState.js
export class ButtonState {
    onClick(context) {
        throw new Error(&quot;This method should be overridden by subclasses.&quot;);
    }
}

export class IdleState extends ButtonState {
    onClick(context) {
        context.setState({ status: &quot;loading&quot; });
        setTimeout(() =&amp;gt; {
            if (Math.random() &amp;gt; 0.5) {
                context.setState({ status: &quot;success&quot; });
            } else {
                context.setState({ status: &quot;error&quot; });
            }
        }, 1000); // Simulate network request
    }
}

export class LoadingState extends ButtonState {
    onClick(context) {
        console.log(&quot;Loading... please wait.&quot;);
    }
}

export class SuccessState extends ButtonState {
    onClick(context) {
        console.log(&quot;Already successful.&quot;);
    }
}

export class ErrorState extends ButtonState {
    onClick(context) {
        context.setState({ status: &quot;idle&quot; });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. React 컴포넌트에서 상태 패턴 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 React 컴포넌트에서 상태에 따라 버튼의 동작을 다르게 구현합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1727421516462&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useState } from &quot;react&quot;;
import {
    ButtonState,
    IdleState,
    LoadingState,
    SuccessState,
    ErrorState,
} from &quot;./ButtonState&quot;;

const StatefulButton = () =&amp;gt; {
    // 상태 관리 (idle, loading, success, error)
    const [status, setStatus] = useState(&quot;idle&quot;);

    // 상태에 따른 클래스 할당
    const getButtonState = () =&amp;gt; {
        switch (status) {
            case &quot;idle&quot;:
                return new IdleState();
            case &quot;loading&quot;:
                return new LoadingState();
            case &quot;success&quot;:
                return new SuccessState();
            case &quot;error&quot;:
                return new ErrorState();
            default:
                return new IdleState();
        }
    };

    const buttonState = getButtonState();

    // 버튼 클릭 시 상태에 따른 동작 수행
    const handleClick = () =&amp;gt; {
        buttonState.onClick({ setState: setStatus });
    };

    // 상태에 따른 버튼 스타일 및 텍스트 변경
    const renderButtonText = () =&amp;gt; {
        switch (status) {
            case &quot;idle&quot;:
                return &quot;Submit&quot;;
            case &quot;loading&quot;:
                return &quot;Loading...&quot;;
            case &quot;success&quot;:
                return &quot;Success!&quot;;
            case &quot;error&quot;:
                return &quot;Error! Try Again&quot;;
            default:
                return &quot;Submit&quot;;
        }
    };

    return (
        &amp;lt;button
            onClick={handleClick}
            disabled={status === &quot;loading&quot;}
            style={{
                padding: &quot;10px 20px&quot;,
                backgroundColor: status === &quot;success&quot; ? &quot;green&quot; : status === &quot;error&quot; ? &quot;red&quot; : &quot;blue&quot;,
                color: &quot;white&quot;,
                border: &quot;none&quot;,
                borderRadius: &quot;5px&quot;,
                cursor: status === &quot;loading&quot; ? &quot;not-allowed&quot; : &quot;pointer&quot;,
            }}
        &amp;gt;
            {renderButtonText()}
        &amp;lt;/button&amp;gt;
    );
};

export default StatefulButton;&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. 설명:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StatefulButton 컴포넌트는 &lt;b&gt;상태&lt;/b&gt;에 따라 버튼의 동작과 UI가 달라집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IdleState&lt;/b&gt;: 사용자가 버튼을 클릭하면, onClick 메소드를 통해 상태가 &lt;b&gt;loading&lt;/b&gt;으로 전환되고, 가상 네트워크 요청이 완료되면 상태가 &lt;b&gt;success&lt;/b&gt; 또는 &lt;b&gt;error&lt;/b&gt;로 전환됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LoadingState&lt;/b&gt;: 버튼이 &quot;Loading...&quot; 상태일 때는 클릭 이벤트가 비활성화되며, 콘솔에 &quot;Loading... please wait.&quot; 메시지가 출력됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SuccessState&lt;/b&gt;: 성공 상태에서는 버튼을 다시 클릭해도 아무런 동작을 하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ErrorState&lt;/b&gt;: 실패 상태에서는 다시 &quot;idle&quot; 상태로 전환됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 상태 변화 과정&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;idle&lt;/b&gt; 상태에서 &quot;Submit&quot; 텍스트를 표시합니다.&lt;/li&gt;
&lt;li&gt;사용자가 버튼을 클릭하면 &lt;b&gt;loading&lt;/b&gt; 상태로 전환되며, &quot;Loading...&quot; 텍스트가 표시됩니다.&lt;/li&gt;
&lt;li&gt;네트워크 요청이 완료된 후 성공 시 &lt;b&gt;success&lt;/b&gt; 상태로, 실패 시 &lt;b&gt;error&lt;/b&gt; 상태로 전환됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;success&lt;/b&gt; 상태에서는 &quot;Success!&quot;를 표시하며, &lt;b&gt;error&lt;/b&gt; 상태에서는 &quot;Error! Try Again&quot;을 표시하여 사용자가 재시도를 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 상태 패턴은 각 버튼 상태(Idle, Loading, Success, Error)에 대한 행동을 별도의 클래스로 관리하고, React의 상태와 연결하여 버튼의 동작과 UI를 제어합니다. 상태 패턴을 사용하면 &lt;b&gt;상태별로 서로 다른 행동을 캡슐화&lt;/b&gt;하고, &lt;b&gt;상태 전환에 따른 행동 변화&lt;/b&gt;를 명확하게 관리할 수 있습니다.&lt;/p&gt;</description>
      <category>개발서적/헤드퍼스트 디자인패턴</category>
      <author>SWKo</author>
      <guid isPermaLink="true">https://sw-ko.tistory.com/474</guid>
      <comments>https://sw-ko.tistory.com/474#entry474comment</comments>
      <pubDate>Sun, 22 Sep 2024 23:47:50 +0900</pubDate>
    </item>
    <item>
      <title>[개발서적] 헤드퍼스트 디자인 패턴 Ch9. 반복자 패턴과 컴포지트 패턴</title>
      <link>https://sw-ko.tistory.com/473</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 객체 마을 식당과 팬케이스 하우스 합병&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팬케이스 하우스에서 파는 아침 메뉴, 객체마을 식당에서 파는 점심 메뉴를 한 곳에서 먹을 수 있게 되었습니다만, 문제가 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;아침, 점심에 각각 다른 메뉴를 써야합니다.&lt;/span&gt; &lt;span style=&quot;background-color: #9feec3;&quot;&gt;일단 MenuItem 클래스의 구현 방법은 합의했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725716558301&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MenuItem {
    String name;           // 메뉴 이름
    String description;    // 메뉴 설명
    boolean vegetarian;    // 채식주의 여부
    double price;          // 가격

    // 생성자: 메뉴 아이템의 속성을 초기화
    public MenuItem(String name, String description, boolean vegetarian, double price) {
        this.name = name;
        this.description = description;
        this.vegetarian = vegetarian;
        this.price = price;
    }

    // 메뉴 이름 반환
    public String getName() {
        return name;
    }

    // 메뉴 설명 반환
    public String getDescription() {
        return description;
    }

    // 가격 반환
    public double getPrice() {
        return price;
    }

    // 채식주의 여부 반환
    public boolean isVegetarian() {
        return vegetarian;
    }
}&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: #9feec3;&quot;&gt;저장하는 방식에서 차이&lt;/span&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;&lt;b&gt;팬케이스 하우스&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725716729514&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PancakeHouseMenu {
    List&amp;lt;MenuItem&amp;gt; menuItems;

    public PancakeHouseMenu() {
        menuItems = new ArrayList&amp;lt;MenuItem&amp;gt;();
        addItem(&quot;K&amp;amp;B 팬케이크 세트&quot;, &quot;스크램블 에그와 토스트가 곁들여진 팬케이크&quot;, false, 2.99);
        addItem(&quot;레귤러 팬케이크 세트&quot;, &quot;달걀 프라이와 소시지가 곁들여진 팬케이크&quot;, false, 2.99);
        addItem(&quot;블루베리 팬케이크&quot;, &quot;신선한 블루베리와 블루베리 시럽으로 만든 팬케이크&quot;, true, 3.49);
        addItem(&quot;와플&quot;, &quot;취향에 따라 블루베리나 딸기를 얹을 수 있는 와플&quot;, true, 3.59);
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.add(menuItem);
    }

    public List&amp;lt;MenuItem&amp;gt; getMenuItems() {
        return menuItems;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;span style=&quot;background-color: #9feec3;&quot;&gt;ArrayList&amp;lt;MenuItem&amp;gt;을 사용&lt;/span&gt;하여 메뉴 항목을 동적으로 관리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유연성:&lt;/b&gt; 메뉴 항목을 추가하는 데 제한이 없으며, ArrayList는 크기가 동적으로 조정됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추가 기능:&lt;/b&gt; addItem() 메서드를 사용해 언제든지 새로운 항목을 추가할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성:&lt;/b&gt; &lt;span style=&quot;background-color: #9feec3;&quot;&gt;ArrayList를 사용하므로 항목 개수에 제한이 없고 유연한 확장이 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객체마을 식당&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725716800301&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DinerMenu {
    static final int MAX_ITEMS = 6;
    int numberOfItems = 0;
    MenuItem[] menuItems;

    public DinerMenu() {
        menuItems = new MenuItem[MAX_ITEMS];
        addItem(&quot;채식주의자용 BLT&quot;, &quot;통밀 위에 콩고기 베이컨, 상추, 토마토를 얹은 메뉴&quot;, true, 2.99);
        addItem(&quot;BLT&quot;, &quot;통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴&quot;, false, 2.99);
        addItem(&quot;오늘의 스프&quot;, &quot;감자 샐러드를 곁들인 오늘의 스프&quot;, false, 3.29);
        addItem(&quot;핫도그&quot;, &quot;사워크라우트, 갖은 양념, 양파, 치즈가 곁들여진 핫도그&quot;, false, 3.05);
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        if (numberOfItems &amp;gt;= MAX_ITEMS) {
            System.err.println(&quot;죄송합니다, 메뉴가 꽉 찼습니다. 더 이상 추가할 수 없습니다.&quot;);
        } else {
            menuItems[numberOfItems] = menuItem;
            numberOfItems++;
        }
    }

    public MenuItem[] getMenuItems() {
        return menuItems;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;span style=&quot;background-color: #9feec3;&quot;&gt;배열(MenuItem[])을 사용&lt;/span&gt;하여 메뉴 항목을 저장하며, 최대 항목 개수를 MAX_ITEMS = 6으로 제한합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;제한점:&lt;/b&gt; 배열을 사용하기 때문에 메뉴 항목의 개수가 고정되어 있으며, 6개를 초과할 수 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효율성:&lt;/b&gt; 배열을 사용하여 메모리 관리가 직관적이지만, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;메뉴 항목을 동적으로 추가하는 데는 어려움이 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추가 기능:&lt;/b&gt; addItem() 메서드를 통해 항목을 추가할 수 있으나, 최대 개수를 넘으면 항목을 추가할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 종업원 구현하기&lt;/h2&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;958&quot; data-origin-height=&quot;1028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XDFuj/btsJuFkACuW/eNEY2o0TWebXWoffKPxAL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XDFuj/btsJuFkACuW/eNEY2o0TWebXWoffKPxAL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XDFuj/btsJuFkACuW/eNEY2o0TWebXWoffKPxAL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXDFuj%2FbtsJuFkACuW%2FeNEY2o0TWebXWoffKPxAL1%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;300&quot; height=&quot;1028&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;1028&quot;/&gt;&lt;/span&gt;&lt;/figure&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;printMenu 메소드를 구현하는 방법을 보겠습니다.&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;1. 각 메뉴에 들어있는 항목을 모두 출력하려면 두 클래스의 getMenuItems() 메소드를 호출해서 메뉴 항목을 가져와야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 메소드의 리턴 형식이 다름에 주의해야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725717445680&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
ArrayList&amp;lt;MenuItem&amp;gt; breakfastItems = pancakeHouseMenu.getMenuItems();

DinerMenu dinerMenu = new DinerMenu();
MenuItem[] lunchItems = dinerMenu.getMenuItems();&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;p data-ke-size=&quot;size16&quot;&gt;2. breakfastItems &lt;span style=&quot;background-color: #9feec3;&quot;&gt;ArrayList&lt;/span&gt;에 들어있는 모든 항목에 순환문을 돌려 PancakeHouse Menu 항목을 출력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 DinerMenu에 들어있는 항목을 출력할 때는 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;배열&lt;/span&gt;에 순환문을 돌립니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725717531183&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (int i = 0; i &amp;lt; breakfastItems.size(); i++) {
    MenuItem menuItem = breakfastItems.get(i);
    System.out.print(menuItem.getName() + &quot; &quot;);
    System.out.println(menuItem.getPrice() + &quot; &quot;);
    System.out.println(menuItem.getDescription());
}

for (int i = 0; i &amp;lt; lunchItems.length; i++) {
    MenuItem menuItem = lunchItems[i];
    System.out.print(menuItem.getName() + &quot; &quot;);
    System.out.println(menuItem.getPrice() + &quot; &quot;);
    System.out.println(menuItem.getDescription());
}&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;3. 종업원은 항상 두 메뉴를 사용하고, 2개의 순환문을 사용해야 합니다. 만약 다른 구현법을 사용하는 레스토랑과 또 합병한다면 3개의 순환문이 필요하게 됩니다.&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;이처럼 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;확장성이 좋지 않습니다.&lt;/span&gt; 어떻게 인터페이스를 통합할 수 있을까요?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 반복자 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;반복자 패턴(Iterator Pattern)&lt;/span&gt;을 도입하면, ArrayList나 배열이 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;어떻게 저장되어 있든 상관없이 &lt;b&gt;동일한 방식으로 반복 작업&lt;/b&gt;&lt;/span&gt;을 처리할 수 있습니다. 즉, size()나 length에 의존하지 않고, Iterator를 사용하여 항목을 순차적으로 처리할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Iterator 인터페이스 구현&lt;/b&gt;: ArrayList나 배열 모두에 대해 Iterator 인터페이스를 구현하면, 클라이언트 코드에서 순회 방법에 의존하지 않고 항목을 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;createIterator 메서드 추가&lt;/b&gt;: 각 메뉴 클래스에 createIterator() 메서드를 추가하여 반복자를 반환하도록 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 코드의 변경&lt;/b&gt;: size()나 length를 직접 호출하는 대신, Iterator를 사용해 항목을 하나씩 가져옵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1725717984198&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ArrayList를 사용하는 PancakeHouseMenu
Iterator&amp;lt;MenuItem&amp;gt; breakfastIterator = pancakeHouseMenu.createIterator();
while (breakfastIterator.hasNext()) {
    MenuItem menuItem = breakfastIterator.next();
    System.out.println(menuItem.getName() + &quot; &quot; + menuItem.getPrice() + &quot; &quot; + menuItem.getDescription());
}

// 배열을 사용하는 DinerMenu
Iterator&amp;lt;MenuItem&amp;gt; lunchIterator = dinerMenu.createIterator();
while (lunchIterator.hasNext()) {
    MenuItem menuItem = lunchIterator.next();
    System.out.println(menuItem.getName() + &quot; &quot; + menuItem.getPrice() + &quot; &quot; + menuItem.getDescription());
}&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;반복자(Iterator) 패턴은 컬렉션(JAVA List, Set, Map 등 객체를 담는 컨테이너) 내의 객체들에 순차적으로 접근하는 방법을 제공하면서, 그 객체들의 내부 표현 방식에는 의존하지 않는 디자인 패턴입니다.&lt;/span&gt; &lt;span style=&quot;background-color: #9feec3;&quot;&gt;이 패턴은 다양한 자료구조에서 일관된 방식으로 객체를 순회할 수 있도록 도와줍니다.&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;4.  두 식당에 반복자 패턴 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Iterator 인터페이스를 정의합니다. 이 인터페이스는 두 가지 중요한 메서드를 포함하고 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;hasNext(): 컬렉션에 더 탐색할 항목이 있는지 확인합니다.&lt;/li&gt;
&lt;li&gt;next(): 다음 항목을 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1725719343310&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Iterator {
    boolean hasNext();  // 다음 항목이 있는지 확인
    MenuItem next();    // 다음 항목을 반환
}&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: #9feec3;&quot;&gt;DinerMenuIterator 클래스는 Iterator 인터페이스를 구현&lt;/span&gt;하며, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;DinerMenu의 배열을 순회하는 역할&lt;/span&gt;을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1725719373562&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DinerMenuIterator implements Iterator {
    MenuItem[] items;  // 메뉴 항목 배열
    int position = 0;  // 현재 반복 작업의 위치

    public DinerMenuIterator(MenuItem[] items) {
        this.items = items;
    }

    // 다음 항목을 반환하고, position을 증가시킴
    public MenuItem next() {
        MenuItem menuItem = items[position];
        position = position + 1;
        return menuItem;
    }

    // 배열에 더 이상 항목이 없거나, null을 만나면 false 반환
    public boolean hasNext() {
        if (position &amp;gt;= items.length || items[position] == null) {
            return false;
        } else {
            return true;
        }
    }
}&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: #9feec3;&quot;&gt;DinerMenu 클래스에서 반복자를 사용&lt;/span&gt;해봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725719824723&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DinerMenu {
    static final int MAX_ITEMS = 6;  // 메뉴의 최대 항목 수
    int numberOfItems = 0;           // 현재 메뉴 항목 수
    MenuItem[] menuItems;            // 메뉴 항목 배열

    public DinerMenu() {
        menuItems = new MenuItem[MAX_ITEMS];
        addItem(&quot;채식주의자용 BLT&quot;, &quot;통밀 위에 콩고기 베이컨, 상추, 토마토를 얹은 메뉴&quot;, true, 2.99);
        addItem(&quot;BLT&quot;, &quot;통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴&quot;, false, 2.99);
        addItem(&quot;오늘의 스프&quot;, &quot;감자 샐러드를 곁들인 오늘의 스프&quot;, false, 3.29);
        addItem(&quot;핫도그&quot;, &quot;사워크라우트, 갖은 양념, 양파, 치즈가 곁들여진 핫도그&quot;, false, 3.05);
        // 기타 메뉴 추가
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        if (numberOfItems &amp;gt;= MAX_ITEMS) {
            System.err.println(&quot;죄송합니다, 메뉴가 꽉 찼습니다. 더 이상 추가할 수 없습니다.&quot;);
        } else {
            menuItems[numberOfItems] = menuItem;
            numberOfItems++;
        }
    }

    // DinerMenuIterator를 생성하고 반환
    public Iterator createIterator() {
        return new DinerMenuIterator(menuItems);
    }
}&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: #9feec3;&quot;&gt;PancakeHouseMenu에도 반복자를 적용&lt;/span&gt;해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;PancakeHouseMenu는 ArrayList를 사용하기 때문에, 기본적으로 제공되는 Iterator를 사용&lt;/span&gt;할 수 있습니다. 여기에도 동일하게 createIterator() 메서드를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725719989134&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PancakeHouseMenu {
    List&amp;lt;MenuItem&amp;gt; menuItems;

    public PancakeHouseMenu() {
        menuItems = new ArrayList&amp;lt;&amp;gt;();
        addItem(&quot;K&amp;amp;B 팬케이크 세트&quot;, &quot;스크램블 에그와 토스트가 곁들여진 팬케이크&quot;, false, 2.99);
        addItem(&quot;레귤러 팬케이크 세트&quot;, &quot;달걀 프라이와 소시지가 곁들여진 팬케이크&quot;, false, 2.99);
        addItem(&quot;블루베리 팬케이크&quot;, &quot;신선한 블루베리와 블루베리 시럽으로 만든 팬케이크&quot;, true, 3.49);
        addItem(&quot;와플&quot;, &quot;취향에 따라 블루베리나 딸기를 얹을 수 있는 와플&quot;, true, 3.59);
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.add(menuItem);
    }

    // ArrayList의 기본 Iterator 반환
    public Iterator&amp;lt;MenuItem&amp;gt; createIterator() {
        return menuItems.iterator();
    }
}&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: #9feec3;&quot;&gt;종업원 코드에 반복자를 적용&lt;/span&gt;해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725720029880&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Waitress {
    PancakeHouseMenu pancakeHouseMenu;
    DinerMenu dinerMenu;

    public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinerMenu = dinerMenu;
    }

    public void printMenu() {
        Iterator&amp;lt;MenuItem&amp;gt; pancakeIterator = pancakeHouseMenu.createIterator();
        Iterator&amp;lt;MenuItem&amp;gt; dinerIterator = dinerMenu.createIterator();

        System.out.println(&quot;아침 메뉴:&quot;);
        printMenu(pancakeIterator);

        System.out.println(&quot;점심 메뉴:&quot;);
        printMenu(dinerIterator);
    }

    private void printMenu(Iterator&amp;lt;MenuItem&amp;gt; iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.print(menuItem.getName() + &quot; &quot;);
            System.out.println(menuItem.getPrice() + &quot; &quot;);
            System.out.println(menuItem.getDescription());
        }
    }
}&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;main 실행 코드를 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725720224561&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MenuTestDrive {
    public static void main(String args[]) {
        // 메뉴 생성
        PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
        DinerMenu dinerMenu = new DinerMenu();

        // 종업원 생성 (두 메뉴를 인자로 전달)
        Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);

        // 메뉴 출력
        waitress.printMenu();
    }
}&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;5. 반복자 패턴의 특징 알아보기&lt;/h2&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: 85px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;관리하기 힘든 종업원 코드&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;Iterator가 장착된 신형 종업원 코드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;메뉴가 캡슐화되어 있지 않아서 각 식당에서 어떤 자료구조를 썼는지 알 수 있습니다.&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;메뉴 구현법이 캡슐화되어 있어서 종업원은 메뉴에서 메뉴 항목의 컬렉션을 어떤 식으로 저장하는지 알 수 없습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;MenuItems로 반복 작업을 하려면 2개의 순환문이 필요합니다.&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;반복자만 구현하면 다형성을 활용해서 어떤 컬렉션이든 1개의 순환문으로 처리할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;종업원이 구상 클래스(MenuItem[], ArrayList)에 직접 연결되어 있습니다.&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;종업원은 인터페이스(반복자)만 알면 됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;유사한 인터페이스를 가졌음에도 2개의 서로 다른 구상 메뉴 클래스에 묶여 있습니다.&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;Menu 인터페이스가 약간 다르지만, 통일시킬 수 있습니다.&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;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 인터페이스 개선하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PancakeHouseMenu와 DinerMenu에서 사용하는 반복자 인터페이스를 자바의 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;기본 java.util.Iterator 인터페이스&lt;/b&gt;&lt;/span&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;1. PancakeHouseMenu와 DinerMenu가 공통으로 사용하는 Menu 인터페이스는 자바의 Iterator를 반환하도록 수정합니다.&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1725722345139&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Iterator;

public interface Menu {
    Iterator&amp;lt;MenuItem&amp;gt; createIterator();  // 자바의 Iterator 인터페이스 사용
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. DinerMenuIterator 클래스는 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;자바의 Iterator 인터페이스를 구현&lt;/span&gt;하여 배열을 순회합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725722554985&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Iterator;

// 자바의 Iterator 인터페이스를 구현
public class DinerMenuIterator implements Iterator&amp;lt;MenuItem&amp;gt; {
    MenuItem[] items;
    int position = 0;

    public DinerMenuIterator(MenuItem[] items) {
        this.items = items;
    }

    // 배열에 다음 항목이 있는지 확인
    @Override
    public boolean hasNext() {
        return position &amp;lt; items.length &amp;amp;&amp;amp; items[position] != null;
    }

    // 배열의 다음 항목 반환
    @Override
    public MenuItem next() {
        MenuItem menuItem = items[position];
        position++;
        return menuItem;
    }

    // remove()는 지원하지 않음
    @Override
    public void remove() {
        throw new UnsupportedOperationException(&quot;remove()는 지원되지 않습니다.&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;3. 이제 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;Waitress 클래스는 PancakeHouseMenu와 DinerMenu의 구체적인 클래스에 의존하지 않고, Menu 인터페이스에 의존&lt;/span&gt;하게 됩니다. =&amp;gt; &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&quot;구현보다는 인터페이스에 맞춰서 프로그래밍한다.&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 더 유연하게 작동하며, 두 메뉴를 같은 방식으로 처리할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725722667055&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Iterator;

public class Waitress {
    Menu pancakeHouseMenu;
    Menu dinerMenu;

    public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinerMenu = dinerMenu;
    }

    public void printMenu() {
        Iterator&amp;lt;MenuItem&amp;gt; pancakeIterator = pancakeHouseMenu.createIterator();
        Iterator&amp;lt;MenuItem&amp;gt; dinerIterator = dinerMenu.createIterator();

        System.out.println(&quot;아침 메뉴:&quot;);
        printMenu(pancakeIterator);
        System.out.println(&quot;\n점심 메뉴:&quot;);
        printMenu(dinerIterator);
    }

    private void printMenu(Iterator&amp;lt;MenuItem&amp;gt; iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.print(menuItem.getName() + &quot;, &quot;);
            System.out.print(menuItem.getPrice() + &quot; -- &quot;);
            System.out.println(menuItem.getDescription());
        }
    }
}&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;7. 반복자 패턴의 정의&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;반복자 패턴(Iterator Pattern)은 컬렉션의 구현 방법을 노출하지 않으면서&lt;br /&gt;집합체 내의 모든 항목에 접근하는 방법을 제공합니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rWcwb/btsJwbbyrZf/fJa04XrFLpoJIigd6kDAT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rWcwb/btsJwbbyrZf/fJa04XrFLpoJIigd6kDAT1/img.png&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;1220&quot; data-is-animation=&quot;false&quot; width=&quot;430&quot; height=&quot;341&quot; style=&quot;width: 44.9255%; margin-right: 10px;&quot; data-widthpercent=&quot;45.45&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rWcwb/btsJwbbyrZf/fJa04XrFLpoJIigd6kDAT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrWcwb%2FbtsJwbbyrZf%2FfJa04XrFLpoJIigd6kDAT1%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;1538&quot; height=&quot;1220&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uJKH2/btsJuXL6ZJS/AlqM3yzfZYvjC0Dz8A3hK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uJKH2/btsJuXL6ZJS/AlqM3yzfZYvjC0Dz8A3hK0/img.png&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;1014&quot; data-is-animation=&quot;false&quot; width=&quot;430&quot; height=&quot;284&quot; style=&quot;width: 53.9117%;&quot; data-widthpercent=&quot;54.55&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uJKH2/btsJuXL6ZJS/AlqM3yzfZYvjC0Dz8A3hK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuJKH2%2FbtsJuXL6ZJS%2FAlqM3yzfZYvjC0Dz8A3hK0%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;1534&quot; height=&quot;1014&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGNgUC/btsJvVfHT3K/ohVFbA6zPR8f0rXPmmdEok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGNgUC/btsJvVfHT3K/ohVFbA6zPR8f0rXPmmdEok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGNgUC/btsJvVfHT3K/ohVFbA6zPR8f0rXPmmdEok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGNgUC%2FbtsJvVfHT3K%2FohVFbA6zPR8f0rXPmmdEok%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;450&quot; height=&quot;161&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복자(Iterator) 패턴의 클래스 다이어그램이 &lt;b&gt;유사한 패턴&lt;/b&gt;은 바로 &lt;b&gt;팩토리 메서드(Factory Method) 패턴&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;팩토리 메서드 패턴과 반복자 패턴의 유사점&lt;/b&gt;:&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&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;: 팩토리 메서드 패턴에서는 객체 생성을 서브클래스에서 결정하고, 반복자 패턴에서는 컬렉션의 세부 구현을 캡슐화하여 Iterator를 통해 항목을 순회합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;팩토리 메서드 패턴&lt;/b&gt;:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팩토리 메서드 패턴은 객체 생성을 &lt;b&gt;서브클래스가 결정하도록&lt;/b&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;b&gt;예시&lt;/b&gt;: 팩토리 메서드 패턴에서 상위 클래스는 특정 인터페이스나 추상 클래스를 통해 객체를 생성하지만, &lt;b&gt;구체적으로 어떤 객체가 생성될지는 서브클래스에서 정의&lt;/b&gt;합니다. 이 방식은 반복자 패턴에서 Iterator를 생성하는 과정과 유사합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;반복자 패턴&lt;/b&gt;:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복자 패턴은 컬렉션의 내부 구조를 숨기고, &lt;b&gt;객체의 순회를 Iterator를 통해 처리&lt;/b&gt;하도록 캡슐화합니다. createIterator() 메서드를 통해 Iterator 객체를 반환하는 방식이, 팩토리 메서드 패턴의 객체 생성 방식과 유사합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;유사점 정리&lt;/b&gt;:&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&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;/li&gt;
&lt;li&gt;&lt;b&gt;반복자 패턴&lt;/b&gt;: 컬렉션의 세부 구조에 관계없이 Iterator를 통해 객체를 순회합니다.&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;&lt;b&gt;반복자 패턴&lt;/b&gt;: 컬렉션 내부 구조를 숨기고, Iterator를 통해 항목을 캡슐화된 방식으로 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 단일 역할 원칙&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;디자인 원칙 - 어떤 클래스가 바뀌는 이유는 하나뿐이어야 한다.&lt;/span&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;집합체에서 컬렉션 관련 기능과 반복자용 메소드 관련 기능을 전부 구현하면 2가지 이유로 그 클래스가 바뀔 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 고치는 일은 최대한 피해야하기 때문에 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;컬렉션 관리와 반복자 생성 기능을 각각&amp;nbsp;&lt;/span&gt;&lt;b&gt;다른 클래스&lt;/b&gt;로 분리하여, 각 클래스가 하나의 책임만 갖도록 설계해야 합니다.&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;9. Iterable 인터페이스 알아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 모든 컬렉션 유형에서 Iterable 인터페이스를 구현합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Iterable 인터페이스에는 Iterator 인터페이스를 구현하는, 반복자를 리턴하는 iterator() 메소드가 들어있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;1072&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo7jOR/btsJvmLjuB1/g22Ku4r1Uq1IeNapK3xGT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo7jOR/btsJvmLjuB1/g22Ku4r1Uq1IeNapK3xGT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo7jOR/btsJvmLjuB1/g22Ku4r1Uq1IeNapK3xGT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo7jOR%2FbtsJvmLjuB1%2Fg22Ku4r1Uq1IeNapK3xGT1%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;500&quot; height=&quot;412&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;1072&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 클래스에서 Iterable을 구현한다면 그 클래스는 Iterator() 메소드를 구현해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;Iterator() 메소드는 Iterator 객체를 반환합니다.이 객체는 hasNext(), next() 메서드를 제공하며, 컬렉션 내의 요소를 순회할 수 있게 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;또한 Iterable 인터페이스에는 컬렉션에 있는 항목을 대상으로 반복 작업을 수행하는 forEach() 메소드가 기본으로 포함됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;그 외에도 JAVA는 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;향상된 for 순환문&lt;/b&gt;&lt;/span&gt;으로 몇 가지 편리한 문법적 기능을 제공합니다.&lt;/span&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;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;기존 방식&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725728016937&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;MenuItem&amp;gt; menuItems = new ArrayList&amp;lt;&amp;gt;();
// 메뉴 항목을 추가했다고 가정
menuItems.add(new MenuItem(&quot;팬케이크&quot;, &quot;버터와 시럽이 함께 제공&quot;, false, 2.99));

// 기존 방식: 반복자를 사용한 컬렉션 순회
Iterator&amp;lt;MenuItem&amp;gt; iterator = menuItems.iterator();
while (iterator.hasNext()) {
    MenuItem menuItem = iterator.next();
    System.out.print(menuItem.getName() + &quot;, &quot;);
    System.out.print(menuItem.getPrice() + &quot; &amp;mdash; &quot;);
    System.out.println(menuItem.getDescription());
}&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;향상된 for 순환문 적용 방식&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725728039824&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;MenuItem&amp;gt; menuItems = new ArrayList&amp;lt;&amp;gt;();
// 메뉴 항목을 추가했다고 가정
menuItems.add(new MenuItem(&quot;팬케이크&quot;, &quot;버터와 시럽이 함께 제공&quot;, false, 2.99));

// 향상된 for 루프를 사용한 컬렉션 순회
for (MenuItem menuItem : menuItems) {
    System.out.print(menuItem.getName() + &quot;, &quot;);
    System.out.print(menuItem.getPrice() + &quot; &amp;mdash; &quot;);
    System.out.println(menuItem.getDescription());
}&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;향상된 for 문을 사용할 때 주의사항이 있습니다. &lt;span style=&quot;background-color: #9feec3;&quot;&gt;배열은 Iterable이 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팬케이크하우스에서는 Iterator 대신 Iterable을 받고, for-each 순환문을 쓰도록 종업원 코드의 printMenu() 메소드를 고쳐봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725728335496&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public void printMenu(Iterable&amp;lt;MenuItem&amp;gt; iterable) {
        for (MenuItem menuItem : iterable) {
            System.out.print(menuItem.getName() + &quot;, &quot;);
            System.out.print(menuItem.getPrice() + &quot; &amp;mdash; &quot;);
            System.out.println(menuItem.getDescription());
        }
    }
    
printMenu(lunchItems)； // 오류 발생&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: #9feec3;&quot;&gt;배열은 Iterable이 아니기 때문에 오류가 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 일단은 두 식당에 향상된 for문을 모두 적용하지 않고, 기존 방식으로 둡니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 객체마을 카페 메뉴 살펴보기&lt;/h2&gt;
&lt;pre id=&quot;code_1725811878011&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CafeMenu {
    // HashMap을 사용하여 메뉴 항목을 저장 (키: 메뉴 이름, 값: MenuItem 객체)
    Map&amp;lt;String, MenuItem&amp;gt; menuItems = new HashMap&amp;lt;&amp;gt;();

    // 생성자: 초기 메뉴 항목 추가
    public CafeMenu() {
        addItem(&quot;베지 버거와 에어 프라이&quot;, &quot;통밀빵, 상추, 토마토, 감자 튀김이 첨가된 베지 버거&quot;, true, 3.99);
        addItem(&quot;오늘의 스프&quot;, &quot;샐러드가 곁들여진 오늘의 스프&quot;, false, 3.69);
        addItem(&quot;부리토&quot;, &quot;통 핀토콩과 살사, 구아카몰이 곁들여진 푸짐한 부리토&quot;, true, 4.29);
    }

    // 메뉴 항목을 HashMap에 추가하는 메서드
    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.put(name, menuItem);  // 이름을 키로, MenuItem 객체를 값으로 저장
    }

    // 메뉴 항목 전체를 반환하는 메서드 (여기서는 Map 전체를 반환)
    public Map&amp;lt;String, MenuItem&amp;gt; getMenuItems() {
        return menuItems;
    }
}&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;Hashtable도 Iterable을 지원하는 자바 컬렉션이지만, ArrayList와는 조금 다릅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725811987979&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class CafeMenu implements Menu {
    // 메뉴 항목을 저장하는 HashMap (키: 메뉴 이름, 값: MenuItem 객체)
    Map&amp;lt;String, MenuItem&amp;gt; menuItems = new HashMap&amp;lt;&amp;gt;();

    // 생성자: 메뉴 항목 추가
    public CafeMenu() {
        addItem(&quot;베지 버거와 에어 프라이&quot;, &quot;통밀빵, 상추, 토마토, 감자 튀김이 첨가된 베지 버거&quot;, true, 3.99);
        addItem(&quot;오늘의 스프&quot;, &quot;샐러드가 곁들여진 오늘의 스프&quot;, false, 3.69);
        addItem(&quot;부리토&quot;, &quot;통 핀토콩과 살사, 구아카몰이 곁들여진 푸짐한 부리토&quot;, true, 4.29);
    }

    // 메뉴 항목을 HashMap에 추가하는 메서드
    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.put(name, menuItem);  // HashMap에 메뉴 항목 저장
    }

    // HashMap에서 메뉴 항목에 대한 Iterator를 반환
    @Override
    public Iterator&amp;lt;MenuItem&amp;gt; createIterator() {
        return menuItems.values().iterator();  // 값들에 대한 Iterator 반환
    }
}&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;/p&gt;
&lt;pre id=&quot;code_1725812079294&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Iterator;

public class Waitress {
    Menu pancakeHouseMenu;
    Menu dinerMenu;
    Menu cafeMenu; // 추가된 곳

    // 생성자: 각 메뉴 객체를 인자로 받아 인스턴스 변수로 저장
    public Waitress(Menu pancakeHouseMenu, Menu dinerMenu, Menu cafeMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinerMenu = dinerMenu;
        this.cafeMenu = cafeMenu; // 추가된 곳
    }

    // 전체 메뉴 출력
    public void printMenu() {
        // 각 메뉴의 반복자(Iterator)를 가져옴
        Iterator&amp;lt;MenuItem&amp;gt; pancakeIterator = pancakeHouseMenu.createIterator();
        Iterator&amp;lt;MenuItem&amp;gt; dinerIterator = dinerMenu.createIterator();
        Iterator&amp;lt;MenuItem&amp;gt; cafeIterator = cafeMenu.createIterator(); // 추가된 곳

        // 각 메뉴 출력
        System.out.println(&quot;메뉴\n---\n아침 메뉴:&quot;);
        printMenu(pancakeIterator);

        System.out.println(&quot;\n점심 메뉴:&quot;);
        printMenu(dinerIterator);

        System.out.println(&quot;\n저녁 메뉴:&quot;); // 추가된 곳
        printMenu(cafeIterator);
    }

    // 개별 메뉴 항목을 출력하는 메서드 =&amp;gt; 이 부분은 전혀 바뀔 것이 없음
    private void printMenu(Iterator&amp;lt;MenuItem&amp;gt; iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = iterator.next();  // 다음 메뉴 항목을 가져옴
            System.out.print(menuItem.getName() + &quot;, &quot;);
            System.out.print(menuItem.getPrice() + &quot; &amp;mdash; &quot;);
            System.out.println(menuItem.getDescription());
        }
    }
}&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;/p&gt;
&lt;pre id=&quot;code_1725812110620&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MenuTestDrive {
    public static void main(String args[]) {
        // 각 메뉴 객체 생성
        PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
        DinerMenu dinerMenu = new DinerMenu();
        CafeMenu cafeMenu = new CafeMenu();

        // Waitress 생성 시, PancakeHouseMenu, DinerMenu, CafeMenu를 전달
        Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu, cafeMenu);

        // 3가지 메뉴 모두 출력
        waitress.printMenu();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 종업원 코드 개선하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재, 종업원 코드 printMenu() 메서드에서 printMenu를 3번 호출하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 새로운 메뉴가 추가될 때마다 printMenu() 메서드를 수정해야 합니다. 이는 **OCP(Open Closed Principle, 개방-폐쇄 원칙)**을 위배하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;여러 메뉴를 한꺼번에 관리할 수 있는 방식&lt;/b&gt;&lt;/span&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;여러 메뉴를 하나의 컬렉션이나 자료구조에 담아 관리하고, 이를 순차적으로 순회하면서 출력하는 방식으로 개선할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1726988121961&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;

public class Waitress {
    List&amp;lt;Menu&amp;gt; menus;

    // 여러 메뉴를 리스트로 받아서 관리
    public Waitress(List&amp;lt;Menu&amp;gt; menus) {
        this.menus = menus;
    }

    // 모든 메뉴를 출력하는 메서드
    public void printMenu() {
        for (Menu menu : menus) {
            printMenu(menu.createIterator());
        }
    }

    private void printMenu(Iterator&amp;lt;MenuItem&amp;gt; iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.println(menuItem.getName() + &quot;, &quot; + menuItem.getPrice() + &quot; -- &quot; + menuItem.getDescription());
        }
    }
}&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;Menu 객체들을 List&amp;lt;Menu&amp;gt;와 같은 컬렉션에 담아 관리하면, 새로운 메뉴가 추가되더라도 Waitress 클래스는 수정할 필요가 없습니다. &lt;span style=&quot;background-color: #9feec3;&quot;&gt;컬렉션에 메뉴를 추가하기만 하면 되므로 OCP 원칙을 지킬 수 있습니다.&lt;/span&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;그런데, 디저트 메뉴를 특정 메뉴의 서브 메뉴로 넣어달라는 요청이 들어왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디저트 메뉴를 DinerMenu 컬렉션의 원소로 넣을 수 있으면 좋겠지만, 현재 코드로는 그럴 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t2mE5/btsJINuUBrJ/8Z3OKkQ4wuIqayRZiOH6ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t2mE5/btsJINuUBrJ/8Z3OKkQ4wuIqayRZiOH6ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t2mE5/btsJINuUBrJ/8Z3OKkQ4wuIqayRZiOH6ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft2mE5%2FbtsJINuUBrJ%2F8Z3OKkQ4wuIqayRZiOH6ok%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;650&quot; height=&quot;531&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;/figure&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;이를 개선하기 위해서는 아래 작업이 필요합니다.&lt;/p&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;b&gt;유연한 반복 작업&lt;/b&gt;: 특정 서브메뉴나 항목만 대상으로 작업할 수 있으면서도, 전체 메뉴를 대상으로 반복 작업을 할 수 있는 유연성을 제공해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. 컴포지트 패턴&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;컴포지트 패턴&lt;/b&gt;&lt;br /&gt;객체를 트리구조로 구성해서 부분-전체 계층구조를 구현합니다.&lt;br /&gt;컴포지트 패턴을 사용하면 클라이언트에서 개별 객체와 복합 객체를 똑같은 방법으로 다룰 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Composite 패턴 사용&lt;/b&gt;: 메뉴 관리에 반복자 패턴만으로는 부족하므로, &lt;b&gt;Composite 패턴&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;: Composite 패턴을 통해, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;개별 객체&lt;/b&gt;와 &lt;b&gt;복합 객체&lt;/b&gt;를 동일한 방식으로 처리&lt;/span&gt;할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트리 구조&lt;/b&gt;: 메뉴, 서브메뉴, 서브서브메뉴 등 중첩된 메뉴 구조를 트리로 구성하고, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;간단한 코드를 반복 적용하여 전체 구조에 똑같이 작업&lt;/span&gt;을 수행할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-22 오후 4.16.06.png&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;1166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2wkEL/btsJIVsTazd/Z8JSpxsLBZZjVzco8Bw0b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2wkEL/btsJIVsTazd/Z8JSpxsLBZZjVzco8Bw0b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2wkEL/btsJIVsTazd/Z8JSpxsLBZZjVzco8Bw0b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2wkEL%2FbtsJIVsTazd%2FZ8JSpxsLBZZjVzco8Bw0b0%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;700&quot; height=&quot;503&quot; data-filename=&quot;스크린샷 2024-09-22 오후 4.16.06.png&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;1166&quot;/&gt;&lt;/span&gt;&lt;/figure&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;13. 컴포지트 패턴으로 메뉴 디자인하기&lt;/h2&gt;
&lt;div&gt;
&lt;div data-message-id=&quot;12d22c88-95ed-4d7e-affe-ea11b43f65d8&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포지트 패턴을 메뉴에 적용하기 위해 먼저 Menu(Composite)와 MenuItem(Leaf)에 공통적으로 적용되는 구성 요소 인터페이스를 만들어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 인터페이스를 통해 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;Menu와 MenuItem을 동일한 방식으로 처리할 수 있으며, 같은 메소드를 호출할 수 있게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, MenuItem(Leaf)에는 적합하지 않은 메소드나 Menu(Composite)에 적합하지 않은 메소드도 있을 수 있지만, 이 문제는 나중에 해결할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 컴포지트 패턴 구조에 Menu를 어떻게 맞출 수 있을지 고민하는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-22 오후 4.17.27.png&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;1258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lGJDT/btsJIg5qWer/MasmDPtKaH0hHcccG2vOt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lGJDT/btsJIg5qWer/MasmDPtKaH0hHcccG2vOt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lGJDT/btsJIg5qWer/MasmDPtKaH0hHcccG2vOt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlGJDT%2FbtsJIg5qWer%2FMasmDPtKaH0hHcccG2vOt0%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;700&quot; height=&quot;569&quot; data-filename=&quot;스크린샷 2024-09-22 오후 4.17.27.png&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;1258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MenuComponent 추상 클래스는 메뉴 구성 요소로, 잎(개별 항목, Leaf)과 복합 객체(메뉴, Composite) 모두에 적용되는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;이 클래스는 각 구성 요소에 공통적으로 필요한 메소드를 정의하지만, 잎과 복합 객체가 모두 사용하지는 않는 메소드도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;그런 메소드들은 기본적으로 UnsupportedOperationException 예외를 던지도록 설정하여, 필요한 곳에서만 메소드를 오버라이드해 사용할 수 있게 합니다&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726990346280&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class MenuComponent {

    // Composite 객체에 요소를 추가하는 메소드 (Menu에서는 사용, MenuItem에서는 사용 안 함)
    public void add(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    // Composite 객체에서 요소를 제거하는 메소드 (Menu에서는 사용, MenuItem에서는 사용 안 함)
    public void remove(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    // Composite 객체에서 하위 요소를 가져오는 메소드 (Menu에서는 사용, MenuItem에서는 사용 안 함)
    public MenuComponent getChild(int i) {
        throw new UnsupportedOperationException();
    }

    // 이름을 가져오는 메소드 (MenuItem에서는 사용, Menu에서는 사용 안 함)
    public String getName() {
        throw new UnsupportedOperationException();
    }

    // 설명을 가져오는 메소드 (MenuItem에서는 사용, Menu에서는 사용 안 함)
    public String getDescription() {
        throw new UnsupportedOperationException();
    }

    // 가격을 가져오는 메소드 (MenuItem에서는 사용, Menu에서는 사용 안 함)
    public double getPrice() {
        throw new UnsupportedOperationException();
    }

    // 채식 여부를 확인하는 메소드 (MenuItem에서는 사용, Menu에서는 사용 안 함)
    public boolean isVegetarian() {
        throw new UnsupportedOperationException();
    }

    // 모든 구성 요소를 출력하는 메소드 (Menu와 MenuItem 모두에서 오버라이드 가능)
    public void print() {
        throw new UnsupportedOperationException();
    }
}&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;b&gt;MenuItem&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726990726298&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MenuItem extends MenuComponent {
    String name;
    String description;
    boolean vegetarian;
    double price;

    // 생성자: 메뉴 항목의 이름, 설명, 채식 여부, 가격을 설정
    public MenuItem(String name, String description, boolean vegetarian, double price) {
        this.name = name;
        this.description = description;
        this.vegetarian = vegetarian;
        this.price = price;
    }

    // 메뉴 항목의 이름을 반환
    public String getName() {
        return name;
    }

    // 메뉴 항목의 설명을 반환
    public String getDescription() {
        return description;
    }

    // 메뉴 항목의 가격을 반환
    public double getPrice() {
        return price;
    }

    // 메뉴 항목이 채식주의자용인지 여부를 반환
    public boolean isVegetarian() {
        return vegetarian;
    }

    // 메뉴 항목의 내용을 출력하는 메소드
    public void print() {
        System.out.print(&quot;  &quot; + getName());
        if (isVegetarian()) {
            System.out.print(&quot;(v)&quot;);
        }
        System.out.println(&quot;, &quot; + getPrice());
        System.out.println(&quot;     -- &quot; + getDescription());
    }
}&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;b&gt;Menu&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726991500219&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.List;

public class Menu extends MenuComponent {
    List&amp;lt;MenuComponent&amp;gt; menuComponents = new ArrayList&amp;lt;&amp;gt;();
    String name;
    String description;

    // 생성자: 메뉴의 이름과 설명을 설정
    public Menu(String name, String description) {
        this.name = name;
        this.description = description;
    }

    // 메뉴에 구성 요소를 추가하는 메소드
    public void add(MenuComponent menuComponent) {
        menuComponents.add(menuComponent);
    }

    // 메뉴에서 구성 요소를 제거하는 메소드
    public void remove(MenuComponent menuComponent) {
        menuComponents.remove(menuComponent);
    }

    // 특정 인덱스의 구성 요소를 반환하는 메소드
    public MenuComponent getChild(int i) {
        return menuComponents.get(i);
    }

    // 메뉴의 이름을 반환하는 메소드
    public String getName() {
        return name;
    }

    // 메뉴의 설명을 반환하는 메소드
    public String getDescription() {
        return description;
    }

    // 가격이나 채식 여부는 메뉴에는 적용되지 않으므로 기본적으로 예외를 던집니다.
    @Override
    public double getPrice() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isVegetarian() {
        throw new UnsupportedOperationException();
    }

    // 메뉴와 메뉴 항목을 출력하는 메소드
    public void print() {
        System.out.print(&quot;\n&quot; + getName());
        System.out.println(&quot;, &quot; + getDescription());
        System.out.println(&quot;---------------------&quot;);

        // 하위 구성 요소들도 출력
        for (MenuComponent menuComponent : menuComponents) {
            menuComponent.print();
        }
    }
}&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;b&gt;Waitress&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726991732158&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Waitress {
    MenuComponent allMenus;

    // 종업원은 최상위 메뉴 구성 요소를 전달받음
    public Waitress(MenuComponent allMenus) {
        this.allMenus = allMenus;
    }

    // 최상위 메뉴의 print() 메소드를 호출하여 전체 메뉴를 출력
    public void printMenu() {
        allMenus.print();
    }
}&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;Waitress 클래스에 &lt;b&gt;컴포지트 패턴&lt;/b&gt;을 적용한 코드는 매우 간단하고 효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 종업원은 메뉴의 최상위 MenuComponent만 받으면 되고, 이를 통해 전체 메뉴와 서브메뉴를 한 번에 출력할 수 있습니다.&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메뉴 Test Code&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726991868058&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MenuTestDrive {
    public static void main(String[] args) {
        // 각 메뉴 생성
        MenuComponent pancakeHouseMenu = new Menu(&quot;팬케이크 하우스 메뉴&quot;, &quot;아침 메뉴&quot;);
        MenuComponent dinerMenu = new Menu(&quot;객체마을 식당 메뉴&quot;, &quot;점심 메뉴&quot;);
        MenuComponent cafeMenu = new Menu(&quot;카페 메뉴&quot;, &quot;저녁 메뉴&quot;);
        MenuComponent dessertMenu = new Menu(&quot;디저트 메뉴&quot;, &quot;디저트를 즐겨 보세요&quot;);

        // 최상위 메뉴 생성
        MenuComponent allMenus = new Menu(&quot;전체 메뉴&quot;, &quot;전체 메뉴&quot;);

        // 최상위 메뉴에 각 하위 메뉴 추가
        allMenus.add(pancakeHouseMenu);
        allMenus.add(dinerMenu);
        allMenus.add(cafeMenu);

        // DinerMenu에 메뉴 항목 추가
        dinerMenu.add(new MenuItem(&quot;파스타&quot;, &quot;마리나라 소스 스파게티, 효모빵도 드립니다.&quot;, true, 3.89));

        // DinerMenu에 디저트 메뉴 추가
        dinerMenu.add(dessertMenu);

        // 디저트 메뉴에 메뉴 항목 추가
        dessertMenu.add(new MenuItem(&quot;애플 파이&quot;, &quot;바삭바삭한 크러스트에 바닐라 아이스크림이 얹혀 있는 애플 파이&quot;, true, 1.59));

        // 종업원 생성
        Waitress waitress = new Waitress(allMenus);

        // 전체 메뉴 출력
        waitress.printMenu();
    }
}&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;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;1674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YsYvi/btsJHzR9LKP/a3lUeoRWTzuhxp39ZPWKxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YsYvi/btsJHzR9LKP/a3lUeoRWTzuhxp39ZPWKxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YsYvi/btsJHzR9LKP/a3lUeoRWTzuhxp39ZPWKxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYsYvi%2FbtsJHzR9LKP%2Fa3lUeoRWTzuhxp39ZPWKxK%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;600&quot; height=&quot;692&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;1674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div data-message-id=&quot;90740aa6-709c-43a5-a2d0-96c5d9901239&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 컴포지트 패턴에서는 Leaf와 Composite 2개의 역할을 갖고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;컴포지트 패턴은 &lt;b&gt;단일 책임 원칙(SRP)&lt;/b&gt;을 일부 희생하고 &lt;b&gt;투명성&lt;/b&gt;을 확보하는 패턴입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Component 인터페이스에 자식 관리 기능과 잎의 기능을 모두 포함시켜, 클라이언트가 복합 객체와 잎을 &lt;b&gt;동일한 방식&lt;/b&gt;으로 다룰 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 클라이언트가 객체의 내부 구조를 신경 쓰지 않아도 되지만, &lt;b&gt;안전성&lt;/b&gt;이 떨어질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 부적절한 메소드 호출이 발생할 수 있으며, 이를 예외로 처리합니다. 결국, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;투명성과 안전성&lt;/b&gt; 사이에서 균형을 맞추는 설계 결정이 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14. 컴포지트 패턴에서 추가로 고려할 점들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;컴포지트 패턴에서 자식 객체가 부모의 레퍼런스를 가질 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식에게 부모의 포인터를 넣으면 &lt;b&gt;트리 구조를 돌아다니기&lt;/b&gt; 편리해지며, 자식 객체를 삭제할 때 &lt;b&gt;부모에게 직접 삭제 요청&lt;/b&gt;을 할 수 있어 관리가 더 쉬워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 복합 객체를 처리할 때 자주 사용하는 패턴 중 하나입니다. 하지만 몇 가지 더 고려해야 할 점이 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가로 고려할 점들:&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;자식의 순서&lt;/b&gt;: 복합 객체 내에서 자식들의 순서를 고려해야 할 때, 자식 추가와 삭제를 &lt;b&gt;더 복잡하게 관리&lt;/b&gt;해야 할 수 있습니다. 예를 들어, 특정 순서대로 자식 노드를 저장하거나 순회할 필요가 있을 때는 순서 관리 로직을 추가해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐시(Caching)&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;b&gt;무한 루프&lt;/b&gt;나 &lt;b&gt;메모리 누수&lt;/b&gt; 같은 문제가 발생하지 않도록 주의해야 합니다.&lt;/li&gt;
&lt;/ol&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: #9feec3;&quot;&gt;컴포지트 패턴의 가장 큰 장점은 &lt;b&gt;클라이언트 코드의 단순화&lt;/b&gt;입니다.&lt;/span&gt; 클라이언트는 복합 객체와 잎 객체를 &lt;b&gt;구분할 필요 없이&lt;/b&gt; 동일한 방식으로 다룰 수 있으며, 객체 구조를 신경 쓰지 않고 일관된 인터페이스를 통해 작업을 처리할 수 있습니다. 이를 통해 코드가 간결해지고, &lt;b&gt;유연성&lt;/b&gt;이 높아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포지트 패턴은 강력한 설계 패턴으로, 복잡한 객체들을 한 번에 관리할 수 있는 방법을 제공합니다. 하지만 그만큼 &lt;b&gt;복잡성&lt;/b&gt;이 높아지므로, 효율적인 관리가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;FE 반복자, 컴포지트 패턴&lt;/h2&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;은 컬렉션에 있는 요소를 순차적으로 처리할 때 사용하며, React의 &lt;b&gt;map() 함수&lt;/b&gt;와 같이 리스트를 렌더링할 때 유용하게 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴포짓 패턴&lt;/b&gt;은 &lt;b&gt;트리 구조&lt;/b&gt;로 컴포넌트를 구성할 때 사용되며, React의 &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;</description>
      <category>개발서적/헤드퍼스트 디자인패턴</category>
      <author>SWKo</author>
      <guid isPermaLink="true">https://sw-ko.tistory.com/473</guid>
      <comments>https://sw-ko.tistory.com/473#entry473comment</comments>
      <pubDate>Wed, 4 Sep 2024 01:37:19 +0900</pubDate>
    </item>
    <item>
      <title>[개발서적] 헤드퍼스트 디자인 패턴 Ch8. 템플릿 메소드 패턴</title>
      <link>https://sw-ko.tistory.com/472</link>
      <description>&lt;h2 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;p data-ke-size=&quot;size16&quot;&gt;추상 클래스 - CaffeineBeverage&lt;/p&gt;
&lt;pre id=&quot;code_1724948532964&quot; class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;abstract class CaffeineBeverage {
    // 템플릿 메서드
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    abstract void brew();

    abstract void addCondiments();

    void boilWater() {
        System.out.println(&quot;물을 끓이는 중&quot;);
    }

    void pourInCup() {
        System.out.println(&quot;컵에 따르는 중&quot;);
    }

    // Hook - 서브클래스에서 필요시 오버라이드
    boolean customerWantsCondiments() {
        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Coffee 클래스&lt;/p&gt;
&lt;pre id=&quot;code_1724948559229&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Scanner;

public class Coffee extends CaffeineBeverage {

    @Override
    void brew() {
        System.out.println(&quot;필터를 통해 커피를 우려내는 중&quot;);
    }

    @Override
    void addCondiments() {
        System.out.println(&quot;설탕과 우유를 추가하는 중&quot;);
    }

    @Override
    boolean customerWantsCondiments() {
        String answer = getUserInput(&quot;커피에 설탕과 우유를 넣어 드릴까요? (y/n): &quot;);
        return answer.toLowerCase().startsWith(&quot;y&quot;);
    }

    private String getUserInput(String prompt) {
        System.out.print(prompt);
        Scanner scanner = new Scanner(System.in);
        String input = scanner.nextLine();
        return input;
    }
}&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;Tea 클래스&lt;/p&gt;
&lt;pre id=&quot;code_1724948578512&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Scanner;

public class Tea extends CaffeineBeverage {

    @Override
    void brew() {
        System.out.println(&quot;찻잎을 우려내는 중&quot;);
    }

    @Override
    void addCondiments() {
        System.out.println(&quot;레몬을 추가하는 중&quot;);
    }

    @Override
    boolean customerWantsCondiments() {
        String answer = getUserInput(&quot;차에 레몬을 넣어 드릴까요? (y/n): &quot;);
        return answer.toLowerCase().startsWith(&quot;y&quot;);
    }

    private String getUserInput(String prompt) {
        System.out.print(prompt);
        Scanner scanner = new Scanner(System.in);
        String input = scanner.nextLine();
        return input;
    }
}&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;/p&gt;
&lt;pre id=&quot;code_1724948600283&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class BeverageTestDrive {
    public static void main(String[] args) {
        CaffeineBeverage tea = new Tea();
        CaffeineBeverage coffee = new Coffee();

        System.out.println(&quot;\n차 준비 중:&quot;);
        tea.prepareRecipe();

        System.out.println(&quot;\n커피 준비 중:&quot;);
        coffee.prepareRecipe();
    }
}&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;CaffeineBeverage 클래스&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;prepareRecipe() 메서드는 알고리즘의 골격을 정의하는 템플릿 메서드입니다. 이 메서드는 final로 선언되어 서브클래스에서 오버라이드할 수 없습니다.&lt;/li&gt;
&lt;li&gt;brew()와 addCondiments()는 추상 메서드로, 서브클래스에서 구체적으로 구현해야 합니다.&lt;/li&gt;
&lt;li&gt;customerWantsCondiments()는 후크(Hook) 메서드로, 기본적으로 true를 반환하며, 서브클래스에서 오버라이드하여 사용자의 입력에 따라 다른 행동을 하도록 할 수 있습니다. 후크(Hook)는 추상 클래스에서 선언되지만 기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않은 메소드입니다. 이러면 서브클래스는 다양한 위치에서 알고리즘에 끼어들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Coffee 클래스&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;brew() 메서드에서는 커피를 우려내는 과정을 정의합니다.&lt;/li&gt;
&lt;li&gt;addCondiments() 메서드에서는 설탕과 우유를 추가하는 과정을 정의합니다.&lt;/li&gt;
&lt;li&gt;customerWantsCondiments() 후크 메서드를 오버라이드하여, 사용자가 설탕과 우유를 원하는지 물어보고 그에 따라 행동을 결정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Tea 클래스&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;brew() 메서드에서는 차를 우려내는 과정을 정의합니다.&lt;/li&gt;
&lt;li&gt;addCondiments() 메서드에서는 레몬을 추가하는 과정을 정의합니다.&lt;/li&gt;
&lt;li&gt;customerWantsCondiments() 후크 메서드를 오버라이드하여, 사용자가 레몬을 원하는지 물어보고 그에 따라 행동을 결정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;BeverageTestDrive 클래스&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tea와 Coffee 객체를 생성하고, 각각의 prepareRecipe() 메서드를 호출하여 음료를 준비하는 전체 과정을 실행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;b&gt;템플릿 메서드 패턴&lt;/b&gt;은 알고리즘의 뼈대를 정의하고, 일부 단계를 서브클래스에서 재정의할 수 있게 하는 패턴입니다. 이 패턴을 통해 코드를 재사용하고, 알고리즘의 구조를 변경하지 않으면서 세부 구현을 다르게 할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 개념&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;템플릿 메서드(Template Method)&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;추상 메서드(Abstract Method)&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;예: brew()와 addCondiments() 메서드는 서브클래스에서 구체적으로 정의해야 하는 단계입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;후크(Hook)&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;예: customerWantsCondiments()는 후크로 사용자가 추가 재료를 원하는지 여부를 확인할 수 있게 합니다.&lt;/li&gt;
&lt;/ul&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;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&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;b&gt;유연성&lt;/b&gt;: 후크 메서드를 통해 서브클래스에서 알고리즘의 동작을 필요에 따라 변경할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성&lt;/b&gt;: 새로운 서브클래스를 추가함으로써 알고리즘의 새로운 변형을 쉽게 구현할 수 있습니다.&lt;/li&gt;
&lt;/ol&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;템플릿 메서드 패턴은 알고리즘의 핵심 구조를 보호하면서도 확장 가능하게 유지할 수 있는 강력한 디자인 패턴입니다.&lt;/p&gt;</description>
      <category>개발서적/헤드퍼스트 디자인패턴</category>
      <author>SWKo</author>
      <guid isPermaLink="true">https://sw-ko.tistory.com/472</guid>
      <comments>https://sw-ko.tistory.com/472#entry472comment</comments>
      <pubDate>Fri, 30 Aug 2024 00:08:52 +0900</pubDate>
    </item>
    <item>
      <title>[개발서적] 헤드퍼스트 디자인 패턴 Ch7. 어댑터 패턴과 퍼사드 패턴</title>
      <link>https://sw-ko.tistory.com/471</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 객체지향 어댑터&lt;/h2&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-filename=&quot;스크린샷 2024-08-29 오후 7.03.37.png&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nOMrP/btsJi55Oc5C/YzZxSdJcuKwzgcAqaJtTGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nOMrP/btsJi55Oc5C/YzZxSdJcuKwzgcAqaJtTGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nOMrP/btsJi55Oc5C/YzZxSdJcuKwzgcAqaJtTGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnOMrP%2FbtsJi55Oc5C%2FYzZxSdJcuKwzgcAqaJtTGk%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;500&quot; height=&quot;159&quot; data-filename=&quot;스크린샷 2024-08-29 오후 7.03.37.png&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Duck과 Turkey 예시를 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1724925987760&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Duck {
    void quack();
    void fly();
}

public interface Turkey {
    void gobble();
    void fly();
}

public class TurkeyAdapter implements Duck {
    Turkey turkey;

    public TurkeyAdapter(Turkey turkey) {
        this.turkey = turkey;
    }

    @Override
    public void quack() {
        turkey.gobble();  // 터키의 골골거리는 소리를 꽥꽥거리는 소리로 대체
    }

    @Override
    public void fly() {
        for (int i = 0; i &amp;lt; 5; i++) {  // 터키는 짧게 날기 때문에 5번 반복하여 날도록 한다
            turkey.fly();
        }
    }
}&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;이제 클라이언트 코드에서는 Turkey 객체를 Duck 인터페이스로 사용하여 다음과 같이 호출할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1724926013390&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DuckTestDrive {
    public static void main(String[] args) {
        MallardDuck duck = new MallardDuck();

        WildTurkey turkey = new WildTurkey();
        Duck turkeyAdapter = new TurkeyAdapter(turkey);

        System.out.println(&quot;The Turkey says...&quot;);
        turkey.gobble();
        turkey.fly();

        System.out.println(&quot;\nThe Duck says...&quot;);
        testDuck(duck);

        System.out.println(&quot;\nThe TurkeyAdapter says...&quot;);
        testDuck(turkeyAdapter);
    }

    static void testDuck(Duck duck) {
        duck.quack();
        duck.fly();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TurkeyAdapter 클래스는 Duck 인터페이스를 구현하고, 내부에 Turkey 객체를 가집니다.&lt;/li&gt;
&lt;li&gt;TurkeyAdapter의 quack() 메서드는 Turkey 객체의 gobble() 메서드를 호출하여 꽥꽥 소리 대신 골골 소리를 냅니다.&lt;/li&gt;
&lt;li&gt;TurkeyAdapter의 fly() 메서드는 터키가 짧게 날기 때문에, 이 동작을 여러 번 반복하여 Duck의 fly() 메서드를 흉내냅니다.&lt;/li&gt;
&lt;li&gt;testDuck() 메서드는 Duck 인터페이스를 사용하여 객체를 테스트하며, 이 메서드를 통해 TurkeyAdapter도 Duck처럼 동작하게 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
&lt;div data-message-id=&quot;1c9ca921-2c28-4e54-ae82-e7dce0d72d6b&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어댑터 패턴을 사용하면 서로 다른 인터페이스를 가진 클래스들이 함께 동작하도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서는 Turkey를 Duck 인터페이스로 사용할 수 있게 하여 코드의 유연성을 높이고, 기존 코드를 변경하지 않고도 새로운 기능을 추가할 수 있음을 보여줍니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 어댑터 패턴 알아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 타깃 인터페이스 : Duck&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어뎁티 인터페이스 : Turkey&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TurckeyAdapter에서는 타깃 인터페이스인 Duck을 구현했습니다.&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;1. 클라이언트에서 타깃 인터페이스(Duck)로 메서드(fly, quack)를 호출해서 어댑터에 요청을 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 어댑터(TurkeyAdapter)는 어댑티 인터페이스(Turkey)로 그 요청을 어댑티에 관한 하나 이상의 메서드(gobble) 호출로 변환합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;3. 클라이언트는 호출 결과를 받긴 하지만 중간에 어댑터(TurkeyAdapter)가 있다는 사실을 모릅니다.&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;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;3. 어댑터 패턴의 정의&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;어댑터 패턴은 특정 클래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;인터페이스가 호환되지 않아 같이 쓸 수 없었던 클래스를 사용할 수 있게 도와줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-29 오후 7.36.35.png&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYMkU1/btsJkSKM8H7/Vj3z7WDNd5WgqUfkMCRPZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYMkU1/btsJkSKM8H7/Vj3z7WDNd5WgqUfkMCRPZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYMkU1/btsJkSKM8H7/Vj3z7WDNd5WgqUfkMCRPZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYMkU1%2FbtsJkSKM8H7%2FVj3z7WDNd5WgqUfkMCRPZK%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;600&quot; height=&quot;218&quot; data-filename=&quot;스크린샷 2024-08-29 오후 7.36.35.png&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;4. &lt;/span&gt;&lt;span&gt;객체&lt;/span&gt;&lt;span&gt; &lt;/span&gt;어댑터와&lt;span&gt; &lt;/span&gt;&lt;span&gt;클래스&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;어댑터&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;1564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UANJ7/btsJlfMgmIx/5jqWerhFfS7fF3qgB6EPok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UANJ7/btsJlfMgmIx/5jqWerhFfS7fF3qgB6EPok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UANJ7/btsJlfMgmIx/5jqWerhFfS7fF3qgB6EPok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUANJ7%2FbtsJlfMgmIx%2F5jqWerhFfS7fF3qgB6EPok%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;700&quot; height=&quot;705&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;1564&quot;/&gt;&lt;/span&gt;&lt;/figure&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;5. 실전 적용&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어댑터 패턴에서 Enumeration과 Iterator가 자주 언급되는 이유는, 이 두 인터페이스의 역할과 변환이 어댑터 패턴의 전형적인 예이기 때문입니다. 이를 통해 어댑터 패턴이 어떻게 사용되는지 더 잘 이해할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Enumeration과 Iterator의 차이&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;Enumeration (Java 1.0):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Enumeration은 Java의 초기 컬렉션 인터페이스입니다.&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;boolean hasMoreElements(): 더 가져올 요소가 있는지 확인합니다.&lt;/li&gt;
&lt;li&gt;Object nextElement(): 다음 요소를 반환합니다.&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;b&gt;Iterator (Java 1.2):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Iterator는 Enumeration의 진화된 형태로, 컬렉션을 순회(iterate)하는 더 현대적이고 강력한 방법을 제공합니다.&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;boolean hasNext(): 다음 요소가 있는지 확인합니다.&lt;/li&gt;
&lt;li&gt;Object next(): 다음 요소를 반환합니다.&lt;/li&gt;
&lt;li&gt;void remove(): 현재 요소를 제거합니다.&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;어댑터 패턴의 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 컬렉션 프레임워크가 발전하면서, 기존에 Enumeration을 사용하는 코드를 Iterator를 사용하는 코드로 변경해야 하는 상황이 발생할 수 있습니다. 하지만, 이미 작성된 코드를 변경하는 대신, 어댑터 패턴을 활용해 Enumeration을 Iterator로 변환하는 방법을 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1724928511169&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Enumeration;
import java.util.Iterator;

public class EnumerationIterator implements Iterator&amp;lt;Object&amp;gt; {
    Enumeration&amp;lt;?&amp;gt; enumeration;

    public EnumerationIterator(Enumeration&amp;lt;?&amp;gt; enumeration) {
        this.enumeration = enumeration;
    }

    @Override
    public boolean hasNext() {
        return enumeration.hasMoreElements();
    }

    @Override
    public Object next() {
        return enumeration.nextElement();
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException(&quot;Remove not supported&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어댑터 패턴의 동작&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;클라이언트 호출&lt;/b&gt;: 클라이언트는 Iterator 인터페이스를 기대하고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어댑터 역할&lt;/b&gt;: EnumerationIterator 어댑터는 Enumeration을 받아 Iterator로 변환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;요청 변환&lt;/b&gt;: 클라이언트가 Iterator의 메서드(hasNext(), next())를 호출하면, 어댑터는 이 요청을 Enumeration의 메서드(hasMoreElements(), nextElement())로 변환하여 호출합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과 반환&lt;/b&gt;: 클라이언트는 Iterator를 사용하여 결과를 얻습니다. 이 과정에서 Enumeration이 실제로 사용되었지만, 클라이언트는 이를 알지 못합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 퍼사드 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈시어터 퍼사드(Home Theater Facade)는 퍼사드(Facade) 패턴의 대표적인 예시 중 하나로, 복잡한 서브시스템들을 단순화하여 사용자가 쉽게 사용할 수 있도록 도와줍니다. 퍼사드 패턴은 여러 개의 클래스로 이루어진 복잡한 시스템에 단순한 인터페이스를 제공하는 구조적 디자인 패턴입니다.&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;TV 또는 프로젝터&lt;/li&gt;
&lt;li&gt;DVD 플레이어 또는 블루레이 플레이어&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;이 각각의 장치는 고유한 기능과 인터페이스를 가지고 있으며, 사용자가 이를 모두 개별적으로 제어하려면 복잡한 절차가 필요합니다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TV 또는 프로젝터를 켜기&lt;/li&gt;
&lt;li&gt;DVD 플레이어를 켜고, 디스크를 넣기&lt;/li&gt;
&lt;li&gt;사운드 시스템을 켜고, 입력 소스를 DVD로 설정하기&lt;/li&gt;
&lt;li&gt;조명을 어둡게 하기&lt;/li&gt;
&lt;/ol&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;pre id=&quot;code_1724929283300&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class HomeTheaterFacade {
    private Amplifier amp;
    private DvdPlayer dvd;
    private Projector projector;
    private Screen screen;
    private TheaterLights lights;
    private PopcornPopper popper;

    public HomeTheaterFacade(Amplifier amp, DvdPlayer dvd, Projector projector, Screen screen, TheaterLights lights, PopcornPopper popper) {
        this.amp = amp;
        this.dvd = dvd;
        this.projector = projector;
        this.screen = screen;
        this.lights = lights;
        this.popper = popper;
    }

    public void watchMovie(String movie) {
        System.out.println(&quot;Get ready to watch a movie...&quot;);
        popper.on();
        popper.pop();
        lights.dim(10);
        screen.down();
        projector.on();
        projector.wideScreenMode();
        amp.on();
        amp.setDvd(dvd);
        amp.setSurroundSound();
        amp.setVolume(5);
        dvd.on();
        dvd.play(movie);
    }

    public void endMovie() {
        System.out.println(&quot;Shutting movie theater down...&quot;);
        popper.off();
        lights.on();
        screen.up();
        projector.off();
        amp.off();
        dvd.stop();
        dvd.eject();
        dvd.off();
    }
}&lt;/code&gt;&lt;/pre&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;watchMovie() 메서드&lt;/b&gt;: 이 메서드를 호출하면 퍼사드는 여러 서브시스템의 복잡한 상호작용을 처리하여, 사용자가 영화 시청을 준비하는 모든 단계를 수행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;endMovie() 메서드&lt;/b&gt;: 이 메서드는 영화를 종료할 때 필요한 모든 단계를 자동으로 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1724929360386&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class HomeTheaterTestDrive {
    public static void main(String[] args) {
        // 홈시어터 서브시스템 객체들 생성
        Amplifier amp = new Amplifier(&quot;Top-O-Line Amplifier&quot;);
        Tuner tuner = new Tuner(&quot;Top-O-Line AM/FM Tuner&quot;, amp);
        DvdPlayer dvd = new DvdPlayer(&quot;Top-O-Line DVD Player&quot;, amp);
        CdPlayer cd = new CdPlayer(&quot;Top-O-Line CD Player&quot;, amp);
        Projector projector = new Projector(&quot;Top-O-Line Projector&quot;, dvd);
        TheaterLights lights = new TheaterLights(&quot;Theater Ceiling Lights&quot;);
        Screen screen = new Screen(&quot;Theater Screen&quot;);
        PopcornPopper popper = new PopcornPopper(&quot;Popcorn Popper&quot;);

        // 홈시어터 퍼사드 생성
        HomeTheaterFacade homeTheater = new HomeTheaterFacade(
                amp, dvd, projector, screen, lights, popper);

        // 영화를 보는 과정
        homeTheater.watchMovie(&quot;Raiders of the Lost Ark&quot;);

        // 영화를 종료하는 과정
        homeTheater.endMovie();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 어댑터 패턴 vs 퍼사드 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어댑터 패턴과 퍼사드 패턴은 둘 다 구조적 디자인 패턴으로, 기존 코드와의 인터페이스를 조정하거나 단순화하는 데 사용됩니다. 하지만 이 두 패턴은 서로 다른 목적과 상황에서 사용됩니다. 각각의 패턴의 차이점과 사용 목적을 비교해 보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;b&gt;목적 (Purpose)&lt;/b&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;어댑터 패턴 (Adapter Pattern)&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;/li&gt;
&lt;li&gt;&lt;b&gt;상황&lt;/b&gt;: 기존의 클래스나 인터페이스를 수정할 수 없거나 수정하지 않고도 다른 인터페이스를 사용해야 할 때 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 예시&lt;/b&gt;: Enumeration을 Iterator로 변환하거나, 서로 다른 API를 사용하여 동일한 기능을 수행해야 할 때.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;퍼사드 패턴 (Facade Pattern)&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;/li&gt;
&lt;li&gt;&lt;b&gt;상황&lt;/b&gt;: 복잡한 서브시스템을 가진 라이브러리나 클래스들을 단순화된 인터페이스로 제공하여 사용 편의성을 높이고 싶을 때 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 예시&lt;/b&gt;: 홈시어터 시스템의 여러 구성 요소를 간단한 인터페이스로 묶어 사용하거나, 복잡한 API를 쉽게 사용할 수 있게 하는 단순화된 클래스 제공.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;b&gt;구조 (Structure)&lt;/b&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;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;예: TurkeyAdapter는 Duck 인터페이스를 구현하면서 Turkey 객체를 사용하여 quack()을 gobble()로 변환합니다.&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;예: HomeTheaterFacade는 여러 서브시스템(앰프, DVD 플레이어, 프로젝터 등)을 관리하여 watchMovie()와 같은 단순한 메서드로 복잡한 작업을 수행할 수 있게 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &lt;b&gt;사용 방법 (Usage)&lt;/b&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;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;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;/li&gt;
&lt;li&gt;복잡한 API나 서브시스템을 사용하는 대신, 퍼사드를 통해 간단한 명령으로 여러 작업을 수행할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. &lt;b&gt;클라이언트의 인식 (Client Awareness)&lt;/b&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;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;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;h3 data-ke-size=&quot;size23&quot;&gt;5. &lt;b&gt;유사점과 차이점&lt;/b&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;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;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;b&gt;호환성&lt;/b&gt; 문제를 해결하고, 퍼사드 패턴은 &lt;b&gt;복잡성&lt;/b&gt; 문제를 해결합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 최소 지식 원칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소 지식 원칙(Principle of Least Knowledge)에 따르면 객체 사이의 상호작용은 될 수 있으면 아주 가까운 '친구' 사이에만 허용하는 편이 좋습니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;진짜 절친에게만 이야기해야 한다.&lt;/span&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;이 원칙을 잘 따르면 여러 클래스가 복잡하게 얽혀 있어서, 시스템의 한 부분을 변경했을 때 다른 부분까지 줄줄이 고쳐야 하는 상황을 방지할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;FE 에서 사용되는 어댑터 패턴&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;예제 1&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@superlipbalm/how-i-use-adapter-pattern-in-reactjs&quot;&gt;https://velog.io/@superlipbalm/how-i-use-adapter-pattern-in-reactjs&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724940874156&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;(번역) 리액트에서 어댑터(Adapter) 패턴을 사용하는 방법&quot; data-og-description=&quot;프론트엔드와 백엔드 사이의 개발은 별도로 진행됩니다. 따라서 데이터 구조가 일치하지 않는 것은 불가피합니다. 바로 이 지점에서 어댑터(Adapter) 패턴이라는 디자인 패턴을 적용할 수 있습니&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@superlipbalm/how-i-use-adapter-pattern-in-reactjs&quot; data-og-url=&quot;https://velog.io/@superlipbalm/how-i-use-adapter-pattern-in-reactjs&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cASE22/hyWV0PeYkm/j1dQfJSBql2nXlD3KVYKWk/img.jpg?width=2000&amp;amp;height=1125&amp;amp;face=0_0_2000_1125,https://scrap.kakaocdn.net/dn/ba2I0f/hyWV4YoIQl/bH8gtVYanYBcKejZWvkr91/img.jpg?width=2000&amp;amp;height=1125&amp;amp;face=0_0_2000_1125&quot;&gt;&lt;a href=&quot;https://velog.io/@superlipbalm/how-i-use-adapter-pattern-in-reactjs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@superlipbalm/how-i-use-adapter-pattern-in-reactjs&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cASE22/hyWV0PeYkm/j1dQfJSBql2nXlD3KVYKWk/img.jpg?width=2000&amp;amp;height=1125&amp;amp;face=0_0_2000_1125,https://scrap.kakaocdn.net/dn/ba2I0f/hyWV4YoIQl/bH8gtVYanYBcKejZWvkr91/img.jpg?width=2000&amp;amp;height=1125&amp;amp;face=0_0_2000_1125');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;(번역) 리액트에서 어댑터(Adapter) 패턴을 사용하는 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드와 백엔드 사이의 개발은 별도로 진행됩니다. 따라서 데이터 구조가 일치하지 않는 것은 불가피합니다. 바로 이 지점에서 어댑터(Adapter) 패턴이라는 디자인 패턴을 적용할 수 있습니&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어댑터 패턴을 프론트엔드(Frontend)에 적용할 수 있는 여러 가지 예제가 있습니다. 프론트엔드 개발에서 어댑터 패턴은 기존 코드와의 호환성을 유지하거나, 다른 API 또는 라이브러리의 인터페이스를 통일하는 데 유용하게 사용됩니다. 다음은 프론트엔드에서 어댑터 패턴을 활용할 수 있는 대표적인 예입니다.&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;예제 2: 서로 다른 API 간의 호환성 문제 해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 흔한 예 중 하나는 서로 다른 서드파티 API를 통합할 때 발생합니다. 예를 들어, 두 개의 서드파티 API가 동일한 기능(예: 데이터 가져오기)을 제공하지만, 각기 다른 인터페이스를 가지고 있다고 가정해 보겠습니다. 이 경우 어댑터 패턴을 사용해 두 API를 통합된 방식으로 사용할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;시나리오&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;API 1&lt;/b&gt;: REST API를 사용해 데이터를 가져옴.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;API 2&lt;/b&gt;: GraphQL을 사용해 데이터를 가져옴.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기존 인터페이스&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1724942128844&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// API 1: REST
function fetchDataFromRestApi(endpoint) {
    return fetch(endpoint).then(response =&amp;gt; response.json());
}

// API 2: GraphQL
function fetchDataFromGraphQL(query) {
    return fetch('/graphql', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query }),
    }).then(response =&amp;gt; response.json());
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;어댑터 패턴 적용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 API를 통일된 인터페이스로 사용할 수 있도록 어댑터를 만듭니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1724942145251&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 어댑터 인터페이스
class DataAdapter {
    fetchData() {
        throw new Error('This method should be overridden.');
    }
}

// REST API 어댑터
class RestApiAdapter extends DataAdapter {
    constructor(endpoint) {
        super();
        this.endpoint = endpoint;
    }

    fetchData() {
        return fetchDataFromRestApi(this.endpoint);
    }
}

// GraphQL API 어댑터
class GraphQLAdapter extends DataAdapter {
    constructor(query) {
        super();
        this.query = query;
    }

    fetchData() {
        return fetchDataFromGraphQL(this.query);
    }
}

// 클라이언트 코드
function displayData(adapter) {
    adapter.fetchData().then(data =&amp;gt; {
        console.log('Fetched Data:', data);
    });
}

// 사용 예시
const restAdapter = new RestApiAdapter('/api/data');
const graphqlAdapter = new GraphQLAdapter('{ data { id name } }');

displayData(restAdapter);
displayData(graphqlAdapter);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서 RestApiAdapter와 GraphQLAdapter는 각각 DataAdapter라는 공통 인터페이스를 구현합니다. 이를 통해 클라이언트 코드(displayData)는 API의 종류에 상관없이 데이터를 가져올 수 있습니다.&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;1. 타겟 인터페이스 (Target Interface)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;타겟 인터페이스&lt;/b&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;b&gt;타겟 인터페이스&lt;/b&gt;: DataAdapter 클래스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DataAdapter 클래스는 fetchData()라는 메서드를 정의하는 인터페이스로, REST API와 GraphQL API를 동일한 방식으로 사용할 수 있도록 합니다.&lt;/li&gt;
&lt;li&gt;클라이언트 코드(displayData 함수)는 이 인터페이스를 통해 데이터를 가져옵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 어댑터 (Adapter)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어댑터&lt;/b&gt;는 타겟 인터페이스를 구현하고, 내부적으로 &lt;b&gt;어댑티&lt;/b&gt;(Adaptee)의 인터페이스를 사용하여 요청을 변환하는 클래스입니다. 어댑터는 타겟 인터페이스와 어댑티 간의 호환성을 제공하여, 클라이언트 코드가 어댑티의 복잡한 인터페이스를 신경 쓰지 않고도 이를 사용할 수 있게 해줍니다.&lt;/p&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;: RestApiAdapter 클래스와 GraphQLAdapter 클래스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RestApiAdapter는 REST API를 사용하여 데이터를 가져오는 어댑터입니다. 이 클래스는 타겟 인터페이스인 DataAdapter를 구현하고, 내부적으로 fetchDataFromRestApi 함수를 호출합니다.&lt;/li&gt;
&lt;li&gt;GraphQLAdapter는 GraphQL API를 사용하여 데이터를 가져오는 어댑터입니다. 이 클래스 역시 타겟 인터페이스인 DataAdapter를 구현하고, 내부적으로 fetchDataFromGraphQL 함수를 호출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 어댑티 (Adaptee)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어댑티&lt;/b&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;b&gt;어댑티&lt;/b&gt;: fetchDataFromRestApi 함수와 fetchDataFromGraphQL 함수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fetchDataFromRestApi는 REST API를 통해 데이터를 가져오는 기존 함수입니다.&lt;/li&gt;
&lt;li&gt;fetchDataFromGraphQL는 GraphQL API를 통해 데이터를 가져오는 기존 함수입니다.&lt;/li&gt;
&lt;li&gt;이 두 함수는 클라이언트 코드에서 직접 사용되기에는 인터페이스가 맞지 않기 때문에, 어댑터를 통해 타겟 인터페이스에 맞게 변환됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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; (DataAdapter): 클라이언트 코드가 기대하는 공통 인터페이스.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어댑터&lt;/b&gt; (RestApiAdapter, GraphQLAdapter): 타겟 인터페이스를 구현하고, 내부적으로 어댑티를 호출하여 요청을 처리하는 클래스.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어댑티&lt;/b&gt; (fetchDataFromRestApi, fetchDataFromGraphQL): 원래의 클래스나 함수로, 어댑터에 의해 타겟 인터페이스에 맞게 변환되는 객체.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 통해 클라이언트 코드(displayData)는 어떤 API가 실제로 사용되는지 알 필요 없이, 일관된 인터페이스(fetchData)를 사용하여 데이터를 가져올 수 있습니다.&lt;/p&gt;</description>
      <category>개발서적/헤드퍼스트 디자인패턴</category>
      <author>SWKo</author>
      <guid isPermaLink="true">https://sw-ko.tistory.com/471</guid>
      <comments>https://sw-ko.tistory.com/471#entry471comment</comments>
      <pubDate>Thu, 29 Aug 2024 20:20:31 +0900</pubDate>
    </item>
    <item>
      <title>[개발서적] 헤드퍼스트 디자인 패턴 Ch6. 커맨드(Command) 패턴</title>
      <link>https://sw-ko.tistory.com/470</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 커맨드 패턴 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커맨드 패턴은 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;요청을 객체로 캡슐화하여 서로 다른 요청을 매개변수화하거나, 요청의 취소 및 재실행 등을 가능하게 하는 디자인 패턴&lt;/span&gt;입니다. 이 패턴을 사용하면 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;요청을 처리하는 객체(Receiver)와 요청을 발행하는 객체(Client)를 분리&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객체 마을 식당 예시&lt;/b&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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주문서 (Command 객체)&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;span style=&quot;background-color: #9feec3;&quot;&gt;주문서 객체는 주문 내용을 캡슐화&lt;/span&gt;합니다. 이는 요청을 특정한 형식으로 묶어서 다른 객체에게 전달할 수 있도록 하는 역할을 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징&lt;/b&gt;: 주문서에는 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;orderUp() 메소드(execute())&lt;/span&gt;가 포함되어 있으며, 이 메소드를 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;호출하면 주문이 실행&lt;/span&gt;됩니다. 주문서에는 식사를 준비할 주방장의 레퍼런스가 포함되어 있지만, 이를 사용하는 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;종업원은 그 세부 내용을 알 필요가 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;종업원 (Invoker)&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;span style=&quot;background-color: #9feec3;&quot;&gt;종업원은 주문의 세부 내용을 알지 못하며, 단지 orderUp() 메소드를 호출&lt;/span&gt;하여 주문이 실행되도록 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징&lt;/b&gt;: 종업원은 주문서를 전달하고 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;orderUp() 메소드를 호출하는 역할만 수행&lt;/span&gt;하며, 주문서에 무엇이 포함되어 있는지, 누가 음식을 준비하는지에 대해서는 알 필요가 없습니다. 이로써 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;종업원과 주방장은 완전히 분리됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주방장 (Receiver)&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;span style=&quot;background-color: #9feec3;&quot;&gt;실제로 주문된 음식을 준비하는 역할&lt;/span&gt;을 합니다. 주문서에 포함된 정보를 사용하여 음식을 만들며, 이 과정은 주방장만이 알고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징&lt;/b&gt;: &lt;span style=&quot;background-color: #9feec3;&quot;&gt;주방장은 종업원과 직접 상호작용하지 않습니다.&lt;/span&gt; 종업원이 orderUp() 메소드를 호출하면, 주방장은 그에 따라 음식을 준비하는 작업을 처리합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;2. 객체마을 식당과 커맨드 패턴&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-15 오후 12.23.47.png&quot; data-origin-width=&quot;1676&quot; data-origin-height=&quot;1674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPrtRz/btsI4vbPh1T/fuIB5agtlQt2onaLnQ41n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPrtRz/btsI4vbPh1T/fuIB5agtlQt2onaLnQ41n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPrtRz/btsI4vbPh1T/fuIB5agtlQt2onaLnQ41n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPrtRz%2FbtsI4vbPh1T%2FfuIB5agtlQt2onaLnQ41n0%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;600&quot; height=&quot;599&quot; data-filename=&quot;스크린샷 2024-08-15 오후 12.23.47.png&quot; data-origin-width=&quot;1676&quot; data-origin-height=&quot;1674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-15 오후 12.26.17.png&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxEvIL/btsI4khkeIH/JK3CKiSu89aEbTDv0O5BRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxEvIL/btsI4khkeIH/JK3CKiSu89aEbTDv0O5BRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxEvIL/btsI4khkeIH/JK3CKiSu89aEbTDv0O5BRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxEvIL%2FbtsI4khkeIH%2FJK3CKiSu89aEbTDv0O5BRK%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;450&quot; height=&quot;197&quot; data-filename=&quot;스크린샷 2024-08-15 오후 12.26.17.png&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 첫번째 커맨드 객체 만들기&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 커맨드 인터페이스 구현&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커맨드 객체는 모두 같은 인터페이스를 구현해야 합니다. &lt;span style=&quot;background-color: #9feec3;&quot;&gt;인터페이스에는 execute() 메소드 하나만 존재&lt;/span&gt;합니다. (이름은 변경 가능 ex. orderUp())&lt;/p&gt;
&lt;pre id=&quot;code_1723692737772&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Command {
    public void execute();
}&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;b&gt;2. 조명을 켤 때 필요한 커맨드 클래스 구현&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;Light는 Receiver&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723693932666&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Light {
    
    // 조명을 켜는 메소드
    public void on() {
        System.out.println(&quot;The light is on&quot;);
    }

    // 조명을 끄는 메소드
    public void off() {
        System.out.println(&quot;The light is off&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1723692870862&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class LightOnCommand implements Command {
    Light light;

    // 생성자: 제어할 Light 객체를 받아서 저장
    public LightOnCommand(Light light) {
        this.light = light;
    }

    // Command 인터페이스의 execute() 메소드 구현
    public void execute() {
        light.on();  // Light 객체의 on() 메소드를 호출하여 조명을 켬
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;LightOnCommand 클래스&lt;/b&gt;: 조명을 켜는 커맨드 객체입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Light 객체&lt;/b&gt;: 커맨드 객체가 제어할 조명을 나타냅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;생성자&lt;/b&gt;: Light 객체를 받아서 내부에 저장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;execute() 메소드&lt;/b&gt;: 저장된 Light 객체의 on() 메소드를 호출하여 조명을 켭니다.&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;&lt;b&gt;3. 커맨드 객체 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;SimpleRemotecontrol은 Invoker&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723692997573&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SimpleRemoteControl {
    Command slot;  // 커맨드를 저장할 슬롯

    public SimpleRemoteControl() {
        // 기본 생성자
    }

    // 커맨드를 설정하는 메소드
    public void setCommand(Command command) {
        slot = command;
    }

    // 버튼이 눌렸을 때 커맨드를 실행하는 메소드
    public void buttonWasPressed() {
        slot.execute();  // 슬롯에 저장된 커맨드를 실행
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SimpleRemoteControl 클래스&lt;/b&gt;: 매우 간단한 리모컨을 나타내며, 하나의 커맨드 객체만을 저장하고 실행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;slot 변수&lt;/b&gt;: Command 인터페이스를 구현한 객체를 저장하는 변수입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;setCommand() 메소드&lt;/b&gt;: 리모컨의 버튼에 연결될 커맨드를 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;buttonWasPressed() 메소드&lt;/b&gt;: 버튼이 눌렸을 때 호출되며, 슬롯에 저장된 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;커맨드 객체의 execute() 메소드를 호출&lt;/span&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1723693126415&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class RemoteControlTest {

    public static void main(String[] args) {
        // SimpleRemoteControl 인스턴스 생성 (Invoker 역할) (종업원)
        SimpleRemoteControl remote = new SimpleRemoteControl();

        // Light 인스턴스 생성 (Receiver 역할) (주방장)
        Light light = new Light();

        // LightOnCommand 인스턴스 생성 (Command 역할), light 객체를 전달
        LightOnCommand lightOn = new LightOnCommand(light);

        // 리모컨에 커맨드 설정
        remote.setCommand(lightOn);

        // 버튼을 눌러 커맨드 실행 (조명을 켜는 작업 수행)
        remote.buttonWasPressed();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Invoker 역할 (SimpleRemoteControl 클래스)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SimpleRemoteControl 클래스의 remote 객체가 Invoker 역할을 합니다. 이 객체는 커맨드를 저장하고, 버튼이 눌렸을 때 해당 커맨드를 실행하는 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Receiver 역할 (Light 클래스)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Light 클래스의 light 객체는 Receiver 역할을 합니다. 실제로 조명을 켜고 끄는 작업을 수행하는 객체입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Command 역할 (LightOnCommand 클래스)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LightOnCommand 클래스는 Command 인터페이스를 구현하여 조명을 켜는 작업을 캡슐화한 커맨드 객체입니다. light 객체를 생성자에서 받아서 execute() 메소드가 호출되면 light.on()을 실행합니다.&lt;/li&gt;
&lt;/ul&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;remote.setCommand(lightOn)을 통해 리모컨의 버튼에 LightOnCommand 커맨드를 설정하고, remote.buttonWasPressed() 메소드를 호출하면 커맨드가 실행되어 조명이 켜집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 커맨트 패턴의 정의 &amp;amp; 다이어그램&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커맨드 패턴(Command Pattern)을 사용하면 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;요청 내역을 객체로 캡슐화해서 객체를 서로 다른 요청 내역에 따라 매개변수화&lt;/span&gt;(command 객체를 넘김)할 수 있습니다. (ex. remote.setCommand(lightOn))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;요청을 큐에 저장하거나 로그로 기록하거나 작업 취소 기능을 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;1354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgG5LE/btsI4oxhfpk/MwEPBycviBnjxIBhQd7fJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgG5LE/btsI4oxhfpk/MwEPBycviBnjxIBhQd7fJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgG5LE/btsI4oxhfpk/MwEPBycviBnjxIBhQd7fJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgG5LE%2FbtsI4oxhfpk%2FMwEPBycviBnjxIBhQd7fJK%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;600&quot; height=&quot;540&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;1354&quot;/&gt;&lt;/span&gt;&lt;/figure&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;5. 리모컨 코드 만들기&lt;/h2&gt;
&lt;pre id=&quot;code_1723695968073&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class RemoteControl {
    Command[] onCommands;
    Command[] offCommands;

    public RemoteControl() {
        onCommands = new Command[7];
        offCommands = new Command[7];
        Command noCommand = new NoCommand();

        // 모든 슬롯에 noCommand로 초기화
        for (int i = 0; i &amp;lt; 7; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
    }

    // 특정 슬롯에 커맨드 설정
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    // on 버튼을 누르면 해당 슬롯의 onCommand 실행
    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
    }

    // off 버튼을 누르면 해당 슬롯의 offCommand 실행
    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
    }

    // 리모컨의 상태를 출력
    public String toString() {
        StringBuffer stringBuff = new StringBuffer();
        stringBuff.append(&quot;\n----- 리모컨 ---------\n&quot;);
        for (int i = 0; i &amp;lt; onCommands.length; i++) {
            stringBuff.append(&quot;[slot &quot; + i + &quot;] &quot; + onCommands[i].getClass().getName()
                              + &quot; &quot; + offCommands[i].getClass().getName() + &quot;\n&quot;);
        }
        return stringBuff.toString();
    }
}&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;b&gt;커맨드 클래스 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 조명 끄는 커맨트&lt;/p&gt;
&lt;pre id=&quot;code_1723696029026&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class LightOffCommand implements Command {
    Light light;

    // 생성자: 리시버인 Light 객체를 받아서 저장
    public LightOffCommand(Light light) {
        this.light = light;
    }

    // execute 메소드: 리시버의 off 메소드를 호출하여 조명을 끔
    public void execute() {
        light.off();
    }
}&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;2. 오디오 켜는 커맨드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오디오를&lt;span&gt; &lt;/span&gt;&lt;span&gt;켤&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;때&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;자동으로&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CD&lt;/span&gt;&lt;span&gt;가&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;재생&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723696157319&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class StereoOnWithCDCommand implements Command {
    Stereo stereo;

    // 생성자: 제어할 Stereo 객체를 받아서 저장
    public StereoOnWithCDCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    // execute 메소드: Stereo 객체의 on(), setCD(), setVolume() 메소드 호출
    public void execute() {
        stereo.on();        // 오디오 시스템을 켬
        stereo.setCD();     // CD 모드로 설정
        stereo.setVolume(11);  // 볼륨을 11로 설정
    }
}&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;b&gt;리모컨 테스트&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723696248835&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class RemoteLoader {
    public static void main(String[] args) {
        // 리모컨 객체 생성
        RemoteControl remoteControl = new RemoteControl();

        // 각 장치 생성 (Receiver 들)
        Light livingRoomLight = new Light(&quot;Living Room&quot;);
        Light kitchenLight = new Light(&quot;Kitchen&quot;);
        CeilingFan ceilingFan = new CeilingFan(&quot;Living Room&quot;);
        GarageDoor garageDoor = new GarageDoor(&quot;Garage&quot;);
        Stereo stereo = new Stereo(&quot;Living Room&quot;);

        // 조명용 커맨드 객체 생성
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
        LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
        LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);

        // 선풍기용 커맨드 객체 생성
        CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);

        // 차고 문용 커맨드 객체 생성
        GarageDoorUpCommand garageDoorUp = new GarageDoorUpCommand(garageDoor);
        GarageDoorDownCommand garageDoorDown = new GarageDoorDownCommand(garageDoor);

        // 오디오 시스템용 커맨드 객체 생성
        StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);
        StereoOffCommand stereoOff = new StereoOffCommand(stereo);

        // 리모컨 슬롯에 커맨드 로드
        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
        remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
        remoteControl.setCommand(3, stereoOnWithCD, stereoOff);

        // 리모컨 버튼 테스트
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);
        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);
        remoteControl.onButtonWasPushed(3);
        remoteControl.offButtonWasPushed(3);

        // 리모컨의 슬롯 정보를 출력
        System.out.println(remoteControl);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 작업 취소 기능 추가하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;작업 취소 기능을 지원하려면 excute()와 비슷한 undo() 메소드가 있어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723696942142&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Command {
    public void execute();  // 커맨드를 실행하는 메소드
    public void undo();     // 커맨드를 취소(되돌리기)하는 메소드
}&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;b&gt;LightOnCommand의 undo 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723697001864&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class LightOnCommand implements Command {
    Light light;

    // 생성자: 제어할 Light 객체를 받아서 저장
    public LightOnCommand(Light light) {
        this.light = light;
    }

    // execute 메소드: Light 객체의 on 메소드를 호출하여 조명을 켬
    public void execute() {
        light.on();
    }

    // undo 메소드: Light 객체의 off 메소드를 호출하여 조명을 끔 (실행 취소)
    public void undo() {
        light.off();
    }
}&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;b&gt;RemoteControlWithUndo&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723697067534&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class RemoteControlWithUndo {
    Command[] onCommands;
    Command[] offCommands;
    Command undoCommand;  // 마지막으로 실행된 커맨드를 저장하는 변수

    public RemoteControlWithUndo() {
        onCommands = new Command[7];
        offCommands = new Command[7];
        
        Command noCommand = new NoCommand();
        for (int i = 0; i &amp;lt; 7; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
        undoCommand = noCommand;  // 초기값으로 noCommand 설정
    }

    // 특정 슬롯에 onCommand와 offCommand 설정
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    // on 버튼이 눌렸을 때 실행
    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();  // 커맨드 실행
        undoCommand = onCommands[slot];  // 마지막으로 실행된 커맨드를 undoCommand에 저장
    }

    // off 버튼이 눌렸을 때 실행
    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();  // 커맨드 실행
        undoCommand = offCommands[slot];  // 마지막으로 실행된 커맨드를 undoCommand에 저장
    }

    // undo 버튼이 눌렸을 때 실행
    public void undoButtonWasPushed() {
        undoCommand.undo();  // 마지막으로 실행된 커맨드의 undo 메소드 호출
    }

    // 리모컨의 상태를 출력하는 toString 메소드 (생략 가능)
    public String toString() {
        StringBuilder stringBuff = new StringBuilder();
        stringBuff.append(&quot;\n----- 리모컨 ---------\n&quot;);
        for (int i = 0; i &amp;lt; onCommands.length; i++) {
            stringBuff.append(&quot;[slot &quot; + i + &quot;] &quot; + onCommands[i].getClass().getName()
                              + &quot;    &quot; + offCommands[i].getClass().getName() + &quot;\n&quot;);
        }
        stringBuff.append(&quot;[undo] &quot; + undoCommand.getClass().getName() + &quot;\n&quot;);
        return stringBuff.toString();
    }
}&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;b&gt;RemoteLoader에서 undo 실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723697146122&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class RemoteLoader {
    public static void main(String[] args) {
        // Undo 기능이 있는 리모컨 객체 생성
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();

        // 거실 조명 객체 생성
        Light livingRoomLight = new Light(&quot;Living Room&quot;);

        // 조명을 켜고 끄는 커맨드 객체 생성
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);

        // 리모컨의 0번 슬롯에 조명 커맨드 설정
        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);

        // 조명을 켜고 끄는 명령을 실행
        remoteControl.onButtonWasPushed(0);  // 조명을 켬
        remoteControl.offButtonWasPushed(0); // 조명을 끔

        // 현재 리모컨의 상태 출력
        System.out.println(remoteControl);

        // 마지막 명령을 취소 (undo)
        remoteControl.undoButtonWasPushed(); // 조명을 다시 켬 (undo)

        // 다시 조명을 끄고, 켜는 명령을 실행
        remoteControl.offButtonWasPushed(0); // 조명을 끔
        remoteControl.onButtonWasPushed(0);  // 조명을 켬

        // 현재 리모컨의 상태 출력
        System.out.println(remoteControl);

        // 마지막 명령을 취소 (undo)
        remoteControl.undoButtonWasPushed(); // 조명을 다시 끔 (undo)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 여러 동작을 한 번에 처리하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;매크로 커맨드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723697324466&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MacroCommand implements Command {
    Command[] commands;  // 여러 커맨드를 담을 배열

    // 생성자: Command 배열을 받아서 MacroCommand 안에 저장
    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }

    // execute 메소드: 배열에 저장된 모든 커맨드를 실행
    public void execute() {
        for (int i = 0; i &amp;lt; commands.length; i++) {
            commands[i].execute();
        }
    }

    // undo 메소드: 배열에 저장된 모든 커맨드를 거꾸로 실행 취소
    public void undo() {
        for (int i = commands.length - 1; i &amp;gt;= 0; i--) {
            commands[i].undo();
        }
    }
}&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;b&gt;매크로 커맨드 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723697359200&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class RemoteLoader {
    public static void main(String[] args) {
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();

        // 장치들 생성
        Light livingRoomLight = new Light(&quot;Living Room&quot;);
        Stereo stereo = new Stereo(&quot;Living Room&quot;);
        CeilingFan ceilingFan = new CeilingFan(&quot;Living Room&quot;);

        // 개별 커맨드 생성
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);
        CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan);

        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
        StereoOffCommand stereoOff = new StereoOffCommand(stereo);
        CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);

        // MacroCommand로 여러 커맨드를 묶음
        Command[] partyOn = { livingRoomLightOn, stereoOnWithCD, ceilingFanOn };
        Command[] partyOff = { livingRoomLightOff, stereoOff, ceilingFanOff };

        MacroCommand partyOnMacro = new MacroCommand(partyOn);
        MacroCommand partyOffMacro = new MacroCommand(partyOff);

        // 리모컨에 MacroCommand 설정
        remoteControl.setCommand(0, partyOnMacro, partyOffMacro);

        // 리모컨 테스트
        System.out.println(&quot;--- Pushing Macro On---&quot;);
        remoteControl.onButtonWasPushed(0);  // 여러 장치가 한 번에 켜짐

        System.out.println(&quot;--- Pushing Macro Off---&quot;);
        remoteControl.offButtonWasPushed(0);  // 여러 장치가 한 번에 꺼짐

        System.out.println(&quot;--- Pushing Undo---&quot;);
        remoteControl.undoButtonWasPushed();  // 마지막 명령들을 모두 취소
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발서적/헤드퍼스트 디자인패턴</category>
      <author>SWKo</author>
      <guid isPermaLink="true">https://sw-ko.tistory.com/470</guid>
      <comments>https://sw-ko.tistory.com/470#entry470comment</comments>
      <pubDate>Thu, 15 Aug 2024 12:23:07 +0900</pubDate>
    </item>
    <item>
      <title>[개발서적] 헤드퍼스트 디자인 패턴 Ch5. 싱글턴(Singleton) 패턴</title>
      <link>https://sw-ko.tistory.com/469</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 싱글턴 패턴 vs 전역 변수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역 변수의 단점은 애플리케이션 시작 시 객체가 생성되어 자원을 낭비할 수 있다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 전역 변수에 자원을 많이 차지하는 객체를 대입했지만, 애플리케이션 종료까지 사용되지 않는다면 쓸모없는 자원 낭비가 됩니다. 이를 방지하기 위해 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;싱글턴 패턴을 사용하면 필요할 때만 객체를 생성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 고전적인 싱글턴 패턴 구현법&lt;/h2&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스 내부에 static 변수를 사용하여 자신의 인스턴스를 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;생성자를 private으로 설정&lt;/b&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: #9feec3;&quot;&gt;생성자를 private으로 선언&lt;/span&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;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: #9feec3;&quot;&gt;정적 메서드를 통해 외부에서 이 클래스의 인스턴스에 접근할 수 있도록 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;이 메서드에서는 객체가 이미 생성되었는지 확인한 후, 생성되지 않았다면 객체를 생성하고, 생성된 인스턴스를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1723465028543&quot; class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Singleton {
    // 유일한 인스턴스를 정적 변수로 선언
    private static Singleton uniqueInstance;

    // private 생성자: 외부에서 인스턴스 생성을 막음
    private Singleton() {}

    // 유일한 인스턴스를 반환하는 정적 메서드
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton(); // 인스턴스가 없으면 생성
        }
        return uniqueInstance; // 이미 생성된 인스턴스를 반환
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 초콜릿 보일러&lt;/h2&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;&amp;nbsp;&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;이를 통해 여러 개의 보일러 인스턴스가 생성되어 발생할 수 있는 문제를 방지할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723465381792&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class ChocolateBoiler {
    private boolean empty;
    private boolean boiled;

    // 유일한 인스턴스를 저장할 static 변수
    private static ChocolateBoiler uniqueInstance;

    // private 생성자: 외부에서 인스턴스를 생성하지 못하게 막음
    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    // 유일한 인스턴스를 반환하는 static 메서드
    public static ChocolateBoiler getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new ChocolateBoiler();
        }
        return uniqueInstance;
    }

    public void fill() {
        if (isEmpty()) {
            empty = false;
            boiled = false;
            // 보일러에 초콜릿과 우유를 채움
        }
    }

    public void drain() {
        if (!isEmpty() &amp;amp;&amp;amp; isBoiled()) {
            // 보일러를 비움
            empty = true;
        }
    }

    public void boil() {
        if (!isEmpty() &amp;amp;&amp;amp; !isBoiled()) {
            // 보일러를 가열
            boiled = true;
        }
    }

    public boolean isEmpty() {
        return empty;
    }

    public boolean isBoiled() {
        return boiled;
    }
}&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;4. 싱글턴 패턴의 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;싱글턴 패턴(Singleton Pattern)&lt;/b&gt;은 클래스 인스턴스를 하나 만들고, 그 인스턴스로의 전역 접근을 제공합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 멀티스레딩 문제 해결하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티쓰레드 환경에서 동시에 여러 쓰레드가 getInstance() 메서드를 호출할 경우, 여러 개의 인스턴스가 생성될 수 있는 위험이 있습니다.&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;&lt;b&gt;메서드 동기화 (synchronized)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;getInstance() 메서드 전체를 synchronized 키워드를 사용해 동기화&lt;/span&gt;하여, 한 번에 하나의 쓰레드만 메서드를 실행할 수 있게 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723465875431&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static synchronized ChocolateBoiler getInstance() {
    if (uniqueInstance == null) {
        uniqueInstance = new ChocolateBoiler();
    }
    return uniqueInstance;
}&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;이 방법은 안전하지만, 모든 접근이 동기화되어 성능이 저하(100배 차이)될 수 있습니다.&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;6. 더 효율적으로 멀티스레딩 문제 해결하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법 1&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getInstance() 메서드의 속도가 중요하지 않다면, 동기화를 그대로 사용해도 괜찮습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동기화는 구현이 간단하고 효율적일 수 있지만, 성능 저하가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 getInstance()가 애플리케이션에서 성능 병목이 된다면, 다른 해결 방법을 고려해야 합니다.&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;&lt;b&gt;방법 2&lt;/b&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;정적 초기화 부분에서 Singleton 인스턴스를 미리 생성하여, 멀티쓰레드 환경에서도 안전하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getInstance() 메서드는 이미 생성된 인스턴스를 반환하기만 하면 되므로 간단하고 효율적입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723466250160&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Singleton {
    // 클래스 로딩 시점에 인스턴스를 생성
    private static final Singleton uniqueInstance = new Singleton();

    // private 생성자: 외부에서 인스턴스 생성 불가
    private Singleton() {}

    // 이미 생성된 인스턴스를 반환
    public static Singleton getInstance() {
        return uniqueInstance;
    }
}&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;b&gt;방법 3&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DCL(Double-Checked Locking)을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getInstance() 메서드에서 인스턴스가 이미 생성되었는지 먼저 확인하고, 생성되지 않은 경우에만 동기화 블록에 들어가 인스턴스를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써, 인스턴스가 처음 생성될 때만 동기화가 일어나고, 그 이후에는 동기화 없이 인스턴스에 접근할 수 있어 성능이 향상됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723466359900&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Singleton {
    // volatile 키워드를 사용하여 인스턴스 변수 선언
    private volatile static Singleton uniqueInstance;

    // private 생성자: 외부에서 인스턴스 생성 불가
    private Singleton() {}

    // 인스턴스 생성 메서드
    public static Singleton getInstance() {
        if (uniqueInstance == null) { // 인스턴스가 없으면
            synchronized (Singleton.class) { // 클래스에 대해 동기화
                if (uniqueInstance == null) { // 다시 한 번 인스턴스가 없는지 확인
                    uniqueInstance = new Singleton(); // 인스턴스 생성
                }
            }
        }
        return uniqueInstance; // 이미 생성된 인스턴스를 반환
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;volatile 키워드&lt;/b&gt;: uniqueInstance 변수를 volatile로 선언하여, 인스턴스가 생성되는 과정을 다른 쓰레드들이 정확하게 인식할 수 있도록 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이중 검증&lt;/b&gt;: getInstance() 메서드에서 인스턴스가 null인지 두 번 확인합니다. 처음에는 동기화 없이 확인하고, 두 번째는 동기화 블록 내에서 확인하여 인스턴스가 없는 경우에만 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법 4&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;enum&lt;/b&gt;을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum은 자바 언어 스펙에 의해 인스턴스가 단 한 번만 생성되고, 그 인스턴스는 enum의 생애주기 동안 계속 유지됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 싱글턴 인스턴스가 하나만 존재하는 것이 자동으로 보장됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723468309152&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum Singleton {
    UNIQUE_INSTANCE;  // enum 상수로 싱글턴 인스턴스 정의

    // 기타 필요한 필드와 메서드를 정의할 수 있습니다.
    public void someMethod() {
        // 싱글턴 객체로 수행할 작업들
        System.out.println(&quot;Doing something with the singleton instance.&quot;);
    }
}

public class SingletonClient {
    public static void main(String[] args) {
        // Singleton 인스턴스에 접근
        Singleton singleton = Singleton.UNIQUE_INSTANCE;
        
        // 여기서 싱글턴을 사용
        singleton.someMethod();  // &quot;Doing something with the singleton instance.&quot; 출력
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://patterns-dev-kr.github.io/design-patterns/singleton-pattern/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://patterns-dev-kr.github.io/design-patterns/singleton-pattern/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723467447305&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Singleton 패턴&quot; data-og-description=&quot;앱 전체에서 공유 및 사용되는 단일 인스턴스 - Singleton은 1회에 한하여 인스턴스화가 가능하며 전역에서 접근 가능한 클래스를 지칭한다. 만들어진 Singleton&amp;hellip;&quot; data-og-host=&quot;patterns-dev-kr.github.io&quot; data-og-source-url=&quot;https://patterns-dev-kr.github.io/design-patterns/singleton-pattern/&quot; data-og-url=&quot;https://patterns-dev-kr.github.io/design-patterns/singleton-pattern/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bRILXR/hyWOfyu8QX/xEKK8MYmAbtH2wLTHV84rK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/bFd1kT/hyWOfSPzxU/tVvR13JE3FtZk8lCD7W1n1/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://patterns-dev-kr.github.io/design-patterns/singleton-pattern/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://patterns-dev-kr.github.io/design-patterns/singleton-pattern/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bRILXR/hyWOfyu8QX/xEKK8MYmAbtH2wLTHV84rK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/bFd1kT/hyWOfSPzxU/tVvR13JE3FtZk8lCD7W1n1/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Singleton 패턴&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;앱 전체에서 공유 및 사용되는 단일 인스턴스 - Singleton은 1회에 한하여 인스턴스화가 가능하며 전역에서 접근 가능한 클래스를 지칭한다. 만들어진 Singleton&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;patterns-dev-kr.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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>개발서적/헤드퍼스트 디자인패턴</category>
      <author>SWKo</author>
      <guid isPermaLink="true">https://sw-ko.tistory.com/469</guid>
      <comments>https://sw-ko.tistory.com/469#entry469comment</comments>
      <pubDate>Mon, 12 Aug 2024 21:45:27 +0900</pubDate>
    </item>
    <item>
      <title>[개발서적] 헤드퍼스트 디자인 패턴 Ch4. 팩토리(Factory) 패턴</title>
      <link>https://sw-ko.tistory.com/468</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 최첨단 피자 코드 만들기&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;초기 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722157866973&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PizzaStore {
    public Pizza orderPizza(String type) {
        Pizza pizza = null;

        if (type.equals(&quot;cheese&quot;)) {
            pizza = new CheesePizza();
        } else if (type.equals(&quot;greek&quot;)) {
            pizza = new GreekPizza();
        } else if (type.equals(&quot;pepperoni&quot;)) {
            pizza = new PepperoniPizza();
        } else if (type.equals(&quot;clam&quot;)) {
            pizza = new ClamPizza();
        } else if (type.equals(&quot;veggie&quot;)) {
            pizza = new VeggiePizza();
        }

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}&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: #9feec3;&quot;&gt;문제점은 인스턴스를 만드는 구상 클래스(new ~~)를 선택하는 부분&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;: 새로운 피자 종류를 추가하거나 기존 피자 종류를 제거하려면 PizzaStore 클래스의 orderPizza 메서드를 수정해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지보수 어려움&lt;/b&gt;: 피자 종류가 많아질수록 orderPizza 메서드의 조건문이 길어지고 복잡해집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 객체 생성 부분 캡슐화하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 생성 부분을 orderPizza() 메소드에서 뽑아내야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;새로 만들 객체를 팩토리라고 부르겠습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 생성을 처리하는 클래스를 팩토리(Factory)라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;SimplePizzaFactory를 만들고 나면 orderPizza() 메소드는 새로 만든 객체의 클라이언트가 됩니다.&lt;/span&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;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;더 이상 orderPizza() 메소드에서 어떤 피자를 만들지 고민하지 않아도 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722158771980&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SimplePizzaFactory {
    public Pizza createPizza(String type) {
        Pizza pizza = null;
        
        if (type.equals(&quot;cheese&quot;)) {
            pizza = new CheesePizza();
        } else if (type.equals(&quot;pepperoni&quot;)) {
            pizza = new PepperoniPizza();
        } else if (type.equals(&quot;clam&quot;)) {
            pizza = new ClamPizza();
        } else if (type.equals(&quot;veggie&quot;)) {
            pizza = new VeggiePizza();
        }
        
        return pizza;
    }
}&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;3. 클라이언트 코드 수정하기&lt;/h2&gt;
&lt;pre id=&quot;code_1722158901574&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// PizzaStore 클래스
public class PizzaStore {
    SimplePizzaFactory factory;

    // 생성자: SimplePizzaFactory 객체를 전달받아 초기화
    public PizzaStore(SimplePizzaFactory factory) {
        this.factory = factory;
    }

    // 피자를 주문하는 메소드
    public Pizza orderPizza(String type) {
        Pizza pizza;

        // SimplePizzaFactory를 사용해 피자 객체 생성
        pizza = factory.createPizza(type);

        // 피자 준비, 굽기, 자르기, 포장 과정 수행
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}&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;4.   '간단한 팩토리'의 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;간단한 팩토리(Simple Factory)&lt;/b&gt;는 디자인 패턴이라기 보다는 프로그래밍에서 자주 쓰이는 &lt;b&gt;관용구&lt;/b&gt;에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-29 오전 1.04.07.png&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkVgDU/btsIRnxaPC1/DaOCuifuPDQfMvbETRkGB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkVgDU/btsIRnxaPC1/DaOCuifuPDQfMvbETRkGB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkVgDU/btsIRnxaPC1/DaOCuifuPDQfMvbETRkGB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkVgDU%2FbtsIRnxaPC1%2FDaOCuifuPDQfMvbETRkGB1%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;700&quot; height=&quot;449&quot; data-filename=&quot;스크린샷 2024-07-29 오전 1.04.07.png&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722182888350&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PizzaStore {
    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory) {
        this.factory = factory;
    }

    public Pizza orderPizza(String type) {
        Pizza pizza;

	// 피자를 만드는 코드가 PizzaStore와 직접 연결되어 있어서, 유연성이 없음(조리 방식이 일관됨)
        pizza = factory.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722182904454&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SimplePizzaFactory {
    public Pizza createPizza(String type) {
        Pizza pizza = null;

        if (type.equals(&quot;cheese&quot;)) {
            pizza = new CheesePizza();
        } else if (type.equals(&quot;clam&quot;)) {
            pizza = new ClamPizza();
        }

        return pizza;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722183068373&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CheesePizza extends Pizza {
    public CheesePizza() {
        name = &quot;Cheese Pizza&quot;;
        dough = &quot;Regular Crust&quot;;
        sauce = &quot;Marinara Pizza Sauce&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722182960734&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class Pizza {
    String name;
    String dough;
    String sauce;

    void prepare() {
        System.out.println(&quot;Preparing &quot; + name);
    }

    void bake() {
        System.out.println(&quot;Baking &quot; + name);
    }

    void cut() {
        System.out.println(&quot;Cutting &quot; + name);
    }

    void box() {
        System.out.println(&quot;Boxing &quot; + name);
    }

    public String getName() {
        return name;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722182937289&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PizzaTestDrive {
    public static void main(String[] args) {
        SimplePizzaFactory factory = new SimplePizzaFactory();
        PizzaStore store = new PizzaStore(factory);

        Pizza pizza = store.orderPizza(&quot;cheese&quot;);
        System.out.println(&quot;We ordered a &quot; + pizza.getName() + &quot;\n&quot;);

        pizza = store.orderPizza(&quot;clam&quot;);
        System.out.println(&quot;We ordered a &quot; + pizza.getName() + &quot;\n&quot;);
    }
}&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;5. 다양한 팩토리 만들기&lt;/h2&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;SimplePizzaFactory 대신, NYPizzaFactory, ChicagoFactory, ... 등을 만듭니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722183404133&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class PizzaTestDrive {
    public static void main(String[] args) {
        PizzaFactory nyFactory = new NYPizzaFactory();
        PizzaStore nyStore = new PizzaStore(nyFactory);

        Pizza pizza = nyStore.orderPizza(&quot;veggie&quot;);
        System.out.println(&quot;We ordered a &quot; + pizza.getName() + &quot;\n&quot;);

        PizzaFactory chicagoFactory = new ChicagoPizzaFactory();
        PizzaStore chicagoStore = new PizzaStore(chicagoFactory);

        pizza = chicagoStore.orderPizza(&quot;veggie&quot;);
        System.out.println(&quot;We ordered a &quot; + pizza.getName() + &quot;\n&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div data-message-id=&quot;0029cecf-f386-4822-beee-c918f94af60f&quot; data-message-author-role=&quot;assistant&quot;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;유연성을 유지하면서도 피자 가게와 피자 제작 과정을 일관되게 관리할 수 있는 새로운 접근법이 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; background-color: #9feec3;&quot;&gt;피자 가게에서 일관된 품질의 피자를 만들기 위해 피자 제작 과정을 하나로 묶는 프레임워크가 필요합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 피자 가게 프레임워크 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피자를 만드는 일 자체는 PizzaStore 클래스에 진행하면서도 지점의 스타일을 살릴 방법이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;createPizza() 메소드를 &lt;b&gt;추상 메소드&lt;/b&gt;로 선언하고, 지역별 스타일에 맞게 PizzaStore의 서브클래스를 만듭니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722183734513&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class PizzaStore {

    public Pizza orderPizza(String type) {
        Pizza pizza;

        pizza = createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }

    // 팩토리 메서드
    protected abstract Pizza createPizza(String type);
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;피자의 스타일은 각 서브클래스에서 결정합니다.&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;7. 서브클래스가 결정하는 것 알아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pizzaStore의 orderPizza() 메소드에 이미 주문 시스템이 잘 갖춰져 있습니다.&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;span style=&quot;background-color: #9feec3;&quot;&gt;PizzaStore의 서브클래스에서 createPizza() 메소드를 구현하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-29 오후 8.49.08.png&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;1154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdnPQ4/btsISc92Pr6/mDZb23bY7FCD3suJChks8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdnPQ4/btsISc92Pr6/mDZb23bY7FCD3suJChks8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdnPQ4/btsISc92Pr6/mDZb23bY7FCD3suJChks8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdnPQ4%2FbtsISc92Pr6%2FmDZb23bY7FCD3suJChks8K%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;600&quot; height=&quot;480&quot; data-filename=&quot;스크린샷 2024-07-29 오후 8.49.08.png&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;1154&quot;/&gt;&lt;/span&gt;&lt;/figure&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;orderPizza() 메소드는 추상 클래스 PizzaStore 클래스에 정의되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;orderPizza()는 실제로 어떤 구상 클래스에서 작업이 처리되고 있는지 알 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;PizzaStore와 Pizza는 서로 완전히 분리되어 있습니다.&lt;/span&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;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;피자의 종류는 &lt;b&gt;어떤 서브클래스를 선택했느냐&lt;/b&gt;에 따라 결정됩니다.&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;8. 피자 스타일 서브클래스 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 지점에는 PizzaStore의 서브클래스를 만들고 지역별 특성에 맞게 createPizza() 메소드만 구현하면 PizzaStore의 기능을 그대로 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722254462601&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class PizzaStore {

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type); // 팩토리 메소드 호출
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    // 팩토리 메소드
    protected abstract Pizza createPizza(String type);
}

// 객체를 생성하는 코드를 캡슐화함
public class NYPizzaStore extends PizzaStore {

    @Override
    protected Pizza createPizza(String item) {
        if (item.equals(&quot;cheese&quot;)) {
            return new NYStyleCheesePizza();
        } else if (item.equals(&quot;veggie&quot;)) {
            return new NYStyleVeggiePizza();
        } else if (item.equals(&quot;clam&quot;)) {
            return new NYStyleClamPizza();
        } else if (item.equals(&quot;pepperoni&quot;)) {
            return new NYStylePepperoniPizza();
        } else {
            return null;
        }
    }
}

public abstract class Pizza {
    String name;
    String dough;
    String sauce;

    void prepare() {
        System.out.println(&quot;Preparing &quot; + name);
    }

    void bake() {
        System.out.println(&quot;Baking &quot; + name);
    }

    void cut() {
        System.out.println(&quot;Cutting &quot; + name);
    }

    void box() {
        System.out.println(&quot;Boxing &quot; + name);
    }

    public String getName() {
        return name;
    }
}

public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() {
        name = &quot;NY Style Cheese Pizza&quot;;
        dough = &quot;Thin Crust Dough&quot;;
        sauce = &quot;Marinara Sauce&quot;;
    }
}

public class NYStyleVeggiePizza extends Pizza {
    public NYStyleVeggiePizza() {
        name = &quot;NY Style Veggie Pizza&quot;;
        dough = &quot;Thin Crust Dough&quot;;
        sauce = &quot;Marinara Sauce&quot;;
    }
}

public class NYStyleClamPizza extends Pizza {
    public NYStyleClamPizza() {
        name = &quot;NY Style Clam Pizza&quot;;
        dough = &quot;Thin Crust Dough&quot;;
        sauce = &quot;White Garlic Sauce&quot;;
    }
}

public class NYStylePepperoniPizza extends Pizza {
    public NYStylePepperoniPizza() {
        name = &quot;NY Style Pepperoni Pizza&quot;;
        dough = &quot;Thin Crust Dough&quot;;
        sauce = &quot;Marinara Sauce&quot;;
    }
}

// 메인 메소드에서 피자 주문 과정 시뮬레이션
public class PizzaTestDrive {
    public static void main(String[] args) {
        PizzaStore nyPizzaStore = new NYPizzaStore();
        Pizza pizza = nyPizzaStore.orderPizza(&quot;cheese&quot;);
        System.out.println(&quot;에단이 주문한: &quot; + pizza.getName());
    }
}&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;9. 팩토리 메소드 패턴 살펴보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 팩토리 패턴은 객체 생성을 캡슐화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;팩토리 메소드 패턴은 서브클래스에서 어떤 클래스를 만들지 결정함으로써 객체 생성을 캡슐화합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-29 오후 9.04.41.png&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;1338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0WzOE/btsIQ0ih3gm/GwBBiTasAG4Raye8kUxwJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0WzOE/btsIQ0ih3gm/GwBBiTasAG4Raye8kUxwJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0WzOE/btsIQ0ih3gm/GwBBiTasAG4Raye8kUxwJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0WzOE%2FbtsIQ0ih3gm%2FGwBBiTasAG4Raye8kUxwJ0%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;600&quot; height=&quot;604&quot; data-filename=&quot;스크린샷 2024-07-29 오후 9.04.41.png&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;1338&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;구상 생산자(NYPizzaStore, ChicagoPizzaStore, ...)별로 수많은 제품을 만들 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림을 아래처럼&lt;span style=&quot;background-color: #9feec3;&quot;&gt; 병렬 계층 구조&lt;/span&gt;로 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-29 오후 9.06.12.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MKvsj/btsIRwunaTF/VL58hXAoaBCo44eVDKBhBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MKvsj/btsIRwunaTF/VL58hXAoaBCo44eVDKBhBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MKvsj/btsIRwunaTF/VL58hXAoaBCo44eVDKBhBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMKvsj%2FbtsIRwunaTF%2FVL58hXAoaBCo44eVDKBhBK%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;600&quot; height=&quot;489&quot; data-filename=&quot;스크린샷 2024-07-29 오후 9.06.12.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 팩토리 메소드 패턴의 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;팩토리 메소드 패턴&lt;/b&gt;에서는 객체를 생성할 때 필요한 인터페이스(createPizza)를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 클래스의 인스턴스를 만들지는 서브클래스&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(NYPizzaStore, ChicagoPizzaStore, ...)&lt;/span&gt;에서 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;팩토리 메소드 패턴을 사용하면 클래스 인스턴스 만드는 일을 서브클래스에게 맡기게 됩니다.&lt;/span&gt;&lt;/p&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-filename=&quot;스크린샷 2024-07-29 오후 9.50.44.png&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcuY0k/btsIQLr2gKg/YJX2Zzwt8pc961s7xlspt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcuY0k/btsIQLr2gKg/YJX2Zzwt8pc961s7xlspt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcuY0k/btsIQLr2gKg/YJX2Zzwt8pc961s7xlspt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcuY0k%2FbtsIQLr2gKg%2FYJX2Zzwt8pc961s7xlspt1%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;400&quot; height=&quot;329&quot; data-filename=&quot;스크린샷 2024-07-29 오후 9.50.44.png&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;/figure&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;팩토리 장점은 우선 객체 생성 코드를 전부 한 객체 또는 메소드에 넣으면 코드에서 중복되는 내용을 제거할 수 있고, 나중에 관리할 때도 한 군데에만 신경을 쓰면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 객체 인스턴스를 만들 때 인터페이스(createPizza)만 있으면 됩니다.&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;11. 객체 의존성 살펴보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 인스턴스를 직접 만들면 구상 클래스에 의존해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 심하게 의존적인 PizzaStore 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 피자 객체를 팩토리에 맡겨서 만들지 않고 PizzaStore 클래스 내에서 직접 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;새로운 피자 종류나 스타일을 추가할 때마다 이 클래스의 조건문을 수정해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722258242328&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class DependentPizzaStore {

    public Pizza createPizza(String style, String type) {
        Pizza pizza = null;
        
        if (style.equals(&quot;NY&quot;)) {
            if (type.equals(&quot;cheese&quot;)) {
                pizza = new NYStyleCheesePizza();
            } else if (type.equals(&quot;veggie&quot;)) {
                pizza = new NYStyleVeggiePizza();
            } else if (type.equals(&quot;clam&quot;)) {
                pizza = new NYStyleClamPizza();
            } else if (type.equals(&quot;pepperoni&quot;)) {
                pizza = new NYStylePepperoniPizza();
            }
        } else if (style.equals(&quot;Chicago&quot;)) {
            if (type.equals(&quot;cheese&quot;)) {
                pizza = new ChicagoStyleCheesePizza();
            } else if (type.equals(&quot;veggie&quot;)) {
                pizza = new ChicagoStyleVeggiePizza();
            } else if (type.equals(&quot;clam&quot;)) {
                pizza = new ChicagoStyleClamPizza();
            } else if (type.equals(&quot;pepperoni&quot;)) {
                pizza = new ChicagoStylePepperoniPizza();
            }
        } else {
            System.out.println(&quot;오류: 알 수 없는 피자 종류&quot;);
            return null;
        }

        if (pizza != null) {
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }

        return pizza;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-31 오후 10.27.42.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;1430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2bM2Y/btsITleZLQ0/ssO3KTqhPLgHOAqj60fmUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2bM2Y/btsITleZLQ0/ssO3KTqhPLgHOAqj60fmUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2bM2Y/btsITleZLQ0/ssO3KTqhPLgHOAqj60fmUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2bM2Y%2FbtsITleZLQ0%2FssO3KTqhPLgHOAqj60fmUK%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;600&quot; height=&quot;535&quot; data-filename=&quot;스크린샷 2024-07-31 오후 10.27.42.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;1430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. 의존성 뒤집기 원칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;구상 클래스 의존성을 줄이면 좋습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것이 바로 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;의존성 뒤집기 원칙(Dependency Inversion Principle)&lt;/b&gt;&lt;/span&gt; 입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;디자인 원칙&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;추상화된 것에 의존하게 만들고, 구상 클래스에 의존하지 않게 만든다.&lt;/span&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;고수준 구성 요소(PizzaStore)가 저수준 구성 요소(Pizza 클래스)에 의존하면 안되며, 항상 추상화에 의존하게 만들어야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;DependentPizzaStore의 가장 큰 문제점은 PizzaStore가 모든 종류의 피자에 의존한다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;orderPizza() 메소드에서 구상 형식의 인스턴스(new ~~Pizza())를 직접 만들기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;인스턴스 만드는 부분을 뽑아낼 생각을 해야합니다. -&amp;gt;&amp;nbsp;&lt;b&gt;팩토리 메소드 패턴 사용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-31 오후 10.25.22.png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D59jy/btsIQHjUpVt/92BCwxfpNAUDXED52pTxi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D59jy/btsIQHjUpVt/92BCwxfpNAUDXED52pTxi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D59jy/btsIQHjUpVt/92BCwxfpNAUDXED52pTxi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD59jy%2FbtsIQHjUpVt%2F92BCwxfpNAUDXED52pTxi0%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;600&quot; height=&quot;381&quot; data-filename=&quot;스크린샷 2024-07-31 오후 10.25.22.png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;팩토리 메소드 패턴을 적용하면 고수준 구성 요소인 PizzaStore와 저수준 구성 요소인 피자 객체 모두가 추상 클래스인 Pizza에 의존하는 걸 알 수 있습니다.&lt;/span&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;의존성 뒤집기에서 '뒤집기(inversion)'라는 말이 있는 이유는 객체 지향 디자인을 할 때 일반적으로 생각하는 방법과는 반대로 뒤집어서 생각해야하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 다이어그램에서 의존성이 위에서 아래로 내려가기만 했지만, &lt;span style=&quot;background-color: #9feec3;&quot;&gt;현재 다이어그램에서는 반대로 뒤집어져 있습니다. 고수준 모듈과 저수준 모듈이 하나의 추상 클래스에 의존하게 됩니다.&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;13. 의존성 뒤집기 원칙을 지키는 방법&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수에 구상 클래스의 레퍼런스를 저장하지 맙시다. new 연산자를 사용하면 구상 클래스의 레퍼런스를 사용하게 됩니다. &lt;span style=&quot;background-color: #9feec3;&quot;&gt;new 를 최대한 자제하자!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;구상 클래스에서 유도된 클래스를 만들지 맙시다.&lt;/span&gt; 구상 클래스에서 유도된 클래스를 만들면 특정 구상 클래스에 의존하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1722433312205&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class NYStyleCheesePizza extends CheesePizza {
  // 이런 식으로 구상 클래스(CheesePizza)에서 유도된 클래스(NYStyleCheesePizza)를 만들지 맙시다.
}&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;span style=&quot;background-color: #9feec3;&quot;&gt;베이스 클래스에 이미 구현되어 있는 메소드를 오버라이드하지 맙시다.&lt;/span&gt; Pizza 클래스는 prepare, bake, cut, box 메소드를 이미 구현하고 있습니다. 이 메소드들은 오버라이드하지 말아야 피자 종류에 관계없이 동일하게 동작할 수 있습니다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14. 원재료 팩토리 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PizzaStore은 어느 정도 모양새를 갖췄습니다.&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;span style=&quot;background-color: #9feec3;&quot;&gt;어떻게 하면 지점에서 좋은 재료를 사용할 수 있도록 관리할 수 있을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-31 오후 10.47.35.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;1026&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mae25/btsISG4Zvsz/iZtsHT3D5gt6B5vDEXQsx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mae25/btsISG4Zvsz/iZtsHT3D5gt6B5vDEXQsx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mae25/btsISG4Zvsz/iZtsHT3D5gt6B5vDEXQsx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMae25%2FbtsISG4Zvsz%2FiZtsHT3D5gt6B5vDEXQsx0%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;600&quot; height=&quot;412&quot; data-filename=&quot;스크린샷 2024-07-31 오후 10.47.35.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;1026&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뉴욕 원재료 팩토리를 만들어보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722434067107&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// PizzaIngredientFactory은 추상 팩토리입니다.
public interface PizzaIngredientFactory {
    Dough createDough();
    Sauce createSauce();
    Cheese createCheese();
    Veggies[] createVeggies();
    Pepperoni createPepperoni();
    Clams createClam();
}

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {

    @Override
    public Dough createDough() {
        return new ThinCrustDough();
    }

    @Override
    public Sauce createSauce() {
        return new MarinaraSauce();
    }

    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }

    @Override
    public Veggies[] createVeggies() {
        Veggies[] veggies = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
        return veggies;
    }

    @Override
    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }

    @Override
    public Clams createClam() {
        return new FreshClams();
    }
}&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;15. Pizza 클래스 변경하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공장도 다 만들었으니 이제 생산할 준비가 끝났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pizza 클래스가 팩토리에서 생산한 원재료만 사용하도록 코드를 수정해보겠습니다. (prepare에 주목)&lt;/p&gt;
&lt;pre id=&quot;code_1722435660414&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class Pizza {
    String name;
    Dough dough;
    Sauce sauce;
    Veggies[] veggies;
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;

    // 재료 준비를 위한 추상 메서드
    abstract void prepare();

    // 피자를 굽는 메서드
    void bake() {
        System.out.println(&quot;175도에서 25분 간 굽기&quot;);
    }

    // 피자를 자르는 메서드
    void cut() {
        System.out.println(&quot;피자를 사선으로 자르기&quot;);
    }

    // 피자를 상자에 담는 메서드
    void box() {
        System.out.println(&quot;상자에 피자 담기&quot;);
    }

    // 피자 이름 설정 메서드
    void setName(String name) {
        this.name = name;
    }

    // 피자 이름 반환 메서드
    String getName() {
        return name;
    }

    // 피자 이름 출력 메서드
    public String toString() {
        return &quot;피자 이름: &quot; + 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;size16&quot;&gt;Pizza 추상 클래스 준비가 끝났으니, 뉴욕 스타일 피자와 시카고 스타일 피자를 만들어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;피자마다&lt;/span&gt;&lt;span&gt; &lt;/span&gt;클래스를&lt;span&gt; &lt;/span&gt;지역별로&lt;span&gt; &lt;/span&gt;&lt;span&gt;따로&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;만들&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;필요가&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;지역별로 다른 점은 원재료 팩토리에서 처리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722435838630&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;

// 각 피자 클래스는 생성자로부터 팩토리를 전달받고 그 팩토리를 인스턴스 변수에 저장합니다.
    public CheesePizza(PizzaIngredientFactory ingredientFactory) {
        this.ingredientFactory = ingredientFactory;
    }

    @Override
    void prepare() {
        System.out.println(&quot;Preparing &quot; + name);
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}&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;올바른 재료 공장을 사용하는 NYPizzaStore를 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722436127767&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class PizzaStore {
    protected abstract Pizza createPizza(String item);

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        System.out.println(&quot;--- Making a &quot; + pizza.getName() + &quot; ---&quot;);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

public class NYPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String item) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();

        if (item.equals(&quot;cheese&quot;)) {
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName(&quot;New York Style Cheese Pizza&quot;);
        }
        // 다른 피자 타입도 추가 가능
        return pizza;
    }
}&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;16. 추상 팩토리 패턴의 정의&amp;nbsp;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;추상 팩토리 패턴(Abstract Factory Pattern)은 구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공합니다. 구상 클래스는 서브클래스에서 만듭니다.&lt;/span&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;&lt;span style=&quot;background-color: #9feec3;&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;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;클라이언트와 팩토리에서 생성되는 제품을 분리할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-01 오전 12.31.33.png&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B0IwB/btsISLrIJW0/0EOknCGBAk1E3v3YHIjXl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B0IwB/btsISLrIJW0/0EOknCGBAk1E3v3YHIjXl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B0IwB/btsISLrIJW0/0EOknCGBAk1E3v3YHIjXl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB0IwB%2FbtsISLrIJW0%2F0EOknCGBAk1E3v3YHIjXl1%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;700&quot; height=&quot;479&quot; data-filename=&quot;스크린샷 2024-08-01 오전 12.31.33.png&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-01 오전 12.32.20.png&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;1632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI4TDO/btsIRuc97tD/cBmhNprCmymF0oSsFZ9dBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI4TDO/btsIRuc97tD/cBmhNprCmymF0oSsFZ9dBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI4TDO/btsIRuc97tD/cBmhNprCmymF0oSsFZ9dBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI4TDO%2FbtsIRuc97tD%2FcBmhNprCmymF0oSsFZ9dBK%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;700&quot; height=&quot;736&quot; data-filename=&quot;스크린샷 2024-08-01 오전 12.32.20.png&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;1632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;17. 팩토리 메소드 패턴과 추상 팩토리 패턴&lt;/h2&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;b&gt;팩토리 메소드 패턴&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;팩토리 메소드 패턴은 객체를 생성하기 위한 인터페이스를 정의하고, 실제 객체 생성은 서브클래스에서 담당하도록 하는 패턴입니다.&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구조&lt;/b&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;b&gt;상위 클래스 (Creator)&lt;/b&gt;: 객체를 생성하는 팩토리 메소드를 정의합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하위 클래스 (Concrete Creator)&lt;/b&gt;: 팩토리 메소드를 구체적으로 구현하여 특정 객체를 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1722443427169&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 상위 클래스
public abstract class PizzaStore {
    protected abstract Pizza createPizza(String type);

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

// 하위 클래스
public class NYPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String type) {
        if (type.equals(&quot;cheese&quot;)) {
            return new NYStyleCheesePizza();
        } else if (type.equals(&quot;veggie&quot;)) {
            return new NYStyleVeggiePizza();
        }
        // 추가적인 피자 타입들
        return null;
    }
}&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추상 팩토리 패턴&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;추상 팩토리 패턴은 관련된 객체들의 군을 생성하기 위한 인터페이스를 제공하며, 구체적인 클래스는 이 인터페이스를 구현하여 객체 군을 생성합니다.&lt;/span&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;&lt;b&gt;구조&lt;/b&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;b&gt;추상 팩토리 (Abstract Factory)&lt;/b&gt;: 객체 군을 생성하기 위한 인터페이스를 정의합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구체 팩토리 (Concrete Factory)&lt;/b&gt;: 이 인터페이스를 구현하여 특정 객체 군을 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추상 제품 (Abstract Product)&lt;/b&gt;: 생성될 객체의 인터페이스를 정의합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구체 제품 (Concrete Product)&lt;/b&gt;: 이 인터페이스를 구현하여 구체적인 객체를 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1722443506285&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 추상 팩토리
public interface PizzaIngredientFactory {
    Dough createDough();
    Sauce createSauce();
    Cheese createCheese();
    Veggies[] createVeggies();
    Pepperoni createPepperoni();
    Clams createClam();
}

// 구체 팩토리
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    @Override
    public Dough createDough() {
        return new ThinCrustDough();
    }

    @Override
    public Sauce createSauce() {
        return new MarinaraSauce();
    }

    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }

    @Override
    public Veggies[] createVeggies() {
        Veggies[] veggies = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
        return veggies;
    }

    @Override
    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }

    @Override
    public Clams createClam() {
        return new FreshClams();
    }
}

// 추상 제품
public interface Dough {}
public interface Sauce {}
public interface Cheese {}
public interface Veggies {}
public interface Pepperoni {}
public interface Clams {}

// 구체 제품
public class ThinCrustDough implements Dough {}
public class MarinaraSauce implements Sauce {}
public class ReggianoCheese implements Cheese {}
public class Garlic implements Veggies {}
public class Onion implements Veggies {}
public class Mushroom implements Veggies {}
public class RedPepper implements Veggies {}
public class SlicedPepperoni implements Pepperoni {}
public class FreshClams implements Clams {}

// 피자 클래스
public abstract class Pizza {
    String name;
    Dough dough;
    Sauce sauce;
    Veggies[] veggies;
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;

    abstract void prepare();

    void bake() {
        System.out.println(&quot;Baking &quot; + name);
    }

    void cut() {
        System.out.println(&quot;Cutting &quot; + name);
    }

    void box() {
        System.out.println(&quot;Boxing &quot; + name);
    }

    void setName(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    public String toString() {
        return &quot;Pizza name: &quot; + name;
    }
}

// 구체 피자 클래스
public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;

    public CheesePizza(PizzaIngredientFactory ingredientFactory) {
        this.ingredientFactory = ingredientFactory;
    }

    @Override
    void prepare() {
        System.out.println(&quot;Preparing &quot; + name);
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

// 피자 스토어 클래스
public abstract class PizzaStore {
    protected abstract Pizza createPizza(String item);

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        System.out.println(&quot;--- Making a &quot; + pizza.getName() + &quot; ---&quot;);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

public class NYPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String item) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();

        if (item.equals(&quot;cheese&quot;)) {
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName(&quot;New York Style Cheese Pizza&quot;);
        }
        // 다른 피자 타입도 추가 가능
        return pizza;
    }
}&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;b&gt;요약&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div data-message-id=&quot;42d1ad3e-779d-433f-8545-78f419e1dadb&quot; data-message-author-role=&quot;assistant&quot;&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;span style=&quot;background-color: #9feec3;&quot;&gt;하나의 객체를 생성하는 데 사용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추상 팩토리 패턴&lt;/b&gt;: 관련된 객체들의 군을 생성하기 위한 인터페이스를 제공하여, 객체 생성의 책임을 구체 팩토리 클래스로 위임합니다. &lt;span style=&quot;background-color: #9feec3;&quot;&gt;여러 객체를 생성하는 데 사용됩니다.&lt;/span&gt;&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;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;div&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=eSLrZbPHgoI&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=eSLrZbPHgoI&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=eSLrZbPHgoI&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/WC6jP/hyWKB85O8h/JsrxkeE8IqQquNI8vTdKSk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;프론트엔드에 디자인패턴 끼얹기1 - 팩토리 패턴(factory pattern)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/eSLrZbPHgoI&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발서적/헤드퍼스트 디자인패턴</category>
      <author>SWKo</author>
      <guid isPermaLink="true">https://sw-ko.tistory.com/468</guid>
      <comments>https://sw-ko.tistory.com/468#entry468comment</comments>
      <pubDate>Sun, 28 Jul 2024 18:47:57 +0900</pubDate>
    </item>
    <item>
      <title>[개발서적] 헤드퍼스트 디자인 패턴 Ch3. 데코레이터(Decorator) 패턴</title>
      <link>https://sw-ko.tistory.com/467</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 초대형 커피 전문점, 스타버즈&lt;/h2&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;초기 주문 시스템 클래스는 아래와 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-28 오전 12.28.11.png&quot; data-origin-width=&quot;1430&quot; data-origin-height=&quot;1038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bM37mo/btsIQeOnt2k/Ai2C9IWiVkccH6wu49db80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bM37mo/btsIQeOnt2k/Ai2C9IWiVkccH6wu49db80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bM37mo/btsIQeOnt2k/Ai2C9IWiVkccH6wu49db80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbM37mo%2FbtsIQeOnt2k%2FAi2C9IWiVkccH6wu49db80%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;600&quot; height=&quot;436&quot; data-filename=&quot;스크린샷 2024-07-28 오전 12.28.11.png&quot; data-origin-width=&quot;1430&quot; data-origin-height=&quot;1038&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;Beverage는 음료를 나타내는 추상 클래스이며, 모든 음료는 Beverage 클래스의 서브클래스가 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 서브클래스는 가격을 리턴하는 cost() 메소드를 구현해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722094449550&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Beverage.java
public abstract class Beverage {
    String description = &quot;Unknown Beverage&quot;;

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

// HouseBlend.java
public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = &quot;House Blend Coffee&quot;;
    }

    public double cost() {
        return .89;
    }
}

// DarkRoast.java
public class DarkRoast extends Beverage {
    public DarkRoast() {
        description = &quot;Dark Roast Coffee&quot;;
    }

    public double cost() {
        return .99;
    }
}&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: #9feec3;&quot;&gt;커피에 첨가물이 추가될 때마다 새로운 Class가 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FRUsx/btsIPytbPHm/P5P5NaoFMexmtdDAgpeZ9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FRUsx/btsIPytbPHm/P5P5NaoFMexmtdDAgpeZ9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FRUsx/btsIPytbPHm/P5P5NaoFMexmtdDAgpeZ9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFRUsx%2FbtsIPytbPHm%2FP5P5NaoFMexmtdDAgpeZ9K%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;600&quot; height=&quot;437&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;/figure&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;만약 우유 가격이 인상되면 어떨까요? 클래스를 모두 수정해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면, Beverage 클래스에 각 첨가물 첨가 여부를 보여주는 인스턴스 변수를 추가해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RLisY/btsIRgxXGfR/orhIFRo8BErIKfPdvTqpL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RLisY/btsIRgxXGfR/orhIFRo8BErIKfPdvTqpL1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1402&quot; data-origin-height=&quot;982&quot; data-filename=&quot;스크린샷 2024-07-28 오전 12.38.08.png&quot; width=&quot;600&quot; height=&quot;420&quot; style=&quot;width: 71.8043%; margin-right: 10px;&quot; data-widthpercent=&quot;72.65&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RLisY/btsIRgxXGfR/orhIFRo8BErIKfPdvTqpL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRLisY%2FbtsIRgxXGfR%2ForhIFRo8BErIKfPdvTqpL1%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;1402&quot; height=&quot;982&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BiZZs/btsIPJnTNd6/2HIdCtfObWHcpYovNyirFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BiZZs/btsIPJnTNd6/2HIdCtfObWHcpYovNyirFK/img.png&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; width=&quot;300&quot; height=&quot;558&quot; style=&quot;width: 27.0329%;&quot; data-widthpercent=&quot;27.35&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BiZZs/btsIPJnTNd6/2HIdCtfObWHcpYovNyirFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBiZZs%2FbtsIPJnTNd6%2F2HIdCtfObWHcpYovNyirFK%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;774&quot; height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722522854892&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기본 Beverage 클래스
class Beverage {
    private String description = &quot;Unknown Beverage&quot;;
    private double baseCost = 0.0;
    private boolean hasMilk = false;
    private boolean hasSoy = false;

    // 기본 생성자
    public Beverage(String description, double baseCost) {
        this.description = description;
        this.baseCost = baseCost;
    }

    // 설명 반환
    public String getDescription() {
        return description;
    }

    // Milk 추가 여부 설정
    public void setMilk(boolean hasMilk) {
        this.hasMilk = hasMilk;
    }

    // Soy 추가 여부 설정
    public void setSoy(boolean hasSoy) {
        this.hasSoy = hasSoy;
    }

    // 총 비용 계산
    public double cost() {
        double totalCost = baseCost;
        if (hasMilk) {
            totalCost += 0.50; // Milk 추가 비용
        }
        if (hasSoy) {
            totalCost += 0.30; // Soy 추가 비용
        }
        return totalCost;
    }
}

// Espresso 클래스
class Espresso extends Beverage {
    public Espresso() {
        super(&quot;Espresso&quot;, 1.99);
    }
}

// HouseBlend 클래스
class HouseBlend extends Beverage {
    public HouseBlend() {
        super(&quot;House Blend&quot;, 0.89);
    }
}

// 메인 클래스
public class CoffeeShop {
    public static void main(String[] args) {
        // 에스프레소 주문
        Beverage beverage1 = new Espresso();
        beverage1.setMilk(true); // 밀크 추가
        System.out.println(beverage1.getDescription() + &quot; $&quot; + beverage1.cost());

        // 하우스 블렌드 주문
        Beverage beverage2 = new HouseBlend();
        beverage2.setSoy(true); // 소이 추가
        beverage2.setMilk(true); // 밀크 추가
        System.out.println(beverage2.getDescription() + &quot; $&quot; + beverage2.cost());
    }
}&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;br /&gt;&lt;br /&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: #9feec3;&quot;&gt;첨가물 가격이 바뀔 때마다 기존 코드를 수정해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;첨가물의 종류가 많아지면 새로운 메소드를 추가해야 하고, 슈퍼클래스의 cost() 메소드도 수정해야 합니다.&lt;/li&gt;
&lt;li&gt;특정 첨가물이 들어가면 안되는 음료가 출시될 수도 있습니다. 새로 출시될 아이스티 같은 경우 hasWhip() 메소드가 필요없지만 여전히 상속되는 문제가 있습니다.&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;2. OCP (Open-Closed Principle)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;디자인 원칙&lt;/b&gt;&lt;br /&gt;&lt;/span&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;&lt;a href=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-OCP-%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84-%EC%9B%90%EC%B9%99&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-OCP-%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84-%EC%9B%90%EC%B9%99&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722154253630&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;  완벽하게 이해하는 OCP (개방 폐쇄 원칙)&quot; data-og-description=&quot;개방 폐쇄 원칙 - OCP (Open Closed Principle) 개방 폐쇄의 원칙(OCP)이란 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙을 말한다. 보통 OCP를 확장에 대해서는 &quot; data-og-host=&quot;inpa.tistory.com&quot; data-og-source-url=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-OCP-%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84-%EC%9B%90%EC%B9%99&quot; data-og-url=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-OCP-%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84-%EC%9B%90%EC%B9%99&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxrtJd/hyWCNX9L92/o9Yw0DWp110HPCGLAoLmRK/img.jpg?width=750&amp;amp;height=422&amp;amp;face=0_0_750_422,https://scrap.kakaocdn.net/dn/oU3Fm/hyWCC3oZxS/10NsllnYSZETKsZOsHHkJ1/img.jpg?width=750&amp;amp;height=422&amp;amp;face=0_0_750_422,https://scrap.kakaocdn.net/dn/bYMIJa/hyWGREz8fn/wv4Jk8WoU61ZFJQmUwPNT0/img.jpg?width=750&amp;amp;height=422&amp;amp;face=0_0_750_422&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-OCP-%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84-%EC%9B%90%EC%B9%99&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-OCP-%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84-%EC%9B%90%EC%B9%99&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxrtJd/hyWCNX9L92/o9Yw0DWp110HPCGLAoLmRK/img.jpg?width=750&amp;amp;height=422&amp;amp;face=0_0_750_422,https://scrap.kakaocdn.net/dn/oU3Fm/hyWCC3oZxS/10NsllnYSZETKsZOsHHkJ1/img.jpg?width=750&amp;amp;height=422&amp;amp;face=0_0_750_422,https://scrap.kakaocdn.net/dn/bYMIJa/hyWGREz8fn/wv4Jk8WoU61ZFJQmUwPNT0/img.jpg?width=750&amp;amp;height=422&amp;amp;face=0_0_750_422');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;  완벽하게 이해하는 OCP (개방 폐쇄 원칙)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개방 폐쇄 원칙 - OCP (Open Closed Principle) 개방 폐쇄의 원칙(OCP)이란 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙을 말한다. 보통 OCP를 확장에 대해서는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;inpa.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;3. 데코레이터 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속을 사용해서 총 가격을 산출하는 방법은 좋은 방법은 아니었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;특정 음료에서 시작해서 첨가물로 그 음료를 &lt;b&gt;장식(decorate)&lt;/b&gt; 해보는 건 어떨까요?&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DarkRoast 객체를 가져온다.&lt;/li&gt;
&lt;li&gt;Mocha 객체로 장식한다.&lt;/li&gt;
&lt;li&gt;Whip 객체로 장식한다.&lt;/li&gt;
&lt;li&gt;cost() 메소드를 호출한다.&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;이때 첨가물의 가격을 계산하는 일은 해당 객체에게 위임한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&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;p data-ke-size=&quot;size16&quot;&gt;1. DarkRoast 객체에서 시작합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m8tIR/btsIPkPsuQz/BqXINGMxX2CbdwQbOzK080/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m8tIR/btsIPkPsuQz/BqXINGMxX2CbdwQbOzK080/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m8tIR/btsIPkPsuQz/BqXINGMxX2CbdwQbOzK080/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm8tIR%2FbtsIPkPsuQz%2FBqXINGMxX2CbdwQbOzK080%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;1218&quot; height=&quot;228&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&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;2. 고객이 모카를 주문했으니 Mocha 객체를 만들고 그 객체로 DarkRoast를 감쌉니다.(장식합니다.)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mocha 객체는 Decorator 입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Mocha 에도 cost() 메소드가 있고, Mocha가 감싸고 있는 것도 Beverage 객체로 간주할 수 있습니다.(Mocha도 Beverage의 서브클래스)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-28 오전 1.33.30.png&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by4m80/btsIQpoGtT6/53DfR3vUbx1GXXFRi6K050/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by4m80/btsIQpoGtT6/53DfR3vUbx1GXXFRi6K050/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by4m80/btsIQpoGtT6/53DfR3vUbx1GXXFRi6K050/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby4m80%2FbtsIQpoGtT6%2F53DfR3vUbx1GXXFRi6K050%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;1364&quot; height=&quot;426&quot; data-filename=&quot;스크린샷 2024-07-28 오전 1.33.30.png&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&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;3. 휘핑크림도 추가했으니 Whip 데코레이터를 만들어 Mocha를 감쌉니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-28 오전 1.34.02.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uI6tH/btsIPXszTWF/57gMGIK9OwOVHFErSJGvYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uI6tH/btsIPXszTWF/57gMGIK9OwOVHFErSJGvYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uI6tH/btsIPXszTWF/57gMGIK9OwOVHFErSJGvYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuI6tH%2FbtsIPXszTWF%2F57gMGIK9OwOVHFErSJGvYk%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;700&quot; height=&quot;295&quot; data-filename=&quot;스크린샷 2024-07-28 오전 1.34.02.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&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;4. 이제 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;가격 계산을 할 때는 Whip의 cost()를 호출&lt;/span&gt;하면 됩니다. 그럼 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;Whip은 그 객체가 장식하고 있는 객체에게 가격 계산을 위임&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-28 오전 1.37.11.png&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nFG9c/btsIQdBWoxy/dnw6YhIr9SLJitMNnKC3ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nFG9c/btsIQdBWoxy/dnw6YhIr9SLJitMNnKC3ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nFG9c/btsIQdBWoxy/dnw6YhIr9SLJitMNnKC3ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnFG9c%2FbtsIQdBWoxy%2Fdnw6YhIr9SLJitMNnKC3ek%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;700&quot; height=&quot;365&quot; data-filename=&quot;스크린샷 2024-07-28 오전 1.37.11.png&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&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: #9feec3;&quot;&gt;데코레이터(Whip, Mocha)의 슈퍼클래스(Beverage)는 자신이 장식하고 있는 객체(DarkRoast)의 슈퍼클래스(Beverage)와 같습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;한 객체(DarkRoast)를 여러 개의 데코레이터(Whip, Mocha)로 감쌀 수 있습니다.&lt;/li&gt;
&lt;li&gt;데코레이터는 자신이 감싸고 있는 객체와&amp;nbsp; 같은 슈퍼클래스(Beverage)를 가지고 있기에 원래 객체(싸여 있는 객체)가 들어갈 자리에 데코레이터 객체를 넣어도 상관 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 일 말고도 추가 작업을 수행할 수 있습니다. &lt;/span&gt;&lt;/b&gt;(cost() 메소드에서 로깅 등 다른 액션 가능)&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;h2 data-ke-size=&quot;size26&quot;&gt;4. 데코레이터 패턴의 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데코레이터 패턴(Decorator Pattern)으로 객체에 추가 요소를 동적으로 더할 수 있습니다.&lt;/p&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;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JQbDB/btsIP6QAA6P/sgQ02GGy0OrdYKxqTMmoc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JQbDB/btsIP6QAA6P/sgQ02GGy0OrdYKxqTMmoc0/img.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;1086&quot; data-is-animation=&quot;false&quot; width=&quot;430&quot; height=&quot;336&quot; data-widthpercent=&quot;50.1&quot; style=&quot;width: 49.5161%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JQbDB/btsIP6QAA6P/sgQ02GGy0OrdYKxqTMmoc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJQbDB%2FbtsIP6QAA6P%2FsgQ02GGy0OrdYKxqTMmoc0%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;1388&quot; height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0vFJh/btsIQI9pwRt/lOs75GROtB6iKc3dBVKR9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0vFJh/btsIQI9pwRt/lOs75GROtB6iKc3dBVKR9k/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;1128&quot; data-filename=&quot;스크린샷 2024-07-28 오후 3.03.16.png&quot; style=&quot;width: 49.3211%;&quot; data-widthpercent=&quot;49.9&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0vFJh/btsIQI9pwRt/lOs75GROtB6iKc3dBVKR9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0vFJh%2FbtsIQI9pwRt%2FlOs75GROtB6iKc3dBVKR9k%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;1436&quot; height=&quot;1128&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;각 데코레이터 안에는 Component 객체가 들어있습니다.&lt;/span&gt; (데코레이터에는 구성 요소의 Reference를 포함한 인스턴스 변수가 있습니다.)&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;5. 데코레이터 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Beverage 클래스&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722148391469&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class Beverage {
    String description = &quot;제목 없음&quot;;

    // 이미 구현된 메서드
    public String getDescription() {
        return description;
    }

    // 서브클래스에서 구현해야 하는 추상 메서드
    public abstract double cost();
}&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;b&gt;첨가물(Condiment) 클래스 = 데코레이터 클래스&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722148444548&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class CondimentDecorator extends Beverage {
    Beverage beverage;

    // 모든 첨가물 데코레이터에 getDescription() 메서드를 새로 구현하도록 하기 위해 추상 메서드로 선언
    public abstract String getDescription();
}&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;b&gt;음료 클래스&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722148557945&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Espresso extends Beverage {
    public Espresso() {
        description = &quot;에스프레소&quot;;
    }

    public double cost() {
        return 1.99;
    }
}&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;b&gt;첨가물 코드 구현&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722148621429&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Mocha extends CondimentDecorator {
    Beverage beverage;

    // 생성자에서 감싸고자 하는 음료 객체를 전달받아 저장
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    // 음료 설명에 '모카' 추가
    public String getDescription() {
        return beverage.getDescription() + &quot;, 모카&quot;;
    }

    // 음료 가격에 모카 가격 추가
    public double cost() {
        return beverage.cost() + 0.20;
    }
}&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;b&gt;주문용 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722149149488&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class StarbuzzCoffee {
    public static void main(String[] args) {
        // 아무것도 넣지 않은 에스프레소를 주문하고 그 음료 설명과 가격을 출력합니다
        Beverage beverage1 = new Espresso();
        System.out.println(beverage1.getDescription() + &quot; $&quot; + beverage1.cost());

        // 다크 로스트 커피에 모카 두 번과 휘핑크림을 추가하여 주문하고, 설명과 가격을 출력합니다
        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2); // 모카샷 하나 추가
        beverage2 = new Mocha(beverage2); // 모카샷 하나 더 추가
        beverage2 = new Whip(beverage2);  // 휘핑크림 추가
        System.out.println(beverage2.getDescription() + &quot; $&quot; + beverage2.cost());

        // 하우스 블렌드 커피에 두유, 모카, 휘핑크림을 추가하여 주문하고, 설명과 가격을 출력합니다
        Beverage beverage3 = new HouseBlend();
        beverage3 = new Soy(beverage3);   // 두유 추가
        beverage3 = new Mocha(beverage3); // 모카 추가
        beverage3 = new Whip(beverage3);  // 휘핑크림 추가
        System.out.println(beverage3.getDescription() + &quot; $&quot; + beverage3.cost());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-28 오후 3.46.05.png&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M1G1N/btsIQvvHV1N/zkO8xuN25z3uNBk5oManb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M1G1N/btsIQvvHV1N/zkO8xuN25z3uNBk5oManb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M1G1N/btsIQvvHV1N/zkO8xuN25z3uNBk5oManb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM1G1N%2FbtsIQvvHV1N%2FzkO8xuN25z3uNBk5oManb1%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;400&quot; height=&quot;110&quot; data-filename=&quot;스크린샷 2024-07-28 오후 3.46.05.png&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;b&gt;전체 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722149199077&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Beverage 클래스
public abstract class Beverage {
    String description = &quot;제목 없음&quot;;

    // 이미 구현된 메서드
    public String getDescription() {
        return description;
    }

    // 서브클래스에서 구현해야 하는 추상 메서드
    public abstract double cost();
}

// CondimentDecorator 클래스
public abstract class CondimentDecorator extends Beverage {
    Beverage beverage;

    // 모든 첨가물 데코레이터에 getDescription() 메서드를 새로 구현하도록 하기 위해 추상 메서드로 선언
    public abstract String getDescription();
}

// Mocha 클래스
public class Mocha extends CondimentDecorator {
    Beverage beverage;

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return beverage.getDescription() + &quot;, 모카&quot;;
    }

    public double cost() {
        return beverage.cost() + 0.20;
    }
}

// Whip 클래스
public class Whip extends CondimentDecorator {
    Beverage beverage;

    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return beverage.getDescription() + &quot;, 휘핑크림&quot;;
    }

    public double cost() {
        return beverage.cost() + 0.30;
    }
}

// Soy 클래스
public class Soy extends CondimentDecorator {
    Beverage beverage;

    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return beverage.getDescription() + &quot;, 두유&quot;;
    }

    public double cost() {
        return beverage.cost() + 0.15;
    }
}

// DarkRoast 클래스
public class DarkRoast extends Beverage {
    public DarkRoast() {
        description = &quot;최고의 다크 로스트 커피&quot;;
    }

    public double cost() {
        return 1.99;
    }
}

// Espresso 클래스
public class Espresso extends Beverage {
    public Espresso() {
        description = &quot;에스프레소&quot;;
    }

    public double cost() {
        return 1.99;
    }
}

// HouseBlend 클래스
public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = &quot;하우스 블렌드 커피&quot;;
    }

    public double cost() {
        return 0.89;
    }
}

// StarbuzzCoffee 클래스
public class StarbuzzCoffee {
    public static void main(String[] args) {
        // 아무것도 넣지 않은 에스프레소를 주문하고 그 음료 설명과 가격을 출력합니다
        Beverage beverage1 = new Espresso();
        System.out.println(beverage1.getDescription() + &quot; $&quot; + beverage1.cost());

        // 다크 로스트 커피에 모카 두 번과 휘핑크림을 추가하여 주문하고, 설명과 가격을 출력합니다
        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2); // 모카샷 하나 추가
        beverage2 = new Mocha(beverage2); // 모카샷 하나 더 추가
        beverage2 = new Whip(beverage2);  // 휘핑크림 추가
        System.out.println(beverage2.getDescription() + &quot; $&quot; + beverage2.cost());

        // 하우스 블렌드 커피에 두유, 모카, 휘핑크림을 추가하여 주문하고, 설명과 가격을 출력합니다
        Beverage beverage3 = new HouseBlend();
        beverage3 = new Soy(beverage3);   // 두유 추가
        beverage3 = new Mocha(beverage3); // 모카 추가
        beverage3 = new Whip(beverage3);  // 휘핑크림 추가
        System.out.println(beverage3.getDescription() + &quot; $&quot; + beverage3.cost());
    }
}&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;b&gt;데코레이터 패턴은 객체의 기능을 쉽게 추가하고 변경할 수 있는 방법을 제공합니다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이를 통해 객체 간 결합도를 낮추고 코드의 재사용성과 유지보수성을 높일 수 있는 강력한 디자인 패턴입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@mr.kashif.samman/flexible-and-maintainable-react-native-applications-with-the-decorator-pattern-32d84de576f6&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@mr.kashif.samman/flexible-and-maintainable-react-native-applications-with-the-decorator-pattern-32d84de576f6&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722154017866&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Flexible and Maintainable React / React Native Applications with the Decorator Pattern&quot; data-og-description=&quot;In the world of software development, there are many design patterns that developers can use to build better and more maintainable&amp;hellip;&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@mr.kashif.samman/flexible-and-maintainable-react-native-applications-with-the-decorator-pattern-32d84de576f6&quot; data-og-url=&quot;https://medium.com/@mr.kashif.samman/flexible-and-maintainable-react-native-applications-with-the-decorator-pattern-32d84de576f6&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/K8HYr/hyWGREz6tL/9Z3ohVF0n1ToXZkbC2gDm0/img.png?width=1200&amp;amp;height=774&amp;amp;face=0_0_1200_774,https://scrap.kakaocdn.net/dn/AES5O/hyWCBQXBUa/sgIlH72kseZBNoZjyWOc7K/img.png?width=1358&amp;amp;height=835&amp;amp;face=0_0_1358_835,https://scrap.kakaocdn.net/dn/e1n8A/hyWGTh7KRM/PmSyDStM16xm5OxyEMPZdK/img.png?width=1242&amp;amp;height=534&amp;amp;face=0_0_1242_534&quot;&gt;&lt;a href=&quot;https://medium.com/@mr.kashif.samman/flexible-and-maintainable-react-native-applications-with-the-decorator-pattern-32d84de576f6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@mr.kashif.samman/flexible-and-maintainable-react-native-applications-with-the-decorator-pattern-32d84de576f6&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/K8HYr/hyWGREz6tL/9Z3ohVF0n1ToXZkbC2gDm0/img.png?width=1200&amp;amp;height=774&amp;amp;face=0_0_1200_774,https://scrap.kakaocdn.net/dn/AES5O/hyWCBQXBUa/sgIlH72kseZBNoZjyWOc7K/img.png?width=1358&amp;amp;height=835&amp;amp;face=0_0_1358_835,https://scrap.kakaocdn.net/dn/e1n8A/hyWGTh7KRM/PmSyDStM16xm5OxyEMPZdK/img.png?width=1242&amp;amp;height=534&amp;amp;face=0_0_1242_534');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Flexible and Maintainable React / React Native Applications with the Decorator Pattern&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In the world of software development, there are many design patterns that developers can use to build better and more maintainable&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;&lt;a href=&quot;https://www.youtube.com/watch?v=PU9sr-q8bys&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=PU9sr-q8bys&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=PU9sr-q8bys&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bSJG33/hyWKyxLKiZ/4AG1z0PuHsdfUL3S3oHjiK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;프론트엔드에 디자인패턴 끼얹기3 - 데코레이터 패턴 (decorator pattern)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/PU9sr-q8bys&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시 : debounce&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고차 컴포넌트(HOC)
&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>SWKo</author>
      <guid isPermaLink="true">https://sw-ko.tistory.com/467</guid>
      <comments>https://sw-ko.tistory.com/467#entry467comment</comments>
      <pubDate>Sun, 28 Jul 2024 14:47:04 +0900</pubDate>
    </item>
  </channel>
</rss>