객체 지향 언어에서 객체는 언제 자체 작업을 수행해야하며 언제 객체에서 작업을 수행해야합니까?


11

Page페이지 렌더러에 대한 명령 세트를 나타내는 클래스 가 있다고 가정 하십시오. 그리고 Renderer화면에 페이지를 렌더링하는 방법을 알고 있는 클래스 가 있다고 가정 합니다. 두 가지 다른 방식으로 코드를 구성 할 수 있습니다.

/*
 * 1) Page Uses Renderer internally,
 * or receives it explicitly
 */
$page->renderMe(); 
$page->renderMe($renderer); 

/*
 * 2) Page is passed to Renderer
 */
$renderer->renderPage($page);

각 접근법의 장단점은 무엇입니까? 언제 더 좋을까요? 다른 쪽은 언제 나을까요?


배경

조금 더 많은 배경을 추가하려면 동일한 코드에서 두 가지 방법을 모두 사용하고 있습니다. 이라는 타사 PDF 라이브러리를 사용하고 TCPDF있습니다. 내 코드에서 어딘가에 나는 일에 PDF 렌더링에 대해 다음을 가지고 :

$pdf = new TCPDF();
$html = "some text";
$pdf->writeHTML($html);

페이지 표현을 만들고 싶다고 가정 해보십시오. 다음과 같이 PDF 페이지 스 니펫을 렌더링하는 지침이 포함 된 템플릿을 만들 수 있습니다.

/*
 * A representation of the PDF page snippet:
 * a template directing how to render a specific PDF page snippet
 */
class PageSnippet
{    
    function runTemplate(TCPDF $pdf, array $data = null): void
    {
        $pdf->writeHTML($data['html']);
    }
}

/* To be used like so */
$pdf = new TCPDF();
$data['html'] = "some text";
$snippet = new PageSnippet();
$snippet->runTemplate($pdf, $data);

1) 첫 번째 코드 예제에서와 같이 $snippet 스스로 실행됩니다 . 또한 알고하고 잘 알고 있어야 $pdf하고, 어떤으로 $data그것을위한 작업.

그러나 PdfRenderer다음과 같이 클래스를 만들 수 있습니다 .

class PdfRenderer
{
    /**@var TCPDF */
    protected $pdf;

    function __construct(TCPDF $pdf)
    {
        $this->pdf = $pdf;
    }

    function runTemplate(PageSnippet $template, array $data = null): void
    {
        $template->runTemplate($this->pdf, $data);
    }
}

그런 다음 내 코드는 다음과 같습니다.

$renderer = new PdfRenderer(new TCPDF());
$renderer->runTemplate(new PageSnippet(), array('html' => 'some text'));

2) 여기에 $renderer수령인이 근무 PageSnippet하는 $data데 필요한 모든 물품이 수령 됩니다. 이것은 두 번째 코드 예제와 비슷합니다.

따라서 렌더러가 페이지 스 니펫을 수신하더라도 렌더러 내에서 스 니펫은 여전히 실행됩니다 . 즉, 두 가지 접근 방식이 모두 유효합니다. OO 사용을 하나만 또는 다른 하나만으로 제한 할 수 있는지 잘 모르겠습니다. 서로 마스킹하더라도 두 가지가 필요할 수 있습니다.


2
불행히도, 당신은 공간이나 탭을 사용할지 여부, 사용 스타일을 버팀대 등을 사용하는 라인을 따라 소프트웨어 "종교 전쟁"의 세계로 방황했습니다. 리치 및 빈혈 도메인 모델의 장단점을 인터넷에서 검색하고 자신의 의견을 제시하십시오.
David Arno

7
@DavidArno 이방인의 공간 을 사용하십시오 ! :)
candied_orange

1
하, 나는 때때로이 사이트를 심각하게 이해하지 못한다. 좋은 답변을 얻는 완벽하게 좋은 질문은 의견에 근거한 것으로 즉시 마감됩니다. 그러나 이와 같은 명백한 의견 기반의 질문이 나오고 일반적인 용의자는 어디에도 없습니다. 글쎄, 당신이 그들과 모든 것을 이길 수 없다면 ... :)
David Arno

@Erik Eidt, 답변을 삭제 취소 할 수 있습니까? 답변이 "좋아요"라고 대답 한 것 같습니다.
David Arno

1
SOLID 원칙 이외에도 특히 전문가 부분 에서 GRASP를 살펴볼 수 있습니다 . 문제는 당신이 책임을 수행하기 위해 어떤 정보를 가지고 있습니까?
OnesimusUnbound

답변:


13

이것은 전적으로 당신이 생각 하는 것에 달려 있습니다 .

OOP = SOLID의 경우 작업이 클래스의 단일 책임의 일부인 경우 클래스의 일부 여야합니다.

OO = 가상 디스패치 / 다형성의 경우, 객체가 동적으로 디스패치되어야하는 경우, 즉 인터페이스를 통해 호출되는 경우 작업이 객체의 일부 여야합니다.

OO = 캡슐화의 경우 노출하지 않으려는 내부 상태를 사용하는 경우 작업이 클래스의 일부 여야합니다.

OO =“유창한 인터페이스가 마음에 듭니다”에 대한 질문은 어떤 변형이 더 자연스럽게 읽히는가입니다.

OO = 실제 엔터티 모델링의 경우이 작업을 수행하는 실제 엔터티는 무엇입니까?


이러한 관점은 모두 일반적으로 잘못되었습니다. 그러나 때때로 이러한 관점 중 하나 이상이 설계 결정에 도달하는 데 도움이됩니다.

예를 들어 다형성 관점 사용 : 다른 렌더링 전략 (예 : 다른 출력 형식 또는 다른 렌더링 엔진) $renderer->render($page)이있는 경우 많은 의미가 있습니다. 그러나 다르게 렌더링해야하는 페이지 유형이 다른 경우 $page->render()더 좋습니다. 출력이 페이지 유형과 렌더링 전략 모두에 의존하는 경우 방문자 패턴을 통해 이중 발송을 수행 할 수 있습니다.

많은 언어에서 함수가 메소드 일 필요는 없다는 것을 잊지 마십시오. render($page)종종 완벽하게 훌륭한 (그리고 놀랍도록 간단한) 솔루션과 같은 간단한 기능 .


잠깐만 요 페이지에 렌더러에 대한 참조가 있지만 페이지에 어떤 렌더러가 있는지 모를 경우에도 다형성 렌더링을 얻을 수 있습니다. 그것은 단지 다형성이 토끼 구멍 아래로 조금 더 떨어져 있음을 의미합니다. 또한 렌더러에 전달할 항목을 선택하여 선택할 수 있습니다. 전체 페이지를 통과 할 필요는 없습니다.
candied_orange

@CandiedOrange 그것은 좋은 지적이지만, SRP 하에서 당신의 주장을 예약 할 것입니다 : 어떻게 렌더링되는지 결정하는 것은 페이지의 자본 -R 책임 일 것입니다. 어쩌면 어떤 종류의 다형성 렌더링 전략을 사용할 것입니다.
amon

$renderer렌더링 방법을 결정할 것이라고 생각했습니다 . 때 $page받는 대화 $renderer가 말한다 모든 렌더링하는 것입니다. 방법이 아닙니다. 는 $page어떻게 아무 생각이 없습니다. SRP 문제가 생겼나요?
candied_orange

나는 우리가 동의하지 않는다고 생각하지 않습니다. 첫 번째 의견을이 답변의 개념적 프레임 워크로 정렬하려고했지만 서투른 단어를 사용했을 수 있습니다. 대답에서 언급하지 않았다는 것을 기억하는 한 가지 : 묻지 말고 데이터 흐름도 좋은 휴리스틱입니다.
amon

음 알았어. 네가 옳아. 내가 말한 것은 tell-don't-ask을 따르는 것입니다. 내가 틀렸다면 지금 바로 고쳐줘 렌더러가 페이지 참조를 취하는 다른 전략은 렌더러가 페이지 게터를 사용하여 페이지를 돌아 서서 물건을 요구해야 함을 의미합니다.
candied_orange

2

Alan Kay에 따르면 , 개체는 자급 자족하고 "성인"이며 책임있는 유기체입니다. 성인은 일을하고 수술을받지 않습니다. 즉, 금융 거래는 저축 자체 를 담당하고 페이지는 자체 렌더링 등을 담당합니다 .보다 간결하게 캡슐화는 OOP에서 가장 중요합니다. 특히, 유명한 Tell do n't ask 원칙 (@CandiedOrange는 항상 언급하고 싶다)과 게터와 세터에 대한 대중의 찬사를 통해 나타납니다 .

실제로 데이터베이스 기능, 렌더링 기능 등과 같이 작업을 수행하는 데 필요한 모든 리소스를 보유한 개체가 생성됩니다.

따라서 귀하의 예를 고려할 때 내 OOP 버전은 다음과 같습니다.

class Page
{
    private $data;
    private $renderer;

    public function __construct(ICanRender $renderer, $data)
    {
        $this->renderer = $renderer;
        $this->data = $data;
    }

    public function render()
    {
        $this->renderer->render($this->data);
    }
}

관심이 있으시다면 David West가 자신의 저서 인 Object Thinking 에서 OOP의 원래 원리에 대해 이야기합니다 .


1
솔직히 말해서 누가 15 년 전 누군가가 역사적 관심사를 제외하고 소프트웨어 개발과 관련하여 말한 것에 관심이 있습니까?
David Arno

1
" 나는 객체 지향 개념을 발명 한 사람이 어떤 객체가 무엇인지에 대해 무슨 말을했는지 신경을 쓴다. "왜? 당신의 주장에 "권리에 대한 호소"오류를 사용하도록 유혹하는 것 외에도, 용어를 발명 한 사람의 생각이 15 년 후이 용어의 적용에 어떤 영향을 미칠 수 있습니까?
David Arno

2
@Zapadlo : 왜 메시지가 Page에서 Renderer로 전달되는지에 대한 논쟁은 없습니다. 둘 다 객관적이며 따라서 성인 둘 다입니다.
JacquesB

1
" 권위 오해에 대한 이의 제기는 여기에 적용 할 수 없습니다. "... " 따라서 귀하의 의견으로는 OOP를 대표하는 일련의 개념은 실제로는 [원래 정의의 왜곡 때문에] 잘못된 것 입니다." 나는 권위 오류에 대한 호소가 무엇인지 모른다고 생각합니까? 실마리 : 당신은 여기에 하나를 사용했습니다. :)
David Arno

1
@David Arno 그렇다면 권위에 대한 모든 호소는 잘못된 것입니까? "내 의견에 어필하기를 원하십니까?" Zapadio는 존경받는 출처를 제공했지만 상충되는 출처를 반대하거나 인용 할 수는 있지만 누군가가 인용을 제공했다고 주장하는 것은 건설적이지 않다고 반복해서 불평합니다.
user949300

2

$page->renderMe();

여기에서 우리는 page렌더링 자체를 완전히 책임지고 있습니다. 생성자를 통해 렌더가 제공되었거나 해당 기능이 내장되어있을 수 있습니다.

매개 변수로 전달하는 것과 매우 유사하므로 첫 번째 경우 (생성자를 통해 렌더와 함께 제공됨)는 무시합니다. 대신 내장 기능의 장단점을 살펴 보겠습니다.

장점은 캡슐화 수준이 매우 높다는 것입니다. 이 페이지는 내부 상태에 대해 아무것도 공개 할 필요가 없습니다. 자체 렌더링을 통해서만 노출합니다.

단점은 단일 책임 원칙 (SRP)을 위반한다는 것입니다. 우리는 페이지의 상태를 캡슐화하는 역할을하는 클래스를 가지고 있으며, 자신을 렌더링하는 방법에 대한 규칙으로 하드 코딩되어 있으며, 따라서 객체가 "자신에게 일을하고, 다른 사람이하지 말아야 할 일"과 같은 다른 모든 책임을 수행해야합니다. ".

$page->renderMe($renderer);

여기서도 여전히 페이지 자체를 렌더링 할 수 있어야하지만 실제 렌더링을 수행 할 수있는 도우미 개체가 제공됩니다. 여기서 두 가지 시나리오가 발생할 수 있습니다.

  1. 페이지는 렌더링을 생성하기 위해 렌더링 규칙 (어떤 순서로 호출 할 메소드)을 알아야합니다. 캡슐화는 유지되지만 페이지가 여전히 렌더링 프로세스를 감독해야하므로 SRP가 여전히 손상되었거나
  2. 이 페이지는 렌더러 객체에 대해 하나의 메소드를 호출하여 세부 정보를 전달합니다. SRP에 점점 가까워지고 있지만 캡슐화는 약화되었습니다.

$renderer->renderPage($page);

여기서 우리는 SRP를 완전히 존중했습니다. 페이지 객체는 페이지에 대한 정보를 보유하고 렌더러는 해당 페이지를 렌더링합니다. 그러나 이제는 페이지 객체의 전체 상태를 공개해야하므로 페이지 객체의 캡슐화가 완전히 약화되었습니다.

또한 새로운 문제가 생겼습니다. 이제 렌더러가 페이지 클래스와 밀접하게 연결되어 있습니다. 페이지와 다른 것을 렌더링하려고하면 어떻게됩니까?

어느 것이 가장 좋은가요? 그들 중 아무도 없습니다. 그들은 모두 결함이 있습니다.


V3가 SRP를 존중한다는 데 동의하지 않습니다. 렌더러에는 변경해야 할 이유가 두 가지 이상 있습니다. 페이지가 변경되거나 렌더링 방식이 변경되는 경우. 그리고 렌더러가 Pages 이외의 객체를 렌더링해야하는 경우 세 번째로 다루어야합니다. 그렇지 않으면, 좋은 분석입니다.
user949300

2

이 질문에 대한 대답은 명백합니다. 이것이 $renderer->renderPage($page);올바른 구현입니다. 이 결론에 어떻게 도달했는지 이해하려면 캡슐화를 이해해야합니다.

페이지 란 무엇입니까? 누군가가 사용할 디스플레이를 나타냅니다. 그 "누군가"는 인간이거나 봇일 수 있습니다. (가) 있습니다 Page표현, 그리고 디스플레이 자체입니다. 표현되지 않은 표현이 있습니까? 렌더러가없는 페이지입니까? 대답은 예입니다. 표현을 나타내지 않고 존재할 수 있습니다. 대표하는 것은 나중 단계입니다.

페이지가없는 렌더러 란 무엇입니까? 렌더러가 페이지없이 렌더링 할 수 있습니까? 아니요. 따라서 렌더러 인터페이스에는 renderPage($page);메서드 가 필요합니다 .

무슨 일이야 $page->renderMe($renderer);?

renderMe($renderer)내부적으로 여전히 전화해야하는 것은 사실입니다 $renderer->renderPage($page);. 이것은 다음 과 같은 데메테르 법칙을 위반 합니다.

각 단위는 다른 단위에 대한 지식이 제한적이어야합니다.

Page클래스는 존재 여부를 상관하지 않는다 Renderer우주에서. 페이지를 나타내는 것만 중요합니다. 따라서 클래스 또는 인터페이스 Renderer는 내부에서 언급해서는 안됩니다 Page.


업데이트 된 답변

질문이 맞다면 PageSnippet수업은 페이지 스 니펫에만 관심이 있어야합니다.

class PageSnippet
{    
    /** string */
    private $html;

    function __construct($data = ['html' => '']): void
    {
        $this->html = $data['html'];
    }

   public function getHtml()
   {
       return $this->html;
   }
}

PdfRenderer 렌더링에 관심이 있습니다.

class PdfRenderer
{
    /**@var TCPDF */
    protected $pdf;

    function __construct(TCPDF $pdf = new TCPDF())
    {
        $this->pdf = $pdf;
    }

    function runTemplate(string $html): void
    {
        $this->pdf->writeHTML($html);
    }
}

클라이언트 사용법

$renderer = new PdfRenderer();
$snippet = new PageSnippet(['html' => '<html />']);
$renderer->runTemplate($snippet->getHtml());

고려해야 할 몇 가지 사항 :

  • $data연관 배열로 전달하는 것은 나쁜 습관 입니다. 클래스의 인스턴스 여야합니다.
  • 페이지 형식이 배열의 html속성 내에 포함되어 있다는 사실은 $data도메인에 특정한 세부 정보 PageSnippet이며이 세부 정보를 알고 있습니다.

그러나 Pages 외에 그림, 기사 및 Triptichs가있는 경우 어떻게해야합니까? 스킴에서 렌더러는 모든 것에 대해 알아야합니다. 그것은 많은 누출입니다. 그냥 생각할 음식.
user949300

@ user949300 : 렌더러가 그림 등을 렌더링 할 수 있어야한다면 분명히 그들에 대해 알아야합니다.
JacquesB

1
Kent Beck의 스몰 토크 모범 사례 패턴은 두 가지를 모두 지원 하는 역전 방법 패턴을 소개합니다 . 링크 된 아티클은 객체가 printOn:aStream메소드를 지원한다는 것을 보여 주지만, 스트림을 통해 객체를 인쇄하도록 지시하기 만하면됩니다. 귀하의 답변과 유추 할 수있는 것은 렌더러에 렌더링 할 수있는 페이지와 페이지를 렌더링 할 수있는 렌더러를 하나의 구현과 편리한 인터페이스 선택으로 가질 수있는 이유가 없다는 것입니다.
Graham Lee

2
어쨌든 SRP를 깨뜨 리거나 퍼지해야하지만 렌더러가 많은 다른 것들을 렌더링하는 방법을 알아야하는 경우 실제로 "많은 많은 책임"이며 가능한 경우 피해야합니다.
user949300

1
나는 당신의 대답을 좋아하지만 Page$ renderer를 알지 못하는 것은 불가능하다고 생각하고 싶습니다 . 내 질문에 코드를 추가했습니다 PageSnippet. 클래스를 참조하십시오 . 이 페이지는 사실상 페이지이지만을 참조하지 않으면 존재할 수 없으며 $pdf실제로는이 경우 타사 PDF 렌더러입니다. .. 그러나 PageSnippetPDF에 대한 텍스트 명령 배열 만 보유하는 클래스를 작성 하고 다른 클래스가 해당 명령을 해석하도록 할 수 있다고 생각 합니다. 그렇게하면 추가 복잡성을 피하면서 주입 $pdf을 피할 수 있습니다PageSnippet
Dennis

1

이상적으로는 클래스 간 종속성을 최소화해야합니다. 복잡성이 줄어 듭니다. 클래스는 실제로 필요한 경우 다른 클래스에만 종속되어야합니다.

당신은 상태 Page"페이지 렌더러에 대한 일련의 지침"을 포함한다. 나는 이와 같은 것을 상상한다 :

renderer.renderLine(x, y, w, h, Color.Black)
renderer.renderText(a, b, Font.Helvetica, Color.Black, "bla bla...")
etc...

따라서 $page->renderMe($renderer)Page 렌더러에 대한 참조가 필요 하기 때문 입니다.

그러나 대안 적으로 렌더링 명령은 직접 호출이 아닌 데이터 구조로 표현 될 수도있다.

[
  Line(x, y, w, h, Color.Black), 
  Text(a, b, Font.Helvetica, Color.Black, "bla bla...")
]

이 경우 실제 렌더러는 페이지에서이 데이터 구조를 가져와 해당 렌더링 명령어를 실행하여 처리합니다. 이러한 접근 방식을 사용하면 종속성이 반전됩니다. 페이지는 렌더러에 대해 알 필요가 없지만 렌더러에는 렌더링 할 수있는 페이지가 제공되어야합니다. 따라서 옵션 2 :$renderer->renderPage($page);

그렇다면 어느 것이 가장 좋습니까? 첫 번째 방법은 구현하기가 가장 간단하고 두 번째 방법은 훨씬 유연하고 강력하므로 요구 사항에 따라 달라집니다.

결정을 할 수 없거나 나중에 접근 방식을 변경할 수 있다고 생각되면 간접 계층, 함수 뒤에 결정을 숨길 수 있습니다.

renderPage($page, $renderer)

내가 권장하지 않는 유일한 방법 $page->renderMe()은 페이지에 단일 렌더러 만 가질 수 있다는 것을 제안하기 때문입니다. 그러나 a가 ScreenRenderer있고 추가하면 PrintRenderer어떻게됩니까? 동일한 페이지가 둘 다에 의해 렌더링 될 수 있습니다.


EPUB 또는 HTML의 맥락에서, 페이지 개념은 렌더러 없이는 존재하지 않습니다.
mouviciel

1
@mouviciel : 나는 당신이 무슨 뜻인지 이해하지 못합니다. 렌더링하지 않고 HTML 페이지를 가질 수 있습니까? 예를 들어 Google 크롤러 프로세스 페이지는 렌더링하지 않고 처리합니다.
JacquesB

2
단어 페이지라는 다른 개념이 있습니다. HTML 페이지를 인쇄하도록 형식화 할 때 페이지 매김 프로세스의 결과, 아마도 @mouviciel이 생각한 것입니다. 그러나이 질문에서 a page는 출력이 아닌 렌더러에 대한 입력이며 분명히 그 개념에 맞지 않습니다.
Doc Brown

1

SOLIDD 부분

"추상은 세부 사항에 의존해서는 안된다. 세부 사항은 추상화에 의존해야한다."

그렇다면 페이지와 렌더러 사이에서 안정적인 추상화가 가능하고 변경 가능성이 적으며 인터페이스를 나타낼 가능성이 더 큽니까? 반대로 "세부 사항"은 무엇입니까?

내 경험상 추상화는 일반적으로 렌더러입니다. 예를 들어, 매우 추상적이고 안정적인 단순한 스트림 또는 XML 일 수 있습니다. 또는 상당히 표준적인 레이아웃. 귀하의 페이지는 "세부 사항"인 사용자 정의 비즈니스 개체 일 가능성이 높습니다. 그리고 "그림", "보고서", "차트"등과 같은 다른 비즈니스 객체를 렌더링해야합니다. (아마도 "tryptich"가 아닙니다)

그러나 그것은 분명히 당신의 디자인에 달려 있습니다. 예를 들어 <article>표준 하위 파트 가있는 HTML 태그와 같은 페이지가 추상적 일 수 있습니다 . 또한 "렌더러"를보고하는 다양한 사용자 지정 비즈니스가 있습니다. 이 경우 렌더러는 페이지에 의존해야합니다.


0

대부분의 수업은 두 가지 범주 중 하나로 나눌 수 있다고 생각합니다.

  • 데이터를 포함하는 클래스 (변경 가능 또는 불변은 중요하지 않음)

이것들은 다른 것에 거의 의존하지 않는 클래스입니다. 이들은 일반적으로 도메인의 일부입니다. 그것들은 로직이 없거나 상태에서 직접 파생 될 수있는 로직 만 포함해야합니다. Employee 클래스 에는 외부 정보 (현재 날짜)가 필요한 함수가 아닌 함수 isAdult에서 직접 파생 될 수 있는 함수 가있을 수 있습니다 .birthDatehasBirthDay

  • 서비스를 제공하는 클래스

이러한 유형의 클래스는 데이터를 포함하는 다른 클래스에서 작동합니다. 일반적으로 한 번 구성되고 변경할 수 없으므로 항상 동일한 종류의 기능을 수행합니다. 그러나 이러한 종류의 클래스는 여전히 상태를 유지하는 단기 도우미 인스턴스를 제공하여 빌더 클래스와 같이 짧은 기간 동안 일부 상태를 유지해야하는보다 복잡한 작업을 수행 할 수 있습니다.

당신의 예

귀하의 예에서 Page데이터를 포함하는 클래스가 될 것입니다. 이 데이터를 가져오고 클래스가 변경 가능 해야하는 경우 데이터를 수정하는 함수가 있어야합니다. 많은 의존성없이 사용할 수 있도록 바보로 유지하십시오.

데이터 또는이 경우 Page다양한 방법으로 표현 될 수 있습니다. 웹 페이지로 렌더링되고 디스크에 기록되고 데이터베이스에 저장되며 JSON으로 변환됩니다. 이러한 각 경우에 대해 이러한 클래스에 메소드를 추가하고 싶지 않습니다 (클래스에 데이터 만 포함되어 있어도 모든 다른 클래스에 대한 종속성을 작성하십시오).

귀하 Renderer는 일반적인 서비스 유형 클래스입니다. 특정 데이터 세트에서 작동하고 결과를 리턴 할 수 있습니다. 자체 상태가 많지 않으며 일반적으로 변경할 수없는 상태는 한 번 구성 한 다음 재사용 할 수 있습니다.

예를 들어, 클래스 의 a MobileRenderer및 a StandardRenderer구현이 Renderer있지만 설정이 다를 수 있습니다.

따라서 Page데이터 를 포함하고 바보처럼 유지되어야 하므로이 경우 가장 깨끗한 솔루션은 다음을 전달하는 Page것입니다 Renderer.

$renderer->renderPage($page)

2
매우 절차적인 논리.
user949300
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.