LabVIEW 객체 지향 프로그래밍: 설계 이면의 결정

개요

LabVIEW: 설계 도구입니까? 프로그래밍 언어입니까? 둘 다입니다. 그리고 두 가지 역할을 모두 하므로 컴퓨터 과학자를 참여시키지 않고 컴퓨터를 프로그램해야 하는 과학자와 공학자에게 큰 도움이 되었습니다. LabVIEW 개발자인 우리는 새로운 기능을 추가하려고 할 때, 고객 대부분이 프로그래머가 아니라는 점을 고려해야 합니다. 버전 8.2에서 우리는 LabVIEW 객체 지향 프로그래밍 (LVOOP)을 도입했습니다. 객체 지향 (OO)은 추상적인 개념과 기술 어휘로 가득 찬 프로그래밍 스타일입니다. 이를 이해하려면 깊은 프로그래밍 지식이나 오랜 기간의 학습이 필요합니다. 우리는 다양한 사용자가 OO의 장점을 활용할 수 있도록 복잡성의 간소화를 목표로 했습니다. 그 결과는 다른 프로그래밍 언어에 익숙한 OO 지지자에게는 낯설 수 있습니다. 이 백서에서는 LVOOP를 만들 당시의 설계 의사 결정과 그 결정 이면의 이유를 설명합니다.

이 문서를 이해하려면 LVOOP에 어느 정도 익숙해야 합니다. 계속하기 전에 LabVIEW 도움말의 관련 섹션과 예제 프로그램을 검토하는 것이 좋을 수도 있습니다.

이 문서는 LabVIEW 2009용으로 업데이트되었습니다.

Contents

LVOOP의 상위 레벨 설계

LabVIEW에 객체 지향 프로그래밍이 필요한 이유는 무엇입니까?
LabVIEW의 목표는 정식으로 프로그래밍 교육을 받지 않은 엔지니어와 과학자도 컴퓨터 프로그래밍을 할 수 있도록 하는 것입니다. 우리는 정식 프로그래밍 교육을 받지 않은 사용자에게 인터페이스가 직관적으로 느껴지도록 LabVIEW를 구조화하려고 합니다.

객체 지향 프로그래밍은 여러 프로그래밍 언어에서 절차적 프로그래밍보다 아키텍처 측면에서 우월함을 보였습니다. 코드 섹션 간의 명확한 구분을 장려하고 디버깅이 더 쉬우며 대규모 프로그래밍 팀에 맞게 확장하기가 더 쉽습니다. LabVIEW R&D는 고객에게 이러한 능력을 제공하고 싶었습니다. 언어를 통해 이러한 소프트웨어 모범 사례를 적용할 수 있게 되기를 바랐기 때문입니다.

OO 프로그래밍에는 절차적 프로그래밍보다 더 많은 계획이 필요합니다. 예를 들어, 어떤 값을 계산하거나 DAQ 카드에서 특정 값을 가져오는 기능을 몇 번 수행하는 VI를 여러 개 구현한다고 하면 이들을 클래스로 묶는 것은 지나친 것일지도 모릅니다. 그러나 이러한 VI가 몇 년 동안 유지할 어플리케이션에 포함되는 경우는 OO가 올바른 선택일 수 있습니다. 우리는 LabVIEW가 잘 알려진 장점인 공학 프로토타이핑을 방해하지 않는 최신 소프트웨어 설계 도구를 갖기를 바랐습니다.

이 문서에서 LabVIEW 2009용으로 수정된 섹션에는 [LV2009]라는 표시가 삽입되었습니다.

 

객체 설계를 선택하는 사람들을 위해
와이어와 노드가
자연스럽게 변해
클래스와 메소드가 되길 바랍니다.


"객체 지향 프로그래밍"에는 어떤 의미가 있다고 결정했습니까?
C++는 객체 지향 언어입니다. 자바도 마찬가지입니다. 스몰토크도 그렇습니다. 그 밖에도 객체 지향 언어는 정말 많습니다. 이 언어들은 각각 고유한 기능을 자랑합니다. OO를 LabVIEW에 추가하기로 결정했을 때 그 고유한 기능들의 의미를 생각해보아야 했습니다. C++ 템플릿은 C++의 기능입니까, 아니면 OO의 기능입니까? 연산자 오버로딩은 어떻습니까? 아니면 자바의 "synchronized" 키워드는요?

우리에게 OO는 캡슐화와 상속이라는 두 가지를 의미합니다. 우리는 OO 언어를 만들기 위한 기능을 선택하면서 이 두 가지를 큰 기둥으로 놓고 작업했습니다. 클래스 데이터 타입을 "캡슐화"할 수 있어야 했습니다. 즉, 클러스터와 같은 데이터 블록을 정의하고 사용자가 지정한 함수에서만 해당 데이터에 액세스할 수 있도록 LabVIEW에 지시할 수 있어야 하는 것입니다. 또한 "상속"으로 새 클래스를 생성할 수도 있어야 했습니다. 기존 클래스를 시작점으로 하여 새 클래스를 만들고 부모 클래스의 메소드를 재정의할 수 있어야 했습니다. 이 두 가지 원칙에 초점을 맞춤으로써 불필요한 기능이 추가되는 것을 막을 수 있었습니다.

OO는 데이터 흐름에 어떻게 적합합니까? (값 기반 대 참조 기반에 대한 논쟁)
LabVIEW는 데이터 흐름 구문을 사용합니다. 다이어그램에 있는 노드는 대부분의 경우 그 노드에 연결된 입력으로만 동작하며 다이어그램의 다른 부분에 있는 와이어가 가진 값에 영향을 주지 않습니다. 와이어가 분기되면 그 값은 복사됩니다. 이러한 와이어는 데이터 흐름에서와 같은 "값 기반" 문법을 사용합니다. 예외는 참조 번호 데이터 타입입니다. 참조 번호는 어떤 보호 메커니즘을 통해 여러 노드에서 사용할 수 있는 공유된 메모리 위치에 대한 참조입니다. LabVIEW의 참조 번호 타입에는 큐, 파일 I/O, VI 서버 타입 등이 포함됩니다. 와이어가 분기될 때 공유 메모리는 복사되지 않습니다. 분기되는 것은 오직 LabVIEW가 공유 메모리를 찾을 수 있도록 하는 숫자입니다. 이러한 와이어는 "참조 기반" 문법을 사용합니다. LabVIEW 클래스 개발 초기에 우리는 클래스 와이어를 값을 기반으로 할지 참조를 기반으로 할지 결정해야 했습니다.

C++를 생각해 보십시오. 그 언어에서 객체를 선언하는 데는 두 가지 방법이 있습니다. 스택 영역에 선언할 수도 있고, 포인터로 히프 (heap) 영역에도 선언할 수 있습니다. 그 객체를 함수에 전달하거나 다른 변수에 할당할 때는 항상 객체를 값으로 전달하는지 참조로 전달하는지 인식하고 있어야 합니다. 방금 데이터를 복사한 것일까요? 아니면 데이터를 공유한 것일까요? 반면에 Java 및 C#에는 참조 기반 문법만 있습니다. 함수 파라미터로 할당된 변수는 항상 원본 객체를 참조합니다. 복제본을 만들려면 원본을 소스로 사용하여 새 객체를 명시적으로 만들어야 합니다.

어떤 방법이 LabVIEW에 적합하겠습니까? 데이터 흐름 언어에서 값 기반 문법을 사용한다는 것은 개별 와이어의 값이 다른 모든 와이어와는 독립적임을 의미합니다. 이는 여러 스레드를 실행할 때 코드의 한 섹션이 다른 섹션의 값에 영향을 미칠지 걱정할 필요가 없다는 뜻입니다. 참조 기반 구문은 그 독립성을 깨뜨립니다. 갑자기 사용자는 참조 공유 횟수를 추적하고 코드의 어떤 섹션도 다른 섹션과 동시에 그 참조에 액세스하지 않도록 해야 합니다. 사용자는 보호된 섹션, 뮤텍스 및 경합 조건을 이해해야 합니다. 장점을 보자면, 특정 데이터 구조, 특히 순환 그래프 또는 관계형 데이터베이스를 만드는 것은 참조를 사용하면 더 간단하며 이러한 복잡한 데이터 구조 때문에 OO를 사용하는 사람도 있습니다.

LabVIEW에는 와이어에서 데이터를 공유하기 위한 메커니즘이 이미 있습니다. 이러한 메커니즘은 어떤 기준으로 볼 때는 불충분하지만 이미 존재하는 기능이며 LabVIEW의 버전이 올라갈 때마다 개선됩니다. LabVIEW는 또 다른 데이터 공유 방법이 필요한 것이 아니었습니다. 데이터를 격리하는 방법이 필요했습니다. 데이터 변경을 제한할 수 없는 경우 데이터 일관성을 보장하기가 어렵습니다. 캡슐화를 지원하는 방법은 근본적으로 LabVIEW의 클래스를 어떤 VI로도 풀 수 없는 클러스터가 되도록 하는 것이었습니다. 따라서 Java 또는 C#과 C++와 달리 LabVIEW의 객체는 순수한 값 기반 문법을 가지게 되었습니다. 와이어가 분기될 때, 객체는 LabVIEW 컴파일러에 의해 복제될 수도 있습니다.

참조 기반 대신 값 기반을 선택하면 다른 모든 설계에 영향을 미칩니다. 많은 표준 OO 디자인 패턴은 하나의 객체가 "저기 저 객체에 관심이 있어"라고 말할 수 있다는 것을 전제로 합니다. LabVIEW에는 데이터에 대한 참조를 생성하는 메커니즘 (초기화되지 않은 시프트 레지스터, 전역 변수, 단일 요소 큐)이 있습니다. 그러나 이러한 메커니즘을 사용하려면 참조 기반 문법이 기본적으로 제공될 때보다 더 많은 일을 해야 합니다. 그런데도 이러한 메커니즘으로도 많은 복잡한 데이터 구조를 구축하기에 충분했습니다. 참조 기반의 솔루션에는 문제가 있습니다. 구조가 데이터 흐름과 일치하지 않으므로 LabVIEW의 큰 장점인 자연스러운 병렬 처리의 이점을 얻지 못하는 것입니다. 다른 언어의 디자인 패턴을 따르면 비효율적이 되고 낯선 버그가 발생합니다. 시간이 지나면서 자체 설계 패턴이 등장했고, 값 기반 문법의 강력함을 보여주었습니다.

이 결정은 사람들이 LVOOP를 처음 접했을 때 가장 큰 논쟁거리가 되지만, LabVIEW R&D 팀은 이를 검토하는 데 수년을 썼으며 이것이 LabVIEW를 위한 올바른 선택이라고 확신합니다. 참조 기능의 개선은 미래를 위해 고려하겠지만, 데이터 흐름과 일치하는 실제 개발에는 탄탄한 값 기반의 구현이 필수적입니다.

[LV2009] LabVIEW 2009에서는 데이터 값 참조 (Data Value Reference)를 도입했습니다. 이는 일반 정수, 배열, 클러스터 또는 LabVIEW 객체와 같은 모든 LabVIEW 데이터에 대한 참조 역할을 할 수 있는 새로운 참조 번호 데이터 타입입니다. 이 새 추가 기능은 문서의 뒷부분에서 설명합니다.

어휘 선택에는 무엇을 고려했습니까?
개념의 이름은 사람들이 그 개념을 생각하는 방식과 개념 이해의 용이성에 영향을 미칩니다. 객체 지향 프로그래밍을 LabVIEW에 도입했을 때 "컴퓨터 과학"을 얼마나 포함해야 하는지에 대해 많은 논의가 있었습니다. LabVIEW는 컴퓨터 과학 교육을 받지 않은 사용자가 프로그래밍에 접근할 수 있도록 하는 것을 목표로 합니다. 테스트 및 측정, 산업 제어 및 하드웨어 설계를 수행하는 과학자와 공학자가 우리 고객입니다. LabVIEW의 언어가 많은 과학자와 공학자에게 다가갈 수 있었던 것은 애초에 이 언어가 배선 회로도와 유사하기 때문이었습니다. 우리는 다양한 OO 개념에 대해서도 비슷하게 접근성이 높은 비유를 찾으려고 노력했습니다.

우리는 상속 계층에 대해 가족 관계를 차용한 용어를 사용하기로 결정했습니다. 즉, 부모와 자식, 형제와 사촌을 사용하기로 한 것입니다. 이 개념은 고객들이 이미 알고 있는 개념입니다. 클래스의 부모라고 하면 관계를 이해하기 쉽고 대화 내용을 따라가기도 쉽습니다. “슈퍼 클래스"나 “베이스 클래스" 따위의 용어를 사용할 수도 있었지만 이런 용어는 가족 용어에 비해 바로 의미를 파악하기가 어렵습니다. 이러한 용어는 번역도 쉬워서 현지화 팀도 좋아했습니다. 우리는 또한 사용자 인터페이스와 문서에서 상속 트리를 그릴 때 항상 조상은 위에 오고 자손은 밑으로 오게 했습니다. 다른 언어를 사용하는 설계 회의에서 개발자들은 뒤집힌 트리를 보고 있다는 것을 깨닫지 못하고 많은 시간을 낭비하곤 합니다. 우리는 LabVIEW 개발자들에게 일관성을 장려하고 싶었습니다.

스코프 (scope) 용어로는 산업 표준인 “프라이빗 (private)", “보호됨 (protected)”, “퍼블릭 (public)”을 선택했습니다. "protected"라는 단어에 반대하는 개발자도 처음에는 있었습니다. 프라이빗 데이터와 퍼블릭 데이터는 직관적인 개념입니다. 반면 "보호됨"이라는 단어는 무엇으로부터 보호되는 것인지 모호합니다. 따라서 상속 개념을 따라 "가족 (family)"과 같이, 실제로 스코프를 잘 나타내는 단어를 선택하는 것을 고려한 적도 있습니다. 그러나 "보호됨"이라는 용어는 언어에 관계없이 업계에서 일관되게 사용되므로 사용자는 타사 도움말이나 교과서에서 이 용어를 보게 될 것입니다. LabVIEW의 기존 개념과 충돌하지 않기 때문에 표준 용어를 고수하기로 결정했습니다. [LV2009] "커뮤니티 (community)" 스코프를 추가했을 때는 대부분의 다른 프로그래밍 언어에 유사한 개념이 없었습니다. 이 경우 우리는 고유의 용어를 만든 것입니다. 상속 트리가 "가족"이면 "커뮤니티"는 친구들입니다. 우리는 "커뮤니티"가 다른 스코프와의 관계를 가장 명확하게 설명한다고 느꼈지만, 솔직히 말해 기존 세 스코프처럼 P로 시작하는 용어를 찾을 수 없다는 점에 실망한 사람도 있습니다.

“클래스 (class)"라는 단어는 가장 큰 논쟁거리였습니다. "클래스"는 "데이터와 그 데이터로 작동하는 함수를 묶은 블록"을 나타내는 업계 표준 용어입니다. OO 프로그래밍의 기초 개념이기도 합니다. 그러나 우리는 더 쉬운 단어가 있는지 토론했습니다. LabVIEW에는 이미 기존 타입을 기반으로 새로운 데이터 타입을 정의하는 컨트롤 VI인 typedef가 있었습니다. "클래스"라는 키워드를 피하고 대신 새로운 종류의 typedef인 객체 typedef를 도입하자는 의견도 있었습니다. 그러나 이것은 OO를 처음 보는 LabVIEW 사용자에게는 개념적으로 이해하기 쉽겠지만, 우리의 얼리 어답터들은 다른 언어에서 OO를 본 적 있는 사람들일 것입니다. "클래스"라는 용어를 사용하지 않으면 이런 사람들에게 혼동을 줄 수 있습니다. 클래스 없는 OO 언어도 있는지 질문할 것입니다. 따라서 "보호됨"과 같이 업계 표준을 따른 것입니다. 문서에서 "LabVIEW 클래스"와 "VI 서버 클래스"는 주의하여 구별했습니다.

"가상 (virtual)"과 "가상 디스패칭 (virtual dispatching)"의 개념은 LabVIEW에서 문제가 되었습니다. 이 용어들도 산업 표준입니다. C#, C++ 또는 Java에서 가상 함수는 입력 중 하나 ("this" 객체)의 런타임 데이터 타입에 따라 다른 버전을 호출하는 함수입니다. LabVIEW 함수는 "가상 계측기"라고 불립니다. "가상 가상 계측기"라고 말하기는 어색할 것입니다. 또한 "가상"은 원래부터 정확한 용어 같지 않았습니다. 어떤 측면이 "가상"인지 이해하기 어렵습니다. 그래서 우리는 "동적 (dynamic)"과 "동적 디스패칭 (dynamic dispatching)"이라는 용어를 선택했습니다. 이 용어들은 LabVIEW가 수십 년 동안 사용해온 subVI 호출의 종류인 정적 디스패치의 개념과 잘 대조됩니다. "동적"과 "정적"을 사용하면 이 두 VI 그룹을 구분하는 요소가 명확하게 식별됩니다. "정적"이라는 단어는 다른 OO 언어에서 온갖 의미를 가지고 있어서 LabVIEW에서 문제가 되었습니다. 이 용어는 이 문서의 뒷부분에서 설명합니다.

다른 용어에 대해서도 소규모의 토의가 있었습니다. 선택한 OO 어휘는 그 어휘가 나타내는 사용자 인터페이스의 실질적인 동작만큼이나 중요한 구성요소입니다. OO의 접근성을 높이려면 모든 새로운 개념에 이의를 제기해야 했습니다. C++ 또는 Java에서 사용한 이름을 LabVIEW에서도 사용하는 것이 올바른 결정이라고 가정할 수 없었습니다.

LabVIEW 클래스 설계


"LabVIEW 객체 (Object)" 클래스의 목적은 무엇입니까?
"LabVIEW Object"는 특정 LabVIEW 클래스의 이름입니다. 이 특수 클래스는 모든 LabVIEW 클래스의 최고 조상입니다. JAVA에도 비슷한 클래스 (java.lang.object)가 있습니다. C++에는 없습니다. C#에는 모든 클래스 타입의 조상이자 숫자 같은 내장 타입인 하이브리드 “object” 타입이 있습니다.

공통 조상 클래스를 사용하면 모든 자손 클래스로 작업할 수 있는 함수를 작성할 수 있습니다. C++에는 템플릿이 있기 때문에 이러한 클래스가 필요하지 않지만, 언어에 템플릿을 추가하면 언어 복잡성이 크게 증가합니다. LabVIEW가 미래에 클래스 템플릿을 도입할 수도 있지만 우리는 덜 복잡한 솔루션이 필요하다고 느꼈습니다. 공통 조상 클래스는 Build Array와 같은 노드에서 단일 배열에 여러 클래스의 객체를 저장하기 위해 사용할 수 있는 타입을 제공합니다. LabVIEW는 모든 LabVIEW 데이터를 포함할 수 있는 배리언트 데이터 타입을 가지고 있어, 이러한 "제네릭 클래스 (generic class)"가 필요 없다고 말할 수도 있습니다. 그러나 LabVIEW 객체는 [보다 구체적인 클래스로] 함수에 연결할 수 있는데 배리언트 타입으로는 불가능 (그리고 LabVIEW가 배리언트로도 가능하게 변경한다면 이상함)합니다.

LabVIEW Object 클래스의 특별한 사용 사례 하나는 프레임워크에 클래스를 동적으로 로드하는 것입니다. LabVIEW 객체를 반환하는 두 가지 주목할 만한 함수가 있는데, 이는 LVClassLibrary 참조의 "기본 인스턴스 (Default Instance)" 속성과 "Get LV Class Default Value.vi"입니다. 이 두 함수는 모두 클래스를 조회 (하나는 참조 번호를 사용하고 다른 하나는 경로를 사용하여)하고 [프라이빗 데이터 컨트롤]을 따라 모든 필드에 대한 기본값을 설정한 인스턴스를 반환합니다. 이러한 함수를 사용하면 모든 클래스를 동적으로 로드하고 인스턴스화할 수 있습니다. 사용자는 "플러그인" 어플리케이션에서 이 일반적인 기법을 사용합니다. LabVIEW Object에는 정의된 메소드가 없으므로 [보다 구체적인 클래스로] 함수를 사용하여 와이어를 메소드가 정의된 클래스 유형으로 변환해야 합니다. 플러그인 아키텍처에서는 모든 메소드가 정의된 클래스는 "Generic Plugin.lvclass"와 같은 이름으로 정의합니다. 모든 플러그인 클래스는 이 클래스를 부모로 사용합니다. 그렇게 하면 자녀 클래스를 동적으로 로드할 수 있습니다. 클래스 참조 번호는 LabVIEW의 런타임 엔진에서 지원되지 않으므로 DLL 또는 EXE로 빌드되거나 타겟에 다운로드되는 모든 항목은 해당 VI를 사용해야 합니다.

 

기존 LV 8.2 방식 (런타임 엔진에서 작동하지 않음)

  1. 디스크에서 .lvclass 파일에 대한 참조를 엽니다. 그러면 LVClassLibrary 참조 번호가 반환됩니다.
  2. 클래스의 기본 인스턴스를 가져옵니다.
  3. 반환되는 LabVIEW Object를 플러그인 데이터 타입으로 캐스트합니다.

 

LV 8.5 이상 방식 (런타임 엔진에서 작동)

  1. 클러스터, 클래스 및 배리언트 (Cluster, Class & Variant) 팔레트에 있는 Get LV Class Default Value.vi를 사용하여 클래스를 메모리에 로드합니다.
  2. 반환되는 LabVIEW Object를 플러그인 데이터 타입으로 캐스트합니다.


클래스에 프라이빗 데이터만 있는 이유는 무엇입니까? 보호됨이나 퍼블릭 데이터가 아닌 이유는 무엇입니까?
다른 언어에서 사용되는 명칭을 도입할 때 논쟁이 있었던 것처럼 모든 기능도 논쟁을 거쳐 도입되었습니다. LabVIEW에는 프라이빗 데이터만 있습니다. 퍼블릭 또는 보호된 데이터를 생성할 수 없습니다. VI만 퍼블릭 또는 보호됨으로 설정할 수 있습니다. 

모든 데이터를 비공개하는 가장 설득력 있는 이유는 사용자를 위한 코드 정확성입니다. 우리는 사용자가 아키텍처 선택을 교육받지 않고도 최상의 아키텍처를 선택할 수 있도록 LabVIEW의 언어와 환경을 확립하기 위해 노력했으며, 이는 이해해야 하는 컴퓨터 과학 개념의 수를 제한하는 것을 의미합니다. 메소드를 통해서만 클래스의 데이터에 접근할 수 있게 하면 값 변경을 디버깅하는 지점을 하나로 줄일 수 있습니다. 범위 확인과 같은 확인 코드를 추가할 수 있는 장소가 생기는 것입니다. 또한 클래스 외부의 코드가 클래스에 대한 바이너리 종속성을 갖지 않도록 하므로 클래스의 새로운 버전이 이미 빌드된 어플리케이션에도 배치될 수 있습니다. 이것은 클래스 데이터 캡슐화의 주요 이점이며, 우리는 사람들이 자연스럽게 이러한 이점을 가진 소프트웨어를 설계하게끔 하고 싶습니다. 공개 또는 보호된 데이터를 지원하지 않음으로써 언어에서 혼동을 일으킬 가능성이 있는 컴퓨터 과학 개념을 제거 (설명이 필요한 개념 한 가지는 최소한 제거됨)하고 사람들을 더 나은 아키텍처로 유도합니다.

[LV2009] 또한 위에서 설명한 것과 같은 이유로 데이터는 커뮤니티 스코프에도 놓을 수 없습니다.

이 선택으로 인해 접근자 메소드가 급증하게 됩니다. 클래스의 모든 데이터 값에 대해 "읽기" 및 "쓰기" 메소드를 만드는 것은 번거로운 작업으로 알려져 있습니다. LabVIEW 8.5는 마법사 대화 상자에서 접근자 VI를 생성하는 기능을 도입했으며 LV2009는 해당 대화 상자에 옵션을 더 많이 추가했습니다.

우리는 데이터 번들을 해제하는 대신 subVI 호출을 수행하는 데 따른 성능 오버헤드에 대해 우려했습니다. SubVI 호출에 대한 현재 컴파일러 오버헤드는 무시해도 될 정도 (평균 PC에서 6~10마이크로초 사이로 측정)였습니다. 호출자 VI에서 직접 묶기/풀기를 할 때와 SubVI 호출에 거의 동일한 시간이 걸린 것입니다. LabVIEW의 고정성 (inplaceness) 알고리즘 대부분이 SubVI 경계를 넘어서면 작동하지 않기 때문에 풀어 놓은 데이터 원소를 복사하는 데는 문제가 있습니다. 해결 방법으로, 접근자 메소드 대신 SubVI에서 원소를 반환하지 않고 실제로 작업을 수행하는 메소드를 선택하여 데이터 복사를 막을 수도 있습니다. 이 선택은 클래스의 비공개 구현 내용을 공개 API의 일부로 노출하지 않으므로 어차피 더 나은 OO 디자인입니다. 향후 버전에서는 SubVI의 인라인 동작으로 인해 오버헤드가 완전히 제거될 것으로 예상합니다.  

장기적으로는 퍼블릭/보호됨/커뮤니티 데이터를 도입하는 것보다 이러한 VI의 편집기 경험과 컴파일러 효율성을 개선하는 것이 더 낫다고 생각합니다. 

컨스트럭터는 어디에 있습니까?
이 질문은 보통 다른 프로그래밍 언어에서 OO를 알고 있는 사용자가 몇 시간 동안 LVOOP를 써 보고 답답한 마음에 하는 질문입니다. 이렇게 근본적인 기능이 이 정도로 숨겨져 있을 리가 없다고 생각하면서 못 찾고 있는 자신을 탓하거나 이렇게 찾기 어렵게 만든 우리를 탓하는 것입니다. 그러나 대답은 간단합니다. 컨스트럭터는 없습니다.

컨스트럭터의 사용 사례를 살펴봅시다.

  1. 객체의 초기값을 설정하는 데 사용됩니다.
  2. 파라미터 세트에서 객체의 초기값을 계산하는 데 사용됩니다.
  3. 환경 데이터 (예: 객체가 생성되었을 때 타임스탬프 기록)에서 객체의 초기값을 계산하는 데 사용됩니다.
  4. 이 객체에서 사용할 시스템 리소스 (메모리, 통신 채널, 하드웨어 등)를 예약 (나중에 디스트럭터에 의해 해제됨)하는 데 사용됩니다.

LabVIEW에는 클래스의 기본값을 설정할 수 있는 기능이 있습니다. [프라이빗 데이터 컨트롤]에서 설정한 값이 클래스의 기본값입니다. 그렇다면 이것은 계산된 값입니까? 아니요. 정적인 값입니다. 기본값의 일부로서 실행 코드를 넣을 장소가 없습니다. 이것은 다른 OO 언어의 간단한 기본 컨스트럭터와 동일한 것입니다. 클래스의 모든 인스턴스에 대한 초기값은 이 기본값이므로 컨스트럭터 사용 사례 #1이 충족되었습니다.

C++에서는 정수를 어떻게 선언합니까?

int k = 1;

LabVIEW에서는 정수를 어떻게 선언합니까?

Numeric 컨트롤을 끌어 놓고 해당 컨트롤을 정수로 설정하고 1과 현재 값을 기본값으로 (Make Current Value Default)를 설정합니다.


이 C++ 클래스를 생각해보십시오...

class Thing {
private:
int mx;
double mz;
std::string ms;
public:
Thing() : mx(1), mz(2.5), ms("abc") { }
Thing(int x) : mx(x), ms("def") {
mz = (x > 1) ? 3.5 : 2.5;
}
~Thing() { }
void Init(int x) {
mx = x; mz = (x > 1) ? 3.5 : 2.5; ms = "def";
}
};


C++에서 기본이 아닌 컨스트럭터는 어떻게 호출합니까?

Thing item(k+3);

LabVIEW에서 기본이 아닌 컨스트럭터는 어떻게 호출합니까?

호출할 수 없습니다. 이 질문은 LabVIEW에서는 의미가 없습니다. 파라미터를 전달하여 컨트롤 값을 초기화하지 않기 때문입니다. 컨트롤은 VI에 전달된 파라미터 또는 사용자가 프런트패널에서 입력한 값에서 값을 얻습니다.


컨스트럭터 개념은 데이터 흐름 환경에서는 문제가 됩니다. 모든 것은 값으로 시작 (외부 입력 없이 완전히 계산됨)하거나 흐르는 값을 받으면 계산됩니다. LabVIEW 클래스는 생성 시 프라이빗 데이터 컨트롤의 데이터 타입으로 정의된 초기값을 갖습니다. 이것은 컨스트럭터 사용 사례 #1입니다. 모든 인스턴스는 이 동일한 값으로 초기화됩니다. 추가 동작을 원한다면, 클래스가 아닌 입력을 받고 클래스를 출력하는 SubVI를 블록다이어그램에서 정의하면 됩니다. 이것은 컨스트럭터 사용 사례 #2입니다.

컨스트럭터 사용 사례 #3은 고급 소프트웨어 프로그래밍 개념입니다. LabVIEW는 이 개념을 뒷받침하지만 명백한 방식으로 그렇게 하지는 않습니다. 클래스의 인스턴스 수를 계산 (클래스 정적 필드 사용)하거나 인스턴스화 당시의 타임스탬프를 기록하는 것은 대부분의 클래스에 필요하지 않은 기능입니다. 이러한 기능은 더욱 고급화된 도구의 몫으로 남겼습니다. 사용자는 클래스 타입의 XControl을 생성합니다. XControl에는 편집 시간과 런타임에 실행되어 클래스 값을 초기화하는 코드가 있습니다. [외관] VI는 데이터 인스턴스의 값을 설정하는 데 필요한 어떤 코드도 포함할 수 있습니다. 현재 이 클래스는 사용하기 약간 번거로울지 모릅니다. 분명히 우리는 이 기능이 더 쉬워지기를 바랍니다. 그러나 이 기능이 컨트롤의 기반이 되는 코드에 있는 것은 올바른 위치입니다.

컨스트럭터 사용 사례 #4는 다음 질문과 함께 논의할 수 있을 뿐입니다.

디스트럭터는 어디에 있습니까?
C++ 정수의 수명은 얼마입니까?

C++ 정수는 선언된 지점부터 닫는 중괄호까지 존재합니다.

LabVIEW 정수의 수명은 얼마나 됩니까?

알 수 없습니다. 와이어가 끝날 때까지? 프런트패널이 닫힐 때까지? VI가 실행을 멈출 때까지? 본질적으로 이는 와이어 또는 컨트롤/인디케이터의 값일 뿐입니다. 해당 항목이 더 이상 필요하지 않을 때까지 존재할 것입니다.


LabVIEW에는 "공간 수명"이 없습니다. LabVIEW에 "시간 수명"은 있습니다. 데이터는 필요한 기간 동안 존재하고 더 이상 필요하지 않을 때 사라집니다. 데이터가 프런트패널 컨트롤에 복사되면 실행이 완료된 후에도 그대로 유지됩니다. 와이어에 복사본은 그 와이어의 다음 실행 시까지 존재합니다. 와이어에 있는 임의의 프로브에 대해 또 다른 복사본이 만들어집니다. 순수한 이론적 데이터 흐름 언어에서는 모든 와이어가 독립적인 계산 단위이므로 모든 개별 와이어에 별도의 복사본이 있을 것입니다. 물론 이것은 실제로 구현하기에는 비효율적이므로 LabVIEW의 컴파일러는 복사본 수를 최적화합니다. 그러나 원칙은 동일합니다. 데이터는 오랫동안 유지되고 때로는 해당 데이터를 생성한 프로그램보다 오래갑니다.

이러한 환경에서 컨스트럭터 사용 사례 #4를 고려해보십시오. 컨스트럭터는 언제 리소스를 예약하겠습니까? 복사본을 만들 때마다 어떤 일이 발생할까요? 와이어의 분기에서 만든 복사본과 인디케이터로의 복사본, 프로브로의 복사본에 대해 다른 복사 컨스트럭터가 필요할까요? 이러한 질문에 대한 답을 구하다 보면 많은 예외를 만나게 됩니다. 리소스를 예약하기 위해 이러한 묵시적 컨스트럭터를 실행할 시기를 정확히 결정하는 것은 매우 어려우며, 만족스러운 답변을 찾았다고 해도 해당 리소스를 해제하기 위해 디스트럭터 실행 시기도 결정해야 합니다.

LabVIEW의 리소스 예약은 참조 번호 데이터 타입을 기준으로 처리됩니다. 참조 번호 타입은 와이어의 값으로 참조 번호를 복사하며, "전체 복사"하거나 해제하려면 함수를 명시적으로 호출해야 합니다. 예를 들어 큐 데이터 타입에는 참조를 예약하는 [큐 얻기]와 참조를 해제하는 명시적인 [큐 해제]가 있습니다. [큐 해제]를 호출하지 않는 한 큐는 VI가 실행을 완료할 때까지 메모리에 남아 있으며, 실행이 끝나면 LabVIEW가 묵시적으로 해제합니다.

더 나은 "수명" 개념이 없으면 사용 사례 #4는 실현 불가능합니다.

요약하면 데이터 흐름 언어인 LabVIEW에는 일반적으로 변수가 없습니다. 와이어는 변수가 아닙니다. 프런트패널 컨트롤은 변수가 아닙니다. 지역 또는 전역 변수조차도 다른 언어의 변수처럼 특정한 수명을 갖도록 할당되지 않습니다. 변수라는 데이터 흐름과 충돌하는 모델의 일부입니다. 변수와 이러한 변수의 수명을 정의하는 방법이 없으면 생성과 소멸은 무의미한 개념이므로 초기 언어 설계에서 제외한 것입니다.

[LV2009] [데이터 값 참조]는 이 문제에 대한 해결책을 제공합니다. 이는 참조 번호 데이터 타입이기 때문에 수명이 잘 정의되어 있습니다. DVR은 모든 LabVIEW 데이터 타입에 대해 생성될 수 있지만 LabVIEW 클래스를 위한 특별한 기능을 추가했습니다. 클래스 속성 (Class Properties) 대화 상자에는 해당 클래스의 객체에 대한 DVR의 생성 및 소멸을, 클래스 멤버 VI로 제한하는 옵션이 기본적으로 활성화되어 있습니다. 다른 말로 하면, 클래스의 멤버 VI만이 클래스에 대한 참조를 생성할 수 있는 것입니다. 그 클래스 타입의 와이어를 새 [데이터 값 참조]에 연결하려는 다른 모든 VI는 에러가 됩니다. 이 제한은 적절한 값 (클래스 작성자가 정의)을 할당하지 않고는 클래스에 대한 참조가 생성되지 않도록 하기 위해, 초기화 코드를 멤버 VI에 넣을 수 있음을 의미합니다. 마찬가지로, 멤버 VI를 제외하고는 참조를 삭제할 수 없게 하여 클래스 작성자가 할당 해제 코드를 확보할 방법을 제공합니다. 이 할당 해제 코드는 VI 중지에 의해 묵시적으로 참조가 해제된 경우 호출되지 않지만, 생성한 참조를 명시적으로 삭제하는 한 DVR이 LabVIEW 객체에 대한 수명 범위를 제공하여 사용 사례 #4가 지원됩니다.

주의: 참조 타입은 VI에서 극히 드물게만 사용해야 합니다. 고객 VI를 많이 살펴본 결과, R&D 팀에서는 모든 LabVIEW 클래스 중 5% 미만 정도만 참조 메커니즘이 필요하다고 보았습니다. 그 이상으로 참조를 사용하고 있다면 분명 성능의 하락이 있을 것이므로 값 기반 클래스를 사용하는 방법을 생각해 보십시오.

클래스의 메모리 내 레이아웃은 어떻습니까?
LabVIEW에서 클래스를 효율적으로 저장하는 것은 흥미로운 도전이었습니다.

클래스의 데이터는 다음 두 부분으로 정의됩니다.

  1. 부모로부터 상속된 데이터 그룹
  2. 자체 프라이빗 데이터 클러스터


모든 클래스는 클러스터의 클러스터로 생각할 수 있습니다. 이 클러스터는 클래스 자체의 프라이빗 데이터 클러스터 하나를 제외하고는 모든 내부 클러스터를 조상들로부터 받습니다. 최상위 조상인 LabVIEW Object는 데이터 필드를 제공하지 않습니다.


LabVIEW에는 함수 스택이 없습니다. 병렬로 실행되는 다른 다이어그램의 노드가 서로 인터리빙할 수 있는 데이터 흐름에서는 스택 아키텍처를 사용하는 데 문제가 있습니다. 대신, VI가 컴파일될 때 LabVIEW는 해당 VI에 "데이터 공간"을 할당합니다. 이 데이터 공간은 해당 VI를 실행하는 데 필요한 모든 데이터의 할당입니다. 모든 스레드는 다른 스레드가 자신의 데이터 공간 영역에 쓰는 것을 걱정할 필요 없이 그곳에 자유롭게 쓸 수 있으므로 뮤텍스 잠금이 필요하지 않습니다.

클래스를 구현하려면 데이터 공간에 클래스를 할당할 수 있어야 했습니다. 부모 타입의 와이어는 그 타입 또는 모든 하위 타입의 데이터를 전달할 수 있어야 하므로 데이터 공간에서 할당된 공간은 어떤 클래스도 포함할 수 있어야 합니다. 그 의미는 이러한 클러스터의 클러스터를 직접 할당할 수 없다는 뜻입니다.

설계를 더 복잡하게 만드는 점은, 객체가 그 객체의 클래스 정보를 포함해야 한다는 것입니다. 객체는 데이터 중 스스로 인식하는 부분입니다. 자신의 타입을 알고 있어야 (덕분에 객체가 [보다 구체적인 클래스로] 및 동적 디스패치와 같은 작업을 수행할 수 있음) 합니다. 클래스 정보는 어느 시점에는 객체에 포함해야 합니다.

따라서 메모리의 최종 구조는 다음과 같습니다.



LabVIEW는 클래스 기본값을 나타내는 단일 인스턴스를 할당합니다. 기본값인 모든 객체는 해당 포인터를 공유합니다. 이는 클래스의 기본값으로 매우 큰 배열이 있는 경우 처음에는 메모리에 해당 배열의 복사본이 하나만 있음을 의미합니다. 객체의 데이터에 쓸 때, 해당 객체가 현재 기본 데이터 포인터를 공유하는 경우 LabVIEW는 기본값을 복사한 다음 복사본에 쓰기를 수행합니다. 기본 인스턴스를 공유하면 전체 시스템이 훨씬 적은 메모리를 사용하고 VI를 훨씬 빠르게 열 수 있습니다.

클래스를 재귀적으로 정의할 수 있습니까?
프라이빗 데이터 컨트롤의 데이터는 다른 LabVIEW 클래스를 포함하여 잘 정의된 모든 LabVIEW 타입일 수 있습니다. 메소드만 정의하고 자체 데이터가 없는 클래스의 경우 클러스터를 비워 둘 수도 있습니다.

재귀 클래스는 자체적인 정의를 자신의 일부로 사용하는 클래스입니다. 이런 종류의 클래스는 다른 객체 언어에서도 나타납니다. 예를 들어 C++에서는 다음과 같을 수 있습니다.

class XYZ {
int data;
XYZ *anotherInstanceOfThisType;
};

이 클래스는 자체 타입을 사용하여 스스로 정의하는 것으로 보입니다. 이는 정확한 설명은 아닙니다. 엄밀히 이야기하면 필드의 타입은 "XYZ" 자체가 아니라 "XYZ에 대한 포인터"입니다. 이러한 기술적 구별은 이러한 언어가 재귀적으로 정의된 객체에 메모리를 할당하는 방법을 이해하는 데 중요합니다. 실제로는 객체를 위한 공간을 예약하지 않습니다. 그와 같은 객체 참조를 위한 공간을 예약합니다.

프라이빗 데이터 클러스터에 LabVIEW 클래스 컨트롤을 포함하면 전체 클래스가 프라이빗 데이터에 포함됩니다. 클래스에 대한 참조는 아닙니다. 따라서 클래스는 자체 프라이빗 데이터에 자신을 포함할 수 없습니다.

[LV2009] LabVIEW에는 참조 번호 컨트롤이 있습니다. 큐 참조 번호 또는 새로운 [데이터 값] 참조 번호 컨트롤을 사용하면 한 클래스의 프라이빗 데이터에 다른 클래스에 대한 참조를 할당할 수 있습니다. 이 기법을 사용하면 Class X가 Class X에 대한 참조 번호를 포함할 수 있다고 생각하는 사람이 많겠지만, 기술적으로는 가능해도 LabVIEW에서는 금지되어 있습니다. R&D 팀에서는 이 주제로 많은 논쟁을 했습니다. 클래스가 어떤 클래스를 참조할 수 있도록 할 것이며, 어떤 경우에 일반적인 프로그래밍이나 OO에 익숙하지 않은 사람들에게 혼동을 주게 될지를 토론했습니다. 결국 우리는 간단하고 강력한 규칙 하나로 합의를 보았습니다. 바로 클래스 X는 클래스 X를 사용하지 않는 모든 클래스를 포함할 수 있다는 규칙입니다. 매우 간단한 규칙입니다. 자식은 부모 클래스를 사용하므로 부모 클래스는 자식 클래스를 포함할 수 없습니다. 그러나 부모는 자식을 사용하지 않으므로 자식은 부모를 포함할 수 있습니다. 규칙을 적용하면 순환 의존성에 대한 걱정 없이 매우 깔끔한 로드 및 언로드 동작이 가능합니다.

클래스 X에 클래스 X에 대한 참조를 포함해야 할 경우 X의 부모에 대한 참조를 대신 사용하면 됩니다. 자식 객체는 항상 부모 값 (부모 컨트롤 또는 부모 타입 와이어에서)으로 사용할 수 있으므로 이러한 방식으로 참조를 작성할 수 있습니다.

LabVIEW OOP에는 다른 프로그래밍 언어 사용자에게는 직관적이지 않은 부분이 있습니다. 바로 자식 클래스는 부모 클래스를 자신의 데이터 멤버로 포함할 수 있다는 것입니다. 부모 클래스는 자식 클래스 없이 완전히 정의되므로 재귀 타입 정의를 만들지 않고도 자식 클래스의 프라이빗 데이터 클러스터의 일부로 사용할 수 있습니다. 런타임에 참조 데이터 타입을 가질 필요가 전혀 없이, 자식 클래스 자체의 다른 인스턴스를 포함하여 모든 값을 해당 위치에 저장할 수 있습니다. 따라서 부모 클래스의 인스턴스로 종료되는 동일한 타입의 객체 체인을 만들 수 있습니다. 실제로 모든 연결 목록 또는 트리 데이터 구조는 이러한 방식으로 구축될 수 있으며 이러한 데이터 구조는 완전히 안전한 데이터 흐름입니다. 이에 대한 예는 ni.com 및 lavag.org의 다양한 페이지에 게시되어 있습니다.

클래스 데이터 (정적 데이터라고도 함)는 어떻게 만듭니까?
클래스 데이터는 객체 인스턴스에 포함되지 않은 데이터입니다. 메모리에 클래스 데이터는 하나만 존재하며 그 단일 인스턴스의 접근 스코프는 퍼블릭, 보호됨 또는 프라이빗입니다. 클래스 데이터를 생성하는 방법에는 여러 가지가 있습니다.

가장 쉬운 방법은 글로벌 VI입니다. 글로벌 VI를 클래스에 추가한 다음 범위를 프라이빗 또는 보호됨으로 설정하면 다른 VI의 해당 데이터에 대한 접근이 제한됩니다. 그러나 글로벌 VI는 스코프가 클래스 내로 지정되었을 때도 일반적으로는 적절하지 않습니다. 데이터 흐름 외부에서 작동하므로 멀티 스레드 환경에서 안전하지 않고, 상당한 성능 오버헤드 (읽기 전용 작업에도 복사본이 필요)가 있기 때문입니다.

더 나은 솔루션은 온라인 도움말에서 "LV2 스타일 글로벌"로 알려진, 초기화되지 않은 시프트 레지스터가 있는 VI입니다. 이 글로벌 데이터 생성 방법은 LabVIEW 2.0 (이름의 유래)부터 LV에 존재해왔습니다. 이러한 VI는 클래스의 멤버로 만든 다음 스코프를 프라이빗 또는 보호됨으로 설정할 수 있습니다. 이러한 글로벌을 사용하면 데이터에서 사용할 수 있는 멀티 스레드 안전 작업 집합을 정의할 수 있습니다. 자세한 내용은 LabVIEW 도움말 또는 예제 VI를 참조하십시오. 업계 표준 "싱글톤" 디자인 패턴을 구현하는 한 가지 가능한 방법을 보여주는 특정 예제를 제공합니다.

클래스 메소드의 설계


"this" 객체는 어떤 와이어입니까? 클래스 정적 메소드는 어떻게 만듭니까?
C++와 Java는 모두 "this" 객체의 개념을 가지고 있습니다. 이러한 언어로 객체의 메소드를 정의할 때는 함수에 명시적 파라미터를 지정합니다. 이 언어는 묵시적 파라미터로 메소드가 호출되는 객체를 받습니다. 묵시적 파라미터는 "this" 객체라고 하며 해당 객체의 부분에 접근하려면 특수 구문을 씁니다.

이러한 언어에서 "클래스 정적 메소드"는 클래스의 일부이지만 묵시적 파라미터가 없는 함수입니다. 묵시적 객체가 없으므로 클래스 정적 메소드에서는 "this" 구문을 사용할 수 없습니다.

LabVIEW는 이러한 두 개념 중 어떤 것도 포함하지 않습니다. 멤버 VI에 대한 모든 입력은 명시적으로 커넥터 구획에서 선언됩니다. LabVIEW는 묵시적 입력을 절대 추가하지 않습니다. 묵시적 입력이 존재하지 않으므로 그러한 입력이 있는 VI와 그렇지 않은 VI 간에 차이가 없습니다.

LabVIEW에는 "정적"이라고 설명하는 메소드가 포함되어 있지만 다른 언어에서와는 다른 의미입니다. LabVIEW는 두 가지 유형의 메소드, 즉 동적 메소드와 정적 메소드를 구별합니다. 정적 메소드는 LabVIEW에 처음부터 있었던 간단한 subVI 호출입니다. SubVI 노드는 항상 동일한 SubVI를 호출하므로 이 메소드는 "정적"이라고 불립니다. 대조적으로, 동적 메소드는 VI 세트입니다. 동적 SubVI 노드는 동적 디스패칭으로 세트에 포함된 VI 중 하나를 호출하지만 정확히 어떤 VI가 호출될지는 런타임에 알 수 있습니다.

동적 디스패치는 어떻게 작동합니까? 일반 SubVI 호출 대비 오버헤드는 얼마입니까?
동적 디스패칭은 SubVI 호출처럼 보이지만 동적 디스패치 입력 터미널의 와이어 값에 따라 런타임 시 실제로는 여러 SubVI 중 하나를 호출하는 기능입니다. 개념적으로는 컴파일 타임이 아닌 런타임에 실행할 VI를 선택하는 다형성 VI라고 보면 됩니다.

모든 VI에 대한 동적 호출을 지원하는 [VI 서버]로 인해 테스터는 종종 동적 디스패치가 SubVI 호출에 비해 느리다고 가정합니다. 그러나 컴파일러는 포괄적인 [VI 서버] 호출보다 동적 디스패치 호출을 위해 호출될 수 있는 특정 VI 세트에 대해 훨씬 더 많은 정보를 알고 있으므로 더 나은 성능을 제공할 수 있습니다.

와이어를 통해 이동하는 각 객체는 자신의 클래스 정보에 대한 포인터를 가지고 있습니다 (이 문서 앞부분의 "클래스의 메모리 내 레이아웃은 어떻습니까?" 섹션 참조). 그 클래스 정보는 VI 참조의 테이블인 “동적 디스패치 테이블"을 포함합니다. 각 클래스는 부모의 테이블을 정확히 복사합니다. 그런 다음 부모 함수에 대한 VI 참조를 자체 VI로 대체합니다. 그리고 부모 VI 대체가 아닌 동적 디스패치 VI를 테이블에 추가합니다.

  • Parent.lvclass는 A.vi와 B.vi라는 두 개의 동적 디스패치 VI를 정의합니다.
  • Child.lvclass는 Parent에서 상속합니다. B.vi와 C.vi라는 두 개의 동적 디스패치 VI를 정의합니다. B.vi는 테이블의 두 번째 항목을 대체합니다.
  • Junior.lvclass는 Child에서 상속합니다. A.vi와 D.vi라는 두 개의 동적 디스패치 VI를 정의합니다. A.vi는 테이블의 첫 번째 항목을 대체합니다.

다이어그램의 동적 디스패치 SubVI 노드는 컴파일될 때 특정 인덱스 번호를 기록합니다. 예를 들어 A.vi의 호출을 나타내는 노드는 인덱스 0을 기록합니다. B.vi 호출을 위한 노드는 인덱스 1을 기록합니다. 런타임에 객체가 와이어에 입력되면 노드는 해당 객체의 동적 디스패치 테이블에 액세스합니다. 기록된 인덱스로 VI를 찾아 호출합니다. 노드는 어떤 SubVI를 호출해야 하는지 알아내기 위해 이름을 조회하거나 목록을 검색할 필요가 없습니다. 상속 트리의 깊이 또는 클래스가 정의하는 동적 디스패치 VI의 수에 관계없이 시간 복잡도는 O(1)입니다.

그 이후부터는 일반적인 SubVI 호출이라고 보면 됩니다. LV는 정적 디스패치 호출과는 달리 동적 디스패치 호출 경계에서는 고정성 (메모리 중복)을 최적화할 수 없어 성능이 저하될 수 있습니다. LabVIEW는 가장 오래된 조상의 고정성을 위해 최적화하여 이를 최소화합니다. 즉, B.vi를 호출하면 B.vi의 Parent.lvclass 버전을 사용하여 입력의 복사본이 필요한지 여부를 결정합니다. 대부분의 경우 자식이 대체하는 VI는 동일한 커넥터 구획을 사용하고 부모 VI와 동일한 기능을 제공하므로 동일한 고정성 패턴이 필요한 경향이 있습니다. 이런 고정성 패턴에 맞으면 오버헤드가 SubVI 호출과 동일합니다. 이는 조상의 구현을 직접 호출할 것으로 예상하지 않는 경우에도 (조상을 추상 클래스로 사용) 조상 구현의 출력에 입력을 연결하여 성능 이점을 얻을 수 있음을 의미합니다.

클래스에서 VI 이름을 오버로드할 수 있습니까?
아니요. 오버로딩은 다른 OO 언어에서 이름은 같지만 파라미터 목록은 다른 두 함수를 지원하는 기능입니다. 컴파일러는 함수 호출에 제공된 파라미터를 보고 프로그래머가 호출하려는 함수를 파악합니다. 이 기능은 예를 들어 "Init"라는 함수를 여러 개 만들어 하나는 파일에서 객체를 초기화하고 (따라서 유일한 파라미터로 경로를 사용하고) 다른 하나는 다른 객체에서 초기화 (그 객체를 파라미터로 사용)할 수 있게 합니다.

이 기능은 특히 동적 디스패치와 같이 사용되는 경우 이러한 언어에서 끔찍한 버그의 원인이 되곤 합니다. 누군가 부모 클래스의 파라미터 목록을 변경하면서 자식 클래스의 파라미터 목록 변경을 잊은 경우 컴파일러는 이를 컴파일러 에러가 아닌 두 개의 다른 함수 선언으로 처리합니다. 이런 버그는 모두 런타임에 발생하는데, 그 이유를 정확히 찾기가 매우 어려울 수 있습니다. LabVIEW에서는 어떤 상황에서도 같은 이름을 가진 두 개의 VI가 메모리에 있을 수 없습니다. 디버깅하기 어려운 새로운 종류의 버그를 도입하는 기능인 오버로딩을 추가하기 위해 그 규칙을 변경할 수는 없었습니다.

고급 OO 기능 지원


한 클래스를 다른 클래스의 친구 (friend)로 선언하는 방법이 있습니까?
[LV2009] 예, LabVIEW 2009 및 이후 버전에서는 지원됩니다. 모든 라이브러리 (.lvlib, .lvclass, .xctl, .lvsc)는 친구 VI 또는 친구 라이브러리 목록을 선언할 수 있습니다. VI를 친구로 선언함으로써 라이브러리는 다른 VI에 특별한 권한을 부여하며, 이 권한이 무엇인지는 잠시 후에 논의하겠습니다. 이 라이브러리는 다른 라이브러리를 친구로 선언해 그 라이브러리의 모든 멤버 VI에 특별 권한을 부여합니다. 

이 특별 권한은 커뮤니티 스코프 멤버 VI를 호출할 수 있는 권한입니다. 라이브러리 X에 멤버 VI로 A.vi, B.vi와 C.vi가 있다고 가정합시다. A.vi의 스코프는 프라이빗입니다. 즉, X의 멤버 VI만 A.vi를 호출할 수 있다는 뜻입니다. B.vi는 퍼블릭 스코프이므로 모든 VI가 호출할 수 있습니다. 그러나 C.vi는 커뮤니티 스코프입니다. 이는 라이브러리 X의 멤버와 라이브러리 X가 아니면서 X의 친구인 라이브러리의 VI가 호출할 수 있다는 뜻입니다.

이 개념에 익숙하지 않은 사람들은 다른 클래스를 한 클래스의 "친구"로 선언하면 그 다른 클래스에게 이 클래스의 비공개 부분에 액세스할 수 있는 권한이 부여된다고 이해하면 됩니다. "친구" 구문은 일부 API에서 중요한 역할을 합니다. 고전적인 예로 Matrix 클래스와 Vector 클래스입니다. 행렬에 벡터를 곱하는 함수는 두 클래스 내부의 비공개 부분에 접근해야 합니다.

우리가 새운 "커뮤니티" 스코프를 만든 이유는 무엇일까요?
[LV2009] "친구" 개념을 포함하는 다른 프로그래밍 언어는 일반적으로 친구 클래스에게 모든 비공개 부분에 접근할 수 있는 폭넓은 접근 권한을 제공합니다. 친구 클래스에게 모든 프라이빗 메소드 및 데이터에 대한 접근 권한을 부여하면 정의된 VI를 거치지 않고 데이터를 변경할 수 있는 백도어가 너무 많이 열립니다. 프라이빗 메소드에만 접근 권한을 부여하는 것조차 거의 항상 불필요하게 큰 권한입니다. 대부분의 경우 친구는 특정 메소드에만 접근하면 됩니다. 커뮤니티 스코프 덕분에 라이브러리의 비공개 부분을 외부 의존성을 깨지 않고 자유롭게 변경할 수 있습니다. 또한 커뮤니티 스코프 VI 목록에는 주어진 VI 또는 라이브러리가 친구로 명명된 이유에 대해 더 나은 기록이 제공됩니다. 여러 프로그래밍 언어에서, 한 요소가 다른 요소의 친구로 설정된 후 그 우정이 왜 필요한지 아무도 기억하지 못하는 경우가 너무 많습니다. 

자손 클래스가 커뮤니티 스코프 VI에 접근할 수 없는 이유는 무엇입니까?
[LV2009] 커뮤니티 스코프의 VI는 소유 라이브러리와 그 라이브러리의 친구가 호출할 수 있습니다. 라이브러리가 클래스 (프로젝트 라이브러리, XControl 또는 StateChart가 아님)인 경우 클래스에는 자손 클래스가 있을 수 있습니다. 자손 클래스의 멤버 VI는 커뮤니티 스코프 VI를 호출할 수 없습니다. 친구와 가족이 동일한 기능을 사용할 수 있다면 좋을 때가 있으며, 이런 차이로 인해 커뮤니티 스코프와 보호 스코프로 두 개의 VI를 작성하기도 합니다.

R&D에서는 이를 버그로 간주하지 않습니다. 그것은 우리가 의도했던 동작입니다. 우리는 "보호됨과 커뮤니티 스코프"를 하나로 결합한 또 다른 스코프를 추가하여 인터페이스를 복잡하게 만들지 않기로 결정했습니다. 그리고 스코프 보호에 허점을 만들지 않고서는 자식 클래스에게 커뮤니티 스코프 VI에 대한 포괄적인 접근을 허용할 수 없었습니다. 친구만 커뮤니티 스코프 VI를 호출할 수 있어야 하는데, 프로그래머가 간단하게 새 자식 VI를 생성하여 퍼블릭 VI에서 커뮤니티 스코프 VI를 호출할 수 있다면 커뮤니티 스코프는 무용지물이 됩니다. 친구 목록은 이 VI를 사용할 수 있는 한정적이고 명시적인 목록입니다. 따라서 해당 VI의 커넥터 구획이 변경되더라도 사용자는 자손 VI 중 어떤 VI가 해당 VI를 사용하는지 걱정할 필요 없이 어떤 호출자 VI를 수정해야 하는지 정확히 알 수 있습니다.

내부 클래스를 만들 수 있습니까?
이 버전에서는 만들 수 없습니다. 클래스 라이브러리는 다른 라이브러리 유형 (일반 라이브러리, XControl 또는 LabVIEW 클래스)을 포함할 수 없습니다. 앞으로는 이것을 가능하게 하고 싶지만 수요에 따라 우선순위가 달라질 것입니다.

프라이빗 상속은 지원합니까?
부모 타입의 모든 컨트롤은 모든 자손 타입의 데이터를 포함할 수 있습니다. 모든 자손 타입의 데이터가 부모 와이어를 따라 흐를 수 있습니다. 이것이 동적 디스패칭이 작동하는 방식의 핵심이며 OO가 각 클래스 타입에 대해 맞춤 단계를 수행하는 제네릭 알고리즘을 작성하는 데 도움이 되는 이유입니다. 개발 과정에서 한 베타 고객은 이것이 강력하게 타입이 지정된 언어로서 LabVIEW의 지위에 해롭다는 피드백을 제공했습니다. 그는 부모 클래스에서 자식 클래스를 상속하지만 자식 클래스가 부모 타입으로 업캐스팅되는 것을 방지할 방법을 원했습니다. 그런 식으로, 자식 와이어를 부모 터미널에 실수로 연결하면 와이어가 끊깁니다.

이는 다른 언어에서 "프라이빗 상속"이라고 부르는 기능을 요청한 것입니다. 프라이빗 상속을 사용하면 클래스는 여전히 부모의 모든 속성을 상속하지만 클래스 외부의 VI는 이 상속에 대해 알지 못하므로 타입을 서로 연결할 수 없습니다. LabVIEW에는 퍼블릭 상속만 있습니다. 프라이빗 상속은 OO 프로그래밍에서 거의 사용되지 않으므로 불필요한 복잡성만 추가합니다. 이를 지원하기 전에는 프라이빗 상속이 우리 언어에서 어떻게 작동할지와 관련된 몇 가지 어려운 질문에 대한 답변이 필요합니다. 부모 클래스에서 상속된 메소드를 호출하려면 때로는 자식 데이터를 부모 터미널에 연결할 수 있어야 합니다. 그러나 그렇게 하면 부모 VI의 프런트패널에서 부모 컨트롤에 자식 데이터가 들어가게 됩니다. 그 부모 컨트롤의 값을 이 클래스에 없는 VI가 프로퍼티 노드를 통해 접근하면 어떻게 되겠습니까? 에러를 반환할까요? 아니면 클래스 외부에서는 아무도 이 부모 자식 관계를 알지 못해야 하는데도 자식 데이터를 반환할까요? 이와 같은 질문이 바로 프라이빗 상속을 다루기 어려운 이유입니다. LabVIEW에 이 기능이 포함될 날은 오지 않을 것입니다.

다중 상속을 지원합니까?
여러 부모 클래스에서 모든 데이터와 동작을 함께 가져오는 다중 상속은 이론적으로는 훌륭하지만 실제로는 해결하는 문제만큼이나 새로운 문제를 일으키는 기능입니다. "A와 B의 모든 속성을 C로 결합하고 싶다"는 말은 이름 충돌 처리 방법, 데이터 결합 방법, 다이아몬드 상속 문제 (D가 B와 C에서 상속하고 B와 C는 A에서 상속) 해결 방법을 고려하기 전까지는 좋아 보입니다. LabVIEW는 다중 상속을 지원하지 않으며 아마도 도입될 일도 절대 없을 것입니다. LabVIEW 2020에서 우리는 클래스와 유사하지만 프라이빗 데이터가 없는 새로운 데이터 타입인 LabVIEW 인터페이스를 추가했습니다. 인터페이스는 다중 상속이 필요한 대부분의 사용 사례에서 쓸 수 있습니다. 인터페이스에 대해 알아보려면 제품 배송 시 포함된 LabVIEW 2020 문서를 참조하십시오. 구현에 대한 자세한 내용은 LabVIEW 인터페이스: 설계 이면의 결정을 참조하십시오.

LabVIEW가 LV로 빌드된 DLL (공유 라이브러리)의 인터페이스에서 LabVIEW 클래스를 금지하는 이유는 무엇입니까?  LabVIEW 객체를 [라이브러리 호출 노드]로 전달할 수 없는 이유는 무엇입니까?
어플리케이션 빌더를 사용하여 DLL (또는 다른 플랫폼의 공유 라이브러리)을 빌드할 때, VI의 커넥터 구획이 LabVIEW 클래스를 사용하는 경우 LabVIEW는 해당 DLL에서 VI 내보내기를 허용하지 않습니다. LabVIEW와 외부 코드 간의 인터페이스는 일반 데이터 타입이어야 합니다.

클래스 인스턴스의 메모리 내 레이아웃을 다시 살펴보면 LabVIEW 고유의 포인터와 할당이 많이 있음을 알 수 있습니다. 다른 EXE 또는 DLL은 해당 데이터 구조를 사용하거나 동적 디스패치를 지원하거나 클래스 스코프를 적용하는 데 필요한 클래스 정보에 접근할 수 없습니다. 마찬가지로 클래스의 C++ 구현도 컴파일러마다 다릅니다. 대부분의 C++ 사용자는 이러한 차이 때문에 DLL 인터페이스에 C++ 클래스를 넣지 않는 것이 좋습니다. OO의 특성으로 인해 한 컴파일러에 가장 적합한 구현 구조라도 다른 컴파일러에는 적합하지 않을 수도 있으므로 상호 작용할 수 있는 경우가 거의 없습니다.

.NET에서 클래스를 가져오거나 내보낼 수 있습니까?
[LV2009] LabVIEW 2009를 사용하면 VI에서 .NET 어셈블리를 생성할 수 있습니다. 이를 위해서는 프로젝트에서 ".NET 어셈블리"에 대한 새 빌드 스펙을 만듭니다. 소스 VI의 일부로 LabVIEW 클래스를 포함할 수 있습니다. 어셈블리 인터페이스에서 이러한 클래스를 반출해 .NET 클래스로 노출할 수 있습니다. 그런 다음 LabVIEW에서 해당 .NET 어셈블리를 사용하려면 클래스는 .NET 참조 번호로 나타날 것이며 다른 여느 참조 번호와 마찬가지로 참조로 작동할 것입니다.

결론


LVOOP가 사용자 VI에 혁명을 일으키고 언젠가는 "LabVIEW의 최고 기능"으로 불릴 수 있겠습니까?
우리 중 누군가는 OO 혁명을 꿈꿉니다. 하지만 솔직히 사실은 모두가 OO를 원하는 건 아닙니다. OO는 LabVIEW가 제공하며 계속 확장되고 있는 도구 세트에 속한 한 가지 도구에 불과합니다. 단일 VI로 빠르게 측정을 수행하는 데 LabVIEW 클래스는 낭비입니다. 그러나 전체 어플리케이션, 심지어 작은 유틸리티에도 객체 지향 프로그래밍은 코드 구성과 유지 보수성을 개선하며 일반적으로 LabVIEW 환경을 보다 쉽게 관리하는 데 도움이 됩니다. 첫 번째 버전은 매끄러운 언어가 아니었지만 고수준의 고객만 고난도의 기능을 사용할 것이라는 점을 알고 있었습니다. 하지만 시간이 지남에 따라 OO 기능이 LabVIEW 개발의 필수 요소가 되는 것을 보았습니다. 다른 프로그래밍 언어에서와 마찬가지로, 첫 번째 프로젝트부터 OO를 사용하는 LabVIEW 사용자도 볼 수 있었습니다.

LVOOP 팀은 여전히 "최고 기능"이 되지 않을까라는 희망을 갖고 있습니다. 여러분의 의견을 기다리겠습니다!

Was this information helpful?

Yes

No