Каков наилучший способ обеспечить наличие свойства во время инициализации? - PullRequest
2 голосов
/ 31 августа 2011

Я создал класс Metrics, который предназначен для создания подклассов для настройки поведения. Чтобы сделать это более надежным, метод init Metrics вызывает метод с именем setup, который по умолчанию ничего не делает. Если подклассы хотят настроить поведение во время инициализации (что они обычно делают), они могут переопределить этот метод. Поскольку реализация по умолчанию ничего не делает, не нужно забывать вызывать [super 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

У меня проблема в том, что setup вызывается до того, как можно установить свойство width. Какие варианты у меня есть здесь? Я могу обойти это, не инициализируя мой Metrics класс в методе init. Я могу сделать это явно после того, как я установил свойства, которые мне нужно установить. Я не фанат этого, так как это требует от меня выполнения действий в определенном порядке и не забудьте настроить. У меня есть другие варианты?

Edit: корень проблемы на самом деле в том, что инициализация класса Metrics делает много вычислений. Результаты этих вычислений будут зависеть от свойств, устанавливаемых в подклассе, поэтому мне нужно, чтобы эти свойства были установлены до инициализации суперкласса.

Ответы [ 6 ]

2 голосов
/ 31 августа 2011

Инициализаторы для объектов уже разработаны, чтобы быть той «установкой», которую вы пытаетесь создать. Вы должны просто использовать инициализатор и выполнить любую настройку, необходимую для данного класса, после вызова суперинициализатора. Если у вас есть новые свойства, которые необходимо учитывать для инициализации (настройки), создайте новый назначенный инициализатор для вашего подкласса. Это внедрение зависимостей улучшит ваш общий дизайн и удовлетворит ваши потребности в инициализации.

@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 голос
/ 31 августа 2011

Если ваш Metrics объект является переменной экземпляра другого объекта, такого как контроллер или любой другой класс, вы можете использовать этот класс для отправки сообщений на Metrics после определения информации, необходимой для настройки объекта,Я часто буду делать что-то вроде этого:

Metrics*myMetrics=[[Metrics alloc] init]; // just creates the Metrics object

Я мог бы сделать это в viewDidLoad контроллера или там, где это уместно.

Тогда позже вы можете сделатьэто из вашего управляющего объекта:

[self.myMetrics setup];

Если для этого требуется что-то вроде «width», вы можете либо установить Metrics ivar и вызвать его из setup, либо отправить ширинуили что-то еще, что вам нужно в качестве аргумента метода setup.Много разных способов сделать это здесь.(или если width уже является свойством Metrics, как свойство frame, то вам не нужно его передавать. Просто вызовите setup после того, как вы установили размер кадра Metrics, если применимо.)

0 голосов
/ 31 августа 2011

Вы можете заставить свой контроллер вызывать методы вашего подкласса (ов) Metrics, прежде чем он затем вызовет setup - или, если вам не нравится этот подход, используйте notification center и зарегистрируйте Metricsчтобы получать уведомления, когда вычисления выполнены в ваших подклассах, а затем в ваших подклассах опубликуйте уведомление, когда они завершат вычисления.Это довольно простой и очень удобный способ сделать такую ​​вещь.Когда Metrics получает это уведомление, он запускает метод setup.Если Metrics является представлением или чем-то, что вы хотите полностью настроить перед отображением на экране, добавьте еще одно notification, которое получит контроллер представления, когда Metrics выполнено с setup, и затем установите контроллер addSubview после все, что завершено.

0 голосов
/ 31 августа 2011

Я не совсем уверен, есть ли конкретная причина, по которой функциональность в -setup просто не может быть реорганизована в -initWithFrame:, поэтому я собираюсь отказаться от этого ответа с просьбой увидеть более точную версиюваши методы, чтобы мы могли видеть, когда -setup вызывается.

В том-то и дело, что в какой-то момент, где-то, вы должны помнить «выполнять действия в определенном порядке», если хотитеправильно инициализированный объект - особенно когда он включает иерархии классов.Как работает ваш текущий пример, все подклассы Metrics, а также их подклассы вызывают один и тот же, в основном, бесполезный метод init.Это может упростить добавление в подкласс первого поколения и автоматический вызов его версии -setup, потому что он переопределяет метод no-op его родителя, если вы когда-нибудь решите, что стоит подкласс subclassOfMetrics, вы собираетесьзакончить замену функциональной версии -setup, или не забыть позвонить [super setup], чтобы получить функциональность, в любом случае победив цель вашего взлома.

Это, конечно, при условии, что вы этого не сделаливам нужно беспокоиться о добавлении новых ivars, что вы, конечно, делаете.

В том-то и дело, что функция -init специально предназначена для инициализации иваров класса, а общее соглашение настроено так, что выесть только одна функция в каждом классе, в которой вы должны помнить, «выполнять действия в определенном порядке».Хорошо написанный ряд функций инициализации позволяет вам не беспокоиться о том, когда вызывать метод, такой как -setup 5 кадров глубже в стеке функций.Действующее соглашение «вызовите super's init, инициализируйте мои ivars, делайте посторонние настройки» существует, потому что оно гарантирует, что все настройки для класса выполняются в правильном порядке, независимо от того, сколько поколений в вашей иерархии заканчивается.

Код является шаблонным (хотя он и остается довольно минимальным), с единственными реальными решениями, которые вам необходимо принять, «если эта часть необходимой настройки войдет в init, где она затронет всех детей или должнаперейти к функции -setup, которая будет вызываться только один раз и будет очень специфична для моего класса? " (выделено жирным шрифтом для важности)

Затем, когда у вас будет хороший инициат, вы сможетесоздайте простой метод класса, который выглядит следующим образом:

+ (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, нокоторый будет отвечать на все вызовы метода как subclassOfMetrics.Вы также можете привести возвращаемый результат, чтобы компилятор не жаловался на вызов для него методов, специфичных для подкласса:

subclassOfMetrics* theSub = (subclassOfMetrics*) [subclassOfMetrics newMetricWithFrame:frame]; [theSub subclassSpecificMethod];

0 голосов
/ 31 августа 2011

Я установил iVars перед инициализацией суперкласса.

- (id)initWithFrame:(CGRect)frame andMetricWidth:(CGFloat)inWidth {
  _width = inWidth

  self = [super initWithFrame:frame];
  if (self) {
  }

  return self;
}

Я намеренно установил iVar напрямую, чтобы избежать возможных побочных эффектов от использования синтаксиса свойства. Я не совсем продумал, как это будет работать с объектами в ARC, но он отлично работает с примитивами.

0 голосов
/ 31 августа 2011

Я не понимаю проблемы.Setup обычно не должно зависеть от поведения подкласса.Если настройка зависит от self.width, переопределите метод получения width в подклассе.Это должно дать вам width подкласса.

Взгляните на следующую простую настройку.Метод - (CGFloat) width переопределяет синтезированный геттер в Metrics и приятно возвращает 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...