使用装饰者模式遇到了问题,iOS / UICollectionViewCells

8

我正在尝试使用装饰者模式来“装饰”UICollectionViewCells。

例如,如果我有一个

 BaseCell : UICollectionViewCell 

我希望能够像这样做某件事情:
 BaseCell *cell = [[BaseCell alloc] initWithFrame]
 cell = [[GlowingCell alloc] initWithCell:cell];
 cell = [[BorderedCell alloc] initWithCell:cell];
 cell = [[LabelledCell alloc] initWithCell:cell];

 // cell is now a glowing, bordered, labelled cell.

我认为装饰者模式非常适用于这种情况,但是我在将其应用到集合视图中时遇到了困难。

首先,在UICollectionViewControllers中,您需要注册一个类,就像这样:

 [self.collectionView registerClass:cellClass forCellWithReuseIdentifier:cellIdentifier];

因此我没有机会创建自己的实例。

其次,我无法看出装饰器如何对“非纯”对象进行装饰,即我没有从头开始创建但具有自己属性和行为的对象(例如UICollectionViewCell)。由于在上面的示例中,cell表示LabelledCell的新实例,如果UICollectionView调用方法,例如isSelected,这将调用aLabelledCellInstance.isSelected,除非我在我的装饰器基类中明确这样做:

 - (BOOL)isSelected {
      return self.decoratedCell.isSelected;
 }

虽然只需要重写一个方法,但在UICollectionViewCell中每个方法都这样做似乎不太合适。我应该使用forwardInvocation:吗?

我是否滥用了这种模式,还有其他的替代方案吗?因为当你只需要重写基本方法如下面所示时,这种方式非常好用。

 getPrice() {
      return decoratedObject.getPrice() + 1.10f;
 }

...但似乎很难适用于实际上使用自定义行为装饰现有UI元素的目的。

谢谢

编辑:我试图避免出现以下类:

  • GlowingBorderedCell
  • LabelledGlowingBorderedCell
  • 等等。

在理论上,装饰器是我尝试实现的完美候选人,但实现绝对让我困惑不解。


1
这是一个有趣的问题,我喜欢将其应用于UI元素的想法。从实现的角度来看,你对于是否滥用模式的疑问,实际上取决于可读性和可维护性。如果使用此模式(即使覆盖了许多方法)可以使您的代码更易读和易维护,请使用它。如果它掩盖了代码的意图和功能,并使下一个人难以更改,则不要使用。 - David Hope
我并不是在批评,我只是陈述一个事实,即装饰器最常用于像他所问的UI方面。 - Rob
需要委托所有方法似乎很烦人,但你可以使用pragma和代码折叠。这就是装饰器的工作原理。我考虑过类似的想法,认为它们很好。那么你不能注册自己的类吗?怎么会这样?他们不是要求提供一个类来实例化吗? - Rob
你注册的是一个类类型,而不是一个装饰实例。并且必须将所有方法委托给系统类,这让我感到警觉。 - Sam
但是您的装饰器应该能够作为类类型的替代品。这就是整个想法的核心。一个关于设计模式的好面试问题是“装饰器是委托还是实现其装饰对象接口的?”答案是两者兼备(几乎没有人能想到这点)。 - Rob
显示剩余4条评论
2个回答

4
首先,装饰器模式要求您覆盖BaseDecorator中的所有基本方法,以便您可以将调用转发到装饰对象。您可以通过覆盖每个单独的方法或者更好的方法是使用 forwardInvocation:来实现。由于所有其他装饰器都将是BaseDecorator的子类,因此现在只需覆盖要更改的方法即可。
其次,对于CollectionView问题,我建议使用带有普通UIView的装饰器模式,然后将装饰视图作为单元格的contentView使用。让我们看一个例子:
我们有一个BaseCellView类,它将是所有装饰器的超类。
BaseCellView : UIView;
GlowingCellView: BaseCellView;
BorderedCell: BaseCellView;
LabelledCell: BaseCellView;

我们仍然有一个名为BaseCell的类,它是UICollectionViewCell的子类:

BaseCell : UICollectionViewCell;

现在,UICollectionViewControllers将始终创建一个BaseCell实例,并让您有机会进行配置,这里您需要执行以下操作:
BaseCellView *cellView = [[BaseCellView alloc] initWithFrame]
cellView = [[GlowingCellView alloc] initWithCellView:cellView];
cellView = [[BorderedCellView alloc] initWithCellView:cellView];
cellView = [[LabelledCellView alloc] initWithCellView:cellView];
cell.contentView = cellView;

如果您希望,您仍然可以将任何 UICollectionViewCell 转发给装饰器。


这就是为什么我喜欢StackOverflow的原因。很棒的答案,我会尝试一下。谢谢! - Sam
接受这个答案后,我最终没有使用这个模式,因为我需要在创建后访问各种单元格属性,并且不想将它们全部包含在基本装饰器接口中-现在我只是坚持更严格的设计。 - Sam
我认为你可以将指向Cell的指针传递给CellView的初始化程序,这样每个CellView都可以访问其装饰的单元格的属性。但是你可能会觉得这变成了一个hackish解决方案。 - Hejazi
不仅如此,将所有装饰视图分配到configureCell而不是重用,也是次优的。 - Sam

3

这里有一篇文章,我在其中描述了这个技巧。虽然我选择装饰UITableView而不是单元格,但它可以很容易地适应您的集合视图。这是一个相当长的阅读,所以我只会在这里做一个简短的总结:

  • 装饰器需要是一个代理对象,才能够将所有消息转发给装饰对象
  • 你需要重载多个方法来使装饰器生效,其中包括respondsToSelectorforwardingTargetForSelector
  • 完成后,你就可以链接多个装饰器,这非常方便:

e.g.:

dec = [[DEFooterDecorator alloc] initWithDecoratedObject:dec];
dec = [[DEHeaderDecorator alloc] initWithDecoratedObject:dec];
dec = [[DEGreenCellDecorator alloc] initWithDecoratedObject:dec];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接