Come definire la definizione della classe quando si eredita da più interfacce

0

Considerate due definizioni di interfaccia ...

IOmniWorkItem = interface ['{3CE2762F-B7A3-4490-BF22-2109C042EAD1}']
  function  GetData: TOmniValue;
  function  GetResult: TOmniValue;
  function  GetUniqueID: int64;
  procedure SetResult(const value: TOmniValue);
//
  procedure Cancel;
  function  DetachException: Exception;
  function  FatalException: Exception;
  function  IsCanceled: boolean;
  function  IsExceptional: boolean;
  property Data: TOmniValue read GetData;
  property Result: TOmniValue read GetResult write SetResult;
  property UniqueID: int64 read GetUniqueID;
end;

IOmniWorkItemEx = interface ['{3B48D012-CF1C-4B47-A4A0-3072A9067A3E}']
  function  GetOnWorkItemDone: TOmniWorkItemDoneDelegate;
  function  GetOnWorkItemDone_Asy: TOmniWorkItemDoneDelegate;
  procedure SetOnWorkItemDone(const Value: TOmniWorkItemDoneDelegate);
  procedure SetOnWorkItemDone_Asy(const Value: TOmniWorkItemDoneDelegate);
//
  property OnWorkItemDone: TOmniWorkItemDoneDelegate read GetOnWorkItemDone write SetOnWorkItemDone;
  property OnWorkItemDone_Asy: TOmniWorkItemDoneDelegate read GetOnWorkItemDone_Asy write SetOnWorkItemDone_Asy;
end;

... quali sono le tue idee per la stesura della dichiarazione di classe che eredita da entrambi?

La mia idea attuale (ma non so se sono felice con questo):

TOmniWorkItem = class(TInterfacedObject, IOmniWorkItem, IOmniWorkItemEx)
strict private
  FData              : TOmniValue;
  FOnWorkItemDone    : TOmniWorkItemDoneDelegate;
  FOnWorkItemDone_Asy: TOmniWorkItemDoneDelegate;
  FResult            : TOmniValue;
  FUniqueID          : int64;
strict protected
  procedure FreeException;
protected //IOmniWorkItem
  function  GetData: TOmniValue;
  function  GetResult: TOmniValue;
  function  GetUniqueID: int64;
  procedure SetResult(const value: TOmniValue);
protected //IOmniWorkItemEx
  function  GetOnWorkItemDone: TOmniWorkItemDoneDelegate;
  function  GetOnWorkItemDone_Asy: TOmniWorkItemDoneDelegate;
  procedure SetOnWorkItemDone(const Value: TOmniWorkItemDoneDelegate);
  procedure SetOnWorkItemDone_Asy(const Value: TOmniWorkItemDoneDelegate);
public
  constructor Create(const data: TOmniValue; uniqueID: int64);
  destructor Destroy; override;
public //IOmniWorkItem
  procedure Cancel;
  function  DetachException: Exception;
  function  FatalException: Exception;
  function  IsCanceled: boolean;
  function  IsExceptional: boolean;
  property Data: TOmniValue read GetData;
  property Result: TOmniValue read GetResult write SetResult;
  property UniqueID: int64 read GetUniqueID;
public  //IOmniWorkItemEx
  property OnWorkItemDone: TOmniWorkItemDoneDelegate read GetOnWorkItemDone write SetOnWorkItemDone;
  property OnWorkItemDone_Asy: TOmniWorkItemDoneDelegate read GetOnWorkItemDone_Asy write SetOnWorkItemDone_Asy;
end;

Come notato nelle risposte, la composizione è un buon approccio per questo esempio, ma non sono sicuro che si applichi in tutti i casi. A volte utilizzo l'ereditarietà multipla solo per dividere l'accesso in lettura e scrittura a qualche proprietà in una parte pubblica (in genere di sola lettura) e privata (di solito in sola scrittura). La composizione si applica ancora qui? Non sono proprio sicuro di dover spostare la proprietà in questione dalla classe principale e non sono sicuro che sia il modo corretto di farlo.

Esempio:

// public part of the interface interface
IOmniWorkItemConfig = interface
  function  OnExecute(const aTask: TOmniBackgroundWorkerDelegate): IOmniWorkItemConfig;
  function  OnRequestDone(const aTask: TOmniWorkItemDoneDelegate): IOmniWorkItemConfig;
  function  OnRequestDone_Asy(const aTask: TOmniWorkItemDoneDelegate): IOmniWorkItemConfig;
end;

// private part of the interface
IOmniWorkItemConfigEx = interface ['{42CEC5CB-404F-4868-AE81-6A13AD7E3C6B}']
  function  GetOnExecute: TOmniBackgroundWorkerDelegate;
  function  GetOnRequestDone: TOmniWorkItemDoneDelegate;
  function  GetOnRequestDone_Asy: TOmniWorkItemDoneDelegate;
end;

// implementing class
TOmniWorkItemConfig = class(TInterfacedObject, IOmniWorkItemConfig, IOmniWorkItemConfigEx)
strict private
  FOnExecute        : TOmniBackgroundWorkerDelegate;
  FOnRequestDone    : TOmniWorkItemDoneDelegate;
  FOnRequestDone_Asy: TOmniWorkItemDoneDelegate;
public
  constructor Create(defaults: IOmniWorkItemConfig = nil);
public //IOmniWorkItemConfig
  function  OnExecute(const aTask: TOmniBackgroundWorkerDelegate): IOmniWorkItemConfig;
  function  OnRequestDone(const aTask: TOmniWorkItemDoneDelegate): IOmniWorkItemConfig;
  function  OnRequestDone_Asy(const aTask: TOmniWorkItemDoneDelegate): IOmniWorkItemConfig;
public //IOmniWorkItemConfigEx
  function  GetOnExecute: TOmniBackgroundWorkerDelegate;
  function  GetOnRequestDone: TOmniWorkItemDoneDelegate;
  function  GetOnRequestDone_Asy: TOmniWorkItemDoneDelegate;
end;
    
posta gabr 22.11.2011 - 08:49
fonte

1 risposta

1

Hai preso in considerazione l'utilizzo della composizione qui? Il mio Delphi è orribile, ma questo dovrebbe farti un'idea:

TOmniWorkItemEx = class(IOmniWorkItemEx)
    //implement it
end;

TOmniWorkItem = class(TInterfacedObject, IOmniWorkItem)
//implement the whole interface
//and now compose:
strict private
  FHandlers          : TOmniWorkItemEx;
protected
  function GetHandlers: IOmniWorkItemEx;
public
  property Handlers:IOmniWorkItemEx read GetHandlers;
end;

Questo divide bene il codice. Tecnicamente, IOmniWorkItemEx (e TOmniWorkItemEx ) sarebbero meglio parametrizzati con il tipo di funzione e avrebbero più nomi di metodi generici (e un nome più generico in primo luogo), e sarebbero quindi riutilizzabili.

Aggiornamento:

Bene, secondo SOLID, le interfacce dovrebbero essere più piccole possibile e classes dovrebbe avere una sola responsabilità (avere una classe implementare troppe interfacce è spesso un indicatore che viola lo SRP).

Per la divisione del codice e il riutilizzo del codice, ci sono due approcci:

  1. ereditarietà e implementazione parziale
  2. composizione e decomposizione

La composizione può essere noiosa, soprattutto perché la maggior parte delle lingue non supporta la delega sintattica, ma a parte questo, quasi certamente porta a risultati migliori.

Nel tuo ultimo esempio, hai anche la duplicazione del codice e la mancanza di normalizzazione. Ecco come lo farei io:

IWorkerItem<Owner, Worker> = interface
    function GetWith(const aTask: Worker):Owner
    function GetWorker: Worker
end;

class TOmniConfigWorkerItem<Worker> = class(IWorkerItem<TOmniWorkItemConfig, Worker>)
    //... implement
end;

E usalo in questo modo:

private strict
   FOnExecute        : TOmniConfigWorkerItem<TOmniBackgroundWorkerDelegate>;
   FOnRequestDone    : TOmniConfigWorkerItem<TOmniWorkItemDoneDelegate>;
   FOnRequestDone_Asy: TOmniConfigWorkerItem<TOmniWorkItemDoneDelegate>;
protected
   function GetOnExecute        : TOmniConfigWorkerItem<TOmniBackgroundWorkerDelegate>;
   function GetOnRequestDone    : TOmniConfigWorkerItem<TOmniWorkItemDoneDelegate>;
   function GetOnRequestDone_Asy: TOmniConfigWorkerItem<TOmniWorkItemDoneDelegate>;
public
   property OnExecute : IWorkerItem<TOmniWorkItemConfig, TOmniBackgroundWorkerDelegate> read GetOnExecute; 
   property OnRequestDone : IWorkerItem<TOmniWorkItemConfig, TOmniBackgroundWorkerDelegate> read GetOnRequestDone; 
   property OnRequestDone_Asy : IWorkerItem<TOmniWorkItemConfig, TOmniBackgroundWorkerDelegate> read GetOnRequestDone_Asy; 

Questo probabilmente non sembra un gran miglioramento. Nelle lingue in cui la dichiarazione di una proprietà di sola lettura è una questione di una riga, diventa veramente evidente. Ma anche in questo caso, hai scambiato due astrazioni parziali con una molto semplice, atomica.

Anche il codice potrebbe non essere compilato. Alcune lingue richiedono che il tipo di accesso sia uguale a quello della sua proprietà, quindi potrebbe essere necessario cambiarlo. Ho solo pensato che sarebbe stato meglio usare l'astrazione per l'interfaccia pubblica e la concretizzazione per l'implementazione interna.

    
risposta data 22.11.2011 - 09:41
fonte

Leggi altre domande sui tag