เมื่อใดฉันจึงควรใช้คลาสใน Python


177

ฉันเขียนโปรแกรมด้วยภาษาไพ ธ อนมาประมาณสองปี ข้อมูลส่วนใหญ่ (pandas, mpl, numpy) แต่ยังเป็นสคริปต์อัตโนมัติและเว็บแอปขนาดเล็ก ฉันพยายามเป็นโปรแกรมเมอร์ที่ดีขึ้นและเพิ่มความรู้ของหลามและหนึ่งในสิ่งที่รบกวนจิตใจฉันคือฉันไม่เคยใช้ชั้นเรียน (นอกการคัดลอกรหัสขวดแบบสุ่มสำหรับเว็บแอปขนาดเล็ก) โดยทั่วไปฉันเข้าใจว่าพวกเขาคืออะไร แต่ฉันไม่สามารถคาดศีรษะได้ว่าทำไมฉันถึงต้องการให้พวกเขาทำหน้าที่ง่ายๆ

ในการเพิ่มความเฉพาะเจาะจงให้กับคำถามของฉัน: ฉันเขียนรายงานอัตโนมัติเป็นตันซึ่งเกี่ยวข้องกับการดึงข้อมูลจากแหล่งข้อมูลหลายแห่ง (mongo, sql, postgres, apis), ทำการแสดงข้อมูลและการจัดรูปแบบจำนวนมากหรือเล็กน้อยเขียนข้อมูลไปยัง csv / excel / html ส่งออกมาทางอีเมล สคริปต์มีตั้งแต่ ~ 250 บรรทัดถึง ~ 600 บรรทัด มีเหตุผลใดที่ฉันจะใช้คลาสเพื่อทำสิ่งนี้และทำไม?


15
ไม่มีอะไรผิดปกติกับรหัสที่ไม่มีคลาสหากคุณสามารถจัดการโค้ดของคุณได้ดีกว่า โปรแกรมเมอร์ OOP มักจะพูดเกินจริงปัญหาเนื่องจากข้อ จำกัด จากการออกแบบภาษาหรือความเข้าใจผิวเผินในรูปแบบที่แตกต่างกัน
Jason Hu

คำตอบ:


133

ชั้นเรียนเป็นเสาหลักของ เขียนโปรแกรมเชิงวัตถุ OOP มีความเกี่ยวข้องอย่างมากกับการจัดระเบียบโค้ดการใช้ซ้ำและการห่อหุ้ม

ข้อแรกข้อจำกัดความรับผิดชอบ: OOP ตรงกันข้ามกับบางส่วน ฟังก์ชั่นการเขียนโปรแกรมซึ่งเป็นกระบวนทัศน์ที่แตกต่างกันมากใน Python ไม่ใช่ทุกคนที่โปรแกรมใน Python (หรือภาษาส่วนใหญ่) ใช้ OOP คุณสามารถทำสิ่งต่างๆมากมายใน Java 8 ที่ไม่ใช่ Object Oriented มาก หากคุณไม่ต้องการใช้ OOP ก็ไม่ต้องใช้ หากคุณเพิ่งเขียนสคริปต์แบบใช้ครั้งเดียวเพื่อประมวลผลข้อมูลที่คุณจะไม่ใช้อีกต่อไปให้เขียนตามที่คุณต้องการ

อย่างไรก็ตามมีเหตุผลมากมายที่จะใช้ OOP

เหตุผลบางอย่าง:

  • องค์กร: OOP กำหนดวิธีที่เป็นที่รู้จักและเป็นมาตรฐานในการอธิบายและกำหนดทั้งข้อมูลและขั้นตอนในรหัส ข้อมูลและขั้นตอนทั้งสองสามารถเก็บไว้ที่ระดับความหมายที่แตกต่างกัน (ในคลาสที่แตกต่างกัน) และมีวิธีมาตรฐานเกี่ยวกับการพูดคุยเกี่ยวกับคำจำกัดความเหล่านี้ นั่นคือถ้าคุณใช้ OOP ด้วยวิธีมาตรฐานมันจะช่วยให้ตัวคุณเองในภายหลังและผู้อื่นเข้าใจแก้ไขและใช้รหัสของคุณ นอกจากนี้แทนที่จะใช้กลไกการจัดเก็บข้อมูลแบบซับซ้อนและซับซ้อน (dicts ของ dicts หรือ list หรือ dicts หรือรายการ dicts ของชุดหรืออะไรก็ตาม) คุณสามารถตั้งชื่อชิ้นส่วนของโครงสร้างข้อมูลและอ้างอิงได้อย่างสะดวก

  • สถานะ: OOP ช่วยให้คุณกำหนดและติดตามสถานะ ตัวอย่างเช่นในตัวอย่างคลาสสิกหากคุณกำลังสร้างโปรแกรมที่ประมวลผลนักเรียน (ตัวอย่างเช่นโปรแกรมเกรด) คุณสามารถเก็บข้อมูลทั้งหมดที่คุณต้องการเกี่ยวกับพวกเขาไว้ในที่เดียว (ชื่ออายุเพศระดับชั้น หลักสูตร, เกรด, ครู, เพื่อน, อาหาร, ความต้องการพิเศษ ฯลฯ ) และข้อมูลนี้ยังคงอยู่ตราบเท่าที่วัตถุยังมีชีวิตอยู่และเข้าถึงได้ง่าย

  • การห่อหุ้ม : ด้วยการห่อหุ้มกระบวนการและข้อมูลจะถูกจัดเก็บไว้ด้วยกัน วิธีการ (คำศัพท์ OOP สำหรับฟังก์ชั่น) ถูกกำหนดไว้ข้างๆข้อมูลที่พวกมันทำงานและผลิต ในภาษาเช่น Java ที่อนุญาตการควบคุมการเข้าถึงหรือใน Python ขึ้นอยู่กับว่าคุณอธิบาย API สาธารณะของคุณอย่างไรซึ่งหมายความว่าวิธีการและข้อมูลสามารถซ่อนจากผู้ใช้ สิ่งนี้หมายความว่าถ้าคุณต้องการหรือต้องการเปลี่ยนรหัสคุณสามารถทำสิ่งที่คุณต้องการนำไปใช้ของรหัส แต่ให้ API สาธารณะเหมือนเดิม

  • การสืบทอด : การสืบทอดทำให้คุณสามารถกำหนดข้อมูลและขั้นตอนได้ในที่เดียว (ในชั้นเดียว) จากนั้นแทนที่หรือขยายการทำงานนั้นในภายหลัง ตัวอย่างเช่นใน Python ฉันมักจะเห็นคนสร้างคลาสย่อยของdictคลาสเพื่อเพิ่มฟังก์ชันการทำงานเพิ่มเติม การเปลี่ยนแปลงทั่วไปกำลังแทนที่วิธีที่ส่งข้อยกเว้นเมื่อมีการร้องขอคีย์จากพจนานุกรมที่ไม่มีอยู่เพื่อให้ค่าเริ่มต้นตามคีย์ที่ไม่รู้จัก สิ่งนี้ช่วยให้คุณสามารถขยายรหัสของคุณเองได้ในขณะนี้หรือภายหลังอนุญาตให้ผู้อื่นขยายรหัสของคุณและอนุญาตให้คุณขยายรหัสของผู้อื่น

  • ความสามารถนำกลับมาใช้ใหม่ได้: เหตุผลทั้งหมดนี้และเหตุผลอื่น ๆ ทำให้สามารถใช้งานโค้ดได้มากขึ้น รหัสเชิงวัตถุช่วยให้คุณสามารถเขียนรหัสทึบ (ทดสอบ) ครั้งเดียวแล้วนำมาใช้ซ้ำแล้วซ้ำอีก หากคุณต้องการปรับแต่งบางอย่างสำหรับกรณีการใช้งานเฉพาะของคุณคุณสามารถสืบทอดจากคลาสที่มีอยู่และเขียนทับพฤติกรรมที่มีอยู่ หากคุณต้องการเปลี่ยนแปลงบางสิ่งคุณสามารถเปลี่ยนแปลงได้ทั้งหมดในขณะที่รักษาลายเซ็นวิธีสาธารณะที่มีอยู่และไม่มีใครฉลาด (หวังว่า)

มีเหตุผลหลายประการที่จะไม่ใช้ OOP และคุณไม่จำเป็นต้องใช้ แต่โชคดีที่มีภาษาเช่น Python คุณสามารถใช้เพียงเล็กน้อยหรือมากก็ได้แล้วแต่คุณ

ตัวอย่างของกรณีการใช้ของนักเรียน (ไม่รับประกันคุณภาพของโค้ดเพียงตัวอย่าง):

วัตถุที่มุ่งเน้น

class Student(object):
    def __init__(self, name, age, gender, level, grades=None):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level
        self.grades = grades or {}

    def setGrade(self, course, grade):
        self.grades[course] = grade

    def getGrade(self, course):
        return self.grades[course]

    def getGPA(self):
        return sum(self.grades.values())/len(self.grades)

# Define some students
john = Student("John", 12, "male", 6, {"math":3.3})
jane = Student("Jane", 12, "female", 6, {"math":3.5})

# Now we can get to the grades easily
print(john.getGPA())
print(jane.getGPA())

Dict มาตรฐาน

def calculateGPA(gradeDict):
    return sum(gradeDict.values())/len(gradeDict)

students = {}
# We can set the keys to variables so we might minimize typos
name, age, gender, level, grades = "name", "age", "gender", "level", "grades"
john, jane = "john", "jane"
math = "math"
students[john] = {}
students[john][age] = 12
students[john][gender] = "male"
students[john][level] = 6
students[john][grades] = {math:3.3}

students[jane] = {}
students[jane][age] = 12
students[jane][gender] = "female"
students[jane][level] = 6
students[jane][grades] = {math:3.5}

# At this point, we need to remember who the students are and where the grades are stored. Not a huge deal, but avoided by OOP.
print(calculateGPA(students[john][grades]))
print(calculateGPA(students[jane][grades]))

เนื่องจากการห่อหุ้มงูหลาม "ผลผลิต" มักจะสะอาดกว่าด้วยเครื่องกำเนิดและผู้จัดการบริบทมากกว่าคลาส
Dmitry Rubanovich

4
@meter ฉันเพิ่มตัวอย่าง ฉันหวังว่ามันจะช่วย หมายเหตุที่นี่คือแทนที่จะต้องพึ่งพาคีย์ของ dicts ของคุณที่มีชื่อที่ถูกต้องตัวแปล Python ทำให้ข้อ จำกัด นี้สำหรับคุณถ้าคุณทำผิดพลาดและบังคับให้คุณใช้วิธีการที่กำหนดไว้ (แม้ว่าจะไม่ได้กำหนด Java และอื่น ๆ ภาษา OOP ไม่อนุญาตให้คุณกำหนดเขตข้อมูลนอกคลาสเช่น Python))
dantiston

5
@meter ยังเป็นตัวอย่างของการห่อหุ้ม: สมมติว่าวันนี้การดำเนินการนี้ใช้ได้เพราะฉันต้องได้เกรดเฉลี่ยสำหรับนักเรียน 50,000 คนที่มหาวิทยาลัยของฉันหนึ่งครั้ง ตอนนี้พรุ่งนี้เราจะได้รับทุนและจำเป็นต้องให้เกรดเฉลี่ยปัจจุบันของนักเรียนทุกคนทุกวินาที (แน่นอนไม่มีใครจะขอสิ่งนี้ แต่เพียงเพื่อให้มันท้าทายการคำนวณ) จากนั้นเราสามารถ "บันทึก" เกรดเฉลี่ยและคำนวณได้เฉพาะเมื่อมีการเปลี่ยนแปลง (ตัวอย่างเช่นโดยการตั้งค่าตัวแปรในเมธอด setGrade) และส่งคืนเวอร์ชันแคช ผู้ใช้ยังคงใช้ getGPA () แต่การใช้งานมีการเปลี่ยนแปลง
dantiston

4
@dantiston ตัวอย่างนี้ต้องการ collection.namedtuple คุณสามารถสร้างประเภทใหม่ Student = collection.namedtuple ("นักเรียน", "ชื่อ, อายุ, เพศ, ระดับ, คะแนน") จากนั้นคุณสามารถสร้างอินสแตนซ์ john = นักเรียน ("John", 12, "male", เกรด = {'math': 3.5}, level = 6) ขอให้สังเกตว่าคุณใช้ทั้งตำแหน่งและอาร์กิวเมนต์ที่มีการตั้งชื่อเช่นเดียวกับที่คุณสร้างคลาส นี่คือประเภทข้อมูลที่มีการใช้งานแล้วใน Python จากนั้นคุณสามารถอ้างถึง john [0] หรือ john.name เพื่อรับองค์ประกอบที่ 1 ของ tuple คุณสามารถรับคะแนนของ john เป็น john.grades.values ​​() ได้ทันที และมันก็ทำเพื่อคุณแล้ว
Dmitry Rubanovich

2
สำหรับฉัน encapsulation เป็นเหตุผลที่ดีที่จะใช้ OOP เสมอ ฉันต่อสู้เพื่อดูว่าค่าไม่ได้ใช้ OOP สำหรับโครงการการเข้ารหัสขนาดใด ๆ ผมคิดว่าผมต้องการคำตอบของคำถามย้อนกลับ :)
ซานเจย์

23

เมื่อใดก็ตามที่คุณต้องการรักษาสถานะการทำงานของคุณและไม่สามารถทำได้กับเครื่องกำเนิดไฟฟ้า (ฟังก์ชันที่ให้ผลตอบแทนมากกว่าการส่งคืน) เครื่องกำเนิดไฟฟ้ารักษาสถานะของตัวเอง

หากคุณต้องการแทนที่โอเปอเรเตอร์มาตรฐานใด ๆคุณต้องมีคลาส

เมื่อใดก็ตามที่คุณมีรูปแบบการเยี่ยมชมคุณจะต้องมีคลาส รูปแบบการออกแบบอื่น ๆ สามารถทำได้อย่างมีประสิทธิภาพและสะอาดยิ่งขึ้นด้วยเครื่องกำเนิดไฟฟ้าผู้จัดการบริบท (ซึ่งยังนำมาใช้ในฐานะเครื่องกำเนิดไฟฟ้าได้ดีกว่าคลาส) และประเภท POD (พจนานุกรมรายการและสิ่งอันดับอื่น ๆ )

หากคุณต้องการเขียนรหัส "pythonic" คุณควรเลือกผู้จัดการบริบทและเครื่องกำเนิดไฟฟ้ามากกว่าคลาส มันจะสะอาดขึ้น

หากคุณต้องการขยายฟังก์ชั่นคุณจะสามารถทำได้ด้วยการ จำกัด มากกว่าการสืบทอด

ทุกกฎมีข้อยกเว้น หากคุณต้องการห่อหุ้มฟังก์ชันการทำงานอย่างรวดเร็ว (เช่นเขียนรหัสทดสอบแทนที่จะเป็นรหัสที่สามารถใช้ซ้ำได้ในระดับห้องสมุด) คุณสามารถสรุปสถานะในชั้นเรียนได้ มันจะง่ายและไม่จำเป็นต้องใช้ซ้ำ

หากคุณต้องการตัวทำลายสไตล์ C ++ (RIIA) แน่นอนว่าคุณไม่ต้องการใช้คลาส คุณต้องการผู้จัดการบริบท


1
@DmitryRubanovich closures ไม่ได้ใช้งานผ่านเครื่องกำเนิดไฟฟ้าใน Python
Eli Korvigo

1
@DmitryRubanovich ฉันหมายถึง "การปิดจะดำเนินการเป็นกำเนิดใน Python" ซึ่งไม่เป็นความจริง การปิดนั้นมีความยืดหยุ่นมากกว่า เครื่องกำเนิดไฟฟ้าถูกผูกไว้เพื่อส่งคืนGeneratorอินสแตนซ์ (ตัววนซ้ำพิเศษ) ในขณะที่การปิดสามารถมีลายเซ็นใด ๆ โดยทั่วไปคุณสามารถหลีกเลี่ยงการเรียนส่วนใหญ่โดยการสร้างการปิด และการปิดไม่ได้เป็นเพียง "ฟังก์ชั่นที่กำหนดไว้ในบริบทของฟังก์ชั่นอื่น ๆ "
Eli Korvigo

3
@Eli Korvigo ในความเป็นจริงเครื่องกำเนิดไฟฟ้าเป็นก้าวกระโดดที่สำคัญ syntactically พวกเขาสร้างนามธรรมของคิวในลักษณะเดียวกับที่ฟังก์ชั่นเป็น abstractions ของสแต็ค และการไหลของข้อมูลส่วนใหญ่สามารถรวมเข้าด้วยกันจากแบบดั้งเดิมสแต็ค / คิว
Dmitry Rubanovich

1
@DmitryRubanovich เรากำลังพูดถึงแอปเปิ้ลและส้มที่นี่ ฉันกำลังพูดว่าเครื่องกำเนิดไฟฟ้ามีประโยชน์ในหลายกรณีที่ จำกัด มากและไม่สามารถถือเป็นการทดแทนสำหรับ callables stateful วัตถุประสงค์ทั่วไป คุณกำลังบอกฉันว่าพวกเขาดีแค่ไหนโดยไม่ขัดแย้งกับคะแนนของฉัน
Eli Korvigo

1
@Eli Korvigo และฉันกำลังบอกว่า callables เป็นเพียงภาพรวมของฟังก์ชั่นเท่านั้น ซึ่งตัวเองเป็นน้ำตาล syntactic มากกว่าการประมวลผลของสแต็ค ในขณะที่เครื่องกำเนิดไฟฟ้าเป็นน้ำตาล syntactic มากกว่าการประมวลผลของคิว แต่เป็นการปรับปรุงในรูปแบบไวยากรณ์ที่ช่วยให้การสร้างที่ซับซ้อนมากขึ้นสามารถสร้างขึ้นได้อย่างง่ายดายและมีไวยากรณ์ที่ชัดเจนยิ่งขึ้น '.next ()' แทบจะไม่เคยใช้เลย btw
Dmitry Rubanovich

11

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

  • ฟังก์ชั่นหลายอย่างที่มีสถานะการแบ่งปัน
  • มากกว่าหนึ่งสำเนาของตัวแปรสถานะเดียวกัน
  • เพื่อขยายพฤติกรรมของการทำงานที่มีอยู่

ฉันขอแนะนำให้คุณดูวิดีโอคลาสสิคนี้ด้วย


3
ไม่จำเป็นต้องใช้คลาสเมื่อฟังก์ชันการโทรกลับต้องการสถานะถาวรใน Python การใช้ผลตอบแทนของไพ ธ อนแทนผลตอบแทนจะทำให้ผู้เข้าร่วมฟังก์ชั่นอีกครั้ง
Dmitry Rubanovich

4

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

หากไม่ใช่กรณีนี้ไม่จำเป็นต้องสร้างคลาส


0

มันขึ้นอยู่กับความคิดและการออกแบบของคุณ หากคุณเป็นนักออกแบบที่ดีกว่า OOPs จะออกมาอย่างเป็นธรรมชาติในรูปแบบของรูปแบบการออกแบบที่หลากหลาย สำหรับ OOP ระดับการประมวลผลสคริปต์อย่างง่ายสามารถค่าใช้จ่าย ง่ายพิจารณาประโยชน์พื้นฐานของ OOPs เช่นสามารถนำมาใช้ซ้ำได้และขยายได้และตรวจสอบให้แน่ใจว่าจำเป็นหรือไม่ OOP ทำให้สิ่งที่ซับซ้อนง่ายขึ้นและซับซ้อนขึ้นกว่าเดิม เพียงทำให้ทุกอย่างง่ายขึ้นด้วยการใช้ OOP หรือไม่ใช้ OOP ซึ่งใช้ง่ายกว่านั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.