รับทําเว็บไซต์ รับทําseo
 
รับทําเว็บไซต์ รับทําseo
บทความที่น่าสนใจ

บทความ ที่น่าสนใจ

Objective-C : More on classes & Property (ตอนที่4)

    Overriding Methods

    ในหัวข้อนี้เราจะได้เรียนรู้เกี่ยวกับ คุณสมบัติอย่างหนึ่งของ Object Oriented Programming ที่สำคัญมากๆ ก็คือ Overriding Methods จากที่เราได้ทราบกันแล้วว่าถ้าหากเราสร้างคลาสใหม่โดยการ sub class คลาสนั้นจะไม่สามารถลบเมธอดที่ติดมากับ parent ออกไปได้ อย่างไรก็ตามเราสามารถเปลี่ยนแปลงแก้ไขการทำงานของเมธอดนั้นได้ โดยการเขียนทับหรือมีศัพท์ทางเทคนิคว่า overriding methods ลองพิจารณาโปรแกรมตัวอย่างง่ายต่อไปนี้

     

    Program 7.5

    Vehicle.h

    1
    2
    3
    4
    5
    6
    
    @interface Vehicle : NSObject
    {
        int speed;
    }
    -(void) initDefaultSpeed;
    -(void) printDetails;

     

    Vehicle.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    @implementation Vehicle
    -(void) initDefaultSpeed
    {
        speed = 140;
    }-(void) printDetails
    {
        NSLog(@"speed %d",speed);
    }
    @end

     

    จากนั้นสร้างคลาส Car โดยการซับคลาส Vehicle

     

    Car.h

    1
    2
    3
    4
    5
    
    @interface Car : Vehicle
    {
    }
    -(void) doubleSpeed;
    @end

     

    Car.m

    1
    2
    3
    4
    5
    6
    
    @implementation Car
    -(void) doubleSpeed
    {
        speed = speed * 2;
    }
    @end

     

    และส่วนสุดท้ายเขียนส่วนการทำงานของโปรแกรมง่ายๆดังนี้


    main.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    #import <Foundation/Foundation.h>
    #import "Vehicle.h"
    #import "Car.h"
     
    int main (int argc, const char *argv[])
    {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
     
        Vehicle *veh = [[Vehicle alloc] init];
        Car *car = [[Car alloc] init];
     
        [veh initDefaultSpeed];
        [car initDefaultSpeed];
     
        
[veh printDetails];
        [car printDetails];
     
        NSLog(@"Car double Speed");
        [car doubleSpeed];
        [car printDetails];
     
        
[veh release];
        [car release];
     
        [pool drain];
        return 0;
     
    }

     

    Program 7.5 Output
    speed 140.0
    speed 140.0
    Car double Speed
    speed 280.0

     

    ก็ อย่างที่ได้กล่าวไปว่า child class นั้นจะได้รับทุกๆอย่างมาเหมือนกับ parent class ถ้าเราอยากจะแก้ไขให้คลาส Car มีค่า speed เริ่มต้นเป็น 200 จะทำอย่างไร ? แน่นอนว่าอย่างแรกที่เราทำได้ก็คือการเขียน method ขึ้นมาใหม่ แต่เราก็ต้องแก้ไขโปรแกรมให้เรียกใช้เมธอดใหม่ด้วย ลองจินตนาการว่าถ้าหากโปรแกรมหลักของเรา เรียกใช้เมธอดนี้หลายๆส่วน ปัญหาย่อมเกิดขึ้นแน่นอน เพราะเราต้องตามไปแก้ไขหลายๆส่วนของโปรแกรม ดังนั้นเราจะใช้การ Overiding Methods เพื่อเขียนทับเมธอดของเก่าที่ได้มาจาก parent วิธีการก็ไม่ได้ยากอะไร ชื่อก็บอกอยู่แล้วว่าเขียนทับ ดังนั้นก็เขียนใหม่ได้เลย

     

    // Car.m
    @implement Car 
    -(void) initDefaultSpeed
    {
        speed = 500;
    }
    @end

     

    ส่วนโปรแกรมหลักก็ไม่ได้แก้ไขอะไร เมื่อคอมไพล์และรันโปรแกรม ก็จะแสดงผลดังนี้

     

    Program 7.5Output (Fixed)
    speed 140.0
    speed 500.0
    Car Double Speed
    speed 1000.0

     

    ลองดูอีกสักตัวอย่างเพื่อความเข้าใจ เราจะแก้ไขการทำงานโปรแกรม 7.5 โดยการเรียกเมธอด doubleSpeed ดังตัวอย่าง

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    #import <Foundation/Foundation.h>
    #import "Vehicle.h"
    #import "Car.h"
     
    int main (int argc, const char *argv[])
    {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
     
        Vehicle *veh = [[Vehicle alloc] init];
        Car *car = [[Car alloc] init];
     
        [veh initDefaultSpeed];
        [car initDefaultSpeed];
     
        
[veh printDetails];
        [car printDetails];
     
        NSLog(@"Car double Speed");
        [car doubleSpeed];
        [car printDetails];
     
        NSLog(@"Veh double Speed");
        [veh doubleSpeed];
        [veh printDetails];
     
        
[veh release];
        [car release];
     
        [pool drain];
        return 0;
     
    }

     

    เมื่อเราคอมไพล์โปรแกรมที่ได้แก้ไขไป XCode จะแจ้งเตือนด้วยข้อความดังนี้

     

    vec_warn

     

    โปรแกรม แจ้งเราว่า ‘Vehicle’ may not respond to ‘doubleSpeed’ ก็หมายความว่า Vehicle อาจจะไม่ตอบสนองต่อการเรียกใช้เมธอด doubleSpeed นั่นก็เพราะว่าคลาส Vehicle นั้นไม่มีเมธอดนี้นั่นเอง เนื่องจากคุณสมบัติ inheritance นั้นจะเป็นลักษณะการสืบทอดจาก parent ไปยัง child อย่างเดียวเท่านั้น การแก้ไขส่วนต่างๆของ child จะไม่ได้เกี่ยวข้อง parent แต่อย่างใด ในกรณีนี้่คลาส Car เป็น child ส่วนคลาส Vehicle เป็น parent การเพิ่มเมธอด doubleSpeed ให้กับ Car ก็ไม่ได้เกี่ยวข้องอะไรกับ Vechicle นั่นเอง

     

    Init , Dealloc and Super keyword

    ย้อนกลับ ไปยังโปรแกรมที่ 7.4 โปรแกรมนี้ก็ทำงานได้สมบูรณ์ดีแต่ก็ยังไม่ใช่แนวทางที่ถูกต้องนัก เพราะถ้าจะพิจารณาดีๆแล้วก็จะเห็นว่า หลังจาก init แล้วเราต้องเรียก initHarddisk เพื่อเตรียมความพร้อมของออบเจ็กต์ต่างๆภายในคลาสอย่างเช่น  externalHarddisk และเมื่อใช้เสร็จก็ต้องคืนหน่วยความจำของออบเจ็กต์ที่เราได้จองไปด้วยคำสั่ง releaseExternalHarddisk และหลังจากนั้นถึงจะเรียก release อีกที ซึ่งค่อนข้างจะยุ่งยากอยู่พอสมควร และจากหัวข้อที่ผ่านมาเราได้ใช้ Overinding Method กันไปแล้ว ก็จะเห็นได้ว่าเราสามารถ overidding เมธอดใดๆของ parent ก็ได้ เมื่อเราทราบเช่นนี้แล้วเราจะอาศัย overriding เพื่อเปลี่ยนการทำงานเมธอด init ของ NSObject ให้มีความสามารถในการจองหน่วยความจำของตัวแปรในคลาสของเรา และ overriding เมธอด dealloc ➊ เพื่อใช้ในการคืนหน่วยความจำให้กับออบเจ็กต์ที่คลาสได้สร้างไว้ เราจะเขียนโปรแกรมใหม่โดยมีโค้ดดังนี้


    Program 7.6

    SolidStateDisk.h

    1
    2
    3
    4
    5
    6
    7
    8
    
    #import <Foundation/Foundation.h>
     
    @interface SolidStateDisk : NSObject
    {
        int capacity;
    }
    @property int capacity;
    @end

     

    SolidStateDisk.m

     

    1
    2
    3
    4
    5
    
    #import "SolidStateDisk.h"
     
    @implementation SolidStateDisk
    @synthesize capacity;
    @end

     

    MacBook.h

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    #import <Foundation/Foundation.h>
    #import "SolidStateDisk.h"
     
    @interface MacBook : NSObject
    {
        float speed;
        SolidStateDisk *internalHarddisk;
    }
    @property float speed;
    -(void) printSpec;
    @end

     

    MacBook.m

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    #import "MacBook.h"
     
    @implementation MacBook
    @synthesize speed;
    -(id)init
    {
        self = [super init];
        if( self != nil )
        {
            internalHarddisk = [[SolidStateDisk alloc] init];
            internalHarddisk.capacity = 500;
        }
        return self;
    }
     
    -(void) printSpec
    {
        NSLog(@"speed %f GHz",speed);
        NSLog(@"ssd %d GB.",internalHarddisk.capacity);
    }
     
    -(void) setExternalHarddisk:(SolidStateDisk *)disk
    {
        internalHarddisk = disk;
    }
     
    -(void) dealloc
    {
        [internalHarddisk release];
        [super dealloc];
    }
    @end

     

    จากโค้ดตัวอย่างของคลาส MacBook จะเห็นว่าเราได้ overridding เมธอด init เพื่อเขียนการทำงานใหม่ การทำงานใหม่นี้ในขึ้นตอนแรกเราจะเรียก

     

        self = [super init];

     

    สิ่ง ที่เพิ่มเข้ามาในบรรทัดนี้คือคีย์เวิดร์ super ซึ่งเป็นคำสั่งเอาไว้อ้างอิงถึง parent ของคลาสที่ได้สืบทอดมา และการที่เราต้องให้ค่า self (ตัวเอง) มีค่าเท่ากับ [super init] ก็เพราะว่า [super init] เปรียบเสมือนการเรียกเมธอด init ของ NSObject

     

    ซึ่ง จะเตรียมค่าพื้นฐานต่างๆของภายใน NSObject หลังจากนั้นจะส่งค่าตำแหน่งหน่วยความจำที่ได้จองไปกลับมาให้ เราจึงต้องให้ self เท่ากับตำแหน่งหน่วยความจำที่ NSObject ได้จองไว้ให้เรานั่นเอง

     

    ในกรณีที่จองหน่วยความจำไม่ได้ก็จะได้ค่า nil กลับมา ดังนั้นเราจึงต้องตรวจสอบค่าด้วยคำสั่ง

     

    if( self != nil )

     

    เมื่อทุกอย่างเรียบร้อย เราก็จะจองหน่วยความจำให้กับตัวแปรในคลาสของเราต่อ นั่นก็คือ

     

    internalHarddisk = [[SolidStateDisk alloc] init];

     

    และสุดท้ายเราก็จะส่ง self หรือตำแหน่งความจำของออบเจ็กต์นี้กลับไปนั่นเอง

     

    return self;

     

    และอีกหนึ่งเมธอดที่เราได้ Overriding ไปนั่นก็คือ dealloc ซึ่งมีโค้ดดังนี้

     

        [internalHarddisk release];

     

    เมื่อดูการทำงานจากโค้ดแล้วก็จะเห็นว่า เราได้คืนหน่วยความจำให้ในส่วนของออบเจ็กต์ต่างๆภายในคลาสก่อน หลังจากนั้น ถึงจะเรียก

     

       [super dealloc];

     

    เพื่อเป็นการคืนหน่วยความจำให้กับออบเจ็กต์ต่างๆของ parent ที่ได้จองไว้นั่นเอง
    ในส่วนของโปรแกรมหลักของเราก็จะมีหน้าตาแบบนี้

     

    main.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    #import <Foundation/Foundation.h>
    #import "MacBook.h"
    #import "SolidStateDisk.h"
     
    int main (int argc, const char *argv[])
    {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
     
        MacBook *macbook = [[MacBook alloc] init];
        macbook.speed = 2.7;
        [macbook printSpec];
     
        [macbook release];
     
        [pool drain];
        return 0;
     
    }

     

    Program 7.6 Output
    speed 2.700000 GHz
    ssd 500.0 GB

     

    เห็น ได้ว่าคลาส MacBook นั้นได้ลดจำนวนเมธอดในการจองหน่วยความจำและคืนหน่วยความจำสำหรับตัวแปรของ คลาส และประโยชน์อีกอย่างคือถ้าหากเราเขียนเมธอดแยกอย่าง initHarddisk บางครั้งเราอาจจะลืมเรียกใช้งาน แต่ถ้าเรา Overiding Method อย่าง init ก็ไม่ต้องกังวลาปัญหานี้

     

    More init

    ถึงแม้ว่าเราจะ overriding method อย่าง init ได้ แต่ในบางครั้งเราอาจจะจำเป็นที่จะต้องความยืดหยุ่น เช่น สมมติว่าถ้าหากเราต้องการจะประกาศออบเจ็กต์ MacBook แบบกำหนดขนาดของฮาร์ดดิสเริ่มต้นได้เอง การใช้ init อาจจะไม่ครอบคลุม ดังนั้นแล้วก็อาจจะจำเป็นต้องเขียนเมธอด init แบบพิเศษเพิ่มเติมเพื่อช่วยอำนวยความสะดวก ดังเช่นตัวอย่าง

     

    -(id)initWithSSDSize:(int) size
    {
        self = [self init];
        if( self != nil )
        {
            internalHarddisk.capacity = size;
        }
        return self;
    }

     

    ถ้าจะสังเกตจะเห็นว่า เราเรียก

     

       self = [self init];

     

    ไม่ ได้เรียก super แต่อย่างใด นั่นก็เพราะว่าเราก็ยังมีเมธอด init ของเก่าที่เราได้เขียนไป ฉะนั้นเราก็สามารถใช้ของเดิมได้ ซึ่งช่วยลดการเขียนโค้ดลง

     

    Program 7.7

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    #import <Foundation/Foundation.h>
    #import "MacBook.h"
     
    int main (int argc, const char *argv[])
    {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
     
        MacBook *macbookA = [[MacBook alloc] init];
        MacBook *macbookB = [[MacBook alloc] initWithSSDSize: 300];
     
        NSLog(@"MacBook A");
        macbookA.speed = 2.7;
        [macbookA printSpec];
     
        NSLog(@"MacBook B");
        macbookB.speed = 1.5;
        [macbookB printSpec];
     
        [macbookA release];
        [macbookB release];
     
        [pool drain];
        return 0;
     
    }

     

    Program 7.7 Output
    MacBook A
    speed 2.700000 GHz
    ssd 500 GB.

     

    MacBook B
    speed 1.50000 GHz
    ssd 300 GB.

     

    ตอนนี้คลาส MacBook สุดท้ายที่เราได้เขียนไปก็มีความยืดหยุ่นเพิ่มมากขึ้นกว่าคลาส Computer ในช่วงแรกๆที่เขียน
    และในบทนี้เราก็ได้เรียนรู้คุณสมบัติที่สำคัญต่างๆของคลาส และการประยุกต์นำไปใช้ ในบทหน้าเราจะยังอยู่กับเรื่องของคลาส และจะก้าวแต่ไปยัง Framework ซึ่งเป็นส่วนสำคัญมากของการเขียนโปรแกรมด้วยภาษา Objective-C

     

     

บทความที่น่าสนใจ

บทความ ล่าสุด

บทความ ความรู้ด้านไอที, คอมพิวเตอร์ Techonlogy, Gadget, ความรู้เกี่ยวกับคอมพิวเตอร์ กับทาง SoftMelt.com