停止客户端代码释放Delphi中的共享对象

停止客户端代码释放Delphi中的共享对象,delphi,Delphi,我已经在我的Delphi应用程序中实现了FlyWeight模式。一切都很好,一切都快得多,占用的内存也少,但有一件事我很担心 只要客户机代码从不调用共享对象上的Free(),我的实现就可以工作。在Flyweight模式中,FlyweightFactory本身应该“保持对flyweights的引用”,即对共享对象的引用 我的问题是,没有(明显的)方法来阻止其他代码在对象具有引用后销毁它们。我可以接受这个,但如果我可以自由地传递这些对象,而不担心意外释放,那将是一个“大胜利” 要显示(人为)示例,请

我已经在我的Delphi应用程序中实现了FlyWeight模式。一切都很好,一切都快得多,占用的内存也少,但有一件事我很担心

只要客户机代码从不调用共享对象上的Free(),我的实现就可以工作。在Flyweight模式中,FlyweightFactory本身应该“保持对flyweights的引用”,即对共享对象的引用

我的问题是,没有(明显的)方法来阻止其他代码在对象具有引用后销毁它们。我可以接受这个,但如果我可以自由地传递这些对象,而不担心意外释放,那将是一个“大胜利”

要显示(人为)示例,请执行以下操作:

我已经考虑过重写析构函数来阻止flyweight对象被完全释放。就我而言,这不是一个选项

a) 我只想阻止缓存对象被释放,而不是不属于缓存的对象。有很多遗留代码不使用缓存;他们仍然需要手动创建和释放对象

b) 我确实希望FlyweightFactory在完成过程中释放对象;我同意沃伦·P的观点,“零泄漏内存”政策是最好的

我将引用GoF的Flyweight章节

共享性意味着某种形式的共享 引用计数还是垃圾 收集时回收存储 不再需要了。然而, 如果 飞锤固定且小。因为 在这种情况下,飞锤是值得保留的 永远在周围

在我的例子中,飞锤是“固定的”,并且(足够)小

[更新查看我的答案了解我如何解决此问题的详细信息]

仍然适用。对象必须通过专用布尔标志知道它们是缓存对象。然后他们可以选择在
销毁
自由实例
中不销毁自己。如果您想允许调用
Free
,实在没有其他选择

要处理终结,您需要将缓存对象添加到缓存对象列表中。该对象列表可以在最终确定时释放。当然,在浏览列表时,禁用释放的标志必须重置

说到最后定稿,我建议您注册一个预期的内存泄漏,然后泄漏这个内存。它使代码更加简单,并且没有任何损失。一旦可执行文件关闭,操作系统将立即回收您未释放的内存。警告一句:如果您的代码被编译成DLL,那么如果您的DLL被加载、卸载、再次加载等,那么泄漏可能会很麻烦


这一切告诉你的是你在逆流游泳。您是否可以使用与Delphi指导您的方式更适合的不同解决方案来实现您的目标?

我建议添加一个引用计数,以便知道您的共享对象是否仍在使用。 每个客户端都应该使用AddRef/Release模式(AddRef增加计数;Release减少计数;如果计数达到零,则调用Free)

您的GetFlyweight方法可以直接调用AddRef;必须使用释放,而不是免费


如果重构类并从中提取接口,那么在接口实现中自然实现AddRef/Release模式。(您可以从TInterfacedObject派生或自己实现IInterface)

理想情况下,您很少需要两种方式来使用相同的东西。从长远来看,这只会使事情复杂化。在6个月的时间里,您可能无法确定特定的代码段是使用新的flyweight范式还是旧范式

防止有人调用
免费
销毁
的最佳方法是确保它不在那里。在Delphi世界中,实现这一点的唯一方法是使用
接口

要扩展您精心设计的示例,请执行以下操作:

flyweight1:=FlyweightFactory.GetFlyweight(42); 
WriteLn('Description is '+flyweight.Description); 
flyweight1.Free;

flyweight2:=FlyweightFactory.GetFlyweight(42); 
WriteLn('Description is '+flyweight.Description); 
// Object has already been Freed!; behaviour is undefined
type
  TFlyweightObject = class
  public
    constructor Create(ANumber: Integer);
    function Description: string;
  end;

  TFlyweightFactory = class
  public
    function GetFlyweight(ANumber: Integer): TFlyweightObject;
  end;
这是一个很容易被恶意客户端破坏的对象。您可以进行以下更改:

type
  IFlyweight = interface
  //place guid here
    function Description: string;
  end;

  TFlyweightObject = class(TInterfacedObject, IFlyweight)
  public
    constructor Create(ANumber: Integer);
    function Description: string;
  end;

  TFlyweightFactory = class
  public
    function GetFlyweight(ANumber: Integer): IFlyweight;
  end;

现在,任何更新为使用flyweight范例的代码都必须按预期使用它。识别仍然需要重构的旧代码也更容易,因为它不使用接口。旧代码仍然可以直接构造“flyweight”对象。

我使用David Heffernan在回答中建议的以下技术,成功地绕过了我在原始问题中引用的问题

a) 我只想停止缓存对象 被释放,而不是 不是缓存的一部分。有一个 很多旧代码没有使用 缓存;他们仍然需要创造 并手动释放对象

我通过对Flyweight类进行子类化并重写destroy,beforedestromission和FreeInstance来修复这个问题。这使父类保持原样。缓存包含子类的实例(不能被释放),而缓存之外的对象可以像往常一样被释放

b) 我确实希望FlyweightFactory 在完成过程中释放对象; 我同意沃伦·P的观点,“零” “内存泄漏”策略是最好的

为了解决这个问题,我添加了一个私有布尔标志,在释放对象之前必须将其设置为true。此标志只能从缓存单元设置,其他代码看不到。这意味着不能通过缓存外部的代码在外部设置标志

析构函数如下所示:

destructor TCachedItem.destroy;
begin
    if destroyAllowed then
        inherited;
end;

如果客户端代码试图释放缓存对象,则调用将无效

您还可以通过将析构函数设置为
受保护
私有
来隐藏析构函数。程序员不会在声明它的单元范围之外看到它

但我发布这个答案更像是出于好奇,因为这不会阻止使用