0

これを最も雄弁に述べる方法はわかりませんが、最善を尽くします。いくつかのプロパティを持つ汎用オブジェクトであるカスタム クラスを作成しました。それを拡張し、スーパークラスよりも具体的にするために、いくつかのサブクラスを作成しました。そのため、例として、私が達成したいことを説明するためだけに、適切な構文である場合とそうでない場合があるいくつかの一般的なサンプル コードをスローします。

@interface Vehicle : NSObject

@property (nonatomic) int wheels;

- (id)initWithNumberOfWheels:(int)wheels;

@end

そこから、クラスに詳細を与える同じ「車」と「トラック」のサブクラスをいくつか作成します。

@interface Car : Vehicle

@property (nonatomic) BOOL convertible;
@property etc...

@end

と...

@interface Truck : Vehicle

@property (nonatomic) BOOL is4x4;
@property (nonatomic) int numberOfDoors;

@end

ここからが興味深いところです。これらのオブジェクトを割り当てる別のクラスを作成したいのですが、init メソッドで車両の「タイプ」を決定したいのですが、同じ @property 変数を使用します。たとえば(繰り返しますが、これは視覚的に表現するためだけのガベージ コードです)。

Road.h

#import "Car.h"
#import "Truck.h"

@interface Road : NSObject

@property (strong, nonatomic) NotSureWhatToUseHereToMakeThisWork *myRide;
// doesn't work: @property (strong, nonatomic) id myRide; 
// doesn't work: @property (strong, nonatomic) Vehicle *myRide; 


- (id)initWithCars;
- (id)initWithTrucks;

@end

Road.m

@implementation Road

- (id)initWithCars
{
//standard init code...

    myRide = [[Car alloc] initWithNumberOfWheels:4];
    myRide.convertable = NO;
}

- (id)initWithTrucks
{
//standard init code...

    myRide = [[Truck alloc] initWithNumberOfWheels:6]; 
//yes, some trucks have more than 4 wheels

    myRide.is4x4 = YES;
}

@end

肝心なのは、 @property でスーパークラスを使用すると、サブクラスのプロパティが明らかに取得されないことです。基本的に、これらすべてを可能な限り一般的で再利用可能なものにしたいと考えています。それ以来、車専用の特別な「道路」クラスとトラック用の特別な「道路」クラスを作成することはできません。やっぱり道は道。とにかく私が求めていることをすることはありますか? このようなことを行うより良い方法はありますか?主な目的は、特定の状況でのみ特定のプロパティを継承するオブジェクトを持つことです。余分な @properties を作成したくない理由は、状況に適用できない場合にそれらを表示したくないからです。

編集:この質問を投稿する前に試したことを示すために、いくつかのスニペットを追加しましたが、うまくいきませんでした。

回答: 誰かが興味を持っている場合の正しい「回答」は、「補遺」の CRD の回答にあります。これが機能する理由は、タイプ「id」がメソッドを呼び出すことしかできず、プロパティを継承しないためです。むしろ回避策(私がこれを研究していたので、これは良いプログラミングではなく、可能であれば避けるべきであるという結論に達しました)は、アクセサメソッドを使用してプロパティを取得/設定することです。

id mySomethingObject = [[SomeClass alloc] init...];
[mySomethingObject setPropertyMethod]...; //sets value
[mySomethingObject propertyMethod]...; //gets value

使おうとするのではなく…

mySomethingObject.property = ; //set
mySomethingObject.property; //get

正解で述べたように、「id」を割り当てたクラスがそのメソッドに応答しない場合、プログラムはクラッシュします。

4

5 に答える 5

5

あなたは多くの問題を混乱させているようです。

まず、インスタンスの型とインスタンスへの参照を保持する変数の型があります。オブジェクトが作成されると、それは特定のタイプであり、そのタイプは変更されませ[*]。変数にも型があり、それも変わりません。サブタイプ化/継承により、が のスーパータイプである場合、あるタイプ のオブジェクトへの参照をT他のタイプの変数に格納できます。SST

次に、静的型付けと動的型付けがあります。Objective-C では動的型付けが使用されます。動的型付けでは、一部の操作で使用されるオブジェクトの実際の型が実行時に決定されますが、コンパイラ自体は静的型付け (コンパイル時に型が決定される) を使用して、正しいプログラムを作成できるようにします。コンパイラの静的チェックで警告が生成される場合もありますが、コンパイラがコンパイルを拒否する場合もあります。特に、プロパティ参照は静的型付けに基づいてコンパイルされます。

あなたの例では、これは、参照されるオブジェクトが aであることがわかっている場合でも、 type の変数によって参照されるオブジェクトのプロパティを直接参照できないことを意味します。CarVehicle *CarVehicle

解決策は、まず参照されるオブジェクトの実際の型をテストしてから、より正確な型のローカル変数を導入するか、多くのキャストを使用することです。例えば:

// (a) create an object of type Car (for a Reliant Robin ;-))
// (b) create a variable of type Car and store in it a reference to the created Car
Car *myCar = [[Car alloc] initWithNumberOfWheels:3];

// Create a variable of type Vehicle and store in it the reference stored in myCar
// The created instance is *still* a Car
Vehicle *myRide = myCar;

// See if myRide is a Car and then do something
if ([myRide isKindOfClass:Car.class])
{
    // create a variable of type Car to avoid having to continually cast myRide
    Car *myCarRide = (Car *)myRide; // due to if above we know this cast is valid

    if (myCarRide.isConvertible) ...

中間変数なしでこれを行うには、キャストを使用します。

...
// See if myRide is a Car and then do something
if ([myRide isKindOfClass:Car.class])
{
    if (((Car *)myCarRide).isConvertible) ...

これは、中間変数アプローチが優れている理由を示しています!

initWithTrucks最後の例として、次のようにメソッドを記述します。

- (id)initWithTrucks
{
   //standard init code...

   Truck *myTruck = [[Truck alloc] initWithNumberOfWheels:6]; 
   //yes, some trucks have more than 4 wheels
   myTruck.is4x4 = YES;

   // Store the reference to the created Truck in myRide
   myRide = myTruck;
}

HTH

補遺

あなたのコメントから、動的型付けを探している可能性があり、コンパイラーに静的型付けを実行させたくないようです。これは (部分的に) サポートされていますが、プロパティにドット表記を使用していません。ゲッター メソッドとセッター メソッドを直接使用する必要があります。

まず、typeであるRoadと宣言します。myRideid

@interface Road : NSObject

@property id myRide;

id型は、(a) 任意のオブジェクト参照と、(b) オブジェクトにメソッドが存在することを静的にチェックしないという 2 つのことを意味します。ただし、コンパイラは、呼び出されたメソッドが何らかのオブジェクトに存在することを認識している必要があり、メソッドへの引数に対して静的な型チェックを引き続き実行します。したがって、完全な動的型付けではありません (ただし、id型付き式を渡すか、引数を取るメソッドを宣言できます)。もちろんタイプid...)。

次に、プロパティへのすべての参照を getter メソッドまたは setter メソッドを直接使用し、ドット表記を使用しないようにします (プロパティ以外のメソッドについては、通常どおり呼び出すだけです)。例えば:

- (id)initWithTrucks
{
   //standard init code...

   myRide = [[Truck alloc] initWithNumberOfWheels:6]; 
   //yes, some trucks have more than 4 wheels
   [myRide setIs4x4:YES];
}

オブジェクトを参照している[myRide setIs4x4:YES]などの呼び出しを行うと、実行時エラーが発生します。myRideCar

一般的な推奨事項は、コンパイラの静的型チェックを可能な限り維持することです。

[*] ランタイム マジックは無視します。ドラゴンが存在します。通常のコードでは、オブジェクトの型が変わることはありません。

于 2013-09-06T23:17:01.120 に答える
3

特定のプロパティを取得するには、タイプ「Vehicle」を使用してから、「Truck」または「Car」でオブジェクトをキャストする必要があります

于 2013-09-06T18:47:25.767 に答える
3

最も一般的なアーキテクチャはVehicleProtocol、どのクラスでも実装できる を作成することです。プロトコルを実装する Vehicle クラスとそのサブクラス (NSObject プロトコルを実装する NSObject に似ています) を持つことも、独立したクラスにそれを実装させることもできます。道路にはプロパティがあります@property (strong) id<VehicleProtocol> myRide


後のアーキテクチャの完全な例: 車両スーパークラスはありませんが、すべて VehicleProtocol です

#import <Foundation/Foundation.h>


@protocol VehicleProtocol <NSObject>
@property (nonatomic) NSUInteger wheels;
@end

@interface Car : NSObject <VehicleProtocol>
@property (nonatomic) BOOL convertible;
@property (nonatomic) NSUInteger wheels;
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels;
@end

@implementation Car
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels
{

    if (self = [super init]) {
        _wheels = numberOfWheels;
    }
    return self;
}
@end


@interface Truck : NSObject <VehicleProtocol>

@property (nonatomic) BOOL is4x4;
@property (nonatomic) int numberOfDoors;
@property (nonatomic) NSUInteger wheels;
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels;

@end

@implementation Truck
-(id)initWithNumberOfWheels:(NSUInteger) numberOfWheels
{

    if (self = [super init]) {
        _wheels = numberOfWheels;
    }
    return self;
}
@end




@interface Road : NSObject
@property (strong) id<VehicleProtocol> myRide;
@end

@implementation Road
@end

int main(int argc, const char * argv[])
{

    @autoreleasepool {

        NSArray *vehicles =  @[[[Car alloc] initWithNumberOfWheels:4], [[Car alloc] initWithNumberOfWheels:3], [[Truck alloc] initWithNumberOfWheels:10]] ;

        for (id v in vehicles) {
            if ([v isKindOfClass:[Truck class]]) {
                [v setIs4x4:YES];
            }
        }


        Road *road = [[Road alloc] init];
        road.myRide = vehicles[0];
        NSLog(@"%@", road.myRide);


        road.myRide = vehicles[2];
        NSLog(@"%@", road.myRide);

        NSObject *obj = [[NSObject alloc] init];
        road.myRide = obj; // warning in this line
        NSLog(@"%@", road.myRide);


    }
    return 0;
}

確かに、「従来のサブクラス化」よりも多くのコード行が含まれる可能性がありますが、依存関係は少なくなります。代わりに、クラスは履行する契約に同意します。ここでのコントラクトは、オブジェクトが任意の数の車輪を持つことのみを要求します。

Road を作成し、最初に車とトラックを割り当てることに注意してください (また、 を介して車とトラックを識別する方法も示します-isKindOfClass:)、Car と Truck はコントラクトを完全に満たすため、両方とも警告やエラーなしで機能します。プレーンな NSObject を割り当てるよりも。ここでコンパイラは、NSObject がプロトコルを実装していないことを認識しているため、警告します。これはコンパイラ エラーではありませんが、そのオブジェクトに対してプロトコル固有のメソッドを使用するかどうかをコンパイラが認識していないためです。

myRide に割り当てられた単純な NSObject の場合、この行

NSLog(@"%@ %ld", road.myRide, (unsigned long)road.myRide.wheels);

(NSObject インスタンスが wheel に応答しないため) ランタイム クラッシュが発生しますが、コンパイル時には警告も発生しません。

于 2013-09-06T19:02:08.023 に答える
0

それを にし、Vehicle*各クラスを実装typeして、そのクラスの型を示す定数を返します。

@property (nonatomic, strong) Vehicle* yourRide;
...
if (yourRide.type == VehicleConstant_Truck) {
  Truck* yourTruck = (Truck*) yourRide;
  NSLog(@"This truck %s a 4x4", yourTruck.is4x4 ? "is" : "isn't");
}

@vikingosegundo を満足させるには、次の方法があります。

if ([yourRide isKindOfClass:[Truck class]]) {

if上記のステートメントの代わりに。

于 2013-09-06T19:32:39.833 に答える