代码之家  ›  专栏  ›  技术社区  ›  jpfollenius Rob Kennedy

对象定向和序列化

  •  4
  • jpfollenius Rob Kennedy  · 技术社区  · 15 年前

    考虑一个接口

    IMyInterface = interface
      procedure DoSomethingRelevant;
      procedure Load (Stream : TStream);
      procedure Save (Stream : TStream);
    end;
    

    以及实现接口的几个类:

    TImplementingClass1 = class (TInterfacedObject, IMyInterface)
      ...
    end;
    TImplementingClass2 = class (TInterfacedObject, IMyInterface)
      ...
    end;
    ...
    

    我有一个类,其中列出了imyinterface实现者:

    TMainClass = class
    strict private
      FItems : TList <IMyInterface>;
    public
      procedure LoadFromFile (const FileName : String);
      procedure SaveToFile (const FileName : String);
    end;
    

    现在问一个问题:如何以面向对象的方式加载主类,尤其是项列表?在调用项目的虚拟加载方法之前,我必须创建它们,从而了解它们的类型。在我当前的实现中,我存储项目的数量,然后为每个项目存储

    • 类型标识符(imyinterface获取额外的getid函数)
    • 调用项的保存方法

    但这意味着在装载过程中我必须做

    ID := Reader.ReadInteger;
    case ID of
      itClass1 : Item := TImplementingClass1.Create;
      itClass2 : Item := TImplementingClass2.Create;
      ...
    end;
    Item.Load (Stream);
    

    但这似乎不是很面向对象的,因为每次添加新的实现者时,我都要处理现有的代码。有没有更好的方法来处理这种情况?

    2 回复  |  直到 15 年前
        1
  •  4
  •   Lieven Keersmaekers    15 年前

    一种解决方案是实现一个工厂,所有类都用一个唯一的ID注册它们自己。

    TCustomClassFactory = class(TObject)
    public      
      procedure Register(AClass: TClass; ID: Integer);
      function Create(const ID: Integer): IMyInterface;
    end;
    
    TProductionClassFactory = class(TCustomClassFactory)
    public
      constructor Create; override;
    end;
    
    TTestcase1ClassFactory = class(TCustomClassFactory);
    public
      constructor Create; override;
    end;
    
    var
      //***** Set to TProductionClassFactory for you production code,
      //      TTestcaseXFactory for testcases or pass a factory to your loader object.
      GlobalClassFactory: TCustomClassFactory;
    
    implementation
    
    constructor TProductionClassFactory.Create;
    begin
      inherited Create;
      Register(TMyImplementingClass1, 1);
      Register(TMyImplementingClass2, 2);
    end;
    
    constructor TTestcase1ClassFactory.Create;
    begin
      inherited Create;
      Register(TMyImplementingClass1, 1);
      Register(TDoesNotImplementIMyInterface, 2);
      Register(TDuplicateID, 1);
      Register(TGap, 4);
      ...
    end;
    

    优势

    • 您可以从当前的加载方法中删除条件逻辑。
    • 一个检查重复或丢失ID的位置。
        2
  •  2
  •   Ozan HELPY    15 年前

    您需要一个类注册中心,在那里您存储每个类引用以及它们的唯一ID。这些类在它们的单元的初始化部分注册它们自己。

    TImplementingClass1 = class (TInterfacedObject, IMyInterface)
      ...
    end;
    TImplementingClass2 = class (TInterfacedObject, IMyInterface)
      ...
    end;
    
    TMainClass = class
    public
      procedure LoadFromFile (const FileName : String);
      procedure SaveToFile (const FileName : String);
    end;
    

    编辑:将类注册表移动到单独的类中:

    TMyInterfaceContainer = class 
    strict private
    class var
      FItems : TList <IMyInterface>;
      FIDs: TList<Integer>;
    public
      class procedure RegisterClass(TClass, Integer);
      class function GetMyInterface(ID: Integer): IMyInterface;
    end;
    
    procedure TMainClass.LoadFromFile (const FileName : String);
      ...
      ID := Reader.ReadInteger;
      // case ID of
      //   itClass1 : Item := TImplementingClass1.Create;
      //   itClass2 : Item := TImplementingClass2.Create;
      //   ...
      // end;
      Item := TMyInterfaceContainer.GetMyInterface(ID);
      Item.Load (Stream);
      ...
    
    initialization
      TMyInterfaceContainer.RegisterClass(TImplementingClass1, itClass1);
      TMyInterfaceContainer.RegisterClass(TImplementingClass2, itClass2);
    

    这应该指向你的方向,为了很好地介绍这些方法,请阅读著名的 Martin Fowler article 特别是关于 Interface Injection