我创建了一个类Metrics
,它被设计为子类化以自定义行为。为了使其更加健壮,Metrics
的init方法调用名为setup
的方法,默认情况下不执行任何操作。如果子类想要在初始化期间自定义行为(他们通常这样做),他们可以覆盖此方法。由于默认实现不执行任何操作,因此无需记住调用[super setup]
。
我喜欢它迄今为止的工作方式,它坚固且易于使用。我现在遇到的问题是有时候setup方法需要设置一些额外的属性。一个例子:
@implimentation Metrics
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Do all the required initialization
}
}
@end
@interface SubclassOfMetrics : Metrics {}
@property (assign) CGFloat width;
@end
@implimentation SubclassOfMetrics
- (void)setup {
// This method is indirectly called as a result of the superclass initialization
// The code here depends on the `width` property being set
}
@end
我遇到的问题是在设置属性width
之前调用setup。我有什么选择?我可以通过不在init方法中初始化我的Metrics
类来解决这个问题。在设置了我需要设置的属性后,我可以明确地执行此操作。我不喜欢这个,因为它要求我以特定顺序执行操作并记住设置。我还有其他选择吗?
编辑:问题的根源实际上是Metrics
类的初始化做了很多计算。这些计算的结果将取决于子类中设置的属性,因此我需要一种方法来在超类完成初始化之前设置的属性集。
答案 0 :(得分:2)
对象的初始化程序已经设计为您尝试创建的“设置”。您应该只使用初始化程序,并在调用超级初始化程序后执行特定于该类所需的任何设置。如果有新属性需要考虑初始化(设置),请为子类创建一个新的指定初始化程序。这种依赖注入将改善您的整体设计并满足您的初始化需求。
@implementation Metrics
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//Just setup here, or call a method if you prefer
}
}
@end
@interface SubclassOfMetrics : Metrics {}
@property (assign) CGFloat width;
- (id)initWithFrame:(CGRect)frame andMetricWidth:(CGFloat)inWidth;
@end
@implementation SubclassOfMetrics
@synthesize width;
- (id)initWithFrame:(CGRect)frame andMetricWidth:(CGFloat)inWidth {
self = [super initWithFrame:frame];
if (self) {
self.width = inWidth;
//do your setup here and use the width
}
}
@end
答案 1 :(得分:1)
如果您的Metrics
对象是另一个对象(例如控制器或任何其他类)的实例变量,则一旦确定了必要的信息,您就可以使用该类向Metrics
发送消息对于对象的设置。我会经常做这样的事情:
Metrics*myMetrics=[[Metrics alloc] init]; // just creates the Metrics object
我可能会在控制器的viewDidLoad
或适当的地方执行此操作。
然后,您可以从控制对象执行此操作:
[self.myMetrics setup];
如果它需要类似“宽度”的内容,您可以设置Metrics
ivar并从setup
内调用它,或者将宽度或其他任何您需要的参数作为参数发送到{{1} } 方法。在这里有很多不同的方法。 (或者如果setup
已经是width
的属性,就像Metrics
属性一样,那么您就不需要传递它。只需在设置完成后调用frame
setup
框架尺寸(如果适用)。)
答案 2 :(得分:0)
我不明白这个问题。 Setup
通常不应该依赖于子类的行为。如果setup取决于self.width
,则覆盖子类中的width
getter。这应该给你width
的子类。
看看以下简单的设置。方法- (CGFloat) width
会覆盖Metrics
中的合成getter并很好地返回17.33
。
#import <Foundation/Foundation.h>
@interface Metrics : NSObject
- (id) init;
- (void) setup;
@property (assign, nonatomic) CGFloat width;
@end
@interface SubclassOfMetrics : Metrics
- (CGFloat) width;
@end
@implementation Metrics
@synthesize width;
- (id) init
{
self = [super init];
if (self)
[self setup];
return self;
}
- (void) setup;
{
NSLog(@"%f", self.width);
}
@end
@implementation SubclassOfMetrics
- (CGFloat) width
{
return 17.33;
}
@end
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Metrics *met = [[SubclassOfMetrics alloc] init];
[met release];
[pool drain];
return 0;
}
正如预期的那样输出:
2011-08-31 18:09:41.630 MetricsTest[44098:707] 17.330000
这假定在width
被调用时initXYZ:
已知(例如,作为SubclassOfMetrics
setup
方法的参数传递)。否则,只要设置了实际的setup
,您就必须手动拨打width
。
答案 3 :(得分:0)
我在初始化超类之前设置了iVars。
- (id)initWithFrame:(CGRect)frame andMetricWidth:(CGFloat)inWidth {
_width = inWidth
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
我故意直接设置iVar以避免使用属性语法产生任何潜在的副作用。我还没有完全考虑过它如何在ARC下使用对象,但它可以很好地处理原语。
答案 4 :(得分:0)
我不确定是否有一个特殊原因导致-setup
中的功能无法简单地重构为-initWithFrame:
,所以我要求拒绝此答案更准确的方法版本,因此我们可以看到何时调用-setup
。
事实是,在某个地方,某个地方,如果你想要一个正确初始化的对象,你必须记住“以某种顺序执行动作” - 特别是当它涉及类层次结构时。当前示例的工作方式,Metrics的所有子类及其子类都调用相同的无用的init方法。这可以很容易地放入第一代子类并自动调用它的-setup
版本,因为如果你有一天认为它值得sublcass subclassOfMetrics
,那么它会覆盖其父级的无操作方法,你将最终取代-setup
的功能版本,或者不得不记得打电话给[super setup]
来获取功能,无论如何都要打败你的黑客目的。
这当然是假设您不必担心添加新的ivars,您当然会这样做。
事实上,-init
函数专门用于初始化类的ivars,并且设置了常规约定,以便在每个类中只有一个函数,您必须记住“按特定顺序执行操作“。一系列精心编写的初始化函数使您不必担心何时调用类似-setup
5个方法的函数堆栈更深入的方法。目前的惯例是“调用super的init,初始化我的ivars,做无关的设置”,因为它确保了一个类的所有设置都以正确的顺序完成,无论你的层次结构最后有多少代。
代码是样板文件(虽然仍然保持非常小),只有才能做出真正的决定“如果必要的设置进入init
,它会影响所有孩子,或者它应该进入-setup
功能,只会被调用一次,并且对我的班级非常具体?“
(重要性加粗)
然后,一旦你有一个好的init,你就可以创建一个看起来像这样的简单类方法:
+ (Metric*) newMetricWithFrame:(CGRect)frame {
Metrics* theMetric = [[self alloc] init]; //this will allow you to call it on whatever class you want and get the correct, fully initialized object
[theMetric setup]; //this will call the correct setup method
return theMetric;
}
然后你可以调用这个函数,就像这个[subclassOfMetrics newMetricWithFrame:frame];
一样,你将得到一个编译器将其视为Metric
的对象,但它将作为{{1}响应所有方法调用}}。您还可以转换返回结果,以便编译器不会抱怨在其上调用特定于子类的方法:
subclassOfMetrics
答案 5 :(得分:0)
您可以让控制器调用Metrics
子类中的方法,然后调用setup
- 或者,如果您不喜欢该方法,请使用{{1} }并注册notification center
以便在子类中完成计算时收到通知,然后在子类中完成计算后发布通知。做这种事情是非常简单和非常方便的方法。当Metrics
收到此通知时,它会运行Metrics
方法。如果setup
是您希望在显示到屏幕之前完全设置的视图或其他内容,请添加Metrics
完成notification
后视图控制器收到的另一个Metrics
之后控制器setup
完成所有操作。