我在Delphi应用程序中实现了FlyWeight模式。一切都运作良好,一切都快得多,记忆力也减少了,但我担心的还有一件事。
只要客户端代码从不在共享对象上调用Free(),我的实现就会起作用。在Flyweight模式中,FlyweightFactory本身应该“维护对flyweights的引用”,即对共享对象。
我的问题是没有(明显的)方法阻止其他代码在引用后销毁它们。我可以忍受这个,但如果我可以自由地绕过这些物体而不用担心意外的自由,那将是一个“大胜利”。
显示一个(人为的)示例:
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
我考虑重写析构函数as shown here以完全释放flyweight对象。在我的情况下,这不是一个选项
a)我只想阻止缓存的对象被释放,而不是不属于缓存的对象。有很多遗留代码不使用缓存;他们仍然需要手动创建和释放对象。
b)我做希望FlyweightFactory在完成期间释放对象;我同意Warren P认为“零泄露记忆”政策是最好的。
我将从GoF的Flyweight章节中引用
可用性意味着某种形式的 引用计数或垃圾 收集回收存储时 它不再需要了。然而, 如果数量不合适,则不需要 flyweights固定而小。在那里面 情况下,飞锤值得保留 永久地。
在我的情况下,飞重是“固定的”和(足够)小。
[更新有关我如何解决此问题的详细信息,请参阅我的回答]
答案 0 :(得分:2)
Destroy
和FreeInstance
中摧毁自己。如果你想允许调用Free
,那就没有其他选择。
要处理完成,您需要将缓存的对象添加到缓存对象列表中。可以在最终确定时释放该对象列表。当然,当您走过列表时,必须重置禁用释放的标志。
关于最终确定,我建议你注册预期的内存泄漏并泄漏这个内存。它使代码更简单,没有什么可失去的。一旦可执行文件关闭,操作系统将回收您没有释放的任何内存。需要注意的一点是:如果您的代码被编译成DLL,那么如果您的DLL被加载,卸载,再次加载等等,则泄漏可能会很麻烦。
这一切告诉你的是你正在逆流而行。您是否有可能通过不同的解决方案实现您的目标,这种解决方案更适合Delphi引导您的方式?
答案 1 :(得分:0)
我建议添加引用计数,以便知道您的共享对象是否仍在使用。 每个客户端都应使用AddRef / Release模式(AddRef增加计数; Release减少;如果count达到零,则调用Free)
可以通过GetFlyweight方法直接调用AddRef;必须使用Release而不是Free。
如果你重构你的类并从中提取一个接口,那么在接口实现中自然实现AddRef / Release模式。 (您可以从TInterfacedObject派生或通过自己实现IInterface)
答案 2 :(得分:0)
理想情况下,您很少需要两种方法来使用相同的东西。从长远来看,这只会使问题复杂化。在6个月的时间内,您可能无法确定某段代码是使用新的轻量级范例还是旧范例。
阻止某人致电Free
或Destroy
的最佳方法是确保不存在。在Delphi世界中,唯一的方法是使用interfaces
。
扩展你人为的例子:
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”对象。
答案 3 :(得分:0)
我设法通过David Heffernan在他的回答中提出的以下技术解决了我在原始问题中引用的问题。
a)我只想停止缓存的对象 从被释放,而不是对象 不是缓存的一部分。有一个 很多遗留代码都没有使用 缓存;他们仍然需要创造 和手动免费对象。
我通过继承Flyweight类并仅在子类中覆盖destroy,BeforeDestruction和FreeInstance来修复此问题。这使父类保持原样。缓存包含子类的实例(无法释放),而缓存外的对象可以按常规释放。
b)我确实想要FlyweightFactory 在完成期间释放对象; 我同意Warren P认为"零 泄露记忆"政策是最好的。
为了解决这个问题,我添加了一个私有布尔标志,在释放对象之前必须将其设置为true。此标志只能从缓存单元设置,对其他代码不可见。这意味着无法通过缓存外部的代码将标志设置在外部。
析构函数看起来像这样:
destructor TCachedItem.destroy;
begin
if destroyAllowed then
inherited;
end;
如果客户端代码尝试释放缓存对象,则该调用将不起作用。
答案 4 :(得分:0)
您还可以通过将析构函数设为protected
或private
来隐藏它。程序员不会在声明它的单元范围之外看到它。
但我发布此答案更像是一种好奇心,因为这不会阻止使用FreeAndNil
或使用"Protected Hack"