วิธีคิดแบบนักวิทยาการคอมพิวเตอร์/Classes and objects

บทที่ 12 Classes and objects แก้ไข

12.1 User-defined compound types แก้ไข

ในการใช้งานลักษณะบางอย่างในภาษา Python นั้น เราจะต้องสร้าง user-defined type : the Point ขึ้นมาเสียก่อน

พิจารณาตามหลักของตำแหน่งในทางคณิตศาสตร์ โดยให้มี 2 มิติ โดยแต่ละ point ( จุด )จะมีตัวเลขอยู่ 2 จำนวน ( coordinate ) ที่จะทำงานร่วมกันเสมือนเป็น object เดียวกัน ในทางคณิตศาสตร์นั้น เครื่องหมาย point จะเขียนอยู่ในวงเล็บพร้อมกับเครื่องหมายจุลภาคเพื่อไว้แยกแยะตำแหน่งพิกัด เช่น ( 0,0 ) โดยแสดงให้เห็นว่าเป็นจุดกำเนิด หรือตำแหน่งของ ( x,y ) โดยค่า x จะเลื่อนไปทางขวา ส่วนค่า y จะเลื่อนขึ้นจากจุดกำเนิด

โดยทั่วไปจะแสดงเป็น point ในภาษา Python ด้วยค่า floating-point 2 จำนวน ซึ่งปัญหาคือจะทำอย่างไรที่จะรวมค่า 2 จำนวนนี้ไปใน object ได้ โดยทางออกที่สะดวกและง่ายคือ การใช้ list หรือ tuple โดยใช้ได้กับบางโปรแกรมเท่านั้นที่อาจจะเป็นทางเลือกที่ดีที่สุด

ทางเลือกคือการกำหนด user-defined โดยเป็นการรวมกันของส่วนต่างๆ ขึ้นมาใหม่ ดังนั้นจึงเรียกส่วนนี้ว่า class การกำหนดคำนิยามของ class ทำได้โดย

class Point:

pass

การกำหนด Class สามารถทำได้ที่ไหนก็ได้ในโปรแกรม แต่โดยปกติจะถูกกำหนดไว้ในตอนแรกๆ ภายหลังจากที่ใส่ค่า statements แล้ว โครงสร้างไวยกรณ์ในการกำหนด class นั้นมีความคล้ายคลึงกันกับการรวม statement (บทที่ 4.4)

นิยามในการสร้าง Class ขึ้นมาใหม่ จะเรียกว่า Point และการถ่ายทอดของ statement จะไม่มีผลกระทบ เพียงแต่จำเป็นเพราะว่า การรวมกันของ statement จะต้องเกิดบางอย่างขึ้นภายในตัวของมันเอง

โดยการสร้าง Point class นั้น เราจะต้องสร้างรูปแบบขึ้นมาใหม่ และเรียกว่า Point เช่นเดียวกัน สมาชิกของรูปแบบที่สร้างขึ้นมาใหม่นี้ จะเรียกว่า instances การสร้าง instance ขึ้นมาใหม่จะเรียกว่า instantiation ยกตัวอย่างตำแหน่งของ object โดยเราเรียกการทำงานนี้ว่า Point:

blank = Point()

ตัวแปรที่ว่างอยู่คือไว้สำหรับกำหนดและอ้างอิง point object อันใหม่ การทำงานจะเหมือนกับ Point ที่ต้องสร้าง object ขึ้นมาใหม่ เรียกว่า constructor

12.2 Attributes แก้ไข

เราสามารถเพิ่มข้อมูลค่าคงที่ โดยใช้จุดเป็นสัญลักษณ์

>>> blank.x = 3.0

>>> blank.y = 4.0

โครงสร้างทางไวยกรณ์ ( syntax ) นี้มีความคล้ายกันกับการเลือกตัวแปรจาก module อย่างเช่น math.pi or string.uppercase. ในกรณีนี้ เราจะเลือกข้อมูล item จาก instance และชื่อของ items เหล่านั้นจะเรียกว่า attributes แผนภาพที่แสดงผลลัพท์จากการกำหนด

 

ตัวแปรที่ว่างนั้นหมายถึง Point object โดยเก็บ attributes ไว้ 2 ค่า ซึ่งแต่ละ attribute จะอ้างถึง floating-point number เราสามารถอ่านค่าของ attribute ที่ใช้โครงสร้างคล้ายกันได้ โดย

>>> print blank.y

4.0

>>> x = blank.x

>>> print x

3.0

เครื่องหมาย blank.x หมายถึง ไปที่ object blank อ้างและรับค่าของ x ในกรณีนี้ เรากำหนดให้ค่าตัวแปรชื่อ x มันไม่ขัดกันระหว่าง variable x กับ attribute x หน้าที่ของจุลภาค ( dot ) คือชี้ว่าแต่ละตัวแปรนั้นถูกอ้างไว้อย่างชัดเจน

คุณสามารถใช้เครื่องหมาย dot ( จุด ) ตามส่วนประกอบต่างๆของชุดคำสั่ง และใน statement ถัดไปเป็นไปตามโครงสร้าง

print '(' + str(blank.x) + ', ' + str(blank.y) + ')'

distanceSquared = blank.x * blank.x + blank.y * blank.y

ในบรรทัดแรก ผลลัพท์ที่ออกมาคือ (3.0, 4.0) ในบรรทัดที่สองจะคำนวณได้ 25.0

คุณอาจจะทดสอบ print ค่าที่ว่าง ด้วยตัวของมันเอง

>>> print blank

<__main__.Point instance at 80f8e70>

ผลลัพท์ที่แสดงให้รู้ว่าว่านั้นคือ กรณีของ Point class และมันเป็นส่วนนิยามใน __main__ 80f8e70 เป็นชื่อเฉพาะของ object นี้ ซึ่งเขียนโดยใช้ hexadecimal (เลขฐาน 16)

12.3 Instances as parameters แก้ไข

คุณสามารถเปลี่ยน instance (ค่าคงที่) เช่นเดียวกับ parameter ได้เหมือนปกติ ตัวอย่าง

def printPoint(p):

print '(' + str(p.x) + ', ' + str(p.y) + ')'

printPoint ทำให้ point เป็น argument และแสดงออกมาในรูปแบบทั่วไป ถ้าใส่คำสั่ง printPoint(blank) ผลลัพท์ที่ออกมาคือ ( 3.0 , 4.0 )

12.4 Sameness แก้ไข

Sameness หมายถึง ความเหมือนกัน คล้ายกับคำว่า same มันดูมีความหมายที่ชัดเจนดี จนกระทั่งคุณกำหนดให้มันคำนวณอะไรบางอย่าง และหลังจากนั้นคุณจะเข้าใจว่ามันมีอะไรมากกว่าที่คุณคาดหวังไว้

สำหรับตัวอย่าง เช่น ถ้าคุณพูดว่า คริสและฉันมีรถเหมือนกัน นั่นคือคุณหมายความว่ารถของคริสและรถของคุณเป็นรุ่นเดียวกัน แต่ความจริงแล้วรถของทั้งสองคนแตกต่างกัน

ถ้าคุณพูดว่า “คริสและฉันมีแม่เหมือนกัน” คุณหมายถึงว่า แม่ของเขาและแม่ของคุณคือคนๆเดียวกัน ดังนั้นแนวคิดของความเหมือนกัน แตกต่างกันขึ้นอยู่กับบริบท

เมื่อคุณพูดถึง objects มันมีความคล้ายเหมือนข้อความที่กำกวม ตัวอย่าง เช่น ถ้า Points 2 ตัวเหมือนกัน นั่นหมายถึงมันบรรจุข้อมูลที่เหมือนกัน ( Coordinates ) หรือที่จริงมันคือ Object ตัวเดียวกัน ?

การตรวจสอบ ถ้าการอ้างอิงทั้งสอง หมายถึงเป็น Object ตัวเดียวกัน ใช้ = = operator ตัวอย่าง

>>> p1 = Point()

>>> p1.x = 3

>>> p1.y = 4

>>> p2 = Point()

>>> p2.x = 3

>>> p2.y = 4

>>> p1 == p2

False

ถึงแม้ว่า p1 และ p2 ประกอบไปด้วย coordinates เดียวกัน แต่มันก้อไม่ใช่ object เดียวกัน ถ้าเรากำหนด p1 ไปยัง p2 แล้ว ทั้งสองตัวแปรจะเป็น aliases ( นามแฝง ) ของ object ตัวเดียวกัน

>>> p2 = p1

>>> p1 == p2

True

รูปแบบของการเท่ากันนี้ถูกเรียกว่า shallow equality เนื่องจากมันเปรียบเทียบแค่เพียงสิ่งที่อ้างถึงเท่านั้น ไม่ใช่หัวข้อของ objects

เพื่อเปรียบเทียบหัวข้อของ object – deep equality – เราสามารถเขียนฟังก์ชั่นที่เรียกว่า Samepoint

def samePoint(p1, p2) :

    return (p1.x == p2.x) and (p1.y == p2.y)

ตอนนี้เราสร้าง objects 2 ตัวที่แตกต่างกัน ซึ่งบรรจุข้อมูลตัวเดียวกัน เราสามารถใช้ samePoint เพื่อตรวจสอบถ้ามันทำหน้าที่แทน point เดียวกัน

>>> p1 = Point()

>>> p1.x = 3

>>> p1.y = 4

>>> p2 = Point()

>>> p2.x = 3

>>> p2.y = 4

>>> samePoint(p1, p2)

True

แน่นอนว่าถ้า 2 ตัวแปรนั้นอ้างถึง object เดียวกัน มันก็จะต้องมี shallow และ deep equality เหมือนกันด้วย

12.5 Rectangles แก้ไข

โดยประมาณถ้าเราต้องการ class ที่แสดงถึง rectangle ( รูปสี่เหลี่ยม ) คำถามนั้นก็คือจะใช้ข้อมูลอะไรในการสร้าง เราต้องจัดเตรียมในส่วนของรายการตามที่ rectangle กำหนดมา ในการคิดแบบง่ายๆ สมมุติว่า rectangle นั้นถูกกำหนดตำแหน่งในแนวตั้งหรือแนวนอนมาแล้วอย่างใดอย่างหนึ่ง

เกี่ยวกับเรื่องนั้นมันมีความเป็นไปได้บ้าง ที่เราสามารถจะกำหนดจุดศูนย์กลางของ rectangle ( two coordinate) และกำหนดขนาดของมัน ( width and height ) หรือเราสามารถกำหนดมุมและขนาดได้ หรือกำหนดมุมตรงข้ามทั้ง 2 โดยธรรมดาทางเลือกที่ดีที่สุดในการกำหนดมุมบนซ้ายของ rectangle และขนาด

ในการกำหนด new class :

class Rectangle:

pass

ตัวอย่างประกอบ box = Rectangle()

box.width = 100.0

box.height = 200.0

code เหล่านี้จะไปสร้าง Rectangle object อันใหม่พร้อมกับ foating-point attributes 2 ค่า ในการกำหนดมุมนั้นเราสามารถใส่ค่าลงไปภายใน object ได้

box.corner = Point()

box.corner.x = 0.0;

box.corner.y = 0.0;

โดยประกอบด้วยจุดควบคุม คำสั่ง box.corner.x หมายถึง ไปที่ object ของ box ที่เกี่ยวข้อง และหา attribute ที่ชื่อ corner จากนั้นไปที่ object และเลือก attribute ที่ชื่อ x

รูปภาพแสดงสภาพของ object

 

12.6 Instances as return values แก้ไข

การทำงาน ( functions ) ที่สามารถเรียกคืนค่า instances ในตัวอย่าง findCenter ที่สร้าง Rectangle เป็น argument นั้นและ เรียกคืนค่า Point ที่ประกอบไปด้วยระยะพิกัดของศูนย์กลางของ Rectangle

def findCenter(box):

p = Point()

p.x = box.corner.x + box.width/2.0

p.y = box.corner.y + box.height/2.0

return p

การเรียก function นี้ เปลี่ยน box เป็น argument และกำหนดผลลัพท์ไปเป็น variable

>>> center = findCenter(box)

>>> printPoint(center)

(50.0, 100.0)

12.7 Objects are mutable แก้ไข

เราสามารถเปลี่ยนสภาพของ object ได้โดยการกำหนด attributes ในตัวอย่าง การเปลี่ยนขนาดของ rectangle ที่ไม่ได้เปลี่ยนตำแหน่งนั้น เราสามารถปรับเปลี่ยนแก้ไขค่า width และ height

box.width = box.width + 50

box.height = box.height + 100

เราสามารถสรุป code นี้ใน method และวางหลักเกณฑ์เงื่อนไขในการขยายขนาดของ rectangle โดยใช้ผลรวมต่างๆ ได้

def growRect(box, dwidth, dheight) :

box.width = box.width + dwidth

box.height = box.height + dheight

ตัวแปร dwidth และ dheight แสดงให้เห็นว่า rectangle จะมีขนาดเพิ่มขึ้นเท่าไหร่ในแต่ละทิศทาง ก่อให้เกิด method ที่มีผลกระทบของการแก้ไขค่า rectangle ที่ถ่ายทอดไปยัง argument

ในตัวอย่าง เราสามารถสร้าง Rectangle ขึ้นมาใหม่ ชื่อ bob และ ถ่ายทอดไปใน growRect

>>> bob = Rectangle()

>>> bob.width = 100.0

>>> bob.height = 200.0

>>> bob.corner = Point()

>>> bob.corner.x = 0.0;

>>> bob.corner.y = 0.0;

>>> growRect(bob, 50, 100)

ขณะที่ growRect ทำงานอยู่นั้น ค่า parameter box ที่มีชื่อเรียกว่า bob จะถูกแก้ไขและได้รับผลกระทบเหมือนกัน

12.8 Copying แก้ไข

Aliasing ( ชื่อเรียก ) สามารถทำให้โปรแกรมเกิดความยุ่งยากขึ้นในการอ่านเพราะว่าการเปลี่ยนแปลงในที่หนึ่งอาจจะมีผลกระทบในที่อื่นๆด้วย มันยากที่จะรักษาเส้นทางของตัวแปรทั้งหมดที่ไว้ใช้อ้างอิงกับ object

การคัดลอก object เป็นทางเลือกที่เกิดขึ้นบ่อยของ aliasing การคัดลอก module ประกอบไปด้วย function ที่เรียก copy ที่สามารถจำลอง object ต่างๆได้

>>> import copy

>>> p1 = Point()

>>> p1.x = 3

>>> p1.y = 4

>>> p2 = copy.copy(p1)

>>> p1 == p2

False

>>> samePoint(p1, p2)

True

ทันทีที่นำ copy module เข้ามา เราสามารถใช้การ copy method เพื่อสร้าง newPoint โดย p1 และ p2 ไม่ใช่จุดเดียวกัน แต่ภายในมีข้อมูลที่เหมือนกัน ในการคัดลอก object โดยทั่วไปเหมือน Point อันที่ไม่ได้บรรจุ objects ที่ถูกฝัง ( embed )

การคัดลอกจะทำได้ดีกว่า โดยจะเรียกว่า shallow copying

สำหรับบางอย่างที่เหมือน Rectangle นั้น อันที่บรรจุการอ้างอิง Point การคัดลอกจะทำได้ไม่ถูกต้องทั้งหมด การคัดลอกส่วนอ้างอิงใน Point object นั้นจะต้องคัดลอกทั้งอันเก่าและอันใหม่ของ Rectangle ที่ใช้อ้างอิงใน single Point

ถ้าเราสร้าง box ( ชื่อ b1 ) ตามปกติแล้วทำการคัดลอก b2 ที่ใช้การคัดลอกจะมีลักษณะของผลลัพท์ตามแผนภาพ

 

ในกรณีนี้แน่นอนว่ามันเป็นสิ่งที่เราไม่ต้องการ โดยก่อให้เกิด growRect บน Rectangles ที่ไม่มีผลกระทบต่ออันอื่น แต่จะก่อให้เกิด moveRect ในแต่ละอัน ซึ่งผลลัพท์ที่เกิดขึ้นนี้จะมีแนวโน้มของการผิดพลาด ( error-prone )

อย่างไรก็ตามการคัดลอก module ที่บรรจุ method ที่เรียกว่า deepcopy นั้นไม่ใช่เป็นการคัดลอก object เพียงอย่างเดียว แต่ยังรวมถึงการคัดลอก embedded objects ด้วย ซึ่งไม่น่าแปลกใจว่าทำไมทำงานในส่วนนี้ถึงเรียกว่า deep copy

>>> b2 = copy.deepcopy(b1)

โดยขณะนี้ b1 และ b2 เป็น object ที่เป็นอิสระต่อกัน

เราสามารถใช้ deepcopy เพื่อเขียน growRect ขึ้นมาใหม่ ทั้งนี้เพื่อแทนที่การแก้ไข Rectangle ที่มีอยู่ โดยมันจะสร้าง Rectangle อันใหม่ ที่มีตำแหน่งเหมือนอันเก่า แต่มีขนาดใหม่

        def growRect(box, dwidth, dheight) :

import copy

newBox = copy.deepcopy(box)

newBox.width = newBox.width + dwidth

newBox.height = newBox.height + dheight

return newBox ===12.9 Glossary class: A user-defined compound type. A class can also be thought of as a template for the objects that are instances of it.

instantiate: To create an instance of a class.

instance: An object that belongs to a class.

object: A compound data type that is often used to model a thing or concept in the real world.

constructor: A method used to create new objects.

attribute: One of the named data items that makes up an instance.

shallow equality: Equality of references, or two references that point to the same object.

deep equality: Equality of values, or two references that point to objects that have the same value.

shallow copy: To copy the contents of an object, including any references to embedded objects; implemented by the copy function in the copy module.

deep copy: To copy the contents of an object as well as any embedded objects, and any objects embedded in them, and so on; implemented by the deepcopy function in the copy module.