รูปแบบการออกแบบหลาม: สำหรับโค้ดที่เก๋ไก๋และทันสมัย
เผยแพร่แล้ว: 2022-03-11พูดอีกครั้ง: Python เป็นภาษาการเขียนโปรแกรมระดับสูงที่มีการพิมพ์แบบไดนามิกและการเชื่อมโยงแบบไดนามิก ฉันจะอธิบายว่าเป็นภาษาไดนามิกระดับสูงที่ทรงพลัง นักพัฒนาหลายคนหลงรัก Python เพราะมีรูปแบบที่ชัดเจน โมดูลและแพ็คเกจที่มีโครงสร้างที่ดี และความยืดหยุ่นมหาศาลและคุณสมบัติที่ทันสมัยมากมาย
ใน Python ไม่มีอะไรบังคับให้คุณเขียนคลาสและสร้างอินสแตนซ์อ็อบเจกต์จากคลาสเหล่านั้น ถ้าคุณไม่ต้องการโครงสร้างที่ซับซ้อนในโครงการของคุณ คุณสามารถเขียนฟังก์ชันได้ ยิ่งไปกว่านั้น คุณยังสามารถเขียนแฟลตสคริปต์เพื่อดำเนินงานที่ง่ายและรวดเร็วโดยไม่ต้องจัดโครงสร้างโค้ดเลย
ในขณะเดียวกัน Python เป็นภาษาเชิงวัตถุ 100 เปอร์เซ็นต์ วิธีที่ว่า? พูดง่ายๆ ว่า ทุกอย่างใน Python นั้นเป็นวัตถุ ฟังก์ชันคืออ็อบเจ็กต์ ออบเจ็กต์ระดับเฟิร์สคลาส ข้อเท็จจริงเกี่ยวกับฟังก์ชันที่เป็นวัตถุมีความสำคัญ ดังนั้นโปรดจำไว้
ดังนั้น คุณสามารถเขียนสคริปต์อย่างง่ายใน Python หรือเพียงแค่เปิดเทอร์มินัล Python และรันคำสั่งตรงนั้น (มีประโยชน์มาก!) แต่ในขณะเดียวกัน คุณสามารถสร้างเฟรมเวิร์ก แอปพลิเคชัน ไลบรารี่ และอื่นๆ ที่ซับซ้อนได้ คุณสามารถทำอะไรได้มากมายใน Python แน่นอนว่ามีข้อจำกัดหลายประการ แต่นั่นไม่ใช่หัวข้อของบทความนี้
อย่างไรก็ตาม เนื่องจาก Python นั้นทรงพลังและยืดหยุ่นมาก เราจึงจำเป็นต้องมีกฎเกณฑ์ (หรือรูปแบบ) บางอย่างในการเขียนโปรแกรม มาดูกันว่ารูปแบบคืออะไรและสัมพันธ์กับ Python อย่างไร เราจะดำเนินการนำรูปแบบการออกแบบ Python ที่จำเป็นบางส่วนไปใช้
ทำไม Python ดีสำหรับรูปแบบ?
ภาษาการเขียนโปรแกรมใด ๆ ก็ดีสำหรับรูปแบบ อันที่จริง รูปแบบควรพิจารณาในบริบทของภาษาโปรแกรมที่กำหนด ทั้งรูปแบบ ไวยากรณ์ภาษา และธรรมชาติกำหนดข้อจำกัดในการเขียนโปรแกรมของเรา ข้อจำกัดที่มาจากรูปแบบภาษาและธรรมชาติของภาษา (ไดนามิก ฟังก์ชัน เชิงวัตถุ และอื่นๆ) อาจแตกต่างกันไป เช่นเดียวกับเหตุผลที่อยู่เบื้องหลังการมีอยู่ของสิ่งเหล่านี้ ข้อจำกัดที่มาจากรูปแบบนั้นมีเหตุผลและมีจุดมุ่งหมาย นั่นคือเป้าหมายพื้นฐานของรูปแบบ เพื่อบอกเราว่าต้องทำอย่างไรและไม่ควรทำ เราจะพูดถึงรูปแบบ และโดยเฉพาะรูปแบบการออกแบบ Python ในภายหลัง
ปรัชญาของ Python สร้างขึ้นจากแนวคิดเรื่องแนวปฏิบัติที่ดีที่สุด Python เป็นภาษาไดนามิก (ฉันพูดไปแล้วเหรอ) และด้วยเหตุนี้ ได้ปรับใช้หรือทำให้ง่ายต่อการใช้งาน รูปแบบการออกแบบยอดนิยมจำนวนหนึ่งพร้อมโค้ดไม่กี่บรรทัด รูปแบบการออกแบบบางรูปแบบถูกสร้างขึ้นใน Python ดังนั้นเราจึงใช้รูปแบบเหล่านี้โดยที่เราไม่รู้ตัว ไม่จำเป็นต้องใช้รูปแบบอื่นเนื่องจากลักษณะของภาษา
ตัวอย่างเช่น Factory เป็นรูปแบบการออกแบบโครงสร้าง Python ที่มุ่งสร้างวัตถุใหม่ โดยซ่อนตรรกะการสร้างอินสแตนซ์จากผู้ใช้ แต่การสร้างอ็อบเจ็กต์ใน Python นั้นเป็นไดนามิกโดยการออกแบบ ดังนั้นการเพิ่มเติมอย่าง Factory จึงไม่มีความจำเป็น แน่นอน คุณสามารถปรับใช้ได้อย่างอิสระหากต้องการ อาจมีบางกรณีที่มันจะมีประโยชน์จริง ๆ แต่ก็เป็นข้อยกเว้น ไม่ใช่บรรทัดฐาน
ปรัชญาของ Python ดีอย่างไร? เริ่มจาก สิ่งนี้ (สำรวจในเทอร์มินัล Python):
> >> import this The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!สิ่งเหล่านี้อาจไม่ใช่รูปแบบในความหมายดั้งเดิม แต่เป็นกฎที่กำหนดแนวทาง "Pythonic" ในการเขียนโปรแกรมในลักษณะที่หรูหราและมีประโยชน์มากที่สุด
นอกจากนี้เรายังมีหลักเกณฑ์เกี่ยวกับรหัส PEP-8 ที่ช่วยจัดโครงสร้างโค้ดของเรา แน่นอนว่าฉันต้องมีข้อยกเว้นที่เหมาะสม อย่างไรก็ตาม PEP-8 สนับสนุนข้อยกเว้นเหล่านี้:
แต่ที่สำคัญที่สุด: รู้ว่าเมื่อใดที่จะไม่สอดคล้องกัน – บางครั้งคู่มือสไตล์ก็ใช้ไม่ได้ เมื่อมีข้อสงสัยให้ใช้วิจารณญาณที่ดีที่สุดของคุณ ดูตัวอย่างอื่นๆ และตัดสินใจว่าสิ่งใดดูดีที่สุด และอย่าลังเลที่จะถาม!
รวม PEP-8 เข้ากับ The Zen of Python (เช่น PEP - PEP-20) และคุณจะมีพื้นฐานที่สมบูรณ์แบบในการสร้างโค้ดที่อ่านได้และบำรุงรักษาได้ เพิ่ม Design Patterns และคุณพร้อมที่จะสร้างระบบซอฟต์แวร์ทุกประเภทที่มีความสม่ำเสมอและสามารถพัฒนาได้
รูปแบบการออกแบบหลาม
รูปแบบการออกแบบคืออะไร?
ทุกอย่างเริ่มต้นด้วย Gang of Four (GOF) ทำการค้นหาออนไลน์อย่างรวดเร็วหากคุณไม่คุ้นเคยกับ GOF
รูปแบบการออกแบบเป็นวิธีทั่วไปในการแก้ปัญหาที่ทราบกันดี หลักการสำคัญสองประการอยู่ในพื้นฐานของรูปแบบการออกแบบที่กำหนดโดย GOF:
- โปรแกรมไปยังส่วนต่อประสานไม่ใช่การนำไปใช้
- ชอบองค์ประกอบวัตถุมากกว่ามรดก
มาดูหลักการทั้งสองนี้อย่างละเอียดยิ่งขึ้นจากมุมมองของโปรแกรมเมอร์ Python
โปรแกรมไปยังอินเทอร์เฟซไม่ใช่การนำไปใช้
คิดถึงเป็ดพิมพ์ดีด ใน Python เราไม่ต้องการกำหนดอินเทอร์เฟซและคลาสของโปรแกรมตามอินเทอร์เฟซเหล่านี้ใช่ไหม แต่ฟังฉันนะ! นี่ไม่ได้หมายความว่าเราไม่ได้คิดถึงอินเทอร์เฟซ จริงๆ แล้ว Duck Typing เราทำอย่างนั้นตลอดเวลา
ลองพูดคำบางคำเกี่ยวกับวิธีการพิมพ์เป็ดที่น่าอับอายเพื่อดูว่ามันเข้ากับกระบวนทัศน์นี้อย่างไร: โปรแกรมกับอินเทอร์เฟซ
เราไม่ยุ่งกับธรรมชาติของวัตถุ เราไม่จำเป็นต้องสนใจว่าวัตถุนั้นคืออะไร เราแค่ต้องการทราบว่าสามารถทำสิ่งที่เราต้องการได้หรือไม่ (เราสนใจเฉพาะส่วนต่อประสานของวัตถุเท่านั้น)
วัตถุสามารถต้มตุ๋น? ดังนั้นปล่อยให้มันต้มตุ๋น!
try: bird.quack() except AttributeError: self.lol()เรากำหนดอินเทอร์เฟซสำหรับเป็ดของเราหรือไม่? ไม่! เราตั้งโปรแกรมไปที่อินเทอร์เฟซแทนการนำไปใช้หรือไม่? ใช่! และฉันคิดว่าสิ่งนี้ดีมาก
ตามที่ Alex Martelli ได้ชี้ให้เห็นในการนำเสนอที่เป็นที่รู้จักกันดีของเขาเกี่ยวกับ Design Patterns ใน Python "การสอนให้เป็ดพิมพ์ดีดจะใช้เวลาสักครู่ แต่ช่วยคุณประหยัดงานได้มากในภายหลัง!"
ชอบองค์ประกอบวัตถุมากกว่ามรดก
นั่นคือสิ่งที่ฉันเรียกว่าหลักการ Pythonic ! ฉันได้สร้างคลาส/คลาสย่อยน้อยกว่าเมื่อเปรียบเทียบกับการรวมคลาสหนึ่ง (หรือมากกว่านั้น หลายคลาส) ในอีกคลาสหนึ่ง
แทนที่จะทำสิ่งนี้:
class User(DbObject): passเราสามารถทำสิ่งนี้ได้:
class User: _persist_methods = ['get', 'save', 'delete'] def __init__(self, persister): self._persister = persister def __getattr__(self, attribute): if attribute in self._persist_methods: return getattr(self._persister, attribute)ข้อดีนั้นชัดเจน เราสามารถจำกัดวิธีการของคลาสที่ห่อหุ้มไว้เพื่อแสดง เราสามารถฉีดอินสแตนซ์ถาวรในรันไทม์ได้! ตัวอย่างเช่น วันนี้เป็นฐานข้อมูลเชิงสัมพันธ์ แต่พรุ่งนี้อาจเป็นอะไรก็ได้ ด้วยอินเทอร์เฟซที่เราต้องการ
องค์ประกอบมีความสง่างามและเป็นธรรมชาติสำหรับ Python
รูปแบบพฤติกรรม
รูปแบบพฤติกรรมเกี่ยวข้องกับการสื่อสารระหว่างวัตถุ วิธีที่วัตถุโต้ตอบและทำงานให้สำเร็จ ตามหลักการของ GOF มีรูปแบบพฤติกรรมทั้งหมด 11 รูปแบบใน Python: Chain ofความรับผิดชอบ, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template, Visitor
ฉันพบว่ารูปแบบเหล่านี้มีประโยชน์มาก แต่ไม่ได้หมายความว่ากลุ่มรูปแบบอื่นจะไม่มีประโยชน์
ตัววนซ้ำ
ตัววนซ้ำถูกสร้างขึ้นใน Python นี่เป็นหนึ่งในคุณลักษณะที่ทรงพลังที่สุดของภาษา หลายปีก่อน ฉันได้อ่านที่ไหนสักแห่งที่ iterators ทำให้ Python ยอดเยี่ยม และฉันคิดว่ายังคงเป็นอย่างนั้น เรียนรู้เกี่ยวกับ Python iterators และ generator ให้เพียงพอ และคุณจะรู้ทุกสิ่งที่คุณต้องการเกี่ยวกับรูปแบบ Python นี้โดยเฉพาะ
สายใยความรับผิดชอบ
รูปแบบนี้ทำให้เรามีวิธีปฏิบัติต่อคำขอโดยใช้วิธีการต่างๆ กัน โดยแต่ละรูปแบบจะจัดการกับส่วนที่เฉพาะเจาะจงของคำขอ คุณรู้ไหม หลักการที่ดีที่สุดประการหนึ่งสำหรับโค้ดที่ดีคือ หลักการ ความรับผิดชอบเดียว
โค้ดทุกชิ้นต้องทำสิ่งเดียวเท่านั้น
หลักการนี้ได้รับการบูรณาการอย่างลึกซึ้งในรูปแบบการออกแบบนี้
ตัวอย่างเช่น หากเราต้องการกรองเนื้อหาบางอย่าง เราสามารถใช้ตัวกรองที่แตกต่างกัน โดยแต่ละตัวกรองจะทำตัวกรองประเภทเดียวที่แม่นยำและกำหนดไว้อย่างชัดเจน ตัวกรองเหล่านี้สามารถใช้เพื่อกรองคำที่ไม่เหมาะสม โฆษณา เนื้อหาวิดีโอที่ไม่เหมาะสม และอื่นๆ
class ContentFilter(object): def __init__(self, filters=None): self._filters = list() if filters is not None: self._filters += filters def filter(self, content): for filter in self._filters: content = filter(content) return content filter = ContentFilter([ offensive_filter, ads_filter, porno_video_filter]) filtered_content = filter.filter(content)สั่งการ
นี่เป็นหนึ่งในรูปแบบการออกแบบ Python แรกๆ ที่ฉันนำมาใช้ในฐานะโปรแกรมเมอร์ นั่นทำให้ฉันนึกถึง: ลวดลายไม่ได้ถูกประดิษฐ์ขึ้น แต่ถูกค้นพบ มันมีอยู่จริง เราแค่ต้องหาและนำไปใช้ ฉันค้นพบสิ่งนี้สำหรับโครงการที่น่าทึ่งที่เราดำเนินการเมื่อหลายปีก่อน: โปรแกรมแก้ไข XML แบบ WYSIWYM วัตถุประสงค์พิเศษ หลังจากใช้รูปแบบนี้อย่างเข้มข้นในโค้ดแล้ว ฉันอ่านเพิ่มเติมเกี่ยวกับรูปแบบนี้ในบางไซต์
รูปแบบคำสั่งมีประโยชน์ในสถานการณ์ที่เราต้องเริ่มต้นด้วยการเตรียมสิ่งที่จะถูกดำเนินการและดำเนินการเมื่อจำเป็นด้วยเหตุผลบางอย่าง ข้อดีคือการกระทำที่ห่อหุ้มในลักษณะดังกล่าวทำให้นักพัฒนา Python สามารถเพิ่มฟังก์ชันการทำงานเพิ่มเติมที่เกี่ยวข้องกับการดำเนินการที่ดำเนินการ เช่น เลิกทำ/ทำซ้ำ หรือเก็บประวัติการกระทำและอื่นๆ ในทำนองเดียวกัน
มาดูกันว่าตัวอย่างที่ง่ายและใช้บ่อยเป็นอย่างไร:
class RenameFileCommand(object): def __init__(self, from_name, to_name): self._from = from_name self._to = to_name def execute(self): os.rename(self._from, self._to) def undo(self): os.rename(self._to, self._from) class History(object): def __init__(self): self._commands = list() def execute(self, command): self._commands.append(command) command.execute() def undo(self): self._commands.pop().undo() history = History() history.execute(RenameFileCommand('docs/cv.doc', 'docs/cv-en.doc')) history.execute(RenameFileCommand('docs/cv1.doc', 'docs/cv-bg.doc')) history.undo() history.undo()รูปแบบการสร้างสรรค์
เริ่มต้นด้วยการชี้ให้เห็นว่ารูปแบบการสร้างสรรค์มักไม่ค่อยใช้ใน Python ทำไม? เนื่องจากธรรมชาติแบบไดนามิกของภาษา
มีคนฉลาดกว่าที่ฉันเคยบอกว่า Factory ถูกสร้างใน Python หมายความว่าภาษานั้นทำให้เรามีความยืดหยุ่นทั้งหมดที่จำเป็นในการสร้างวัตถุในลักษณะที่หรูหราเพียงพอ เราแทบไม่ต้องติดตั้งอะไรเพิ่มเลย เช่น Singleton หรือ Factory
ในบทช่วยสอน Python Design Patterns ฉบับหนึ่ง ฉันพบคำอธิบายของรูปแบบการออกแบบการสร้างสรรค์ที่ระบุว่าการออกแบบเหล่านี้ “รูปแบบให้วิธีการสร้างวัตถุในขณะที่ซ่อนตรรกะการสร้าง แทนที่จะสร้างอินสแตนซ์ของวัตถุโดยตรงโดยใช้ตัวดำเนินการ ใหม่ ”

นั่นเป็นการสรุปปัญหา: เรา ไม่มีโอเปอเรเตอร์ ใหม่ ใน Python!
อย่างไรก็ตาม เรามาดูกันว่าเราจะนำไปใช้ได้อย่างไร หากเรารู้สึกว่าเราอาจได้เปรียบจากการใช้รูปแบบดังกล่าว
ซิงเกิลตัน
รูปแบบ Singleton จะใช้เมื่อเราต้องการรับประกันว่ามีเพียงอินสแตนซ์เดียวของคลาสที่กำหนดที่มีอยู่ระหว่างรันไทม์ เราต้องการรูปแบบนี้ใน Python จริงหรือ? จากประสบการณ์ของฉัน การสร้างอินสแตนซ์โดยเจตนานั้นง่ายกว่าโดยตั้งใจแล้วใช้แทนการใช้รูปแบบ Singleton
แต่หากคุณต้องการนำไปใช้จริง นี่คือข่าวดี: ใน Python เราสามารถเปลี่ยนแปลงกระบวนการสร้างอินสแตนซ์ (รวมถึงอย่างอื่นแทบทั้งหมด) จำวิธีการ __new__() ที่ฉันกล่าวถึงก่อนหน้านี้ได้หรือไม่ ไปเลย:
class Logger(object): def __new__(cls, *args, **kwargs): if not hasattr(cls, '_logger'): cls._logger = super(Logger, cls ).__new__(cls, *args, **kwargs) return cls._loggerในตัวอย่างนี้ Logger เป็น Singleton
นี่เป็นทางเลือกแทนการใช้ Singleton ใน Python:
- ใช้โมดูล
- สร้างอินสแตนซ์หนึ่งอินสแตนซ์ที่ระดับบนสุดของแอปพลิเคชันของคุณ อาจเป็นในไฟล์กำหนดค่า
- ส่งผ่านอินสแตนซ์ไปยังทุกอ็อบเจ็กต์ที่ต้องการ นั่นคือการฉีดพึ่งพาและเป็นกลไกที่ทรงพลังและเข้าใจได้ง่าย
การฉีดพึ่งพา
ฉันไม่ได้ตั้งใจจะอภิปรายว่าการฉีดการพึ่งพาเป็นรูปแบบการออกแบบหรือไม่ แต่ฉันจะบอกว่ามันเป็นกลไกที่ดีมากในการใช้ข้อต่อแบบหลวม ๆ และช่วยให้แอปพลิเคชันของเราสามารถบำรุงรักษาและขยายได้ ผสมผสานกับ Duck Typing แล้วพลังจะอยู่กับคุณ เสมอ.
ฉันระบุไว้ในส่วนรูปแบบการสร้างสรรค์ของโพสต์นี้เนื่องจากเกี่ยวข้องกับคำถามที่ว่าวัตถุถูกสร้างขึ้นเมื่อใด (หรือดีกว่านั้น: ที่ไหน) มันถูกสร้างขึ้นภายนอก ดีกว่าที่จะบอกว่าอ็อบเจ็กต์ไม่ได้ถูกสร้างขึ้นในทุกที่ที่เราใช้ ดังนั้นการขึ้นต่อกันจะไม่ถูกสร้างขึ้นในที่ที่มันถูกใช้ไป รหัสผู้บริโภคได้รับวัตถุที่สร้างขึ้นจากภายนอกและใช้งาน สำหรับการอ้างอิงเพิ่มเติม โปรดอ่านคำตอบ upvoted มากที่สุดสำหรับคำถาม Stackoverflow นี้
เป็นคำอธิบายที่ดีเกี่ยวกับการฉีดการพึ่งพาและให้แนวคิดที่ดีเกี่ยวกับศักยภาพของเทคนิคเฉพาะนี้ โดยพื้นฐานแล้ว คำตอบจะอธิบายปัญหาด้วยตัวอย่างต่อไปนี้: อย่านำของไปดื่มจากตู้เย็นด้วยตัวเอง ให้ระบุความต้องการแทน บอกพ่อแม่ว่าคุณต้องการอะไรดื่มกับอาหารกลางวัน
Python เสนอทุกสิ่งที่เราต้องการเพื่อนำไปใช้อย่างง่ายดาย ลองนึกถึงการใช้งานที่เป็นไปได้ในภาษาอื่นๆ เช่น Java และ C# แล้วคุณจะเห็นความงามของ Python ได้อย่างรวดเร็ว
ลองนึกถึงตัวอย่างง่ายๆ ของการฉีดพึ่งพา:
class Command: def __init__(self, authenticate=None, authorize=None): self.authenticate = authenticate or self._not_authenticated self.authorize = authorize or self._not_autorized def execute(self, user, action): self.authenticate(user) self.authorize(user, action) return action() if in_sudo_mode: command = Command(always_authenticated, always_authorized) else: command = Command(config.authenticate, config.authorize) command.execute(current_user, delete_user_action)เราใส่วิธีการ ตรวจสอบ และ อนุญาต ในคลาสคำสั่ง ความต้องการของคลาส Command ทั้งหมดคือการดำเนินการให้สำเร็จโดยไม่ต้องกังวลเกี่ยวกับรายละเอียดการนำไปใช้ ด้วยวิธีนี้ เราอาจใช้คลาส Command กับกลไกการพิสูจน์ตัวตนและการอนุญาตใดก็ตามที่เราตัดสินใจใช้ในรันไทม์
เราได้แสดงวิธีการฉีดการพึ่งพาผ่านตัวสร้างแล้ว แต่เราสามารถฉีดได้อย่างง่ายดายโดยการตั้งค่าคุณสมบัติของวัตถุโดยตรง ปลดล็อกศักยภาพมากยิ่งขึ้น:
command = Command() if in_sudo_mode: command.authenticate = always_authenticated command.authorize = always_authorized else: command.authenticate = config.authenticate command.authorize = config.authorize command.execute(current_user, delete_user_action)มีอะไรอีกมากมายให้เรียนรู้เกี่ยวกับการฉีดพึ่งพา คนที่อยากรู้อยากเห็นจะค้นหา IoC เช่น
แต่ก่อนที่คุณจะทำเช่นนั้น โปรดอ่านคำตอบ Stackoverflow อื่น ซึ่งเป็นคำตอบที่ได้รับการโหวตมากที่สุดสำหรับคำถามนี้
อีกครั้ง เราเพิ่งแสดงให้เห็นว่าการนำรูปแบบการออกแบบที่ยอดเยี่ยมนี้ไปใช้ใน Python นั้นเป็นเพียงเรื่องของการใช้ฟังก์ชันในตัวของภาษาอย่างไร
อย่าลืมว่าทั้งหมดนี้หมายความว่าอย่างไร: เทคนิคการฉีดขึ้นต่อกันช่วยให้ทำการทดสอบหน่วยได้ง่ายและยืดหยุ่นมาก ลองนึกภาพสถาปัตยกรรมที่คุณสามารถเปลี่ยนการจัดเก็บข้อมูลได้ทันที การเยาะเย้ยฐานข้อมูลกลายเป็นงานที่ไม่สำคัญใช่ไหม สำหรับข้อมูลเพิ่มเติม คุณสามารถดู Toptal's Introduction to Mocking in Python
คุณอาจต้องการศึกษารูปแบบการออกแบบ Prototype , Builder และ Factory
รูปแบบโครงสร้าง
ซุ้ม
นี่อาจเป็นรูปแบบการออกแบบ Python ที่มีชื่อเสียงที่สุด
ลองนึกภาพคุณมีระบบที่มีวัตถุจำนวนมาก ทุกอ็อบเจ็กต์เสนอชุดเมธอด API ที่หลากหลาย คุณสามารถทำสิ่งต่างๆ ได้มากมายกับระบบนี้ แต่จะลดความซับซ้อนของอินเทอร์เฟซได้อย่างไร ทำไมไม่เพิ่มอ็อบเจ็กต์อินเทอร์เฟซที่เผยให้เห็นชุดย่อยที่คิดมาอย่างดีของวิธีการ API ทั้งหมด? ซุ้ม!
ตัวอย่างรูปแบบการออกแบบ Python Facade:
class Car(object): def __init__(self): self._tyres = [Tyre('front_left'), Tyre('front_right'), Tyre('rear_left'), Tyre('rear_right'), ] self._tank = Tank(70) def tyres_pressure(self): return [tyre.pressure for tyre in self._tyres] def fuel_level(self): return self._tank.level ไม่มีความประหลาดใจ ไม่มีลูกเล่น คลาส Car คือ Facade และนั่นคือทั้งหมด
อะแดปเตอร์
หาก Facades ถูกใช้เพื่อทำให้อินเทอร์เฟซง่ายขึ้น Adapters ทั้งหมดเกี่ยวกับการปรับเปลี่ยนอินเทอร์เฟซ เหมือนใช้วัวตอนที่ระบบรอเป็ด
สมมติว่าคุณมีวิธีการทำงานในการบันทึกข้อมูลไปยังปลายทางที่กำหนด เมธอดของคุณคาดหวังให้ปลายทางมีเมธอด write() (เช่น ทุกอ็อบเจ็กต์ไฟล์มี เป็นต้น)
def log(message, destination): destination.write('[{}] - {}'.format(datetime.now(), message)) ฉันจะบอกว่ามันเป็นวิธีการเขียนที่ดีด้วยการฉีดพึ่งพา ซึ่งช่วยให้ขยายได้ดี สมมติว่าคุณต้องการเข้าสู่ระบบซ็อกเก็ต UDP บางตัวแทนไฟล์ คุณทราบวิธีเปิดซ็อกเก็ต UDP นี้ แต่ปัญหาเดียวคือวัตถุ socket ไม่มีเมธอด write() คุณต้องมี อแดปเตอร์ !
import socket class SocketWriter(object): def __init__(self, ip, port): self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self._ip = ip self._port = port def write(self, message): self._socket.send(message, (self._ip, self._port)) def log(message, destination): destination.write('[{}] - {}'.format(datetime.now(), message)) upd_logger = SocketWriter('1.2.3.4', '9999') log('Something happened', udp_destination)แต่ทำไมฉันถึงพบว่า อะแดปเตอร์ มีความสำคัญมาก เมื่อรวมเข้ากับการแทรกการพึ่งพาอย่างมีประสิทธิภาพ มันทำให้เรามีความยืดหยุ่นอย่างมาก เหตุใดจึงต้องแก้ไขโค้ดที่ผ่านการทดสอบอย่างดีของเราเพื่อรองรับอินเทอร์เฟซใหม่ ในเมื่อเราสามารถใช้อะแดปเตอร์ที่จะแปลอินเทอร์เฟซใหม่เป็นอินเทอร์เฟซที่รู้จักกันดีได้
นอกจากนี้ คุณควรตรวจสอบและมาสเตอร์ บริดจ์ และรูปแบบการออกแบบ พร็อกซี เนื่องจากมีความคล้ายคลึงกันกับ อะแด็ปเตอร์ ลองคิดดูว่าการนำไปใช้ใน Python นั้นง่ายเพียงใด และลองนึกถึงวิธีต่างๆ ที่คุณจะนำไปใช้ในโปรเจ็กต์ของคุณ
มัณฑนากร
โอ้ช่างโชคดีเหลือเกิน! มัณฑนากร นั้นดีมาก และเราได้รวมพวกเขาเข้ากับภาษาแล้ว สิ่งที่ฉันชอบมากที่สุดใน Python คือการใช้มันสอนให้เราใช้แนวปฏิบัติที่ดีที่สุด ไม่ใช่ว่าเราไม่จำเป็นต้องมีสติเกี่ยวกับแนวทางปฏิบัติที่ดีที่สุด (และโดยเฉพาะรูปแบบการออกแบบ) แต่ด้วย Python ฉันรู้สึกเหมือนกำลังปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุด โดยส่วนตัวแล้ว ฉันพบว่าแนวทางปฏิบัติที่ดีที่สุดของ Python นั้นเป็นธรรมชาติและเป็นธรรมชาติรอง และนี่คือสิ่งที่ชื่นชมจากนักพัฒนามือใหม่และนักพัฒนาชั้นยอด
รูปแบบ มัณฑนากร เป็นเรื่องเกี่ยวกับการแนะนำฟังก์ชันเพิ่มเติมและโดยเฉพาะอย่างยิ่งการทำโดยไม่ต้องใช้การสืบทอด
มาดูว่าเราตกแต่งวิธีการโดยไม่ต้องใช้ฟังก์ชัน Python ในตัวอย่างไร นี่คือตัวอย่างที่ตรงไปตรงมา
def execute(user, action): self.authenticate(user) self.authorize(user, action) return action() ที่ไม่ค่อยดีนักที่นี่คือฟังก์ชัน execute ทำมากกว่าดำเนินการบางอย่าง เราไม่ได้ปฏิบัติตามหลักการความรับผิดชอบเดียวในจดหมาย
เป็นการดีที่จะเขียนเพียงต่อไปนี้:
def execute(action): return action()เราสามารถใช้ฟังก์ชันการอนุญาตและการรับรองความถูกต้องในที่อื่นใน มัณฑนากร เช่น:
def execute(action, *args, **kwargs): return action() def autheticated_only(method): def decorated(*args, **kwargs): if check_authenticated(kwargs['user']): return method(*args, **kwargs) else: raise UnauthenticatedError return decorated def authorized_only(method): def decorated(*args, **kwargs): if check_authorized(kwargs['user'], kwargs['action']): return method(*args, **kwargs) else: raise UnauthorizeddError return decorated execute = authenticated_only(execute) execute = authorized_only(execute) ตอนนี้วิธีการ execute() คือ:
- อ่านง่าย
- ทำสิ่งเดียวเท่านั้น (อย่างน้อยเมื่อดูรหัส)
- ตกแต่งด้วยการรับรองความถูกต้อง
- ตกแต่งด้วยใบอนุญาต
เราเขียนสิ่งเดียวกันโดยใช้ไวยากรณ์มัณฑนากรในตัวของ Python:
def autheticated_only(method): def decorated(*args, **kwargs): if check_authenticated(kwargs['user']): return method(*args, **kwargs ) else: raise UnauthenticatedError return decorated def authorized_only(method): def decorated(*args, **kwargs): if check_authorized(kwargs['user'], kwargs['action']): return method(*args, **kwargs) else: raise UnauthorizedError return decorated @authorized_only @authenticated_only def execute(action, *args, **kwargs): return action() สิ่งสำคัญคือต้องทราบว่าคุณ ไม่ได้จำกัดอยู่เพียงหน้าที่ เป็นนักตกแต่งเท่านั้น มัณฑนากรอาจเกี่ยวข้องกับชั้นเรียนทั้งหมด ข้อกำหนดเพียงอย่างเดียวคือต้อง สามารถเรียก ได้ แต่เราไม่มีปัญหากับเรื่องนั้น เราแค่ต้องกำหนดวิธีการ __call__(self)
คุณอาจต้องการดูโมดูล functools ของ Python อย่างละเอียดยิ่งขึ้น มีอะไรให้ค้นหามากมายที่นั่น!
บทสรุป
ฉันได้แสดงให้เห็นว่าการใช้รูปแบบการออกแบบของ Python นั้นเป็นธรรมชาติและง่ายดายเพียงใด แต่ฉันยังแสดงให้เห็นด้วยว่าการเขียนโปรแกรมใน Python ควรจะดำเนินไปอย่างง่ายดายเช่นกัน
“ง่ายดีกว่าซับซ้อน” จำได้ไหม? บางทีคุณอาจสังเกตเห็นว่าไม่มีรูปแบบการออกแบบใดที่อธิบายได้ครบถ้วนและเป็นทางการ ไม่มีการแสดงการใช้งานเต็มรูปแบบที่ซับซ้อน คุณต้อง "รู้สึก" และนำไปใช้ในลักษณะที่เหมาะสมกับสไตล์และความต้องการของคุณมากที่สุด Python เป็นภาษาที่ยอดเยี่ยมและให้พลังทั้งหมดที่คุณต้องการเพื่อสร้างโค้ดที่ยืดหยุ่นและนำกลับมาใช้ใหม่ได้
อย่างไรก็ตาม มันให้คุณมากกว่านั้น มันให้ "อิสระ" แก่คุณในการเขียนโค้ด ที่แย่ มาก อย่าทำอย่างนั้น! อย่าทำซ้ำตัวเอง (DRY) และอย่าเขียนโค้ดบรรทัดที่ยาวเกิน 80 อักขระ และอย่าลืมใช้รูปแบบการออกแบบตามความเหมาะสม เป็นวิธีที่ดีที่สุดวิธีหนึ่งในการเรียนรู้จากผู้อื่นและรับประสบการณ์มากมายโดยไม่เสียค่าใช้จ่าย
