Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4456.html
 

Еще немного о IEnumerable, generics, interfaces

Опубликовано 24.01.2011 г. 18:20

В одной из предыдущих статей упоминалась реализация интерфейса IEnumerable для передачи коллекции параметров в другой модуль. Теперь перейдем к более общей реализации как самой перечисляемой коллекции, так и других интерфейсов.

Предположим, что нам требуется между модулями передавать некоторые коллекции данных, с итератором. Т.е это коллекция, содержащая ссылки на интерфейсы. Мы при этом хотим абстрагироваться от типа содержащихся в ней объектов, и ограничимся тем, что внутри коллекции содержится какой то интерфейсный объект, так что по крайней мере он поддерживает интерфейс IInterface(IUnknown). Нам по прежнему требуется реализовать два класса Enumerable и Enumerator. В основном, на мой взгляд, наиболее встречающаяся в использовании коллекция - TList, поэтому базой для нашего перечисляемого объект будет как раз TList. Т.е смысл такой: исходный модуль содержит некоторый список объектов TList, содержащий ссылки на интерфейсы, далее мы хотим передать этот список, например, в вызывающую программу для просмотра, в виде IEnumerable. Здесь к нам на помощь придут обобщенные описания, generics. Если в прошлый раз мы использовали IEnumerable<IConfigOption>, то сейчас мы сделаем лишь одно ограничение, объект коллекции должен поддерживать базовый интерфейс IInterface, чего мы можем добиться описанием реализующего класса в виде TClassName<I : IInterface>. Следовательно описания классов коллекции и итератора будут иметь следующий вид:
    TListEnumerator<I : IInterface> = class(TInterfacedObject, IEnumerator<I>)
      strict private
        FIndex : integer;
        FList : TList<I>
        function getCurrentItem() : I;
      public
        constructor Create(var aList : TList<I>);
        function getCurrent : TObject;
        function IEnumerator<I>.getCurrent = getCurrentItem;
        function MoveNext : Boolean;
        procedure Reset;
        property Current : I read getCurrentItem;
    end;

    TListEnumerable<I : IInterface> = class(TInterfacedObject, IEnumerable<I>)
      strict private
        FList : TList<I>;
        function getListEnumerator() : IEnumerator<I>;
      public
        constructor Create(var aList : TList<I>);
        function getEnumerator : IEnumerator;
        function IEnumerable<I>.getEnumerator = getListEnumerator;
    end;
Параметром конструктора является исходный список. Т.е предполагается следующее использование: имеем в модуле список myList : TList<IMyTestIntf>, итератор для которого мы хотим экспортировать в другой модуль, чего можем добиться возвратив объект TListEnumerable<IMyTestIntf>.Create(myList) as IEnumerable<IMyTestIntf>; Подобная коллекция имеет существенные недостатки, навигация возможна только в одном направлении. В обычных условиях гораздо удобней иметь индексированный доступ к элементам коллекции. Но при этом допустим нам необходимо экспортировать коллекцию, с учетом того, что вызывающий модуль не сможет ее пополнить или удалить элементы (коллекция только для чтения). Следовательно интерфейс описывающий нашу коллекцию, должен иметь всего два метода: getCount & getItem:
    IReadonlyList<T> = interface
        ['{0448F5B0-9D16-4880-8BB6-9B752EE3F4E3}']
        function getCount() : integer; safecall;
        function getItem(itemIndex : integer) : T; safecall;

        property count  : integer read getCount;
        property items[ itemIndex: integer]: T read getItem;
    end;
Реализация объекта поддерживающего данный интерфейс опять же будет построена на использовании списка TList. Причем тип T должен поддерживать интерфейс IInterface. Описание класса выглядит следующим образом:
    TReadonlyList<I : IInterface> = class(TInterfacedObject, IReadonlyList<I>)
      strict private
        FList : TList<I>;
      public
        constructor Create(var aList : TList<I>);

        function getCount() : integer; safecall;
        function getItem(itemIndex : integer) : I; safecall;
        property count  : integer read getCount;
        property items[ itemIndex: integer] : I read getItem;
    end;
И реализация:
constructor TReadonlyList<I>.Create(var aList: TList<I>);
begin
    inherited Create();
    FList := aList;
end;

function TReadonlyList<I>.getCount: integer;
begin
    result := 0;
    if FList <> nil then
        result := FList.count;
end;

function TReadonlyList<I>.getItem(itemIndex: integer): I;
begin
    if ItemIndex < self.count then  result := FList[itemIndex]
    else result := Default(I);  //  I(nil^);
end;
Для чего это: допустим в библиотеке имеется некоторый менеджер модулей, модули сами являются реализацией некоторого интерфейса, скажем IModule. Т.е менеджер содержит список модулей: FModuleList : TList<IModule> который где то на этапе запуска заполняется. Вызывающая программа, хочет построить главное меню программы, и для этого опрашивает каждый модуль, для выяснения требуется ли ему, например, добавить элемент в главное меню. Следовательно вызывающая программа должна получить в свое распоряжение этот самый список модулей, но здесь желательно ограничить возможности действий с этим списком. Т.е оставить лишь для удобства индексированный доступ, без возможности, например, добавления элементов в коллекцию.
Метки:  IEnumerable  |  generics  |  interface 

Комментарии

Denis
26.11.2012 в 18:56
ОО)) как удачно) опять порадовал доступным изложением и краткостью. а то реализаций море, изза большого количества теряется смысл. Спасибо
en
31.01.2018 в 14:39
Driven that that sublingual reviews for men not not not.
generic
12.03.2018 в 09:59
Does work for someone who doesn't have ED?
James
25.04.2018 в 10:26
Greetings! Very useful advice within this article! It is the little changes that make the most important changes. Many thanks for sharing!
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно