การผสมคุณสมบัติส่วนตัวและสาธารณะและผู้เข้าถึงข้อมูลใน Raku


12
#Private attribute example
class C { 
    has $!w;                            #private attribute
    multi method w { $!w }              #getter method
    multi method w ( $_ ) {                 #setter method
        warn “Don’t go changing my w!”;   #some side action
        $!w = $_
    }  
}
my $c = C.new
$c.w( 42 )
say $c.w #prints 42
$c.w: 43
say $c.w #prints 43

#but not
$c.w = 44
Cannot modify an immutable Int (43)

จนถึงตอนนี้สมเหตุสมผลแล้ว

#Public attribute example
class C { 
    has $.v is rw    #public attribute with automatic accessors
}
my $c = C.new
$c.v = 42
say $c.v #prints 42

#but not
$c.v( 43 ) #or $c.v: 43
Too many positionals passed; expected 1 argument but got 2

ฉันชอบความกระฉับกระเฉงของการมอบหมาย '=' แต่ฉันต้องการความง่ายในการกระโจนเข้าข้างการกระทำที่มีหลายวิธี ฉันเข้าใจว่านี่เป็นโลกที่แตกต่างกันสองแห่งและพวกเขาไม่ได้ปะปนกัน

แต่ - ฉันไม่เข้าใจว่าทำไมฉันไม่สามารถไป $ cv (43) เพื่อตั้งค่าคุณลักษณะสาธารณะ

  1. ฉันรู้สึกว่า raku กำลังแนะนำให้ฉันไม่ผสมสองโหมดนี้ - คุณลักษณะบางอย่างเป็นส่วนตัวและสาธารณะและความกดดันเป็นไปตามวิธีการ (กับบางส่วน: น้ำตาลจากลำไส้ใหญ่) - นี่คือความตั้งใจในการออกแบบของ Raku หรือไม่?
  2. ฉันพลาดอะไรไปรึเปล่า?

คุณสามารถใช้พร็อกซี
user0721090601

คุณใช้พร็อกซีได้อย่างไร? ฉันหมายถึง accessor คืนคอนเทนเนอร์เมื่อis rwระบุแล้ว การส่งคืนพร็อกซีจะไม่เปลี่ยนจำนวนพารามิเตอร์ที่อนุญาตในการเข้าถึง
Elizabeth Mattijsen

@ElizabethMattijsen บางทีฉันอาจเข้าใจผิดคำถาม แต่สิ่งนี้ดูเหมือนจะทำงานในสิ่งที่เขาต้องการ (เปิดใช้งานทั้งสอง= fooและ.(foo)สำหรับการตั้งค่า) และการเปิดใช้งานผลข้างเคียงที่จะต้องทำในทั้งสองกรณี (แต่ไม่ใช่เมื่อเรียกเท่านั้น): tio.run/…
user0721090601

คำตอบ:


13

นี่เป็นความตั้งใจในการออกแบบของ Raku หรือไม่?

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

Raku มีค่า l ชั้นหนึ่ง

Raku ใช้ประโยชน์จาก l-value อย่างมากมายเป็นสิ่งที่ดีเลิศ เมื่อเราเขียน:

has $.x is rw;

วิธีการที่สร้างขึ้นคือ:

method x() is rw { $!x }

is rwนี่แสดงให้เห็นว่าวิธีการที่จะส่งคืนลิตรมูลค่า - นั่นคือสิ่งที่สามารถมอบหมายให้ ดังนั้นเมื่อเราเขียน:

$obj.x = 42;

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

my $target := $obj.x;
$target = 42;

จะถูกกำหนดให้กับคุณลักษณะของวัตถุ กลไกเดียวกันนี้อยู่เบื้องหลังคุณสมบัติอื่น ๆ มากมายรวมถึงการกำหนดรายการ ตัวอย่างเช่นสิ่งนี้:

($x, $y) = "foo", "bar";

ทำงานโดยการสร้างListภาชนะบรรจุที่มี$xและ$yจากนั้นผู้ประกอบการที่ได้รับมอบหมายในกรณีนี้มันวนซ้ำแต่ละด้านเพื่อทำการมอบหมาย ซึ่งหมายความว่าเราสามารถใช้ตัวเข้าถึงrwวัตถุที่นั่น:

($obj.x, $obj.y) = "foo", "bar";

และมันก็เป็นไปตามธรรมชาติ นี่เป็นกลไกหลังการมอบหมายให้กับอาร์เรย์และแฮช

ท่านสามารถใช้Proxyเพื่อสร้างคอนเทนเนอร์ l-value ที่พฤติกรรมการอ่านและการเขียนมันอยู่ภายใต้การควบคุมของคุณ ดังนั้นคุณสามารถใส่การกระทำข้างเคียงลงไปSTOREได้ อย่างไรก็ตาม ...

Raku สนับสนุนวิธีการทางความหมายมากกว่า "setters"

เมื่อเราอธิบาย OO คำศัพท์เช่น "encapsulation" และ "data hiding" มักจะเกิดขึ้น แนวคิดหลักในที่นี้คือโมเดลของรัฐภายในวัตถุ - นั่นคือวิธีที่มันเลือกที่จะเป็นตัวแทนของข้อมูลที่ต้องการเพื่อใช้งานพฤติกรรม (วิธีการ) - มีอิสระที่จะพัฒนาตัวอย่างเช่นเพื่อจัดการกับข้อกำหนดใหม่ ยิ่งวัตถุมีความซับซ้อนมากเท่าไหร่ก็ยิ่งมีการปลดปล่อยมากขึ้นเท่านั้น

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

ที่กล่าวว่าการใช้งานจำนวนมากclassเป็นประเภทบันทึก / ผลิตภัณฑ์จริง ๆ : พวกมันมีอยู่เพื่อรวมกลุ่มรายการข้อมูลไว้ด้วยกัน มันไม่ใช่อุบัติเหตุที่.sigil ไม่เพียง แต่สร้าง accessor แต่ยัง:

  • เลือกใช้แอตทริบิวต์ที่ถูกตั้งค่าโดยตรรกะการกำหนดค่าเริ่มต้นของวัตถุ (นั่นคือ a class Point { has $.x; has $.y; }สามารถสร้างอินสแตนซ์เป็นPoint.new(x => 1, y => 2)) และแสดงผลใน.rakuวิธีการถ่ายโอนข้อมูล
  • เลือกใช้แอตทริบิวต์เป็น.Captureวัตถุเริ่มต้นซึ่งหมายความว่าเราสามารถใช้ในการทำลาย (เช่นsub translated(Point (:$x, :$y)) { ... })

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

การออกแบบ Raku ไม่ได้รับการปรับให้เหมาะสมสำหรับการทำสิ่งที่ฉลาดใน setters เพราะถือว่าเป็นสิ่งที่ไม่ดีสำหรับการปรับให้เหมาะสม มันเกินความจำเป็นสำหรับประเภทบันทึก ในบางภาษาเราอาจโต้แย้งว่าเราต้องการตรวจสอบสิ่งที่ได้รับมอบหมาย แต่ใน Raku เราสามารถเปลี่ยนเป็นsubsetประเภทนั้นได้ ในเวลาเดียวกันถ้าเราออกแบบ OO จริงๆเราต้องการ API ของพฤติกรรมที่มีความหมายที่ซ่อนโมเดลรัฐแทนที่จะคิดในแง่ของ getters / setters ซึ่งมีแนวโน้มที่จะนำไปสู่ความล้มเหลวในการ colocate ข้อมูลและพฤติกรรมซึ่งเป็นสิ่งสำคัญในการทำ OO อยู่แล้ว


จุดที่ดีในการเตือนProxys (แม้ว่าฉันจะแนะนำมันฮ่า) LanguageTagเวลาเท่านั้นที่ฉันได้พบพวกเขามีประโยชน์ชะมัดสำหรับฉัน ภายในที่$tag.regionส่งกลับวัตถุของการพิมพ์Region(ที่มันเก็บไว้ภายใน) แต่ในความเป็นจริงมันเป็นอนันต์สะดวกมากขึ้นสำหรับคนที่จะพูดมากกว่า$tag.region = "JP" $tag.region.code = "JP"และนั่นเป็นเพียงชั่วคราวเท่านั้นจนกระทั่งฉันสามารถแสดงการบีบบังคับจากStrในประเภทเช่นhas Region(Str) $.region is rw(ซึ่งต้องการคุณลักษณะสองอย่างที่แยกจากกัน แต่มีลำดับความสำคัญต่ำ)
user0721090601

ขอบคุณ @Jonathan ที่สละเวลาเพื่ออธิบายเหตุผลการออกแบบ - ฉันเคยสงสัยบางอย่างเกี่ยวกับน้ำมันและน้ำและสำหรับผู้ที่ไม่ใช่ CS เช่นฉันฉันได้รับความแตกต่างระหว่าง OO ที่เหมาะสมกับสภาพที่ซ่อนอยู่และการประยุกต์ใช้ es es ในชั้นเรียน วิธีที่เป็นมิตรในการสร้างผู้ถือข้อมูลลึกลับซึ่งจะใช้เวลาปริญญาเอกใน C ++ และสำหรับผู้ใช้งาน 072 ... สำหรับความคิดเห็นของคุณเกี่ยวกับ Proxy s ฉันเคยรู้จักพร็อกซีมาก่อน แต่สงสัยว่าพวกเขา (และ / หรือลักษณะ) เป็นรูปแบบที่ค่อนข้างลำบากในการกีดกันการผสมน้ำมันและน้ำ ...
p6steve

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

7

แต่ - ฉันไม่เข้าใจว่าทำไมฉันไม่สามารถไป $ cv (43) เพื่อตั้งค่าคุณลักษณะสาธารณะ

มันขึ้นอยู่กับสถาปนิกจริงๆ แต่อย่างจริงจังไม่ใช่นั่นไม่ใช่วิธีการมาตรฐานของ Raku

ตอนนี้มันเป็นไปได้ทั้งหมดที่จะสร้างAttributeลักษณะในพื้นที่โมดูลสิ่งis settableที่จะสร้างวิธีการเข้าถึงสำรองที่จะยอมรับค่าเดียวเพื่อตั้งค่า ปัญหาในการทำสิ่งนี้ในแกนหลักคือฉันคิดว่ามี 2 ค่ายในโลกที่เกี่ยวกับค่าตอบแทนของการเปลี่ยนแปลงเช่นนั้นมันจะคืนค่าใหม่หรือค่าเก่าหรือไม่?

กรุณาติดต่อฉันหากคุณสนใจที่จะใช้คุณสมบัติดังกล่าวในพื้นที่โมดูล


1
ขอบคุณ @Elizabeth - นี่เป็นมุมที่น่าสนใจ - ฉันแค่ตีคำถามนี้ที่นี่และมีและมีการคืนทุนไม่เพียงพอในการสร้างลักษณะที่จะทำเคล็ดลับ (หรือทักษะในส่วนของฉัน) ฉันพยายามที่จะทำให้หัวของฉันฝึกการเขียนโค้ดที่ดีที่สุดและปรับให้เข้ากับ - และหวังว่าการออกแบบของ Raku จะสอดคล้องกับแนวปฏิบัติที่ดีที่สุดซึ่งฉันรวบรวมไว้
p6steve

6

ขณะนี้ฉันสงสัยว่าคุณเพิ่งสับสน 1ก่อนที่ฉันจะสัมผัสสิ่งนั้นเรามาเริ่มกันใหม่กับสิ่งที่คุณไม่สับสน:

ฉันชอบ=ความกระฉับกระเฉงของงานที่มอบหมาย แต่ฉันต้องการความง่ายในการกระโจนเข้าข้างการกระทำที่มีหลายวิธี ... ฉันไม่เข้าใจว่าทำไมฉันไม่สามารถไป$c.v( 43 )เพื่อตั้งค่าคุณลักษณะสาธารณะ

คุณสามารถทำสิ่งเหล่านี้ทั้งหมด กล่าวคือคุณใช้=งานที่ได้รับมอบหมายและหลายวิธีและ "เพียงแค่$c.v( 43 )" ในเวลาเดียวกันถ้าคุณต้องการ:

class C {
  has $!v;
  multi method v                is rw {                  $!v }
  multi method v ( :$trace! )   is rw { say 'trace';     $!v }
  multi method v ( $new-value )       { say 'new-value'; $!v = $new-value }
}
my $c = C.new;
$c.v = 41;
say $c.v;            # 41
$c.v(:trace) = 42;   # trace
say $c.v;            # 42
$c.v(43);            # new-value
say $c.v;            # 43

แหล่งที่มาของความสับสน1

เบื้องหลังhas $.foo is rwสร้างแอททริบิวต์และวิธีการเดียวตามแนวของ:

has $!foo;
method foo () is rw { $!foo }

ข้างต้นไม่ถูกต้องแม้ว่า ที่กำหนดพฤติกรรมที่เราเห็นกำลัง, autogenerated คอมไพเลอร์ของfooวิธีการเป็นอย่างใดถูกประกาศในลักษณะที่ว่าใหม่วิธีการที่มีชื่อเดียวกันเงียบเงามัน 2

ดังนั้นหากคุณต้องการวิธีการที่กำหนดเองอย่างน้อยหนึ่งวิธีที่มีชื่อเดียวกันกับแอตทริบิวต์คุณต้องทำซ้ำวิธีที่สร้างขึ้นโดยอัตโนมัติหากคุณต้องการรักษาการทำงานตามปกติ

เชิงอรรถ

1ดูคำตอบของ jnthn สำหรับบัญชีที่ชัดเจนและมีอำนาจอย่างชัดเจนเกี่ยวกับความคิดเห็นของ Raku เกี่ยวกับการได้รับส่วนตัว / ผู้ตั้งภาคประชาชนและสิ่งที่มันทำอยู่เบื้องหลังเมื่อคุณประกาศผู้เริ่มต้น / ผู้ตั้งhas $.fooตัว

2หากมีการประกาศวิธีการเข้าถึงอัตโนมัติสำหรับแอตทริบิวต์onlyนั้น Raku จะถือว่าเป็นข้อยกเว้นถ้ามีการประกาศวิธีที่มีชื่อเดียวกัน ถ้ามันถูกประกาศmultiก็ไม่ควรจะเป็นเงาถ้ามีการประกาศวิธีการใหม่multiและควรโยนข้อยกเว้นถ้าไม่ ดังนั้น accessor autogenerated จะถูกประกาศด้วยไม่ใช่onlyหรือmultiแต่แทนที่ในบางวิธีที่ช่วยให้เกิดเงาเงียบ


Aha - ขอบคุณ @raiph - นั่นคือสิ่งที่ฉันรู้สึกว่าขาดไป ตอนนี้มันสมเหตุสมผลแล้ว ต่อ Jnthn ฉันอาจจะพยายามที่จะเป็น Coder OO ที่แท้จริงที่ดีกว่าและรักษาสไตล์ของตัวตั้งค่าสำหรับคอนเทนเนอร์ข้อมูลบริสุทธิ์ แต่มันเป็นเรื่องดีที่รู้ว่านี่คือในกล่องเครื่องมือ!
p6steve
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.