2009-03-30 1 views
4

При разработке библиотек я часто прибегаю к следующему шаблону, который мне не нравится, так как это приводит к большому количеству тиков.
Избегайте кастинга при передаче объектов через библиотечный код (в Delphi)

Основная картина:
код с использованием библиотеки руки объект в библиотеке, библиотека затем передает объект обратно в вызывающий код. Вызывающий код вынужден выдать объект, так как библиотека возвращает общий тип. (Урезанный пример кода ниже)

Библиотека определяет следующие объекты и функцию:

TThing = Class 
End; 

TThingProcessor = Class 
Public 
    Function CreateThing : TThing; Virtual; Abstract; 
    Procedure ProcessThing (Thing : TThing); Virtual; Abstract; 
End; 

Procedure DoEverything (Processor : TThingProcessor); 

Вызывающий код затем использует библиотеку путем переопределения объектов и вызова DoEverything, следующим образом: -

TMyThing = Class(TThing) 
Public 
    X : Integer; 
End; 

TMyThingProcessor = Class(TThingProcessor) 
Public 
    XSum : Integer; 

    Function CreateThing : TThing; Override; 
    Procedure ProcessThing (Thing : TThing); Override; 
End; 

Function TMyThingProcessor.CreateThing : TThing; 
Begin 
    Result := TMyThing.Create; 
End; 

Procedure TMyThingProcessor.ProcessThing (Thing : TThing); 
Begin 
    XSum := XSum + (Thing As TMyThing).X; 
    //Here is the problem, the caller is forced to cast to do anything 
End; 

Класс процессора также является фабрикой TThing. Библиотека гарантирует, что она будет передавать только TThings в соответствующий твингпроцессор, который их создал, поэтому он работает, но не безопасен для типов. Хотя приведенный выше код немного глуп в том, что он на самом деле ничего не делает, он показывает, почему ProcessThing нельзя просто переключить на TThing и быть полиморфным - переменная XSum нуждается в обновлении.

Как я могу реструктурировать код, чтобы сделать бросок ненужным? Мне нужно сохранить код библиотеки отдельно, но иметь возможность принимать любой тип.

Edit: Изменена жесткие преобразованный в литом из-за предложения, так что, по крайней мере, два исключения вместо аварии в случае несогласованных типов

+0

Я думаю, что http://stackoverflow.com/questions/681522/casting-between-parent-and-child-classes-in-delphi "может повлиять на то, что вы пытаетесь сделать – RobS

+0

Спасибо за link, было полезно читать.Я не думаю, что это применимо напрямую, но думать о реструктуризации подобным образом - это бросать идеи для меня, хотя я ничего не смог сфокусировать. – David

ответ

3

Вы используете Delphi 2009? Это очень полезно для дженериков. Измените свои объявления на:

TThingProcessor<T: TThing> = Class 
Public 
    Function CreateThing : T; Virtual; Abstract; 
    Procedure ProcessThing (Thing : T); Virtual; Abstract; 
End; 


TMyThingProcessor = Class(TThingProcessor<TMyThing>) 
Public 
    XSum : Integer; 

    Function CreateThing : TMyThing; Override; 
    Procedure ProcessThing (Thing : TMyThing); Override; 
End; 

Больше нет литья.

+0

У меня нет D2009, чтобы попробовать, но мне нравится внешний вид этого. Означает ли это, что библиотека должна быть распределена как исходный код (например, не может создать DLL)? Также я предполагаю, что DoEverything тоже должен быть общим - будет ли компилятор создавать новую версию для каждого типа? – David

+0

Вам не нужно распространять исходный код. DCU, безусловно, будут работать. Пакеты могут (не пробовались). Обычные DLL не будут (не могут совместно использовать даже не общие объекты с простой DLL). Я не могу сказать о DoEverything, не видя реализации. –

+0

Я думал, что DoEverything должен быть общим только из его объявления, потому что если он был объявлен как принимающий TThingProcessor , тогда он не будет принимать классы, полученные от TThingProcessor . Это мышление исходит из шаблонов C++, работают ли деликатные функции Delphi по-разному? – David

3

Вы должны перестроить свой код. Если вы делаете много создания «TThing», тогда у него должен быть класс предков с методом обработки IRS, объявленным как виртуальный, тогда вы просто работаете с предком. Как правило, всегда можно определить общий «базовый класс», когда вы делаете подобные вызовы для очень разных классов. Если вы не можете создать такую ​​структуру классов, используйте интерфейс, чтобы определить свои требования к обработке и подключите этот интерфейс к вашему TThing.

В крайнем случае, когда вы не можете избежать слепок, используйте следующее для удобства чтения и отладки ...

procedure Something(ASender : TObject); 
var 
    MyThing : TMyThing; 
begin 
    MyThing := ASender as TMyThing; 
    MyTHing.DoSomething; 
    MyThing.DoSomethingElse; 
end; 
+0

Мне обычно нужны все TThings для доступа к некоторым общим данным, хранящимся в TThingProcessor (в этом случае XSum), поэтому я не могу поместить функцию процесса в TThing. В общем, любое дополнение к TThing (например, прикрепление интерфейса) делает библиотеку менее общей - чего я хочу избежать. – David

+0

Другой способ сделать это - использовать «абсолютный» var MyThing: TMyThing absolute Sender; начните, если отправитель является TMyThing, тогда .... –

1

Если TMyThingProcessor будет принимать только объекты TMyThing и основные объекты TThing не будет работать, то не пытайтесь делать это полиморфно. Объявите ProcessThing с помощью повторно; вместо переопределить; директива.

Кроме того, если у вас есть, функции Generics Delphi 2009 помогают в сокращении ложных приемов в определенных ситуациях.

+0

Если TMyThingProcessor.ProcessThing было объявлено повторно, то DoEverything всегда будет ссылаться на базовый TThingProcessor.ProcessThing, чего я не хочу. К сожалению, у меня нет D2009, но я открыт для решений, которые его используют, так как меня больше интересует структура кода, чем специфика языка. – David

+0

О, я понимаю, что вы имеете в виду. В этом случае вы застряли в кастинге. Вы пытаетесь смешивать полиморфизм (в буквальном смысле, «many-forms-ism») с чем-то, что работает только с * одной * формой, и они не подходят друг к другу, если вы выполняете небольшую работу вручную. –