클래스가있는 OOP와 비교 한 기능적 프로그래밍


32

최근에 함수형 프로그래밍 개념에 관심이있었습니다. 나는 얼마 동안 OOP를 사용했다. OOP에서 상당히 복잡한 앱을 작성하는 방법을 알 수 있습니다. 각 객체는 그 객체가하는 일을 수행하는 방법을 알고 있습니다. 아니면 부모님의 수업도 마찬가지입니다. 그래서 나는 단순히 Person().speak()그 사람 에게 말 을하도록 말할 수 있습니다 .

그러나 함수형 프로그래밍에서 비슷한 작업을 어떻게 수행합니까? 함수가 일등 항목 인 방법을 봅니다. 그러나 그 기능은 하나의 특정 기능 만 수행합니다. 단순히 say()떠 다니는 메소드 를 가지고 동등한 Person()인수로 호출하여 어떤 종류의 말이 무엇인지 알 수 있습니까?

그래서 간단한 것을 볼 수 있습니다. 기능 프로그래밍에서 OOP와 객체를 어떻게 비교할 수 있습니까? 그래서 코드 기반을 모듈화하고 구성 할 수 있습니까?

참고로 OOP에 대한 나의 주요 경험은 Python, PHP 및 일부 C #입니다. 내가보고있는 언어에는 기능적인 특징이 있으며 스칼라와 하스켈입니다. 나는 스칼라를 향해 기울고 있지만.

기본 예 (Python) :

Animal(object):
    def say(self, what):
        print(what)

Dog(Animal):
    def say(self, what):
        super().say('dog barks: {0}'.format(what))

Cat(Animal):
    def say(self, what):
        super().say('cat meows: {0}'.format(what))

dog = Dog()
cat = Cat()
dog.say('ruff')
cat.say('purr')

스칼라는 OOP + FP로 설계되었으므로 선택할 필요가 없습니다.
Karthik T

1
예, 알고 있지만 지적적인 이유로 알고 싶어합니다. 기능적 언어로 된 객체와 동등한 것을 찾을 수 없습니다. 스칼라에 관해서는, 언제 / 어디서 / 어떻게 함수를 사용해야하는지 알고 싶어하지만 IMHO는 또 다른 질문입니다.
skift

2
"특히 지나치게 강조된 IMO는 우리가 상태를 유지하지 않는다는 개념입니다.": 이것은 잘못된 개념입니다. FP가 상태를 사용하지 않고 FP가 상태를 다른 방식으로 처리하는 것은 사실이 아닙니다 (예 : Haskell의 모나드 또는 Clean의 고유 한 유형).
Giorgio


답변:


21

여기서 실제로 묻는 것은 기능적 언어로 다형성 을 수행 하는 방법, 즉 인수에 따라 다르게 동작하는 함수를 만드는 방법입니다.

함수에 대한 첫 번째 인수는 일반적으로 OOP의 "개체"와 동일하지만 함수형 언어에서는 일반적으로 함수를 데이터와 분리하려고하므로 "개체"는 순수한 (불변) 데이터 값일 수 있습니다.

일반적으로 기능적 언어는 다형성을 달성하기위한 다양한 옵션을 제공합니다.

  • 제공된 인수를 검토하여 다른 함수를 호출하는 멀티 메소드 와 같은 것 . 이는 첫 번째 인수 유형 (대부분의 OOP 언어의 동작과 실질적으로 동일 함)에서 수행 될 수 있지만 인수의 다른 속성에서도 수행 될 수 있습니다.
  • 일류 함수를 멤버로 포함하는 프로토 타입 / 객체 형 데이터 구조 . 따라서 개와 고양이 데이터 구조에 "say"함수를 포함시킬 수 있습니다. 효과적으로 코드의 일부를 데이터로 만들었습니다.
  • 패턴 일치 -패턴 일치 논리가 함수 정의에 내장되어 있으며 매개 변수마다 다른 동작을 보장합니다. 하스켈에서 일반적입니다.
  • 분기 / 조건-OOP의 if / else 절과 동일합니다. 확장 성이 높지는 않지만 가능한 값이 제한된 경우 (예 : 함수가 숫자 나 문자열 또는 null을 전달 했는가) 많은 경우에 여전히 적절할 수 있습니다.

예를 들어, 다음은 다중 방법을 사용하여 문제의 Clojure 구현입니다.

;; define a multimethod, that dispatched on the ":type" keyword
(defmulti say :type)  

;; define specific methods for each possible value of :type. You can add more later
(defmethod say :cat [animal what] (println (str "Car purrs: " what)))
(defmethod say :dog [animal what] (println (str "Dog barks: " what)))
(defmethod say :default [animal what] (println (str "Unknown noise: " what)))

(say {:type :dog} "ruff")
=> Dog barks: ruff

(say {:type :ape} "ook")
=> Unknown noise: ook

이 동작에는 명시 적 클래스를 정의 할 필요가 없습니다. 일반 맵은 정상적으로 작동합니다. 디스패치 함수 (이 경우 : type)는 인수의 임의 함수일 수 있습니다.


100 % 명확하지 않지만 어디로 가고 있는지 알기에 충분합니다. 나는 이것을 주어진 파일에서 '동물'코드로 볼 수 있습니다. 또한 분기 / 조건 부분도 좋습니다. 나는 그것을 if / else의 대안으로 고려하지 않았습니다.
skift

11

이것은 직접적인 답변이 아니며, 기능적 언어 전문가가 아니기 때문에 반드시 100 % 정확하지도 않습니다. 그러나 어느 경우 에나 나는 당신과 나의 경험을 나눌 것입니다 ...

약 1 년 전에 나는 당신과 비슷한 보트에있었습니다. 나는 C ++과 C #을 해왔고 모든 디자인은 항상 OOP에서 매우 무거웠다. FP 언어에 대해 들어 보았고 온라인에서 정보를 읽었으며 F # 서적을 넘겨 보았지만 여전히 FP 언어가 OOP를 대체하거나 일반적으로 유용 할 수있는 방법을 파악할 수 없었습니다.

나를 위해 파이썬을 배우기로 결정했을 때 "획기적인"것이 나타났습니다. 나는 파이썬을 다운로드 한 다음 프로젝트 오일러 홈페이지 로 가서 방금 한 문제를 일으켰습니다 . 파이썬은 반드시 FP 언어 일 필요는 없으며 확실히 클래스를 만들 수는 있지만 C ++ / Java / C #에 비해 훨씬 더 많은 FP 구문을 가지고 있기 때문에 게임을 시작했을 때 내가 절대로 필요하지 않은 한 클래스를 정의하십시오.

파이썬에서 흥미로운 점은 더 복잡한 함수를 작성하기 위해 함수를 가져 와서 "스티치 (stitch)"하는 것이었고, 결국 단일 함수를 호출하여 문제를 해결하는 것이 었습니다.

코딩 할 때 단일 책임 원칙을 따라야하며 이는 절대적으로 정확하다고 지적했습니다. 그러나 함수가 단일 작업을 담당한다고해서 절대 최소값 만 수행 할 수있는 것은 아닙니다. FP에서는 여전히 추상화 수준이 있습니다. 따라서 상위 수준의 함수는 여전히 "한 가지"작업을 수행 할 수 있지만 하위 수준의 함수에 위임하여 해당 "한"작업이 달성되는 방식에 대한 세부적인 세부 사항을 구현할 수 있습니다.

그러나 FP의 핵심은 부작용이 없다는 것입니다. 정의 된 입력 세트와 출력 세트를 사용하여 응용 프로그램을 간단한 데이터 변환으로 취급하는 한 필요한 것을 달성 할 수있는 FP 코드를 작성할 수 있습니다. 분명히 모든 응용 프로그램이이 금형에 잘 맞지는 않지만, 일단 시작하면 몇 개의 응용 프로그램이 적합한 지 놀랄 것입니다. 파이썬, F # 또는 스칼라가 FP 구조를 제공하기 때문에 여기에서 빛을 발한다고 생각합니다. 그러나 상태를 기억하고 "부작용을 도입해야"할 때 항상 참으로 돌아가 OOP 기술을 시도 할 수 있습니다.

그 이후로 나는 내부 작업을위한 유틸리티 및 기타 도우미 스크립트로 많은 파이썬 코드를 작성했으며 그중 일부는 상당히 확장되었지만 기본 SOLID 원칙을 기억함으로써 대부분의 코드는 여전히 유지 관리 가능하고 유연했습니다. OOP에서와 마찬가지로 인터페이스는 클래스이며 리팩터링 및 / 또는 기능을 추가 할 때 클래스를 이동합니다. FP에서는 함수와 정확히 동일한 기능을 수행합니다.

지난주에 Java로 코딩을 시작한 이래로 거의 매일 OOP에서 함수를 재정의하는 메소드로 클래스를 선언하여 인터페이스를 구현해야한다는 것을 상기시킵니다. 어떤 경우에는 Python을 사용하여 동일한 것을 달성 할 수 있습니다 간단한 람다 식, 예를 들어 디렉토리를 스캔하기 위해 작성한 20-30 줄의 코드는 Python에서 1-2 줄이었고 클래스는 없었습니다.

FP 자체는 고급 언어입니다. 파이썬 (미안하지만 유일한 FP 경험)에서 람다 및 다른 것들과 함께 다른 목록 이해 안에 목록 이해를 함께 넣을 수 있었고 모든 것은 3-4 줄의 코드 일뿐입니다. C ++에서는 절대 똑같은 일을 할 수는 있지만 C ++이 하위 수준이기 때문에 3-4 줄보다 훨씬 많은 코드를 작성해야하며 줄 수가 증가함에 따라 SRP 교육이 시작되고 시작됩니다. 코드를 더 작은 조각 (즉, 더 많은 기능)으로 나누는 방법에 대해 생각합니다. 그러나 유지 관리 가능성과 구현 세부 정보 숨기기를 위해 모든 기능을 동일한 클래스에 넣고 비공개로 만들 것입니다. 그리고 당신은 그것을 가지고 있습니다 ... 나는 방금 클래스를 만들었지 만 파이썬에서는 "return (.... lambda x : .. ....)"이라고 썼을 것입니다.


그렇습니다. 질문에 직접 대답하지는 않지만 여전히 훌륭한 답변입니다. 파이썬으로 작은 스크립트 나 패키지를 작성할 때 항상 클래스를 사용하지는 않습니다. 여러 번 패키지 형식으로 사용하면 완벽하게 맞습니다. 특히 상태가 필요하지 않은 경우. 또한 목록 이해도 유용하다는 데 동의합니다. FP를 읽은 후, 나는 그들이 얼마나 더 강력한 지 깨달았습니다. 그런 다음 OOP와 비교하여 FP에 대해 더 많이 배우고 싶었습니다.
skift

좋은 대답입니다. 기능적인 수영장 옆에 서있는 모든 사람과 대화하지만 발가락을 물에 담글 지 여부는 확실하지 않습니다.
Robben_Ford_Fan_boy

그리고 루비 ... 디자인 철학 중 하나는 코드 블록을 인수로 취하는 방법, 일반적으로 선택적인 방법에 중점을 둡니다. 깔끔한 구문을 생각하고 코딩하면이 방법이 쉽습니다. C #에서는 이와 같이 생각하고 작성하기가 어렵습니다. C # writ은 기능적으로 장황하고 혼란 스럽습니다. 저는 루비가 기능적으로 더 쉽게 생각하고 안정적인 C # 사고 상자에서 잠재력을 볼 수 있도록 도와주었습니다. 최종 분석에서 나는 기능적 및 OO를 보완적인 것으로 본다. 루비가 그렇게 생각한다고 말하고 싶습니다.
radarbob 2016 년

8

Haskell에서 가장 가까운 것은 "class"입니다. 이 클래스 는 Java 및 C ++ 클래스와 동일하지 않지만 이 경우 원하는대로 작동합니다.

귀하의 경우 코드가 어떻게 보일지입니다.

동물 클래스 어디 
say :: String-> 소리 

그런 다음 이러한 방법에 적합한 개별 데이터 유형을 가질 수 있습니다.

인스턴스 동물 개
s = "bark"++ s라고 말합니다. 

편집 :-당신이 개를 말을 전문화하기 전에 개가 동물임을 시스템에 알려야합니다.

data Dog = \-여기서 뭔가-\ (동물 유래)

편집 :-Wilq.
이제 foo라고하는 함수에서 say를 사용하려면 foo가 Animal에서만 작동 할 수 있음을 haskell에 알려야합니다.

foo :: (동물 a) => a-> 문자열-> 문자열
foo a str = str 말한다 

이제 강아지와 함께 foo를 호출하면 짖을 것이고, 고양이와 함께 호출하면 야옹이됩니다.

주요 = 할 
let d = dog (\-cstr 매개 변수-\)
    c = 고양이  
show $ foo d "Hello World"에서

이제 함수 say의 다른 정의를 가질 수 없습니다. 동물이 아닌 것으로 호출하면 컴파일 오류가 발생합니다.


티를 완전히 이해하려면 하스켈에 대해 좀 더 현실적으로 생각해야하지만, 나는 그것의 jist를 얻는다고 생각합니다. 이것이 더 복잡한 코드베이스와 어떻게 정렬되는지 여전히 궁금합니다.
skift

nitpick 동물 대문자로한다
다니엘 Gratzer에게

1
say 함수는 String 만 취하는 경우 Dog에서 호출한다는 것을 어떻게 알 수 있습니까? 그리고 일부 내장 클래스에 대해서만 "파생"하지 않습니까?
WilQu

6

기능적 언어는 다형성을 달성하기 위해 2 개의 구성을 사용합니다.

  • 1 차 함수
  • 제네릭

이것으로 다형성 코드를 작성하는 것은 OOP가 상속 및 가상 메소드를 사용하는 방법과 완전히 다릅니다. C #과 같은 좋아하는 OOP 언어로 두 언어를 모두 사용할 수 있지만 Haskell과 같은 대부분의 기능적 언어는 최대 11 개 언어로 사용할 수 있습니다. 일반적이지 않은 기능은 거의 없으며 대부분의 기능에는 매개 변수 기능이 있습니다.

이와 같이 설명하기는 어렵고이 새로운 방식을 배우려면 많은 시간이 필요합니다. 그러나이를 위해서는 OOP를 완전히 잊어 버려야합니다. 왜냐하면 OOP는 기능적 세계에서 작동하는 방식이 아니기 때문입니다.


2
OOP는 다형성에 관한 것입니다. OOP가 함수를 데이터에 묶는 것에 관한 것이라고 생각하면 OOP에 대해 아무것도 모릅니다.
Euphoric

4
다형성은 OOP의 한 측면 일 뿐이며 OP가 실제로 요구하는 것은 아닙니다.
Doc Brown

2
다형성은 OOP의 주요 측면입니다. 그것을 지원하기 위해 다른 모든 것이 있습니다. 상속 / 가상 메소드가없는 OOP는 절차 적 프로그래밍과 거의 동일합니다.
Euphoric

1
@ErikReppen "인터페이스 적용"이 자주 필요하지 않으면 OOP를 수행하지 않는 것입니다. 또한 Haskell에는 모듈도 있습니다.
Euphoric

1
항상 인터페이스가 필요하지는 않습니다. 그러나 필요할 때 매우 유용합니다. 그리고 IMO는 OOP의 또 다른 중요한 부분입니다. 모듈이 Haskell에 들어가면 코드 구성과 관련하여 기능 언어의 OOP에 가장 가까운 것으로 생각됩니다. 적어도 내가 지금까지 읽은 것에서. 나는 그들이 여전히 매우 다르다는 것을 안다.
skift

0

실제로 달성하고자하는 것에 달려 있습니다.

선택적 기준에 따라 동작을 구성하는 방법이 필요한 경우 함수 객체와 함께 사전 (해시 테이블)을 사용할 수 있습니다. 파이썬에서는 다음과 같이 보일 수 있습니다.

def bark(what):
    print "barks: {0}".format(what) 

def meow(what):
    print "meows: {0}".format(what)

def climb(how):
    print "climbs: {0}".format(how)

if __name__ == "__main__":
    animals = {'dog': {'say': bark},
               'cat': {'say': meow,
                       'climb': climb}}
    animals['dog']['say']("ruff")
    animals['cat']['say']("purr")
    animals['cat']['climb']("well")

즉, (a)는 더의 '인스턴스'있다, 그러나주의하지 또는 고양이 당신이 당신의 객체 자신의 '형'을 추적해야합니다와 (b).

예를 들어 같은 : pets = [['martin','dog','grrrh'], ['martha', 'cat', 'zzzz']]. 다음과 같은 목록 이해를 할 수 있습니다[animals[pet[1]]['say'](pet[2]) for pet in pets]


0

OO 언어는 때때로 기계와 직접 인터페이스하기 위해 저수준 언어 대신 사용될 수 있습니다. C ++ 물론 C #의 경우에도 어댑터 등이 있습니다. 기계 부품을 제어하고 메모리를 미세하게 제어하기위한 코드 작성은 가능한 한 낮은 수준에 가깝게 유지하는 것이 가장 좋습니다. 그러나이 질문이 Line Of Business, 웹 응용 프로그램, IOT, 웹 서비스 및 대부분의 대량 사용 응용 프로그램과 같은 현재 객체 지향 소프트웨어와 관련이 있다면 ...

해당되는 경우 답변

독자는 SOA (Service-Oriented Architecture)로 작업을 시도 할 수 있습니다. 즉, DDD, N-Layered, N-Tiered, Hexagonal 등입니다. 지난 10 년 동안 70 년대와 80 년대에 설명 된대로 대기업 응용 프로그램에서 "전통적인"OO (Active-Record 또는 Rich-Models)를 효율적으로 사용하는 것을 보지 못했습니다. (참고 1 참조)

결함은 OP와 관련이 없지만 질문에는 몇 가지 문제가 있습니다.

  1. 제공하는 예제는 단순히 다형성을 보여주기위한 것이며 프로덕션 코드가 아닙니다. 때로는 정확히 같은 예가 문자 그대로 사용됩니다.

  2. FP 및 SOA에서 데이터는 Business Logic과 분리됩니다. 즉, 데이터와 논리는 함께 가지 않습니다. 논리는 서비스로 들어가고 데이터 (도메인 모델)에는 다형성 동작이 없습니다 (주 2 참조).

  3. 서비스 및 기능은 다형성 일 수 있습니다. FP에서는 함수를 매개 변수로 값 대신 다른 함수에 자주 전달합니다. Callable 또는 Func와 같은 유형의 OO 언어에서 동일한 작업을 수행 할 수 있지만 만연하지는 않습니다 (주 3 참조). FP 및 SOA에서 모델은 다형성이 아니며 서비스 / 기능 만 있습니다. (주 4 참조)

  4. 이 예에서는 하드 코딩이 잘못되었습니다. 나는 붉은 색 끈 "개 껍질"에 대해서만 말하는 것이 아닙니다. CatModel과 DogModel 자체에 대해서도 이야기하고 있습니다. 양을 추가하려고하면 어떻게됩니까? 코드에 들어가서 새 코드를 만들어야합니까? 왜? 프로덕션 코드에서는 속성이있는 AnimalModel 만 보입니다. 최악의 경우, AmphibianModel과 FowlModel의 속성과 처리가 너무 다른 경우.

이것이 현재 "OO"언어로 예상되는 것입니다.

public class Animal
{
    public int AnimalID { get; set; }
    public int LegCount { get; set; }
    public string Name { get; set; }
    public string WhatISay { get; set; }
}

public class AnimalService : IManageAnimals
{
    private IPersistAnimals _animalRepo;
    public AnimalService(IPersistAnimals animalRepo) { _animalRepo = animalRepo; }

    public List<Animal> GetAnimals() => _animalRepo.GetAnimals();

    public string WhatDoISay(Animal animal)
    {
        if (!string.IsNullOrWhiteSpace(animal.WhatISay))
            return animal.WhatISay;

        return _animalRepo.GetAnimalNoise(animal.AnimalID);
    }
}

기본 흐름

OO의 클래스에서 함수형 프로그래밍으로 어떻게 이동합니까? 다른 사람들이 말했듯이; 가능하지만 실제로는 그렇지 않습니다. 위의 요점은 Java 및 C #을 수행 할 때 (전세계적인 의미에서) 클래스를 사용하지 않아야 함을 보여줍니다. 서비스 지향 아키텍처 (DDD, 계층화, 계층화, 6 각형 등)로 코드를 작성하면 데이터 (도메인 모델)와 논리 함수 (서비스)를 분리하므로 기능에 한 걸음 더 다가 갈 수 있습니다.

OO FP에 한 단계 더 가까운 언어

좀 더 나아가 SOA 서비스를 두 가지 유형으로 나눌 수도 있습니다.

선택적인 수업 유형 1 : 진입 점에 대한 공통 인터페이스 구현 서비스. 이것들은 "순수"또는 "불완전"다른 기능을 호출 할 수있는 "불완전한"진입 점입니다. RESTful API의 진입 점이 될 수 있습니다.

선택적인 클래스 유형 2 : Pure Business Logic Services. 이것들은 "순수한"기능을 가진 정적 클래스입니다. FP에서 "순수"는 부작용이 없음을 의미합니다. 명시 적으로 상태 또는 지속성을 설정하지 않습니다. (주 5 참조)

따라서 서비스 지향 아키텍처에서 사용되는 객체 지향 언어의 클래스를 생각하면 OO 코드에 도움이 될뿐만 아니라 기능 프로그래밍을 이해하기 쉽게 보이게됩니다.

노트

참고 1 : 원본 "Rich"또는 "Active-Record"객체 지향 디자인은 여전히 ​​사용 중입니다. 사람들이 10 년 이상 전에 "올바른 일을했을 때"와 같은 많은 레거시 코드가 있습니다. 지난번에 C ++의 비디오 게임 Codebase에서 메모리가 정확하게 제어되고 공간이 매우 제한적인 코드를 보았습니다. 말할 것도없이 FP와 서비스 지향 아키텍처 (Service-Oriented Architectures)는 야수이며 하드웨어를 고려해서는 안됩니다. 그러나 그들은 끊임없이 변화하고, 유지되고, 가변적 인 데이터 크기를 가지고 있으며, 다른 측면들을 우선 순위로두고 있습니다. 비디오 게임 및 머신 AI에서는 신호와 데이터를 매우 정확하게 제어합니다.

참고 2 : 도메인 모델에는 다형성 동작이나 외부 종속성이 없습니다. 그들은 "절연"입니다. 그렇다고 100 % 빈혈 상태 여야한다는 의미는 아닙니다. 해당되는 경우 구성 및 변경 가능한 속성 변경과 관련하여 많은 논리를 가질 수 있습니다. Eric Evans와 Mark Seemann의 DDD "값 개체"및 엔터티를 참조하십시오.

참고 3 : Linq와 Lambda는 매우 일반적입니다. 그러나 사용자가 새 함수를 만들 때 Func 또는 Callable을 매개 변수로 사용하는 경우는 거의 없지만 FP에서는 해당 패턴을 따르는 함수가없는 앱을 보는 것이 이상합니다.

참고 4 : 다형성과 상속을 혼동하지 마십시오. CatModel은 AnimalBase를 상속하여 동물이 일반적으로 가지고있는 속성을 결정할 수 있습니다. 그러나 내가 보여주는 것처럼, 이와 같은 모델은 코드 냄새 입니다. 이 패턴이 표시되면이를 분해하여 데이터로 전환하는 것을 고려할 수 있습니다.

참고 5 : 순수 함수는 함수를 매개 변수로 사용할 수 있습니다. 들어오는 기능은 불완전하지만 순수 할 수 있습니다. 테스트 목적으로 항상 순수합니다. 그러나 프로덕션 환경에서는 순수하게 취급되지만 부작용이있을 수 있습니다. 순수한 기능이 순수하다는 사실은 변하지 않습니다. 매개 변수 기능이 불완전 할 수 있지만. 혼란스럽지 않습니다! :디


-2

당신은 이런 식으로 할 수 있습니다 .. PHP

    function say($whostosay)
    {
        if($whostosay == 'cat')
        {
             return 'purr';
        }elseif($whostosay == 'dog'){
             return 'bark';
        }else{
             //do something with errors....
        }
     }

     function speak($whostosay)
     {
          return $whostosay .'\'s '.say($whostosay);
     }
     echo speak('cat');
     >>>cat's purr
     echo speak('dog');
     >>>dogs's bark

1
나는 부정적인 투표를하지 않았다. 그러나 내 생각에 그것은이 접근법이 객체 지향이 아닌 기능적이지 않기 때문입니다.
Manoj R

1
그러나 전달 된 개념은 기능적 프로그래밍에 사용 된 패턴 매칭에 가깝습니다. 즉, $whostosay실행되는 것을 결정하는 객체 유형이됩니다. 위의 매개 변수 $whattosay를 지원하는 유형 (예 :)이 다른 매개 변수를 추가로 승인하도록 수정할 수 있습니다 'human'.
syockit
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.