2009-02-02 7 views
4

각 클래스가 특정 TComponent 자손 (예 : TComponent, TCustomAction 및 TMenuItem에 해당하는 자손 TAductFrobber 및 TMenuItemFrobber로 기본 클래스 TDefaultFrobber를 말함)에 해당하는 작은 클래스 계층 구조가 있습니다. (?) :"병렬"클래스 계층 구조를 연결하는 방법은 무엇입니까?

function CreateFrobber(AComponent: TComponent): IFrobber; 
begin 
    if AComponent is TCustomAction then 
    Result := TActionFrobber.Create(TCustomAction(AComponent)) 
    else if AComponent is TMenuItem then 
    Result := TMenuItemFrobber.Create(TMenuItem(AComponent)) 
    else 
    Result := TDefaultFrobber.Create(AComponent); 
end; 

어떻게 든이 가상 함수 또는 대신하는 경우 - 다른 폭포 또는 RTTI 유사한 것을 사용하는 리팩토링 이제이 같은 기능 뭔가 공장을 줄까?

편집 : 지금은 내 솔루션 : 세자르, Gamecat 및 mghie에

unit Frobbers; 

interface 

uses 
    Classes; 

type 
    IComponentFrobber = interface 
    end; 

    TComponentFrobberClass = class of TComponentFrobber; 

    TComponentFrobber = class(TInterfacedObject, IComponentFrobber) 
    strict private 
    FComponent: TComponent; 
    protected 
    constructor Create(AComponent: TComponent); 
    property Component: TComponent read FComponent; 
    public 
    class function FindFrobberClass(AComponentClass: TComponentClass): TComponentFrobberClass; overload; static; 
    class function FindFrobberClass(AComponent: TComponent): TComponentFrobberClass; overload; static; 
    class procedure RegisterFrobber(AComponentClass: TComponentClass; AFrobberClass: TComponentFrobberClass); static; 
    end; 

implementation 

uses 
    ActnList, 
    Menus; 

type 
    TComponentFrobberRegistryItem = record 
    ComponentClass: TComponentClass; 
    FrobberClass: TComponentFrobberClass; 
    end; 

var 
    FComponentFrobberRegistry: array of TComponentFrobberRegistryItem; 

class function TComponentFrobber.FindFrobberClass(AComponentClass: TComponentClass): TComponentFrobberClass; 
var 
    i: Integer; 
begin 
    // Search backwards, so that more specialized frobbers are found first: 
    for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do 
    if FComponentFrobberRegistry[i].ComponentClass = AComponentClass then 
    begin 
     Result := FComponentFrobberRegistry[i].FrobberClass; 
     Exit; 
    end; 
    Result := nil; 
end; 

constructor TComponentFrobber.Create(AComponent: TComponent); 
begin 
    inherited Create; 
    FComponent := AComponent; 
end; 

class function TComponentFrobber.FindFrobberClass(AComponent: TComponent): TComponentFrobberClass; 
var 
    i: Integer; 
begin 
    // Search backwards, so that more specialized frobbers are found first: 
    for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do 
    if AComponent is FComponentFrobberRegistry[i].ComponentClass then 
    begin 
     Result := FComponentFrobberRegistry[i].FrobberClass; 
     Exit; 
    end; 
    Result := nil; 
end; 

class procedure TComponentFrobber.RegisterFrobber(AComponentClass: TComponentClass; 
    AFrobberClass: TComponentFrobberClass); 
var 
    i: Integer; 
begin 
    Assert(FindFrobberClass(AComponentClass) = nil, 'Duplicate Frobber class'); 
    i := Length(FComponentFrobberRegistry); 
    SetLength(FComponentFrobberRegistry, Succ(i)); 
    FComponentFrobberRegistry[i].ComponentClass := AComponentClass; 
    FComponentFrobberRegistry[i].FrobberClass := AFrobberClass; 
end; 

function CreateComponentFrobber(AComponent: TComponent): IComponentFrobber; 
var 
    FrobberClass: TComponentFrobberClass; 
begin 
    FrobberClass := TComponentFrobber.FindFrobberClass(AComponent); 
    Assert(FrobberClass <> nil); 
    Result := FrobberClass.Create(AComponent); 
end; 

type 
    TActionFrobber = class(TComponentFrobber); 
    TMenuItemFrobber = class(TComponentFrobber); 

initialization 
    TComponentFrobber.RegisterFrobber(TCustomAction, TActionFrobber); 
    TComponentFrobber.RegisterFrobber(TMenuItem, TMenuItemFrobber); 
end. 

감사합니다.

+0

여기에 결코 들어갈 수없는 몇 가지 의견이있는 답변을 추가했습니다 .- – mghie

답변

2

이 제안 : 클래스의 만들기 클래스 쌍의 배열, 당신은 색인을 얻고 클래스 생성자의 쌍을 사용

var 
    ArrayItem: array[0..1] of TComponentClass = (TActionFrobber, TMenuItemFrobber); 
    ArrayOwner: array[0..1] of TComponentClass = (TCustomAction, TMenuItem); 

function CreateFrobber(AComponent: TComponentClass): IFrobber; 
var 
    Index: Integer; 
begin 
    Result:= nil; 
    for I := Low(ArrayOwner) to High(ArrayOwner) do 
    if AComponent is ArrayOwner[I] then 
    begin 
     Result:= ArrayItem[I].Create(AComponent); 
     Break; 
    end; 

    if Result = nil then 
    Result:= TDefaultFrobber.Create(AComponent); 
end; 

또는 RTTI + 클래스 이름 규칙을 사용할 수 있습니다,

function CreateFrobber(AComponent: TComponentClass): IFrobber; 
const 
    FrobberClassSuffix = 'Frobber'; 
var 
    LClass: TComponentClass; 
    LComponent: TComponent; 
begin 
    LClass:= Classes.FindClass(AComponent.ClassName + FrobberClassSuffix); 
    if LClass <> nil then 
    LComponent:= LClass.Create(AComponent) 
    else 
    LComponent:= TDefaultFrobber.Create(AComponent); 

    if not Supports(LComponent, IFrobber, Result) then 
    Result:= nil; 
end; 
+0

RTTI + ClassName 컨벤션 아이디어가 너무 약해서 IMO가 왜 그런 구석에 페인트합니까? – mghie

+0

나는 클래스 쌍을 선호한다. 나는 방금 하나 더 옵션을 가져온다. 적어도 사용하거나 사용하지 않아도됩니다. 적어도이 방법으로 수행 할 수 있음을 알고 있습니다. –

3

가상 생성자를 사용하여 클래스를 만들고 해당 클래스의 클래스 유형을 만드는 경우. 구성 요소 클래스 이름을 기반으로 lookuplist를 만들 수 있습니다.

예 :

type 
    TFrobber = class 
    public 
    constructor Create; virtual; 

    class function CreateFrobber(const AComponent: TComponent): TFrobber; 
    end; 
    TFrobberClass = class of TFrobber; 

    type 
    TFrobberRec = record 
     ClassName: ShortString; 
     ClassType: TFrobberClass; 
    end; 

    const 
    cFrobberCount = 3; 
    cFrobberList : array[1..cFrobberCount] of TFrobberRec = (
     (ClassName : 'TAction'; ClassType: TActionFrobber), 
     (ClassName : 'TButton'; ClassType: TButtonFrobber), 
     (ClassName : 'TMenuItem'; ClassType: TMenuItemFrobber) 
    ); 

    class function TFrobber.CreateFrobber(const AComponent: TComponent): TFrobber; 
    var 
    i : Integer; 
    begin 
    Result := nil; 
    for i := 1 to cFrobberCount do begin 
     if AComponent.ClassName = cFrobberList[i].ClassName then begin 
     Result := cFrobberList[i].ClassType.Create(); 
     Exit; 
     end; 
    end; 
    end; 

당신은 물론 또한 동적 목록 (사전)와 함께 작업 할 수 있지만, 당신은 어떻게 든 각 조합을 등록해야합니다.

업데이트

는 mghie의 발언에 commnent합니다.

당신은 완벽합니다. 그러나 이것은 아마도 추악한 트릭이있는 것은 아닙니다. 유닛의 초기화/종료 섹션을 사용하여 클래스를 리 보스터 토리해야합니다. 하지만 초기화/finalization 클래스 메서드를 클래스에 추가하는 것은 멋지다. 이것들은 유닛의 초기화 (그리고 종료)와 함께 호출되어야합니다. 이처럼 :

class 
    TFrobber = class 
    private 
    initialization Init; // Called at program start just after unit initialization 
    finalization Exit; // called at program end just before unit finalization. 
    end; 
+0

+1 아이디어에는 있지만, 동적으로 등록 된 표준 클래스 쌍과 일치하는 Frobber 클래스가 있어야합니다. 이렇게하면 모든 것을 느슨하게 연결할 수 있습니다. 대답의 코드가 필요하기 때문에 클래스 레지스트리가 모든 구체적인 클래스를 알아야 할 필요는 없습니다. – mghie

+0

이전 코드를 변경하지 않고 새 장치를 추가하기 만하면 새로운 Frobber 클래스를 시스템에 추가 할 수있는 완벽한 설계가 가능합니다. – mghie

+0

저는 "컴파일 시간 안전"접근 방식을 더 찾고 있습니다.하지만 RegisterClass 유형이 가장 가까운 접근 방식이라고 생각합니다. 아마도 Cesar와 같은 클래스 이름 대신 TComponentClass를 제안했을 것입니다. –

1

나는이 정말 코멘트 섹션에서 수행 할 수 없으므로 여기에 대답, 현재 솔루션에 몇 가지 주석을 추가하고 싶습니다 :

0과 같은
type 
    IComponentFrobber = interface 
    end; 

    TComponentFrobberClass = class of TComponentFrobber; 

    TComponentFrobber = class(TInterfacedObject, IComponentFrobber) 
    strict private 
    FComponent: TComponent; 
    protected 
    constructor Create(AComponent: TComponent); 
    property Component: TComponent read FComponent; 
    public 
    class function FindFrobberClass(AComponentClass: TComponentClass): 
     TComponentFrobberClass; overload; static; 
    class function FindFrobberClass(AComponent: TComponent): 
     TComponentFrobberClass; overload; static; 
    class procedure RegisterFrobber(AComponentClass: TComponentClass; 
     AFrobberClass: TComponentFrobberClass); static; 
    end; 

기본 클래스를 TInterfacedObject를 사용하여 훨씬 포인트가 없다 당신은 객체가 아니라 그것을 구현하는 인터페이스가 필요 항상 것 같이 - 어떻게 다른 사람이 당신의 콘크리트 Frobber 클래스를 찾을 것입니까? 이 클래스를 TInterfacedObject에서 내림차순으로 TComponentFrobber로, 클래스 메서드가있는 TComponentRegistry 클래스 (TObject에서 내림차순)로 분할합니다. 물론 레지스트리 클래스를 더 일반적인 것으로 만들 수 있습니다. TComponentFrobber에 연결되지 않고 재사용 할 수 있습니다.

편집 : 다음과 같은 클래스 레지스트리를 사용했습니다. 그런 다음 객체를 만들고로드하십시오.

type 
    TComponentFrobberRegistryItem = record 
    ComponentClass: TComponentClass; 
    FrobberClass: TComponentFrobberClass; 
    end; 

var 
    FComponentFrobberRegistry: array of TComponentFrobberRegistryItem; 

추가하거나 레지스트리에서에/클래스를 제거 할 수 있지만 일반적으로 내가 레지스트리 항목에 대한 배열하지만 목록을 사용하지 않을 않을 경우는 OK입니다. 당신이 올바른 순서로 추가하지 않는 한 (첫번째 적어도 전문), 가장 전문 frobber을 찾는 데 도움이되지 않습니다 배열에 거꾸로 검색

class function TComponentFrobber.FindFrobberClass(AComponentClass: TComponentClass): 
    TComponentFrobberClass; 
var 
    i: Integer; 
begin 
    // Search backwards, so that more specialized frobbers are found first: 
    for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do 
    if FComponentFrobberRegistry[i].ComponentClass = AComponentClass then 
    begin 
     Result := FComponentFrobberRegistry[i].FrobberClass; 
     Exit; 
    end; 
    Result := nil; 
end; 

. ClassType이 동일하지 않은 이유는 무엇입니까? 기본 클래스도 테스트해야하는 경우 클래스 계층을 트래버스하는 ClassParent도 있습니다.

+0

IComponentFrobber 인터페이스는 순전히 수명 관리를위한 것입니다. frobbers를 사용할 때 try/finally가 필요하지 않습니다. 대신 일부 IObjectSafe 구현을 사용할 수 있다고 생각합니다. –

+0

Re 배열 : 배열을 사용하는 것이 더 간단합니다. 자유롭게 할 필요가없고 컨테이너 클래스를 파생 할 필요가 없으며 포인터 쌍을 저장할 수 있습니다. 레지스트리에서 제거하는 것은 발생하지 않습니다 - 초기화 섹션의 RegisterFrobber에 대한 호출 만 발생합니다 AFAICT 지금. –

+0

확인. 그것은 당신이 코드를 변경해야합니다, 어쩌면 누군가 다른 사람에게 도움이 될 것 아니에요, StackOverflow 결국 위키처럼 될 것입니다. – mghie