มิกซ์อินคืออะไรและทำไมจึงมีประโยชน์


954

ใน " Programming Python " Mark Markz พูดถึง "mixins" ฉันมาจากพื้นหลัง C / C ++ / C # และฉันไม่เคยได้ยินคำศัพท์มาก่อน มิกซ์อินคืออะไร?

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

เหตุใดฉันจึงต้องการทำเช่นนั้นแทนที่จะใส่ฟังก์ชันใหม่ลงในคลาสย่อย สำหรับเรื่องนั้นทำไมการผสมผสานมิกซ์ / การสืบทอดหลายวิธีจึงดีกว่าการใช้การแต่งเพลง?

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

คำตอบ:


710

มิกซ์อินเป็นมรดกพิเศษหลายชนิด มีสองสถานการณ์หลักที่ใช้ mixins:

  1. คุณต้องการให้คุณสมบัติเสริมมากมายสำหรับชั้นเรียน
  2. คุณต้องการใช้คุณสมบัติหนึ่งอย่างในคลาสที่แตกต่างกันมากมาย

สำหรับตัวอย่างของหมายเลขหนึ่งให้พิจารณาคำขอและการตอบสนองของระบบ Werkzeug ของ ฉันสามารถสร้างคำขอวัตถุแบบเดิมโดยพูดว่า:

from werkzeug import BaseRequest

class Request(BaseRequest):
    pass

หากฉันต้องการเพิ่มการสนับสนุนส่วนหัวยอมรับฉันจะทำเช่นนั้น

from werkzeug import BaseRequest, AcceptMixin

class Request(AcceptMixin, BaseRequest):
    pass

ถ้าฉันต้องการสร้างวัตถุคำขอที่รองรับการยอมรับส่วนหัว, etags, การรับรองความถูกต้องและการสนับสนุนตัวแทนผู้ใช้ฉันสามารถทำได้:

from werkzeug import BaseRequest, AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin

class Request(AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin, BaseRequest):
    pass

ความแตกต่างนั้นบอบบาง แต่ในตัวอย่างข้างต้นคลาส mixin ไม่ได้ถูกสร้างขึ้นมาเพื่อยืนเอง ในการรับมรดกหลายแบบดั้งเดิมมากขึ้นAuthenticationMixin(ตัวอย่าง) Authenticatorอาจจะเป็นสิ่งที่มากขึ้นเช่น นั่นคือคลาสนั้นอาจถูกออกแบบมาให้โดดเด่นด้วยตัวของมันเอง


123
สถานการณ์ที่สามคือ: คุณต้องการให้คุณสมบัติ (ไม่จำเป็น) จำนวนมากสำหรับคลาส แต่คุณต้องการคุณลักษณะในคลาสที่แยกต่างหาก (และในโมดูลที่แยกต่างหาก) ดังนั้นแต่ละโมดูลจะเกี่ยวกับคุณลักษณะ (ลักษณะการทำงาน) IOW ไม่ใช่ สำหรับการนำกลับมาใช้ใหม่ แต่สำหรับการแบ่งระดับ
bootchk

60
อาจไม่ใช่ปัญหาในตัวอย่างนี้ แต่โดยทั่วไปคุณต้องการให้คลาสฐานหลักเป็นองค์ประกอบสุดท้ายในวงเล็บเพื่อสร้างเชนการสืบทอด: Request ==> Mixin ==> ... ==> BaseRequest ดูที่นี่: ianlewis.org/en/mixins-and-python
hillel

10
@Helel จุดดี แต่โปรดจำไว้ว่า Python จะเรียกเมธอดของ superclasses จากซ้ายไปขวา (เมื่อคุณต้องการแทนที่ Constructor เป็นต้น)
Eliseu Monar dos Santos

9
ฟังดูเหมือนรูปแบบการออกแบบของมัณฑนากร
D-Jones

4
สถานการณ์ที่ 4 คือมีอยู่แล้วครอบครัวที่มีอยู่ของParentชั้นเรียนและChild1, Child2, ChildNsubclasses ภายในห้องสมุดของบุคคลที่ 3 และคุณต้องการเป็นพฤติกรรมที่กำหนดเองสำหรับทั้งครอบครัว เป็นการดีที่คุณต้องการที่จะเพิ่มพฤติกรรมดังกล่าวParentและหวังว่านักพัฒนาห้องสมุดบุคคลที่สามจะใช้คำขอดึงของคุณ มิฉะนั้นคุณจะต้องใช้งานของคุณเองclass NewBehaviorMixinแล้วกำหนดชุดคลาส wrapper เต็มรูปแบบเช่นclass NewParent(NewBehaviorMixin, Parent): passและclass NewChildN(NewBehaviorMixin, ChildN): passอื่น ๆ (PS: คุณรู้วิธีที่ดีกว่าหรือไม่)
RayLuo

240

ขั้นแรกคุณควรทราบว่ามิกซ์อินมีอยู่ในหลายภาษาที่สืบทอดเท่านั้น คุณไม่สามารถผสมมิกซ์ใน Java หรือ C # ได้

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

โดยทั่วไปแล้วมิกซ์อินจะแคบในขอบเขตและไม่ได้หมายถึงการขยาย

[แก้ไข - เพื่อเหตุผล:]

ฉันคิดว่าฉันควรจะอธิบายว่าทำไมเพราะคุณถาม ประโยชน์ใหญ่คือคุณไม่ต้องทำเองซ้ำแล้วซ้ำอีก ใน C #, สถานที่ที่ใหญ่ที่สุดที่จะได้รับประโยชน์ mixin อาจจะมาจากรูปแบบการกำจัด เมื่อใดก็ตามที่คุณใช้ IDisposable คุณเกือบจะต้องการทำตามรูปแบบเดียวกันเสมอ แต่คุณจะสิ้นสุดการเขียนและเขียนรหัสพื้นฐานเดียวกันอีกครั้งพร้อมกับการเปลี่ยนแปลงเล็กน้อย หากมีมิกซ์การกำจัดที่ขยายได้คุณสามารถบันทึกการพิมพ์พิเศษได้ด้วยตนเอง

[แก้ไข 2 - เพื่อตอบคำถามอื่นของคุณ]

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

ใช่. ความแตกต่างระหว่างมิกซ์อินกับการรับมรดกหลายมาตรฐานเป็นเพียงเรื่องของความหมาย คลาสที่มีหลายสืบทอดอาจใช้ mixin เป็นส่วนหนึ่งของการสืบทอดหลายที่

จุดประสงค์ของ mixin คือการสร้างประเภทที่สามารถ "ผสม" กับชนิดอื่นผ่านการสืบทอดโดยไม่ส่งผลกระทบต่อชนิดการสืบทอดขณะที่ยังเสนอฟังก์ชันการทำงานที่เป็นประโยชน์สำหรับประเภทนั้น

ลองคิดถึงอินเตอร์เฟสที่ใช้งานแล้ว

โดยส่วนตัวแล้วฉันไม่ได้ใช้มิกซ์อินเพราะฉันพัฒนาเป็นภาษาที่ไม่รองรับพวกเขาดังนั้นฉันจึงมีช่วงเวลาที่ยากลำบากในการหาตัวอย่างที่ดีที่จะให้ "ahah!" ช่วงเวลาสำหรับคุณ แต่ฉันจะลองอีกครั้ง ฉันจะใช้ตัวอย่างที่ประดิษฐ์ขึ้นมาแล้ว - ภาษาส่วนใหญ่มีคุณสมบัติอยู่แล้วไม่ทางใดก็ทางหนึ่ง - แต่หวังว่าจะอธิบายได้ว่าควรจะสร้างและใช้มิกซ์อินอย่างไร ไปที่นี่:

สมมติว่าคุณมีประเภทที่คุณต้องการให้เป็นอนุกรมและจาก XML คุณต้องการให้ประเภทเพื่อให้ "ToXML" วิธีการที่ส่งกลับสตริงที่มีส่วน XML ด้วยค่าข้อมูลของประเภทและ "FromXML" ที่ช่วยให้ประเภทที่จะสร้างค่าข้อมูลจากชิ้นส่วน XML ในสตริง อีกครั้งนี่เป็นตัวอย่างที่วางแผนไว้ดังนั้นบางทีคุณอาจใช้สตรีมไฟล์หรือคลาส XML Writer จากไลบรารีรันไทม์ของภาษาของคุณ ... ไม่ว่าอะไรก็ตาม ประเด็นก็คือคุณต้องการทำให้เป็นอันดับวัตถุของคุณเป็น XML และรับวัตถุใหม่กลับมาจาก XML

จุดสำคัญอื่น ๆ ในตัวอย่างนี้คือคุณต้องการทำสิ่งนี้ด้วยวิธีทั่วไป คุณไม่ต้องการใช้วิธี "ToXML" และ "FromXML" สำหรับทุกประเภทที่คุณต้องการทำให้เป็นอนุกรมคุณต้องการวิธีทั่วไปในการรับรองว่าประเภทของคุณจะทำสิ่งนี้และใช้งานได้ คุณต้องการใช้รหัสซ้ำ

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

และ .. เพียงเท่านี้ หากต้องการใช้งานคุณจะต้องมีชนิดใด ๆ ที่ต้องถูกทำให้เป็นอนุกรมกับ XML ที่สืบทอดมาจาก XmlSerializable เมื่อใดก็ตามที่คุณต้องการทำให้ซีเรียลไลซ์หรือดีซีเรียลไลซ์ชนิดนั้นคุณเพียงโทร ToXML หรือ FromXML ในความเป็นจริงเนื่องจาก XmlSerializable เป็นประเภทที่เต็มเปี่ยมและ polymorphic คุณสามารถสร้าง serializer เอกสารที่ไม่รู้อะไรเลยเกี่ยวกับประเภทดั้งเดิมของคุณยอมรับเฉพาะพูดประเภทของ XmlSerializable

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

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

หวังว่า. :)


25
สวัสดีคุณชอบวลีที่ว่า "polymorphic resonance" หรือไม่? ทำมันเอง ฉันคิด. บางทีฉันได้ยินมันในฟิสิกส์บาง ...
Randolpho

50
ฉันไม่เห็นด้วยกับประโยคแรกของคุณเล็กน้อย Ruby เป็นภาษาที่สืบทอดเดี่ยวและมิกซ์อินเป็นวิธีเพิ่มเมธอดให้กับคลาสที่กำหนดโดยไม่มีคลาสจากคลาสอื่น
Keltia

23
@ Keltia: ฉันคิดว่า mixin เป็น - โดยความหมาย - หลายมรดก ในกรณีทับทิมพวกมันเป็นลิง (หรืออย่างอื่น) ไม่ใช่มิกซ์อินที่เหมาะสม คนทับทิมอาจเรียกมันว่ามิกซ์มิน แต่มันเป็นสิ่งที่ต่างออกไป
S.Lott

10
อันที่จริงมิกซ์อินที่แท้จริงไม่สามารถใช้การสืบทอดหลายอย่างได้ มิกซ์อินประกอบด้วยเมธอดแอ็ตทริบิวต์และอื่น ๆ จากคลาสหนึ่งไปอีกคลาสหนึ่งโดยไม่สืบทอด สิ่งนี้มีแนวโน้มที่จะให้ประโยชน์ของการนำโค้ดกลับมาใช้ใหม่ด้วย polymorphism แต่ทำให้เกิดปัญหาในการระบุบิดามารดา (เพชรแห่งความตาย ฯลฯ ) ภาษาที่สนับสนุน Mixin ก็มีแนวโน้มที่จะอนุญาตให้รวมคลาส Mixin บางส่วน (สิ่งต่างๆ ด้านนี้)
เทรเวอร์

8
สำหรับเรกคอร์ดแล้ว Java รองรับมิกซ์อินด้วยวิธีการเริ่มต้น
shmosel

170

คำตอบนี้มีจุดมุ่งหมายเพื่ออธิบายมิกซ์อินพร้อมด้วยตัวอย่างที่:

  • อยู่ในตัวเอง : สั้น ๆ โดยไม่จำเป็นต้องรู้จักห้องสมุดใด ๆ เพื่อทำความเข้าใจกับตัวอย่าง

  • ใน Pythonไม่ใช่ภาษาอื่น

    เป็นที่เข้าใจได้ว่ามีตัวอย่างจากภาษาอื่นเช่น Ruby เนื่องจากคำนี้มีอยู่ทั่วไปในภาษาเหล่านั้นมากขึ้น แต่นี่เป็นPython thread

นอกจากนี้ยังจะต้องพิจารณาคำถามแย้ง:

จำเป็นต้องมีการสืบทอดหลายรายการหรือไม่แสดงลักษณะมิกซ์อินหรือไม่?

คำนิยาม

ฉันยังไม่เห็นการอ้างอิงจากแหล่ง "เชิงอำนาจ" อย่างชัดเจนว่ามิกซ์อินในงูใหญ่คืออะไร

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

ฉันทามติอาจแตกต่างกันในแต่ละภาษา

คำจำกัดความที่ 1: ไม่มีการสืบทอดหลายรายการ

มิกซ์อินเป็นคลาสที่เมธอดของคลาสใช้เมธอดที่ไม่ได้กำหนดไว้ในคลาส

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

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

ตัวอย่างคลาสสิกคือการใช้งานตัวดำเนินการเปรียบเทียบทั้งหมดจากเท่านั้น<=และ==:

class ComparableMixin(object):
    """This class has methods which use `<=` and `==`,
    but this class does NOT implement those methods."""
    def __ne__(self, other):
        return not (self == other)
    def __lt__(self, other):
        return self <= other and (self != other)
    def __gt__(self, other):
        return not self <= other
    def __ge__(self, other):
        return self == other or self > other

class Integer(ComparableMixin):
    def __init__(self, i):
        self.i = i
    def __le__(self, other):
        return self.i <= other.i
    def __eq__(self, other):
        return self.i == other.i

assert Integer(0) <  Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) >  Integer(0)
assert Integer(1) >= Integer(1)

# It is possible to instantiate a mixin:
o = ComparableMixin()
# but one of its methods raise an exception:
#o != o 

ตัวอย่างนี้สามารถทำได้โดยใช้functools.total_ordering()มัณฑนากร แต่เกมที่นี่คือการคิดค้นล้อใหม่:

import functools

@functools.total_ordering
class Integer(object):
    def __init__(self, i):
        self.i = i
    def __le__(self, other):
        return self.i <= other.i
    def __eq__(self, other):
        return self.i == other.i

assert Integer(0) < Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) > Integer(0)
assert Integer(1) >= Integer(1)

คำจำกัดความที่ 2: การสืบทอดหลายรายการ

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

คำว่าmixin classหมายถึงคลาสพื้นฐานที่มีจุดประสงค์เพื่อใช้ในรูปแบบการออกแบบนั้น (สิ่งที่ต้องทำที่ใช้วิธีหรือสิ่งที่ใช้มัน)

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

รูปแบบนี้น่าสนใจเพราะเป็นไปได้ที่จะรวมฟังก์ชันการทำงานที่มีตัวเลือกต่าง ๆ ของคลาสพื้นฐานไว้:

class HasMethod1(object):
    def method(self):
        return 1

class HasMethod2(object):
    def method(self):
        return 2

class UsesMethod10(object):
    def usesMethod(self):
        return self.method() + 10

class UsesMethod20(object):
    def usesMethod(self):
        return self.method() + 20

class C1_10(HasMethod1, UsesMethod10): pass
class C1_20(HasMethod1, UsesMethod20): pass
class C2_10(HasMethod2, UsesMethod10): pass
class C2_20(HasMethod2, UsesMethod20): pass

assert C1_10().usesMethod() == 11
assert C1_20().usesMethod() == 21
assert C2_10().usesMethod() == 12
assert C2_20().usesMethod() == 22

# Nothing prevents implementing the method
# on the base class like in Definition 1:

class C3_10(UsesMethod10):
    def method(self):
        return 3

assert C3_10().usesMethod() == 13

หลามเผด็จการเกิดขึ้น

ที่documentatiton อย่างเป็นทางการสำหรับ collections.abcเอกสารอย่างชัดเจนใช้คำว่าวิธี Mixin

มันระบุว่าถ้าชั้นเรียน:

  • การดำเนินการ __next__
  • สืบทอดมาจากคลาสเดียว Iterator

จากนั้นชั้นเรียนจะได้รับ__iter__ วิธีมิกซ์อินฟรี

ดังนั้นอย่างน้อยในจุดนี้ของเอกสารคู่มือmixin ไม่ต้องการการสืบทอดหลายแบบและสอดคล้องกับข้อกำหนด 1

แน่นอนว่าเอกสารอาจขัดแย้งกับจุดต่าง ๆ และห้องสมุด Python สำคัญอื่น ๆ อาจใช้คำจำกัดความอื่นในเอกสารประกอบ

หน้านี้ยังใช้คำศัพท์Set mixinซึ่งแสดงให้เห็นอย่างชัดเจนว่าคลาสที่ต้องการSetและIteratorสามารถเรียกคลาส Mixin ได้

ในภาษาอื่น ๆ

  • Ruby: เห็นได้ชัดว่าไม่ต้องการการสืบทอดหลายรายการสำหรับมิกซ์อินดังที่กล่าวไว้ในหนังสืออ้างอิงที่สำคัญเช่นการเขียนโปรแกรม Rubyและภาษาการเขียนโปรแกรม Ruby

  • C ++: วิธีการที่ไม่ได้ใช้งานเป็นวิธีเสมือนจริง

    นิยาม 1 เกิดขึ้นพร้อมกับคำจำกัดความของคลาสนามธรรม (คลาสที่มีวิธีเสมือนจริง) คลาสนั้นไม่สามารถสร้างอินสแตนซ์ได้

    นิยาม 2 เป็นไปได้ด้วยการสืบทอดเสมือน: การสืบทอดหลายรายการจากสองคลาสที่ได้รับ


37

ฉันคิดว่าพวกเขาเป็นวิธีที่มีระเบียบวินัยในการใช้การสืบทอดหลายอย่าง - เนื่องจากท้ายที่สุดแล้ว mixin เป็นเพียงคลาสไพ ธ อนอื่นที่ (อาจ) ปฏิบัติตามอนุสัญญาเกี่ยวกับคลาสที่เรียกว่ามิกซ์อิน

ความเข้าใจของฉันเกี่ยวกับการประชุมที่ควบคุมสิ่งที่คุณจะเรียก Mixin นั้นคือ Mixin:

  • เพิ่มวิธี แต่ไม่ใช่ตัวแปรอินสแตนซ์ (ค่าคงที่คลาสเป็น OK)
  • รับเฉพาะจากobject(ใน Python)

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

ถ้าฉันต้องการเพิ่มตัวแปรอินสแตนซ์ (มีความยืดหยุ่นมากกว่าที่อนุญาตโดยการสืบทอดเดี่ยว) ดังนั้นฉันมักจะแต่งเพลง

ต้องบอกว่าฉันได้เห็นคลาสที่เรียกว่า XYZMixin ที่มีตัวแปรอินสแตนซ์


30

มิกซ์อินเป็นแนวคิดในการเขียนโปรแกรมซึ่งคลาสให้ฟังก์ชันการทำงาน แต่ไม่ได้มีไว้สำหรับใช้ในการสร้างอินสแตนซ์ จุดประสงค์หลักของ Mixins คือเพื่อให้ฟังก์ชั่นที่เป็นแบบสแตนด์อโลนและมันจะดีที่สุดถ้า mixins นั้นไม่มีการสืบทอดกับ mixins อื่นและหลีกเลี่ยงสถานะ ในภาษาเช่น Ruby มีการสนับสนุนบางภาษาโดยตรง แต่สำหรับ Python ไม่มี อย่างไรก็ตามคุณสามารถใช้การสืบทอดหลายคลาสเพื่อใช้งานฟังก์ชันที่มีให้ใน Python

ฉันดูวิดีโอนี้http://www.youtube.com/watch?v=v_uKI2NOLEMเพื่อทำความเข้าใจพื้นฐานของมิกซ์อิน มันค่อนข้างมีประโยชน์สำหรับผู้เริ่มต้นที่จะเข้าใจพื้นฐานของมิกซ์อินและวิธีที่พวกเขาทำงานและปัญหาที่คุณอาจเผชิญในการใช้พวกเขา

วิกิพีเดียยังดีที่สุด: http://en.wikipedia.org/wiki/Mixin


29

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

มิกซ์อินเป็นรูปแบบที่ จำกัด ของการสืบทอดหลายแบบ ในบางภาษากลไกในการเพิ่มมิกซ์อินให้กับคลาสนั้นแตกต่างกันเล็กน้อย (ในแง่ของไวยากรณ์) จากการสืบทอด

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

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

ตัวอย่างของการสืบทอดหลายรายการ

ตัวอย่างนี้จากเอกสารเป็น OrderedCounter:

class OrderedCounter(Counter, OrderedDict):
     'Counter that remembers the order elements are first encountered'

     def __repr__(self):
         return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))

     def __reduce__(self):
         return self.__class__, (OrderedDict(self),)

มันคลาสย่อยทั้งCounterและOrderedDictจากcollectionsโมดูล

ทั้งสองCounterและOrderedDictตั้งใจที่จะอินสแตนซ์และใช้ด้วยตัวเอง อย่างไรก็ตามโดย subclassing พวกเขาทั้งสองเราสามารถมีเคาน์เตอร์ที่สั่งซื้อและนำรหัสในแต่ละวัตถุ

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

ตัวอย่างของ Mixin

โดยปกติแล้ว Mixins จะได้รับการส่งเสริมเนื่องจากวิธีการรับรหัสซ้ำโดยไม่มีปัญหาการเชื่อมต่อที่อาจเกิดขึ้นได้ซึ่งการร่วมมือกันหลายมรดกเช่น OrderedCounter เมื่อคุณใช้มิกซ์อินคุณจะใช้ฟังก์ชั่นที่ไม่ได้อยู่คู่กับข้อมูลอย่างแน่นหนา

ต่างจากตัวอย่างข้างต้น mixin ไม่ได้ตั้งใจที่จะใช้ด้วยตัวเอง มันมีฟังก์ชั่นใหม่หรือแตกต่างกัน

ยกตัวอย่างเช่นห้องสมุดมาตรฐานมีคู่ของmixins ในsocketserverห้องสมุด

เวอร์ชันการแยกและการเธรดของเซิร์ฟเวอร์แต่ละชนิดสามารถสร้างขึ้นได้โดยใช้คลาสผสมเหล่านี้ ตัวอย่างเช่น ThreadingUDPServer ถูกสร้างขึ้นดังนี้:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

คลาส mix-in มาก่อนเนื่องจากจะแทนที่เมธอดที่กำหนดใน UDPServer การตั้งค่าคุณลักษณะต่าง ๆ ยังเปลี่ยนลักษณะการทำงานของกลไกเซิร์ฟเวอร์ต้นแบบ

ในกรณีนี้เมธอด mixin จะแทนที่เมธอดในการUDPServerกำหนดออบเจ็กต์เพื่อให้เกิดการทำงานพร้อมกัน

ดูเหมือนว่าจะมีวิธีการแทนที่process_requestและมันก็มีวิธีอื่น, process_request_thread. นี่มันมาจากซอร์สโค้ด :

class ThreadingMixIn:
        """Mix-in class to handle each request in a new thread."""

        # Decides how threads will act upon termination of the
        # main process
        daemon_threads = False

        def process_request_thread(self, request, client_address):
            """Same as in BaseServer but as a thread.
            In addition, exception handling is done here.
            """
            try:
                self.finish_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
            finally:
                self.shutdown_request(request)

        def process_request(self, request, client_address):
            """Start a new thread to process the request."""
            t = threading.Thread(target = self.process_request_thread,
                                 args = (request, client_address))
            t.daemon = self.daemon_threads
            t.start()

ตัวอย่างที่ได้วางแผนไว้

นี่คือมิกซ์อินที่ส่วนใหญ่ใช้สำหรับการสาธิตวัตถุส่วนใหญ่จะวิวัฒนาการไปเหนือประโยชน์ของการพิมพ์นี้:

class SimpleInitReprMixin(object):
    """mixin, don't instantiate - useful for classes instantiable
    by keyword arguments to their __init__ method.
    """
    __slots__ = () # allow subclasses to use __slots__ to prevent __dict__
    def __repr__(self):
        kwarg_strings = []
        d = getattr(self, '__dict__', None)
        if d is not None:
            for k, v in d.items():
                kwarg_strings.append('{k}={v}'.format(k=k, v=repr(v)))
        slots = getattr(self, '__slots__', None)
        if slots is not None:
            for k in slots:
                v = getattr(self, k, None)
                kwarg_strings.append('{k}={v}'.format(k=k, v=repr(v)))
        return '{name}({kwargs})'.format(
          name=type(self).__name__,
          kwargs=', '.join(kwarg_strings)
          )

และการใช้งานจะเป็น:

class Foo(SimpleInitReprMixin): # add other mixins and/or extend another class here
    __slots__ = 'foo',
    def __init__(self, foo=None):
        self.foo = foo
        super(Foo, self).__init__()

และการใช้งาน:

>>> f1 = Foo('bar')
>>> f2 = Foo()
>>> f1
Foo(foo='bar')
>>> f2
Foo(foo=None)

11

ฉันคิดว่ามีคำอธิบายที่ดีบางอย่างที่นี่ แต่ฉันต้องการให้มุมมองอื่น

ในสกาล่าคุณสามารถทำมิกซ์อินตามที่อธิบายไว้ที่นี่ แต่สิ่งที่น่าสนใจมากคือมิกซ์อินนั้น 'หลอมรวม' เข้าด้วยกันเพื่อสร้างคลาสใหม่ที่สืบทอดมา โดยพื้นฐานแล้วคุณไม่ได้สืบทอดจากหลายคลาส / มิกซ์อิน แต่ให้สร้างคลาสใหม่ที่มีคุณสมบัติทั้งหมดของมิกซ์อินเพื่อรับมรดก สิ่งนี้เหมาะสมแล้วเนื่องจาก Scala นั้นใช้ JVM ซึ่งไม่รองรับการสืบทอดหลายแบบในปัจจุบัน (จาก Java 8) ประเภทคลาสมิกซ์อินนี้เป็นรูปแบบพิเศษที่เรียกว่า Trait in Scala

มันเป็นนัยในวิธีที่กำหนดคลาส: คลาส NewCix ขยาย FirstMixin ด้วย SecondMixin ด้วย ThirdMixin ...

ฉันไม่แน่ใจว่าล่าม CPython ทำเหมือนกัน (มิกซ์อินคลาสประกอบ) แต่ฉันไม่แปลกใจเลย นอกจากนี้มาจากพื้นหลัง C ++ ฉันจะไม่เรียก ABC หรือ 'interface' เทียบเท่ากับ mixin - มันเป็นแนวคิดที่คล้ายกัน แต่แตกต่างกันในการใช้งานและการใช้งาน


9

ฉันขอแนะนำให้ผสมกับในรหัส Python ใหม่ถ้าคุณสามารถหาวิธีอื่น ๆ รอบ ๆ มัน (เช่นองค์ประกอบแทนการสืบทอดหรือเพียงวิธีการแก้ไขลิงในชั้นเรียนของคุณเอง) ที่ไม่มาก ความพยายาม

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

มีวิธีที่จะทำให้ MRO แบบหลายมรดกทำงานใน Python ได้อย่างยอดเยี่ยมที่สุดคือฟังก์ชั่น super () แต่หมายความว่าคุณต้องทำลำดับชั้นของคลาสทั้งหมดโดยใช้ super () และมันยากกว่าที่จะเข้าใจการไหลของการควบคุม


3
ตั้งแต่รุ่น 2.3 หลามใช้ "วิธีการความละเอียด C3" อธิบายในงูใหญ่ 2.3 วิธีการสั่งซื้อมติหรือวิธีการสั่งซื้อมติ
webwurst

11
โดยส่วนตัวแล้วฉันจะใช้มิกซ์อินมากกว่าการปะแก้ลิงในกรณีส่วนใหญ่ มันง่ายกว่าที่จะให้เหตุผลและตามด้วยรหัส
tdammers

5
downvoted ในขณะที่คำตอบของคุณแสดงความคิดเห็นที่ถูกต้องเกี่ยวกับรูปแบบการพัฒนาคุณไม่ได้ตอบคำถามจริง
Ryan B. Lynch

8

บางทีตัวอย่างสองสามข้อจะช่วยได้

หากคุณกำลังสร้างชั้นเรียนและคุณต้องการให้มันทำหน้าที่เหมือนพจนานุกรมคุณสามารถกำหนด__ __วิธีการต่าง ๆ ที่จำเป็นทั้งหมดได้ แต่นั่นเป็นความเจ็บปวดเล็กน้อย คุณสามารถกำหนดจำนวนหนึ่งและสืบทอด (นอกเหนือจากมรดกอื่น ๆ ) จากUserDict.DictMixin(ย้ายไปที่collections.DictMixinpy3k) นี่จะมีผลในการกำหนดส่วนที่เหลือทั้งหมดของ api ของพจนานุกรมโดยอัตโนมัติ

ตัวอย่างที่สอง: ชุดเครื่องมือ GUI wxPython ช่วยให้คุณสามารถควบคุมรายการด้วยหลายคอลัมน์ (เช่นพูดแสดงไฟล์ใน Windows Explorer) โดยค่าเริ่มต้นรายการเหล่านี้ค่อนข้างพื้นฐาน คุณสามารถเพิ่มฟังก์ชันการทำงานเพิ่มเติมเช่นความสามารถในการเรียงลำดับรายการตามคอลัมน์ใดคอลัมน์หนึ่งโดยคลิกที่ส่วนหัวของคอลัมน์โดยสืบทอดจาก ListCtrl และเพิ่มมิกซ์อินที่เหมาะสม


8

มันไม่ได้เป็นตัวอย่างของ Python แต่ในภาษาการเขียนโปรแกรม Dคำmixinนี้ใช้เพื่ออ้างถึงโครงสร้างที่ใช้ในลักษณะเดียวกัน การเพิ่มสิ่งต่าง ๆ ลงในชั้นเรียน

ใน D (ซึ่งโดยวิธีที่ไม่ได้ทำ MI) จะทำโดยการแทรกแม่แบบ (คิดว่าแมโครตระหนักถึงความปลอดภัยและแมโครและคุณจะปิด) ในขอบเขต สิ่งนี้ยอมให้มีโค้ดหนึ่งบรรทัดในคลาส, struct, function, module หรืออะไรก็ตามที่จะขยายไปยังการประกาศจำนวนเท่าใดก็ได้


2
Mixin เป็นคำทั่วไปที่ใช้ใน D, Ruby, ฯลฯ ตาม Wikipedia พวกเขามาในระบบเสียงกระเพื่อมโรงเรียนเก่าและได้รับการบันทึกครั้งแรกในปี 1983: en.wikipedia.org/wiki/…
Lee B

7

OP กล่าวว่าเขา / เธอไม่เคยได้ยิน mixin ใน C ++ อาจเป็นเพราะพวกเขาถูกเรียกว่า Curiously Recurring Template Pattern (CRTP) ใน C ++ นอกจากนี้ @Ciro Santilli ยังกล่าวว่า mixin ถูกใช้งานผ่านคลาสฐานนามธรรมใน C ++ ในขณะที่คลาสฐานนามธรรมสามารถนำมาใช้ในการประยุกต์ใช้ mixin ได้มันเป็น overkill เนื่องจากการทำงานของฟังก์ชั่นเสมือนจริงในเวลาทำงานสามารถทำได้โดยใช้แม่แบบในเวลารวบรวมโดยไม่มีค่าใช้จ่ายของการค้นหาตารางเสมือนจริงในเวลาทำงาน

รูปแบบ CRTP อธิบายไว้โดยละเอียดที่นี่

ฉันแปลงตัวอย่างไพ ธ อนในคำตอบ @Ciro Santilli เป็น C ++ โดยใช้คลาสเทมเพลตด้านล่าง:

    #include <iostream>
    #include <assert.h>

    template <class T>
    class ComparableMixin {
    public:
        bool operator !=(ComparableMixin &other) {
            return ~(*static_cast<T*>(this) == static_cast<T&>(other));
        }
        bool operator <(ComparableMixin &other) {
            return ((*(this) != other) && (*static_cast<T*>(this) <= static_cast<T&>(other)));
        }
        bool operator >(ComparableMixin &other) {
            return ~(*static_cast<T*>(this) <= static_cast<T&>(other));
        }
        bool operator >=(ComparableMixin &other) {
            return ((*static_cast<T*>(this) == static_cast<T&>(other)) || (*(this) > other));
        }
        protected:
            ComparableMixin() {}
    };

    class Integer: public ComparableMixin<Integer> {
    public:
     Integer(int i) {
         this->i = i;
     }
     int i;
     bool operator <=(Integer &other) {
         return (this->i <= other.i);
     }
     bool operator ==(Integer &other) {
         return (this->i == other.i);
     }
    };

int main() {

    Integer i(0) ;
    Integer j(1) ;
    //ComparableMixin<Integer> c; // this will cause compilation error because constructor is protected.
    assert (i < j );
    assert (i != j);
    assert (j >  i);
    assert (j >= i);

    return 0;
}

แก้ไข: เพิ่มตัวสร้างที่มีการป้องกันใน ComparableMixin เพื่อให้สามารถสืบทอดเท่านั้นและไม่ได้สร้างอินสแตนซ์ อัปเดตตัวอย่างเพื่อแสดงว่า Constructor ที่ได้รับการป้องกันจะทำให้เกิดข้อผิดพลาดในการคอมไพล์เมื่อวัตถุของ ComparableMixin ถูกสร้างขึ้นได้อย่างไร


Mixins และ CRTP นั้นไม่เหมือนกันใน C ++
ashrasmun

6

บางทีตัวอย่างจากทับทิมสามารถช่วย:

คุณสามารถรวมมิกซ์อินComparableและกำหนดฟังก์ชั่นเดียว"<=>(other)"มิกซ์อินมีฟังก์ชั่นเหล่านั้นทั้งหมด:

<(other)
>(other)
==(other)
<=(other)
>=(other)
between?(other)

ทำได้โดยเรียกใช้<=>(other)และให้ผลลัพธ์ที่ถูกต้อง

"instance <=> other"ส่งกลับค่า 0 หากวัตถุทั้งสองมีค่าเท่ากันน้อยกว่า 0 ถ้าinstanceมากกว่าotherและมากกว่า 0 ถ้าotherใหญ่กว่า


นี่คือโพสต์ที่ให้ mixin ที่คล้ายกันสำหรับ Python แม้ว่าข้อเสนอแนะจะถูกกำหนด__lt__เป็นฐานแทนที่จะเป็นข้อเสนอแนะ__cmp__หลังเลิกใช้งานจริงและไม่สนับสนุนให้ใช้ สำหรับผมแล้วมันดูเหมือนง่ายในการใช้ mixin แทนความซับซ้อนมากที่ตกแต่ง (ส่วนหนึ่งของfunctools ) - แม้ว่าคนนี้อาจจะไม่สามารถที่จะตอบสนองมากขึ้นแบบไดนามิกในซึ่งมีการเปรียบเทียบให้ไว้ ...
โทเบียส KIENZLER

6

mixin ให้วิธีเพิ่มฟังก์ชันการทำงานในคลาสเช่นคุณสามารถโต้ตอบกับวิธีการที่กำหนดไว้ในโมดูลโดยรวมถึงโมดูลภายในคลาสที่ต้องการ แม้ว่า ruby ​​จะไม่รองรับการสืบทอดหลายแบบ แต่ให้ mixin เป็นทางเลือกเพื่อให้ได้สิ่งนั้น

นี่คือตัวอย่างที่อธิบายวิธีการรับมรดกหลายรายการโดยใช้ mixin

module A    # you create a module
    def a1  # lets have a method 'a1' in it
    end
    def a2  # Another method 'a2'
    end
end

module B    # let's say we have another module
    def b1  # A method 'b1'
    end
    def b2  #another method b2
    end
end

class Sample    # we create a class 'Sample'
    include A   # including module 'A' in the class 'Sample' (mixin)
    include B   # including module B as well

    def S1      #class 'Sample' contains a method 's1'
    end
end

samp = Sample.new    # creating an instance object 'samp'

# we can access methods from module A and B in our class(power of mixin)

samp.a1     # accessing method 'a1' from module A
samp.a2     # accessing method 'a2' from module A
samp.b1     # accessing method 'b1' from module B
samp.b2     # accessing method 'a2' from module B
samp.s1     # accessing method 's1' inside the class Sample

4
อะไรคือความแตกต่างระหว่างสิ่งนี้กับมรดกโดยทั่วไป?
Ciro Santilli 冠状病毒审查六四事件法轮功

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

ดังนั้นใน Ruby mixins เป็นคลาสที่ไม่สามารถสร้างอินสแตนซ์ได้ แต่ต้องใช้สำหรับการสืบทอดหลาย ๆ
Trilarion

6

ฉันเพิ่งใช้ python mixin เพื่อทำการทดสอบหน่วยสำหรับ python milters โดยปกติแล้ว milter จะพูดคุยกับ MTA ทำให้การทดสอบหน่วยทำได้ยาก การทดสอบ mixin จะแทนที่วิธีที่พูดคุยกับ MTA และสร้างสภาพแวดล้อมจำลองที่ขับเคลื่อนโดยกรณีทดสอบแทน

ดังนั้นคุณใช้แอปพลิเคชัน milter ที่ไม่ได้แก้ไขเช่น spfmilter และ mixin TestBase เช่นนี้:

class TestMilter(TestBase,spfmilter.spfMilter):
  def __init__(self):
    TestBase.__init__(self)
    spfmilter.config = spfmilter.Config()
    spfmilter.config.access_file = 'test/access.db'
    spfmilter.spfMilter.__init__(self)

จากนั้นใช้ TestMilter ในกรณีทดสอบสำหรับแอปพลิเคชัน milter:

def testPass(self):
  milter = TestMilter()
  rc = milter.connect('mail.example.com',ip='192.0.2.1')
  self.assertEqual(rc,Milter.CONTINUE)
  rc = milter.feedMsg('test1',sender='good@example.com')
  self.assertEqual(rc,Milter.CONTINUE)
  milter.close()

http://pymilter.cvs.sourceforge.net/viewvc/pymilter/pymilter/Milter/test.py?revision=1.6&view=markup


4

ฉันคิดว่าคำตอบก่อนหน้านี้ให้คำจำกัดความว่าMixInคืออะไร อย่างไรก็ตามเพื่อให้เข้าใจได้ดีขึ้นอาจเป็นประโยชน์ในการเปรียบเทียบMixInกับAbstract ClassesและInterfacesจากมุมมองของรหัส / การนำไปใช้:

1. คลาสนามธรรม

  • คลาสที่ต้องมีหนึ่งหรือหลายวิธีนามธรรม

  • ระดับนามธรรม สามารถมีสถานะ (ตัวแปรอินสแตนซ์) และวิธีการที่ไม่ใช่นามธรรม

2. อินเตอร์เฟซ

  • ส่วนต่อประสานมีวิธีนามธรรมเท่านั้น (ไม่มีวิธีที่ไม่เป็นนามธรรมและไม่มีสถานะภายใน)

3. MixIns

  • MixIns (เช่นอินเทอร์เฟซ) ไม่มีสถานะภายใน (ตัวแปรอินสแตนซ์)
  • MixInsมีวิธีการที่ไม่เป็นนามธรรมอย่างน้อยหนึ่งวิธี (พวกเขาสามารถมีวิธีที่ไม่เป็นนามธรรมซึ่งแตกต่างจากอินเตอร์เฟส)

ในตัวอย่างของ Python นี่เป็นเพียงการประชุมเพราะทั้งหมดข้างต้นถูกกำหนดให้เป็นclasses อย่างไรก็ตามคุณสมบัติทั่วไปของทั้งคลาสนามธรรม, อินเทอร์เฟซและมิกซ์อินคือพวกเขาไม่ควรอยู่ด้วยตนเองเช่นไม่ควรสร้างอินสแตนซ์


3

ฉันอ่านว่าคุณมีพื้นหลัง ac # จุดเริ่มต้นที่ดีอาจเป็นการนำมิกซ์อินมาใช้ใน. NET

คุณอาจต้องการตรวจสอบโครงการ codeplex ที่http://remix.codeplex.com/

ชมลิงค์ lang.net Symposium เพื่อดูภาพรวม ยังมีเอกสารมาให้อ่านเพิ่มเติมในหน้า codeplex

ขอแสดงความนับถือสเตฟาน

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