작별, 객체 지향 프로그래밍

저는 수십 년 동안 객체 지향 언어로 프로그래밍했습니다. 내가 사용한 첫 번째 OO 언어는 C ++이고 Smalltalk와 .NET 및 Java였습니다.

상속, 캡슐화 및 다형성의 이점을 활용하기 위해 gung-ho였습니다. 패러다임의 세 기둥.

나는 재사용의 약속을 얻고이 새롭고 흥미로운 풍경에서 나보다 앞서 온 사람들이 얻는 지혜를 활용하기를 간절히 바랐습니다.

실제 객체를 클래스에 매핑하고 전 세계가 깔끔하게 자리 잡을 것이라고 생각했을 때 나는 흥분을 품을 수 없었습니다.

더 잘못 될 수 없었습니다.

상속, 첫 번째 기둥

언뜻보기에 상속은 객체 지향 패러다임의 가장 큰 이점으로 보입니다. 새로 교리 된 사람들에게 모범이되는 형태 계층의 모든 단순한 예는 논리적으로 의미가있는 것 같습니다.

그리고 재사용은 오늘의 단어입니다. 아니요 .. 아마도 그 해 그리고 아마도 더 많은 것을 만드십시오.

나는 이것을 통째로 삼켜 새로운 통찰력으로 세상에 몰려 들었다.

바나나 원숭이 정글 문제

마음에 종교가 있고 해결해야 할 문제가 생겨서 클래스 계층을 구축하고 코드를 작성하기 시작했습니다. 그리고 모든 것이 세상과 옳았습니다.

기존 수업에서 물려 받아 재사용 약속에 대해 현금으로 바꿀 준비가 된 그날을 결코 잊지 않을 것입니다. 내가 기다리는 순간이었다.

새로운 프로젝트가 나왔고 나는 그 마지막 프로젝트에서 너무 좋아하는 클래스로 돌아왔다.

문제 없어. 구조에 재사용하십시오. 내가해야 할 일은 단순히 다른 프로젝트에서 해당 클래스를 가져 와서 사용하는 것입니다.

글쎄요… 실제로… 그 클래스 만이 아닙니다. 우리는 부모 클래스가 필요합니다. 하지만 ... 그게 다입니다.

어… 잠깐만 요 .. 부모님도 필요할 것 같습니다 ... 그리고 ...… 부모님 모두가 필요할 것 같습니다. 알았어 ... 알았어. 문제 없어.

그리고 훌륭합니다. 이제 컴파일되지 않습니다. 왜?? 아, 알겠습니다…이 물체에는 다른 물체가 들어 있습니다. 그래서 저도 필요합니다. 문제 없어.

잠깐만 .. 난 그 물건이 필요하지 않습니다. 개체의 부모와 부모의 부모 등이 포함 된 모든 개체와 부모, 부모, 부모와 함께 포함 된 개체의 모든 부모 등이 필요합니다.

어.

Erlang의 창시자 인 Joe Armstrong은 다음과 같이 인용합니다.

객체 지향 언어의 문제점은 이러한 암시 적 환경을 가지고 있다는 것입니다. 바나나를 원했지만 바나나와 정글 전체를 들고있는 고릴라였습니다.

바나나 원숭이 정글 솔루션

너무 깊은 계층을 만들지 않으면 서이 문제를 해결할 수 있습니다. 그러나 상속이 재사용의 열쇠라면, 그 메커니즘에 대한 제한은 반드시 재사용의 이점을 제한 할 것입니다. 권리?

권리.

그렇다면 쿨 에이드를 건강하게 도와 준 열악한 객체 지향 프로그래머는 무엇입니까?

포함하고 위임합니다. 이것에 대해서는 나중에 더 설명하겠습니다.

다이아몬드 문제

조만간 다음 문제는 언어에 따라보기 흉한 문제를 해결합니다.

논리적으로 이해되는 것처럼 보이지만 대부분의 OO 언어는이를 지원하지 않습니다. OO 언어로 이것을 지원하는 것이 어려운 이유는 무엇입니까?

다음 의사 코드를 상상해보십시오.

클래스 전원 장치 {
}
클래스 스캐너는 PoweredDevice {
  함수 시작 () {
  }
}
클래스 프린터는 PoweredDevice {
  함수 시작 () {
  }
}
클래스 복사기는 스캐너, 프린터 {
}

Scanner 클래스와 Printer 클래스는 모두 start라는 함수를 구현합니다.

Copier 클래스는 어떤 시작 함수를 상속합니까? 스캐너 하나? 프린터 하나? 둘 다 될 수는 없습니다.

다이아몬드 솔루션

해결책은 간단합니다. 그러지 마

네 맞습니다. 대부분의 OO 언어는 이것을 허용하지 않습니다.

그러나, 그러나 ... 이것을 모델링해야한다면 어떻게해야합니까? 나는 재사용을 원한다!

그런 다음 포함하고 위임해야합니다.

클래스 전원 장치 {
}
클래스 스캐너는 PoweredDevice {
  함수 시작 () {
  }
}
클래스 프린터는 PoweredDevice {
  함수 시작 () {
  }
}
클래스 복사기 {
  스캐너 스캐너
  프린터 프린터
  함수 시작 () {
    printer.start ()
  }
}

복사기 클래스에는 이제 프린터 및 스캐너의 인스턴스가 포함되어 있습니다. start 함수를 Printer 클래스의 구현에 위임합니다. 스캐너에 쉽게 위임 할 수 있습니다.

이 문제는 상속 기둥의 또 다른 균열입니다.

깨지기 쉬운 기본 클래스 문제

그래서 계층 구조를 얕게 만들고 주기적으로 유지하지 않습니다. 나를위한 다이아몬드는 없습니다.

그리고 모든 것이 세상과 옳았습니다. 그 때까지는…

언젠가 내 코드가 작동하고 다음 날 작동이 중지됩니다. 키커입니다. 코드를 변경하지 않았습니다.

글쎄요, 아마도 버그 일 것입니다 ...하지만 잠깐만 요 ... 뭔가 변했습니다…

그러나 내 코드에는 없었습니다. 내가 상속 한 클래스의 변경 사항이 밝혀졌습니다.

Base 클래스를 변경하면 어떻게 코드가 깨질 수 있습니까?

방법은 ...

다음 기본 클래스를 상상해보십시오 (Java로 작성되었지만 Java를 모르면 이해하기 쉬워야 함).

import java.util.ArrayList;
 
공개 클래스 배열
{
  개인 ArrayList  a = new ArrayList  ();
 
  공공 무효 add (오브젝트 요소)
  {
    a.add (요소);
  }
 
  공공 무효 addAll (객체 요소 [])
  {
    for (int i = 0; i 

중요 : 주석 처리 된 코드 줄을 확인하십시오. 이 줄은 나중에 변경되어 문제가 발생합니다.

이 클래스에는 인터페이스에 add () 및 addAll ()의 두 가지 함수가 있습니다. add () 함수는 단일 요소를 추가하고 addAll ()은 add 함수를 호출하여 여러 요소를 추가합니다.

파생 클래스는 다음과 같습니다.

공개 클래스 ArrayCount는 배열을 확장합니다
{
  private int count = 0;
 
  @보수
  공공 무효 add (오브젝트 요소)
  {
    super.add (요소);
    ++ 수;
  }
 
  @보수
  공공 무효 addAll (객체 요소 [])
  {
    super.addAll (요소);
    count + = elements.length;
  }
}

ArrayCount 클래스는 일반적인 Array 클래스를 전문화 한 것입니다. 동작상의 유일한 차이점은 ArrayCount가 요소 수를 유지한다는 것입니다.

이 두 클래스를 자세히 살펴 보겠습니다.

Array add ()는 요소를 로컬 ArrayList에 추가합니다.
Array addAll ()은 각 요소에 대한 로컬 ArrayList add를 호출합니다.

ArrayCount add ()는 부모의 add ()를 호출 한 다음 카운트를 증가시킵니다.
ArrayCount addAll ()은 부모의 addAll ()을 호출 한 다음 요소 수로 카운트를 증가시킵니다.

그리고 모든 것이 잘 작동합니다.

이제 중요한 변화를 위해. Base 클래스의 주석 처리 된 코드 줄은 다음과 같이 변경됩니다.

  공공 무효 addAll (객체 요소 [])
  {
    for (int i = 0; i 

Base 클래스의 소유자에 관한 한 여전히 광고 된대로 작동합니다. 그리고 모든 자동 테스트는 여전히 통과합니다.

그러나 주인은 파생 클래스에 대해 잊어 버렸습니다. 그리고 Derived 클래스의 소유자는 무례한 각성에 있습니다.

이제 ArrayCount addAll ()은 부모의 addAll ()을 호출하여 내부적으로 Derived 클래스에 의해 초과 된 add ()를 호출합니다.

이로 인해 파생 클래스의 add ()가 호출 될 때마다 개수가 증가한 다음 파생 클래스의 addAll ()에 추가 된 요소 수만큼 다시 증가합니다.

두 번 계산됩니다.

이런 일이 일어날 수 있다면, 파생 클래스의 작성자는 기본 클래스가 어떻게 구현되었는지 알아야합니다. 또한 Base 클래스의 모든 변경 사항에 대해 알려야합니다. 파생 클래스를 예측할 수없는 방식으로 중단 할 수 있기 때문입니다.

어! 이 거대한 균열은 소중한 상속 기둥의 안정성을 영원히 위협하고 있습니다.

깨지기 쉬운 기본 클래스 솔루션

다시 한 번 구조를 포함하고 위임하십시오.

Contain and Delegate를 사용하여 White Box 프로그래밍에서 Black Box 프로그래밍으로 이동합니다. White Box 프로그래밍을 사용하면 기본 클래스의 구현을 살펴 봐야합니다.

Black Box 프로그래밍을 사용하면 함수 중 하나를 재정 의하여 Base 클래스에 코드를 삽입 할 수 없으므로 구현을 완전히 무시할 수 있습니다. 인터페이스에만 관심을 가져야합니다.

이 추세는 혼란 스럽습니다…

상속은 Reuse에게 큰 승리로 여겨졌습니다.

객체 지향 언어는 포함 및 위임을 쉽게 수행 할 수 없습니다. 상속을 쉽게하도록 설계되었습니다.

당신이 나와 같다면, 당신은이 상속에 대해 궁금해하기 시작했습니다. 그러나 더 중요한 것은 계층을 통한 분류의 힘에 대한 확신을 떨쳐 야합니다.

계층 문제

새 회사를 시작할 때마다 회사 문서를 넣을 장소를 만들 때 문제가 발생합니다 (예 : 직원 안내서.

Documents라는 폴더를 생성 한 다음 Company라는 폴더를 생성합니까?

아니면 회사라는 폴더를 만든 다음 그 폴더에 Documents라는 폴더를 생성합니까?

둘 다 작동합니다. 그러나 어느 것이 맞습니까? 어느 것이 가장 좋은가요?

범주 형 계층 구조의 개념은 더 일반적인 기본 클래스 (부모)가 있고 파생 클래스 (자식)가 해당 클래스의 더 특수화 된 버전이라는 것이 었습니다. 상속 체인을 내려갈 때 더욱 전문화됩니다. (위의 모양 계층을 참조하십시오)

그러나 부모와 자녀가 임의로 장소를 바꿀 수 있다면 분명히이 모델에 문제가 있습니다.

계층 솔루션

문제는 ...

범주 계층 구조가 작동하지 않습니다.

그렇다면 계층 구조는 무엇입니까?

봉쇄.

실제 세계를 보면 모든 곳에 격리 (또는 독점 소유권) 계층이 표시됩니다.

찾을 수없는 것은 범주 형 계층 구조입니다. 잠깐 동안 들어 오십시오. 객체 지향 패러다임은 객체로 채워진 현실 세계를 전제로했다. 그러나 깨진 모델 인 viz를 사용합니다. 실제 비유가없는 범주 계층 구조.

그러나 실제 세계는 격리 계층으로 채워져 있습니다. 격리 계층의 좋은 예는 양말입니다. 그들은 당신의 집에 포함 된 침실에 포함 된 옷장에 하나의 서랍에 포함 된 양말 서랍에 있습니다.

하드 드라이브의 디렉토리는 격리 계층의 또 다른 예입니다. 파일이 포함되어 있습니다.

그렇다면 어떻게 분류합니까?

음, 회사 문서를 생각하면 내가 어디에 두 었는지는 중요하지 않습니다. Documents 폴더 나 Stuff 폴더에 넣을 수 있습니다.

내가 분류하는 방식은 태그를 사용하는 것입니다. 파일에 다음 태그를 태그합니다.

문서
회사
안내서

태그에는 순서 나 계층이 없습니다. (이것은 다이아몬드 문제도 해결합니다.)

태그는 문서와 연관된 여러 유형을 가질 수 있으므로 인터페이스와 유사합니다.

그러나 균열이 너무 많으면 상속 기둥이 떨어진 것처럼 보입니다.

안녕히 계승

캡슐화, 두 번째 기둥이 떨어짐

언뜻보기에 캡슐화는 객체 지향 프로그래밍의 두 번째로 큰 이점 인 것으로 보입니다.

객체 상태 변수는 외부 액세스로부터 보호됩니다. 즉, 객체에 캡슐화되어 있습니다.

더 이상 who-knows-who가 액세스하는 전역 변수에 대해 걱정할 필요가 없습니다.

캡슐화는 변수에 안전합니다.

이 캡슐화는 믿어지지 않습니다!

긴 라이브 캡슐화…

그 때까지는…

참조 문제

효율성을 위해 객체는 값이 아닌 참조로 함수에 전달됩니다.

그 의미는 함수가 객체를 전달하지 않고 객체에 대한 참조 또는 포인터를 전달한다는 것입니다.

Object가 Object Constructor를 참조하여 전달되면 생성자는 해당 Object 참조를 Encapsulation에 의해 보호되는 개인 변수에 넣을 수 있습니다.

그러나 전달 된 객체는 안전하지 않습니다!

왜 안돼? 다른 코드 조각에는 Object, 즉 포인터가 있습니다. 생성자를 호출 한 코드 그렇지 않으면 생성자에게 전달할 수없는 오브젝트에 대한 참조가 있어야합니까?

참조 솔루션

생성자는 전달 된 Object를 복제해야합니다. 얕은 클론이 아니라 깊은 클론, 즉 전달 된 Object에 포함 된 모든 객체 및 해당 객체의 모든 객체 등.

효율성을 위해 너무 많은.

그리고 여기에 키커가 있습니다. 모든 객체를 복제 할 수있는 것은 아닙니다. 일부 운영 체제 리소스와 관련하여 복제를 사용하지 못하게하는 것이 불가능하거나 불가능합니다.

그리고 모든 단일 주류 OO 언어에는이 문제가 있습니다.

안녕, 캡슐화.

다형성, 쓰러지는 세 번째 기둥

다형성은 Object Oriented Trinity의 빨간 머리 계승자였습니다.

일종의 래리 파인입니다.

그들이가는 곳마다 그는 그곳에 있었지만 단지지지하는 인물이었습니다.

다형성이 좋은 것은 아닙니다. 단지 이것을 얻기 위해 객체 지향 언어가 필요하지 않다는 것입니다.

인터페이스가이를 제공합니다. 그리고 OO의 모든 수하물이 없습니다.

또한 인터페이스를 사용하면 다양한 행동을 혼합 할 수있는 횟수에 제한이 없습니다.

따라서 많은 어려움없이 우리는 OO 다형성과 작별 인사를하고 인터페이스 기반 다형성과 인사합니다.

깨진 약속

OO는 초기에 많은 것을 약속했습니다. 그리고 이러한 약속은 여전히 ​​교실에 앉아 블로그를 읽고 온라인 과정을 수강하는 순진한 프로그래머에게 이루어지고 있습니다.

OO가 어떻게 거짓말을했는지 깨닫는 데 몇 년이 걸렸습니다. 나도 눈이 넓고 경험이없고 신뢰했습니다.

그리고 나는 화상을 입었다.

작별, 객체 지향 프로그래밍.

그럼 뭐?

안녕하세요, 기능적 프로그래밍. 지난 몇 년 동안 당신과 함께 일하게되어 기뻤습니다.

아시다시피, 나는 당신의 약속을 액면가로 취하지 않습니다. 나는 그것을 믿기 위해 그것을보아야 할 것입니다.

일단 불에 타면 수줍음이 두 배가됩니다.

당신은 이해합니다.

이것을 좋아한다면 아래의 을 클릭하면 다른 사람들이 이것을 Medium에서 볼 수 있습니다.

Elm에서 Functional Programming을 사용하여 서로 배우고 웹 응용 프로그램을 개발하도록 돕는 웹 개발자 커뮤니티에 참여하려면 Facebook Group, Elm Programming Learn https://www.facebook.com/groups/learnelm/을 확인하십시오.

내 트위터 : @cscalfani