Jump to content
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr ×
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr
  • [DelphiCon 요약] Spring4D 소개 - 델파이 개발을 한수준 높이기


    Kori
     공유하기

    << DelphiCon 2020 목록으로 이동

    DelphiCon 의 2020 시리즈 중, Introduction to Spring4D - Taking Delphi Development to the Next Level - Stefan Glienke 의 한글 요약본입니다.  

    • 오픈 소스인 스프링4D를 사용하면, 코드는 더 짧아지고, 일관성과 품질은 더 높아집니다.
    • 예를 들어,
      • 스마트 포인터를 사용하면 오브젝트를 만들어 쓸 때 메모리 누수를 걱정할 필요가 없습니다.
      • Spring4D의 컬렉션은 컬렉션이나 리스트 관리가 매우 간편합니다.
    • 발표자 (Stefan Glienke)는 델파이 경력 20년이 넘은 개발자이며, Spring4D 오픈소소의 개발 책임자입니다.
    • 원본 비디오에는 아래 요약보다 자세한 데모와 설명이 있습니다. 

    목차


    Spring4D 란

    • 델파이 개발을 더 쉽고 더 견고하게 할 수 있도록 미리 구성된 오픈소스 라이브러리
      • 2010년에 출시된 XE와 그 이후 버전에서 사용 가능)
      • 상업용 무료 사용 (아파치 2.0 오픈 소스 라이선스)
      • 델파이 RTL 확장
      • 제네릭스(Generics)와 RTTI를 적극 활용
      • 계속 발전하는 중이고 상업용 소프트웨어 개발에 활용되고 있음
    • 원칙: “골라 쓰기” - 원하는 것만 골라서 사용할 수 있다. 강제하지 않는다.
    • 버전: 2020년 12월 현재 1.2.4 - 곧 2.0 발표 예정

    Spring4D를 받는 방법

    • Git에서 내려받기 (버전콘트롤을 사용하지 않는다면 전체 파일 다운로드): https://bitbucket.org/sglienke/spring4d.git 
    • Build.exe 를 실행하여 간편하게 설치/설정 가능 (또는 전체 파일을 직접 컴파일 하여 사용하기)
    • 코드를 모두 받아서 컴파일하여 사용할 수 있지만, 미리 컴파일된 .dcu를 사용하는 것이 편함 (단 몇초지만 매번 컴파일하는 시간도 아끼자)

    Spring4D의 기본 구성

    • Base: RTL 확장, 컬렉션
    • Core: 의존성 주입(DI, Dependency Injection) 컨테이너, 인터셉션/목킹(Mocking)
    • Data: ObjectDataSet을 이용해 (TDBGrid 등) 데이터를 인식하는 UI 콘트롤에 연결
    • Persistence: ORM
    • Extensions: 암호화 및 기타 유틸리티 (암호화는 다른 암호화 전문 라이브러리보다 약함)

    Spring4D 구성 요소: Spring.Base

    • Nullable<T>
    • Event<T> 멀티 캐스트 이벤트
    • 스마트 포인터 (Shared<T> 와 Weak<T>)
    • Collections: 가장 많이 활용되는 라이브러리
    • IEnumerable<T>
    • 리스트, 딕셔너리, 멀티맵, 세트, 큐

    Nullable<T>

    • 특정 데이터 타입을 유지하면서도, Null값을 가질 수 있도록 한다.
    • 이제는 억지값 즉, Null (값이 없음)을 넣기 위해 아래와 같은 값이나 코드가 필요가 없다.
      • 날짜 타입에 “날짜가 주어지지 않음”을 넣기 위해 -40000 이라는 가상의 값 넣기
      • Boolean에 “참인지 거짓인지 모름”을 넣기 위해 True/False/FileNotFound로 Inum을 직접 만들어 사용하기
    • Variant 타입과는 다름
      • Variant는 호환 가능한 타입 변환을 암시적으로 수행하므로 언제든 다른 타입으로 변환될 여지가 있다(type unsafe).
        • 예를 들어 Variant에는 숫자 3과 문자열 ‘3’을 모두 넣을 수 있고, 상황에 맞게 반영된다.
      • Nullable<T>는 타입을 유지하기 때문에, 정해진 타입만 담을 수 있다(type safe).
        • Nullable<Integer>에는 정수 3만 넣을 수 있고 문자열 3을 넣을 수는 없다.
    // [Nullable<T> 예문]
    
    uses
      Spring;
    procedure 
    var
      n, n2: Nullable<TDateTime>; // Spring 유닛 필요
      d: TDateTime;
    begin
      d := now;
      Log.Lines.Add(n.HasValue.ToString); //[결과: False]: 디펄트에서 Nullable.HasValue의 결과는 False
      Log.Lines.Add(n.ToString); //[결과: Null]:디펄트에서 Nullable.ToString의 결과는 ‘Null’(값없음)
    
      n := d;
      Log.Lines.Add(n.HasValue.ToString); //[결과: True]: 특정 시간을 넣으면, Nullable.HasValue의 결과는 True
      Log.Lines.Add(n.ToString); //[결과: 19.11.2020 18:11:51]: 독일 기준 날짜 형식 (발표자의 시간대가 독일로 되어 있음)
    
      n := nil; //n에 들어있는 값을 없앤다
      Log.Lines.Add(n.HasValue.ToString); //[결과: False]: nil을 넣으면, Nullable.HasValue의 결과는 False
      Log.Lines.Add(n.GetValueOrDefault().ToString); //[결과: 30.12.1899] // Nullable.GetValueOrDefault().ToString의 결과는 값이 있으면 그 값을, 없으면 기본값인 30.12.1899을 출력
    
      Log.Lines.Add((n=n2).ToString); //[결과: True]: n과 n2는 둘다 값이 없으므로, n과 n2를 비교하면 ‘같음’이 된다
      n := d;
      Log.Lines.Add((n=n2).ToString); //[결과: False]: n에만 d를 넣으면, n과 n2를 비교하면 ‘다름’이 된다
      n2 := d;
      Log.Lines.Add((n=n2).ToString); //[결과: True]: n과 n2는 둘다 d와 같은 값이므로, n과 n2를 비교하면 ‘같음’이 된다.
    end;

     

    Event<T>

    • (필요한 타입을 따로 만들 필요가 없어서 쉽게 사용되는) Observer 패턴과 유사하다.
    • publish와 Subscribe를 구현하기가 간편하하다.
    • 델파이에서 일반 이벤트 사용하는 것과 방식이 같다.
    • 제네릭 타입을 사용하므로 파라미터와 리턴 타입이 유연하다
      • <T>에 (TNotifyEvent 등) 원하는 타입을 지정하면 된다.
    • 빠르고 쓰레드에 안전하다
    // [Event<T> 멀티 캐스트 이벤트 예문]
    uses
      Spring;
    
    private fOnMouseMove: Event<TMouseMoveEvent>; // 델파이 기본 이벤트에서 마우스 상태값을 받아온다
    
    // Event<TMouseMoveEvent>를 구독(subscribe)하는 함수
    procedure 
    var
      subscriber: TEventSubscriber; //구독자 변수
    begin
      subscriber := TEventSubscriber.Create(Self); //구독자 생성
      fOnMouseMove.Add(subscriber.HandleMouseMove); //구독자가 잡은 마우스 이동 이벤트를 넣는다 (fOnMouseMove는 실제로 이벤트핸들러들의 목록이므로 Add 메소드를 가지도록 되어있다. 그 결과 한번 작동하면 목록에 있는 모든 이벤트핸들러가 작동한다.)
      subscriber.OnUpdate.Add(ChangeCaption); //구독자의 OnUpdate이벤트 발생시 실행할 이벤트핸들러 지정 (좌표값을 폼의 캡션에 찍도록 함)
      subscriber.OnUpdate.Add(ChangeColor); // (마우스 Y좌표가 300이상이면 빨강으로 바꾸고 아니면, 일반 윈도우 색을 유지하도록 함)
      Log.OnMouseMove := fOnMouseMove; //TMomo인 Log의 OnMouseMove 이벤트에 Event<TMouseMoveEvent>를 연결하여 포함된 이벤트핸들러가 모두 실행되도록 한다.
      fOnMouseMove.OnChange := NotifyEventChange; //Event<T>에 이벤트핸들러가 추가/삭제 되면 공지한다. (예를 들어 이 예문에서 폼을 종료하면, 이 이벤트가 작동된다. 그 이유는 TEventSubscriber.Create(Self)로 생성되었고, Self는 MainForm였다. 따라서 TEventSubscriber의 owner 역시 메인폼이 된다. 메인 폼이 제거되면, 이 구독자 역시 제거되고 등록된 이벤트핸들러 역시 제거된다.)
    end;

     

    스마트 포인터 (Shared<T> 와 Weak<T>)

    • 오브젝트와 기타 리소스의 생명주기 관리 능력 추가
    • try finally 를 사용하여 명시적으로 오브젝트를 제거하지 않아도 된다.
      • 이점은 여러 리소스 간에 오브젝트를 주고 받는 것 역시 간편하게 해준다.(어느 곳에서 제거해야 하는지를 걱정할 필요가 없다)
    • Weak<T>에서는 순환 참조 문제가 없다 (심지어 델파이에 [Weak] 속성이 추가되기 전부터 있었다)
    • 보너스: Weak<T>로 만든 오브젝트는 모든 플랫폼에서 작동한다. 또한 오브젝트가 제거될 때 알림을 받을 수 있다
    // [스마트 포인터 (Shared<T> 와 Weak<T>) 예문]
    
    // Shared<T>: 레코드 타입으로 사용할 때
    procedure 
    var
      sl: Shared<TStringList>;
    begin
      sl := TStringList.Create; // 연산자 오버로딩
      sl.Value.Add(‘델파이콘’); // Value 프로퍼티를 사용하여 접근한다 (멤버 변수에 직접 접근 불가) 
      Log.Lines.AddStrings(sl); // <T>에서 지정한 타입을 그냥 전달하는 것과 같은 방식으로 전달될 수 있다.
    end;
    
    // IShared<T>: 인터페이스 타입으로 사용할 때
    procedure 
    var
      sl: IShared<TStringList>; // 인터페이스 타입으로 사용할 때
    begin
      sl := Shared.Make<TStringList>(TStringList.Create); // 직접 생성하지 않고 인터페이스에 맞추어야 한다.
      sl.Add(‘델파이콘’); // (이 인터페이스는 익명메소드처럼 지정된 타입을 반환하기 때문에) 멤버에 직접 액세스할 수 있다.
      Log.Lines.AddStrings(sl); //<T>에서 지정한 타입을 그냥 전달하는 것과 같은 방식으로 전달될 수 있다.
    end;
    
    // IShared<T>로 생성된 오브젝트를 직접 제거하고 싶으면, 파라미터로 익명메소드를 넣는다.
    procedure 
    var
      sl: IShared<TStrings>;
    begin
      sl := Shared.Make<TStrings>(TStringList.Create,
        procedure(const s: TStrings)
        begin
          Log.Lines.AddStrings(sl); //TMemo인 Log에 ‘델파이콘’을 출력하고
          s.Free; //생성된 TStrings를 제거
        end);
      sl.Add(‘델파이콘’);
    end;
    
    // 스마트 포인터로 만든 오브젝트는 오브젝트 제거 코드를 쓰지 않아도. 자동 제거되므로 메모리 누수를 염려하지 않아도 된다.
    // ReportMemoryLeakOnShutdown := True; //델파이 프로젝트에서 메모리 누수를 확인하는 코드
    
    // Weak<T>
    procedure 
    var
      sl: TStringList; //Shared<T>가 아닌 일반 TStringList
      weakRef: Weak<TStrings>; //문자열을 Weak 참조하기로 한다.
    begin
      sl := TStringList.Create;
      weakRef := sl; // 연산자 오버로딩
      sl.Add(‘델파이콘’);
    
      Log.Lines.Add(weakRef.IsAlive.ToString); //[결과: True]: 참조 타겟이 살아있다
      Log.Lines.AddStrings(weakRef); //[결과: 델파이콘]: 참조 대상의 값, 연산자 오버로딩
      Log.Lines.AddParagraph;
    
      sl.Free; //Share<T> 가 아니므로 직접 Free 했다. 그러면, Weak 참조하고 있는 곳에 모두 공지된다.
    
      // Weak<T>는 모든 플랫폼에서 작동된다.
      Log.Lines.Add(weakRef.IsAlive.ToString); //[결과: False]: 참조 대상이 제거되었음’을 알고 있음
      Log.Lines.Add(Assigned(weakRef.Target).ToString); //[결과: False]: 참조 대상이 Assigned 되지 않았음’을 알고 있음, Weak<T>.Target와 Shared<T>.Value는 같은 역할
      Log.Lines.Add(weakRef <> nil).ToString;  //[결과: False]: 참조 대상이 nil 임’을 알고 있음
    end;

     

    Collections: 가장 많이 활용되는 라이브러리

    • 인터페이스 기반이다.
      • 메모리 관리가 매우 쉽다
      • Collection를 함수에서 반환하고, 어느 곳에서든 사용하기만 하면 된다. 사용이 끝나면 자동 제거 된다.
    • IEnumberable<T>를 사용하여 API가 확장되었다.
      • 명확하게 표현되는 코드를 사용하도록 메소드가 제공된다.
      • 성가시게 루프를 사용할 필요가 적어진다.
    • Systems.Generics.Collections 대체한다.
      • 100%는 아니지만 왠만한 건 다 있고 델파이 컬렛션을 Spring.Collections로 마이그래이션도 쉽다.
    • TCollections와 IEnumberable에서 항상 팩토리 메소드를 사용한다.
      • (델파이 컬렉션과 달리) 클래스를 직접 사용하면 안된다. 제공되는 팩토리 메소드를 사용해야 한다.
      • Spring.Collection.* 유닛을 uses에 추가하면 해당 컬렉션을 사용할 수 있다.

    Collections 인터페이스

    • IEnumberable<T>
      • (정렬되지 않은) 항목들의 나열을 제공하는 기반 타입
    • ICollection<T>, IReadOnlyCollection<T>
      • (인덱스 번호가 지정되지 않은) 항목들의 컬렉션, List의 기반 타입
    • IList<T>, IReadOnlyList<T>
      • (인덱스 기반의 메소드가 제공되는) 리스트
    • IMap<TKey, TValue>, IReadOnlyMap<TKey, TValue>
      • Dictionary와 MultiMap의 기반 타입
    • IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>
    • IMultiMap<TKey, TValue>, IReadOnlyMultiMap<TKey, TValue>
    • ISet<T>
      • MultiSet의 기반 타입
    • IMultiSet<T>, IReadOnlyMultiSet<T>

     

    읽기전용 vs 불변

    • Spring4D에는 불변 컬렉션이 없다. (추가/변경/삭제가 해당 컬렉션 안에서 발생한다)
    • 하지만, 모든 타입은 ReadOnly 버전이 제공된다. (AsReadOnly 메소드 사용), 멀티쓰레드에 안전하지 않지만, 컬렉션을 사용하는 곳에서 추가/변경/삭제를 할 수 없다.
    • 컬렉션을 읽기 전용 목적으로 전달할 때에는 IEnumberable<T> 또는 알맞은 ReadOnly 컬렉션을 사용하자.

    IEnumberable<T>

    • 정렬되지 않고, 실현되지 않은(not materialized) 항목들을 열거할 수 있는 기반 타입
    • 모든 컬렉션 타입의 기반
    • 사용할 수 있는 메소드가 매우 많음
    • ‘실현되지 않은 (not materialized)’ 이란?
      • 쿼리문 이라고 생각하면 됨 (Where절에 명시한 조건에 따라 가져올 항목이 결정되는 SQL구문과 유사)
      • SQL문이 실행되어야 해당 데이터 집합이 생기는 것과 같은 방식 (실행이 되고 난 후에야 해당 조건에 해당하는 컬렉션이 생겨난 다)
      • 물론 어딘가에는 전체 데이터가 있겠지만, 내가 원하는 컬렉션 세트는 조건이 실행되고 난 후에야 생긴다.
    // [IEnumberable<T>예문 (주문 총액이 9,000 이상인 고객 목록)]
    customers.Where (
      function (const c : TCustomer): Boolean
      begin
        Result := c.OrderTotal > 9000;
      end);

    스트리밍과 지연 실행

    • IEnumberable<T>의 작동은 최대한 늦게 실행된다
      • 예를 들어,
      • customer.Where(조건).Take(10);
      • 에서 조건에 해당하는 대상을 모두 구하고 나서 거기에서 10개를 뽑는 것이 아니라, 대상 10개를 얻을 때까지만 조건이 작동한다. 
    • 즉, 작동이 되고 나서야 컬렉션이 만들어진다.

    ICollection<T>

    • 정렬되지 않은 항목들의 (변경 가능한) 컬렉션
      • Add / Remove / Extract 메소드 제공
    • IEnumberable<T>와 달리 언제나 항목을 가지고 있는 컬렉션
    • OnChange 이벤트가 제공되므로, 어떠한 변경도 공지할 수 있음

    IList<T>

    • 정렬된 항목들의 (변경 가능한) 리스트
    • 인덱스를 통해 각 항목에 접근 가능
    • 정렬 기반 기능 제공 (Insert, Delete, IndexOf, Sort,…)
    // [IEnumberable<T> 예문]
    
    procedure 
    var
      numbers: IEnumberable<Integer>;
      i: Integer; 
      oddNumbers: IEnumberable<Integer>;
    begin
      numbers := TEnumberable.Range(1,10);
      for i in numbers do
        Log.Lines.Add(i.ToString); // 1˜10까지 숫자 출력
        Log.Lines.AddParagraph;
    
      oddNumbers := numbers.Where(
        function(const n: Integer): Boolean
        begin
          Result := Odd(n);
        end);
      for i in oddNumbers do
        Log.Lines.Add(i.ToString); // 1~10 중 홀수만 출력
        Log.Lines.AddParagraph;
        Log.Lines.Add(oddNumbers.Last.ToString); // 9 출력
    end;
    
    // [IList<T> 예문]
    
    // IList<T>에는 인덱스 위치에 삽입이 가능하다.
    procedure 
    var
      list: IList<Integer>;
      i: Integer;
      oddNumbers: IEnumberable<Integer>;
    begin
      list := TCollection.CreateList<Integer>([4,5,6]);
      list.AddRange([7,8,9]);
      list.InsertRange(0, [1,2,3]);
    
      for i in list do
        Log.Lines.Add(i.ToString); // 1˜9까지 순서대로 숫자가 출력
        Log.Lines.AddParagraph;
    
      oddNumbers := list.Where(
        function(const n: Integer): Boolean
        begin
          Result := Odd(n);
        end);
    
      Log.Lines.Add(’숫자 11까지’);
      list.Add(11); // 뒤늦게 숫자 11을 List에 추가
      for i in oddNumbers do // oddNumbers는 정의된 조건에 맞는 컬렉션을 이 시점에서 다시 제공
        Log.Lines.Add(i.ToString); // 1~11까지 중 홀수만 출력 (11도 포함된다)
        Log.Lines.AddParagraph;
    end;

     

    IMap, IDictionary, IMultiMap

    • IMap은 IDictionary와 IMultiMap의 기반 타입이다
      • Add, Remove, ContainKey 등등의 메소드가 제공된다.
    • IDictionary는 ‘키-값’으로 구성된 쌍들이 모여있는 컬렉션 (키는 고유해야 함)
    • IMultiMap는 일종의 딕셔너리이며 각 값에 리스트가 들어간다 (즉, 키 하나에 여러 항목이 들어갈 수 있다)
      • 값에 들어 있는 컬렉션을 다룰 수 있는 여러 가지 기능이 제공된다.
    // [IMultiMap<TKey, TValue> 예문]
    
    // IMultiMap<TKey, TValue> 에는 키 하나에 여러 값이 들어갈 수 있다.
    procedure 
    var
      words: IMultiMap<Integer, string>;
      s: string;
      i: Integer;
      oddNumbers: IEnumberable<Integer>;
    begin
      words := TCollection.CreateMap<Integer, string>;
    
      for s in LorenIpsum do // LorenIpsum은 저자가 만든 무작위 글자 생성기
        words.Add(s.Length, s); //키에는 단어 수를, 값에는 해당 단어를 넣어 추가한다.
    
      for i in words.Keys.Ordered do
      begin
        Log.Lines.Add(‘글자 길이가 %d자인 단어:’, [i]);
        for s in words[i] do
          Log.Lines.Add(s);
      end;
    end;
    
    {
    [결과 (및 해설)]
    
    // 키(글자 수)는 정렬되었지만
    // 값(해당 글자)는 정렬되지 않고, 중복되기도 한다.
    글자 길이가 2자인 단어:
    ut
    et
    At
    et
    et
    
    글자 길이가 3자인 단어:
    sit
    sed
    sed
    eos
    
    글자 길이가  
    
    // words := TCollection.CreateMap<Integer, string>; 대신
    // words := TCollection.CreateHashMap<Integer, string>;을 사용하면
    // 값(해당 글자)의 중복이 제거된다.
    // 중복된 값은 받지 않기 때문이다.
    글자 길이가 2자인 단어:
    ut
    et
    At
    
    글자 길이가 3자인 단어:
    sit
    sed
    eos
    
    글자 길이가 
    
    
    // 이 때, 값을 출력하는 루프에서 값도 정렬하고 싶다면,
    // for s in words[i] do를
    // for s in words[i].Ordered do로 변경하면 된다 
    // 다른 방법으로는 (더 간단하고 성능이 좋은 방법으로는),
    // words := TCollection.CreateTreeMap<Integer, string>;을 사용하면
    // Ordered 없이도 값(해당 글자)의 중복이 제거될 뿐만 아니라 값도 정렬된다.
    글자 길이가 2자인 단어:
    At
    ea
    et
    no
    ut
    
    글자 길이가 3자인 단어:
    duo
    eos
    est
    sea
    sed
    sit
    
    글자 길이가 
    

     

    ISet, IMultiSet

    • Enum 세트와 유사하다. 하지만 enum 이외의 어떤 타입도 가능하다
    • 고유한 항목들로만 구성된 컬렉션이다 (마치 딕셔너리에서 값이 없는 키들의 집합과 같다.)
      • 내부적으로 hashtable 또는 tree를 통해 구현된다.
        • HashTable은 델파이 해시테이블과 달리 순서에 맞게 삽입한다.
        • Tree는 비교 기준에 맞추어 알맞은 순서로 저장한다.
    • IMultiSet은 Dictionary<T, Integer>이며 숫자 즉 갯수는 추가/삭제에 따라 증가/감소한다.
      • 갯수 파악이 매우 쉽다.
    // [IMultiSet 예문]
    
    procedure 
    var
      wordCounts: IMultiSet<string>;
    begin
      wordCounts := TCollection.CreateMultiSet<string>(LorenIpsum); //LorenIpsum은 문자열 배열을 반환
    
      for var entry s in wordCounts.Entries do
        Log.Lines.Add(‘%s %d, [entry.Item, entry.Count]);
      end;
    end;
    
    {
    [결과 (및 해설)]
    
    // 각 항목 별 갯수가 표시된다
    Lorem 4
    ipsum 4
    
    elitr 2
    sed 4
    
    labore 2
    et 8
    
    // 만약 글자수 순으로 정렬되도록 하려면
    // for var entry s in wordCounts.Entries do를
    // for var entry s in wordCounts.OrderedByCount.Entries do 로 바꾸면 된다.
    et 8
    Lorem 4
    ipsum 4
    dolor 4
    sit 4
    amet 4
    
    consetetur 2
    sadipscing 2 

     

    더 많은 내용

    • 오픈 소스 코드 살펴보기 (유닛 테스트 코드 역시 많이 있다)
    • 코드에는 xml 문서도 많다.
    • 도움말과 시작하기 문서
    • 질문 또는 의견: https://groups.google.com/g/spring4d
    • 버그 리포트: https://bitbucket.org/sglienke/spring4d/issues

     

     

    << DelphiCon 2020 목록으로 이동

     공유하기


    User Feedback

    Recommended Comments

    There are no comments to display.


×
×
  • Create New...

중요한 정보

이용약관 개인정보보호정책 이용규칙 이 사이트가 더 잘 작동하기 위해 방문자의 컴퓨터에 쿠키가 배치됩니다. 쿠키 설정 변경에서 원하는 설정을 할 수 있습니다. 변경하지 않으면 쿠키를 허용하는 것으로 이해합니다.