อะไรคือข้อดีของการใช้คลาสนามธรรมแทนลักษณะ?


371

อะไรคือข้อดีของการใช้คลาสนามธรรมแทนลักษณะ (นอกเหนือจากประสิทธิภาพ) ดูเหมือนว่าคลาสนามธรรมสามารถถูกแทนที่ด้วยลักษณะในกรณีส่วนใหญ่

คำตอบ:


371

ฉันสามารถนึกถึงความแตกต่างสองอย่าง

  1. คลาสนามธรรมสามารถมีพารามิเตอร์ตัวสร้างเช่นเดียวกับพารามิเตอร์ชนิด ลักษณะสามารถมีพารามิเตอร์ประเภทได้เท่านั้น มีการถกเถียงกันว่าในอนาคตแม้กระทั่งคุณลักษณะอาจมีพารามิเตอร์คอนสตรัคเตอร์
  2. คลาสนามธรรมนั้นทำงานร่วมกันอย่างสมบูรณ์กับ Java คุณสามารถเรียกพวกเขาจากรหัส Java โดยไม่ต้องห่อหุ้มใด ๆ ลักษณะการทำงานร่วมกันอย่างสมบูรณ์เฉพาะในกรณีที่พวกเขาไม่มีรหัสการใช้งานใด ๆ

172
ภาคผนวกที่สำคัญมาก: คลาสสามารถสืบทอดจากหลายคุณลักษณะ แต่มีเพียงคลาสนามธรรมเท่านั้น ฉันคิดว่านี่เป็นคำถามแรกที่นักพัฒนาถามเมื่อพิจารณาว่าจะใช้ในกรณีใดเกือบทุกกรณี
BAR

15
ผู้ช่วยชีวิต: "ลักษณะการทำงานร่วมกันอย่างสมบูรณ์เฉพาะในกรณีที่พวกเขาไม่ได้มีรหัสการใช้งานใด ๆ "
Walrus the Cat

2
นามธรรม - เมื่อพฤติกรรมส่วนรวมกำหนดหรือนำไปสู่วัตถุ (สาขาของวัตถุ) แต่ยังไม่ได้ประกอบเป็นวัตถุ (พร้อม) ลักษณะเมื่อคุณต้องการความสามารถในการเหนี่ยวนำเช่นความสามารถที่ไม่เคยเกิดขึ้นกับการสร้างวัตถุมันจะวิวัฒนาการหรือจำเป็นเมื่อวัตถุออกมาจากการแยกและต้องสื่อสาร
Ramiz Uddin

5
ความแตกต่างที่สองไม่มีอยู่ใน Java8 คิดว่า
Duong Nguyen

14
ต่อ Scala 2.12 ลักษณะที่คอมไพล์อินเตอร์เฟซ Java 8 - scala-lang.org/news/2.12.0#traits-compile-to-interfaces
เควินเมเรดิ ธ

209

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

เมื่อใดก็ตามที่คุณใช้คอลเลกชันของพฤติกรรมที่ใช้ซ้ำได้คุณจะต้องตัดสินใจว่าคุณต้องการใช้คุณลักษณะหรือคลาสนามธรรม ไม่มีกฎของ บริษัท แต่ส่วนนี้มีหลักเกณฑ์ที่ควรพิจารณา

หากพฤติกรรมจะไม่ถูกนำมาใช้ซ้ำแล้วทำให้มันเป็นระดับที่เป็นรูปธรรม มันไม่ใช่พฤติกรรมที่นำมาใช้ซ้ำได้หลังจากทั้งหมด

ถ้ามันอาจถูกนำมาใช้ซ้ำในหลายคลาสที่ไม่เกี่ยวข้องทำให้มันเป็นลักษณะ ลักษณะเฉพาะสามารถผสมเป็นส่วนต่าง ๆ ของลำดับชั้นของคลาส

หากคุณต้องการรับช่วงจากในโค้ด Javaให้ใช้คลาสนามธรรม เนื่องจากคุณสมบัติที่มีรหัสไม่มีอะนาล็อก Java แบบปิดดังนั้นจึงมีความแปลกที่จะสืบทอดจากลักษณะในคลาส Java ขณะที่การสืบทอดจากคลาส Scala นั้นเหมือนกับการสืบทอดจากคลาส Java เป็นข้อยกเว้นประการหนึ่งคุณลักษณะของ Scala ที่มีสมาชิกแบบนามธรรมเท่านั้นแปลโดยตรงไปยังส่วนต่อประสาน Java ดังนั้นคุณควรกำหนดลักษณะดังกล่าวแม้ว่าคุณจะคาดหวังว่ารหัส Java จะสืบทอดมาจากมันก็ตาม ดูบทที่ 29 สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการทำงานกับ Java และ Scala ด้วยกัน

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

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

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

ตามที่ @Mushtaq Ahmed ได้กล่าวถึงคุณลักษณะไม่สามารถส่งผ่านพารามิเตอร์ใด ๆ ไปยังตัวสร้างหลักของคลาสได้

superแตกต่างก็คือการรักษา

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

ดูส่วนที่เหลือของบทที่ 12สำหรับรายละเอียดเพิ่มเติม

แก้ไข 1 (2013):

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

แก้ไข 2 (2018):

ตั้งแต่ Scala 2.12 พฤติกรรมการทำงานร่วมกันของไบนารีของ trait ได้เปลี่ยนไป ก่อน 2.12 การเพิ่มหรือลบสมาชิกไปยังคุณลักษณะที่จำเป็นต้องคอมไพล์ใหม่ของคลาสทั้งหมดที่สืบทอดคุณสมบัติแม้ว่าคลาสจะไม่เปลี่ยนแปลง นี่เป็นเพราะลักษณะการเข้ารหัสใน JVM

ในฐานะของ Scala 2.12 ลักษณะการคอมไพล์ไปยังอินเทอร์เฟซของ Javaดังนั้นความต้องการจึงผ่อนคลายลงเล็กน้อย หากลักษณะดังกล่าวทำสิ่งใดสิ่งหนึ่งต่อไปนี้คลาสย่อยนั้นยังต้องการการคอมไพล์ใหม่:

  • การกำหนดเขตข้อมูล ( valหรือvarแต่ค่าคงที่ก็โอเค - final valไม่มีประเภทผลลัพธ์)
  • โทร super
  • งบ initializer ในร่างกาย
  • ขยายชั้นเรียน
  • ขึ้นอยู่กับการทำให้เป็นเชิงเส้นเพื่อค้นหาการใช้งานใน supertrait ด้านขวา

แต่ถ้าคุณสมบัติไม่ได้ตอนนี้คุณสามารถปรับปรุงได้โดยไม่ทำลายความเข้ากันได้ของไบนารี


2
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine- มีคนอธิบายความแตกต่างที่นี่ได้อย่างไร extendsVS with?
0fnt

2
@ 0fnt ความแตกต่างของเขาไม่ได้เกี่ยวกับการขยายตัวเทียบกับ สิ่งที่เขาพูดคือถ้าคุณผสมเฉพาะในการรวบรวมเดียวกันปัญหาความเข้ากันได้ของไบนารีจะไม่มีผล อย่างไรก็ตามหาก API ของคุณได้รับการออกแบบมาเพื่อให้ผู้ใช้สามารถผสมผสานในลักษณะของตัวเองแล้วคุณจะต้องกังวลเกี่ยวกับความเข้ากันได้ไบนารี
John Colanduoni

2
@ 0fnt: มีความแตกต่างกันอย่างไม่มีความหมายระหว่างเป็นและextends withมันเป็นการสร้างประโยคอย่างหมดจด หากคุณได้รับมรดกจากหลายแม่แบบที่ได้รับครั้งแรกextend, คนอื่น ๆ ทั้งหมดจะได้รับwithที่มัน คิดว่าwithเป็นเครื่องหมายจุลภาค: class Foo extends Bar, Baz, Qux.
Jörg W Mittag


20

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

จากคำตอบของโทมัสในความแตกต่างระหว่างคลาสนามธรรมและคุณลักษณะ :

trait A{
    def a = 1
}

trait X extends A{
    override def a = {
        println("X")
        super.a
    }
}  


trait Y extends A{
    override def a = {
        println("Y")
        super.a
    }
}

scala> val xy = new AnyRef with X with Y
xy: java.lang.Object with X with Y = $anon$1@6e9b6a
scala> xy.a
Y
X
res0: Int = 1

scala> val yx = new AnyRef with Y with X
yx: java.lang.Object with Y with X = $anon$1@188c838
scala> yx.a
X
Y
res1: Int = 1

9

เมื่อขยายคลาสนามธรรมสิ่งนี้แสดงให้เห็นว่าคลาสย่อยมีลักษณะคล้ายกัน นี่ไม่ใช่กรณีที่จำเป็นเมื่อใช้คุณลักษณะฉันคิดว่า


สิ่งนี้มีความหมายในทางปฏิบัติหรือไม่เพียงแค่ทำให้เข้าใจรหัสได้ง่ายขึ้น?
Ralf


5

คลาสที่เป็นนามธรรมสามารถมีพฤติกรรมได้ - พวกมันสามารถกำหนดพารามิเตอร์ด้วยตัวสร้าง args (ซึ่งลักษณะไม่สามารถทำได้) และแสดงถึงเอนทิตีที่ใช้งานได้ ลักษณะแทนเพียงแค่แสดงคุณสมบัติเดียวเป็นอินเทอร์เฟซของฟังก์ชันการทำงานหนึ่ง


8
หวังว่าคุณจะไม่ได้หมายความว่าลักษณะไม่สามารถมีพฤติกรรม ทั้งสองสามารถมีรหัสการใช้งาน
Mitch Blevins

1
@ Mitch Blevins: ไม่แน่นอน พวกเขาสามารถมีรหัส แต่เมื่อคุณกำหนดtrait Enumerableด้วยฟังก์ชั่นผู้ช่วยจำนวนมากฉันจะไม่เรียกพวกเขาพฤติกรรมแต่เพียงฟังก์ชั่นการเชื่อมต่อกับคุณสมบัติเดียว
Dario

4
@Dario ฉันเห็นว่า "พฤติกรรม" และ "การทำงาน" เป็นคำพ้องความหมายดังนั้นฉันจึงพบว่าคำตอบของคุณสับสนมาก
David J.

3
  1. คลาสสามารถสืบทอดจากหลาย ๆ ลักษณะ แต่คลาสนามธรรมเพียงคลาสเดียวเท่านั้น
  2. คลาสนามธรรมสามารถมีพารามิเตอร์ตัวสร้างเช่นเดียวกับพารามิเตอร์ชนิด ลักษณะสามารถมีพารามิเตอร์ประเภทได้เท่านั้น ตัวอย่างเช่นคุณไม่สามารถพูดลักษณะได้ t (i: Int) {}; พารามิเตอร์ i นั้นผิดกฎหมาย
  3. คลาสนามธรรมนั้นทำงานร่วมกันอย่างสมบูรณ์กับ Java คุณสามารถเรียกพวกเขาจากรหัส Java โดยไม่ต้องห่อหุ้มใด ๆ ลักษณะการทำงานร่วมกันอย่างสมบูรณ์เฉพาะในกรณีที่พวกเขาไม่มีรหัสการใช้งานใด ๆ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.