“ คำอวยพร” ของ Perl นั้นทำอะไรกันแน่?


142

ฉันเข้าใจหนึ่งใช้คำหลัก "อวยพร" ใน Perl ภายในวิธีการ "ใหม่" ของชั้นเรียน:

sub new {
    my $self = bless { };
    return $self;
}    

แต่อะไรคือ "อวยพร" ที่ทำกับการอ้างอิงแฮช


2
ดู"Bless My Referents"ย้อนกลับไปจากปี 1999 ดูรายละเอียดแล้วสวยดี (ในPerl คู่มือรายการไม่ได้มีการจัดการที่ดีที่จะบอกว่ามันน่าเสียดาย.)
จอน Skeet

คำตอบ:


143

โดยทั่วไปblessเชื่อมโยงวัตถุกับคลาส

package MyClass;
my $object = { };
bless $object, "MyClass";

ตอนนี้เมื่อคุณเรียกใช้เมธอดบน$objectPerl รู้ว่าแพ็กเกจใดที่จะค้นหาเมธอด

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

เพื่อความชัดเจนตัวอย่างของคุณอาจถูกเขียนดังนี้:

sub new { 
  my $class = shift; 
  my $self = { }; 
  bless $self, $class; 
} 

แก้ไข: ดูคำตอบที่ดีของkixxเพื่อดูรายละเอียดเพิ่มเติมเล็กน้อย


79

bless เชื่อมโยงการอ้างอิงกับแพ็คเกจ

มันไม่สำคัญว่าการอ้างอิงนั้นจะเกี่ยวกับแฮช (กรณีที่พบบ่อยที่สุด) กับอาเรย์ (ไม่ใช่เรื่องธรรมดา) ไปยังสเกลาร์ (โดยปกติจะระบุว่าวัตถุที่อยู่ด้านใน ) ไปยังนิพจน์ทั่วไป , รูทีนย่อยหรือ TYPEGLOB (ดูหนังสือObject Oriented Perl: คู่มือครอบคลุมแนวคิดและเทคนิคการเขียนโปรแกรมโดย Damian Conwayสำหรับตัวอย่างที่มีประโยชน์) หรือแม้แต่การอ้างอิงไปยังไฟล์หรือไดเรกทอรีจัดการ (กรณีที่พบบ่อยที่สุด)

เอฟเฟกต์blessมีคือช่วยให้คุณสามารถใช้ไวยากรณ์พิเศษกับการอ้างอิงที่ได้รับพร

ตัวอย่างเช่นหากการอ้างอิงที่มีความสุขถูกเก็บไว้ใน$obj(เชื่อมโยงblessกับแพ็คเกจ "คลาส") จากนั้น$obj->foo(@args)จะเรียกรูทีนย่อยfooและส่งผ่านเป็นอาร์กิวเมนต์แรกการอ้างอิง$objตามด้วยอาร์กิวเมนต์ที่เหลือ ( @args) ควรกำหนดรูทีนย่อยในแพ็คเกจ "Class" หากไม่มีรูทีนย่อยfooในแพ็คเกจ "Class" จะมีการค้นหารายการแพ็กเกจอื่น (นำมาจากอาร์เรย์@ISAในแพ็คเกจ "Class") และค้นหารูทีนย่อยแรกที่fooพบ


16
ใบแจ้งยอดครั้งแรกของคุณไม่ถูกต้อง ใช่อวยพรใช้การอ้างอิงเป็นอาร์กิวเมนต์แรก แต่เป็นตัวแปรอ้างอิงที่ได้รับพรไม่ใช่การอ้างอิง $ perl -le 'sub Somepackage :: foo {42}; % h = (); $ H = \% h; อวยพร $ h "Somepackage"; $ j = \% h; พิมพ์ $ j-> UNIVERSAL :: can ("foo") -> () '42
converter42

1
คำอธิบายของ Kixx นั้นครอบคลุม เราไม่ควรกังวลกับการเลือกของตัวแปลงในส่วนย่อยทางทฤษฎี
ความสุขเกินบรรยาย

19
@ Blessed Geek มันไม่ใช่ทฤษฎีย่อย ความแตกต่างมีการใช้งานจริง
ikegami

3
ลิงก์ perlfoundation.org เก่าสำหรับ "วัตถุเข้าด้านใน" คือที่ดีที่สุดหลังกำแพงเข้าสู่ระบบในขณะนี้ การเชื่อมโยง Archive.org ของเดิมอยู่ที่นี่
ruffin

2
บางทีนี่อาจจะทำหน้าที่แทนลิงก์ที่เสีย @harmic แสดงความคิดเห็นบน: perldoc.perl.org/perlobj.html#Inside-Out-objects
Rhubbarb


7

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

ตัวอย่าง :

bless REF, CLASSNAME
bless REF

ส่งคืนค่า

ฟังก์ชันนี้ส่งคืนการอ้างอิงไปยังวัตถุที่มีความสุขใน CLASSNAME

ตัวอย่าง :

ต่อไปนี้เป็นตัวอย่างรหัสแสดงการใช้งานขั้นพื้นฐานของการอ้างอิงวัตถุที่ถูกสร้างขึ้นโดยอวยพรอ้างอิงถึงชั้นเรียนของแพคเกจ -

#!/usr/bin/perl

package Person;
sub new
{
    my $class = shift;
    my $self = {
        _firstName => shift,
        _lastName  => shift,
        _ssn       => shift,
    };
    # Print all the values just for clarification.
    print "First Name is $self->{_firstName}\n";
    print "Last Name is $self->{_lastName}\n";
    print "SSN is $self->{_ssn}\n";
    bless $self, $class;
    return $self;
}

4

ฉันจะให้คำตอบที่นี่เพราะคนที่นี่ไม่ได้คลิกสำหรับฉัน

ฟังก์ชันอวยพรของ Perl เชื่อมโยงการอ้างอิงถึงทุกฟังก์ชั่นภายในแพ็คเกจ

ทำไมเราต้องการสิ่งนี้

เริ่มต้นด้วยการแสดงตัวอย่างใน JavaScript:

(() => {
    'use strict';

    class Animal {
        constructor(args) {
            this.name = args.name;
            this.sound = args.sound;
        }
    }

    /* [WRONG] (global scope corruption)
     * var animal = Animal({
     *     'name': 'Jeff',
     *     'sound': 'bark'
     * }); 
     * console.log(animal.name + ', ' + animal.sound); // seems good
     * console.log(window.name); // my window's name is Jeff?
     */

    // new is important!
    var animal = new Animal(
        'name': 'Jeff',   
        'sound': 'bark'
    );

    console.log(animal.name + ', ' + animal.sound); // still fine.
    console.log(window.name); // undefined
})();

ตอนนี้ให้ตัดการสร้างคลาสออกและทำโดยไม่ทำ:

(() => {
    'use strict';

    var Animal = function(args) {
        this.name = args.name;
        this.sound = args.sound;
        return this; // implicit context hashmap
    };

    // the "new" causes the Animal to be unbound from global context, and 
    // rebinds it to an empty hash map before being constructed. The state is
    // now bound to animal, not the global scope.
    var animal = new Animal({
        'name': 'Jeff',
        'sound': 'bark'
    });
    console.log(animal.sound);    
})();

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

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

# self contained scope 
(sub {
    my $Animal = (sub {
        return {
            'name' => $_[0]{'name'},
            'sound' => $_[0]{'sound'}
        };
    });

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound' => 'bark'
    });

    print $animal->{sound};
})->();

ตอนนี้เรามีปัญหา: จะเป็นอย่างไรถ้าเราต้องการให้สัตว์แสดงเสียงด้วยตัวเองแทนที่จะพิมพ์ออกมาเป็นเสียงของพวกมัน นั่นคือเราต้องการฟังก์ชั่นการทำงานเสียงที่พิมพ์เสียงของสัตว์

วิธีหนึ่งในการทำเช่นนี้คือการสอนให้สัตว์แต่ละตัวรู้ว่าต้องทำอย่างไร ซึ่งหมายความว่าแมวแต่ละตัวมีฟังก์ชั่นที่ซ้ำกันเพื่อทำการเสียง

# self contained scope 
(sub {
    my $Animal = (sub {
        $name = $_[0]{'name'};
        $sound = $_[0]{'sound'};

        return {
            'name' => $name,
            'sound' => $sound,
            'performSound' => sub {
                print $sound . "\n";
            }
        };
    });

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound' => 'bark'
    });

    $animal->{'performSound'}();
})->();

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

(() => {
    'use strict';

    /* a function that creates an Animal constructor which can be used to create animals */
    var Animal = (() => {
        /* function is important, as fat arrow does not have "this" and will not be bound to Animal. */
        var InnerAnimal = function(args) {
            this.name = args.name;
            this.sound = args.sound;
        };
        /* defined once and all animals use the same single function call */
        InnerAnimal.prototype.performSound = function() {
            console.log(this.name);
        };

        return InnerAnimal;
    })();

    /* we're gonna create an animal with arguments in different order
       because we want to be edgy. */
    var animal = new Animal({
        'sound': 'bark',
        'name': 'Jeff'
    });
    animal.performSound(); // Jeff
})();

นี่คือที่ที่ขนานกับ Perl kinda หยุด

ตัวดำเนินการใหม่ของ JavaScript ไม่ใช่ตัวเลือกหากไม่มีวิธีนี้ภายในวิธีวัตถุจะทำลายขอบเขตทั่วโลก:

(() => {
    // 'use strict'; // uncommenting this prevents corruption and raises an error instead.

    var Person = function() {
        this.name = "Sam";
    };
//    var wrong = Person(); // oops! we have overwritten window.name or global.main.
//    console.log(window.name); // my window's name is Sam?
    var correct = new Person; // person's name is actually stored in the person now.

})();

เราต้องการที่จะมีฟังก์ชั่นเดียวสำหรับสัตว์แต่ละตัวที่ค้นหาเสียงของสัตว์ตัวนั้นมากกว่าการเข้ารหัสแบบแข็งที่การสร้าง

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

package Animal;
sub new {
    my $packageRef = $_[0];
    my $name = $_[1]->{'name'};
    my $sound = $_[1]->{'sound'};

    my $this = {
        'name' => $name,
        'sound' => $sound
    };   

    bless($this, $packageRef);
    return $this;
}

# all animals use the same performSound to look up their sound.
sub performSound {
    my $this = shift;
    my $sound = $this->{'sound'};
    print $sound . "\n";
}

package main;
my $animal = Animal->new({
    'name' => 'Cat',
    'sound' => 'meow'
});
$animal->performSound();

สรุป / TL; DR :

Perl ไม่มี "นี่", "คลาส" หรือ "ใหม่" การให้พรวัตถุไปยังแพ็คเกจให้วัตถุนั้นอ้างอิงถึงแพ็คเกจและเมื่อมันเรียกใช้ฟังก์ชันในแพ็คเกจอาร์กิวเมนต์ของพวกเขาจะถูกชดเชยด้วย 1 สล็อตและอาร์กิวเมนต์แรก ($ _ [0] หรือ shift) จะเทียบเท่ากับ javascript ของ "นี่" ในทางกลับกันคุณสามารถจำลองโมเดลต้นแบบของ JavaScript ได้บ้าง

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

มีห้องสมุด Perl บางตัวที่สร้างวิธีการของตัวเองในการลดข้อ จำกัด นี้ในลักษณะที่แสดงออกเช่น Moose

ทำไมต้องสับสน? :

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

สิ่งที่เราต้องการทำ :

บางสิ่งเช่นนี้ แต่มีผลผูกพันกับแผนที่ต้นแบบแบบเรียกซ้ำและถูกผูกไว้กับต้นแบบโดยปริยายแทนที่จะต้องทำอย่างชัดเจน

นี่คือความพยายามที่ไร้เดียงสา: ปัญหาคือ "การโทร" ไม่ทราบ "สิ่งที่เรียกว่า" ดังนั้นจึงอาจเป็นฟังก์ชั่นที่เป็นสากล "objectInvokeMethod (object, method)" ซึ่งตรวจสอบว่าวัตถุนั้นมีวิธีการหรือไม่ หรือต้นแบบมีหรือมีต้นแบบจนกว่าจะถึงจุดสิ้นสุดและค้นหามันหรือไม่ (สืบทอดมาจากต้นแบบ) Perl มีเวทย์มนตร์ที่ดีที่จะทำ แต่ฉันจะปล่อยให้สิ่งที่ฉันสามารถลองทำในภายหลัง

อย่างไรก็ตามนี่คือความคิด:

(sub {

    my $Animal = (sub {
        my $AnimalPrototype = {
            'performSound' => sub {
                return $_[0]->{'sound'};
            }
        };

        my $call = sub {
            my $this = $_[0];
            my $proc = $_[1];

            if (exists $this->{$proc}) {
                return $this->{$proc}->();
            } else {
                return $this->{prototype}->{$proc}->($this, $proc);
            }
        };

        return sub {
            my $name = $_[0]->{name};
            my $sound = $_[0]->{sound};

            my $this = { 
                'this' => $this,
                'name' => $name,
                'sound' => $sound,
                'prototype' => $AnimalPrototype,
                'call' => $call                
            };
        };
    })->();

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound'=> 'bark'
    });
    print($animal->{call}($animal, 'performSound'));
})->();

อย่างไรก็ตามหวังว่าใครบางคนจะพบว่าโพสต์นี้มีประโยชน์


ไม่สามารถสร้างคลาสใหม่ที่รันไทม์ได้ my $o = bless {}, $anything;จะอวยพรวัตถุใน$anythingชั้นเรียน ในทำนองเดียวกัน{no strict 'refs'; *{$anything . '::somesub'} = sub {my $self = shift; return $self->{count}++};จะสร้างวิธีการตั้งชื่อ 'somesub' $anythingในชั้นเรียนที่มีชื่อใน ทั้งหมดนี้เป็นไปได้ที่รันไทม์ อย่างไรก็ตาม "เป็นไปได้" ไม่ได้ทำให้เป็นการฝึกฝนที่ยอดเยี่ยมในการใช้รหัสทุกวัน แต่มันมีประโยชน์ในการสร้างระบบวางซ้อนวัตถุเช่นมูหรือหมู่
DavidO

ที่น่าสนใจดังนั้นคุณกำลังพูดว่าฉันสามารถอวยพรผู้อ้างอิงในชั้นเรียนที่มีชื่อตัดสินใจที่รันไทม์ ดูเหมือนว่าน่าสนใจและถือเป็นโมฆะการunfortunately it makes it impossible(to my understanding) to create "new classes" at runtimeเรียกร้องของฉัน ฉันเดาว่าในที่สุดความกังวลของฉันจะลดลงอย่างเห็นได้ชัดว่ามันใช้งานง่ายน้อยลงในการจัดการ / ตรวจสอบระบบแพ็คเกจที่รันไทม์ แต่จนถึงตอนนี้ฉันไม่ได้แสดงอะไรที่มันไม่สามารถทำได้ ระบบแพคเกจดูเหมือนจะสนับสนุนเครื่องมือทั้งหมดที่จำเป็นในการเพิ่ม / ลบ / ตรวจสอบ / แก้ไขตัวเองในขณะทำงาน
Dmitry

สิ่งนี้ถูกต้อง คุณสามารถจัดการตารางสัญลักษณ์ของ Perl ได้โดยทางโปรแกรมและดังนั้นจึงสามารถจัดการกับแพ็คเกจของ Perl และสมาชิกของแพ็คเกจได้ที่รันไทม์แม้ว่าจะไม่ได้ประกาศ "package Foo" ไว้ที่ใดก็ตาม การตรวจสอบตารางสัญลักษณ์และการจัดการที่รันไทม์, ซีแมนทิกส์ AUTOLOAD, รูทีนย่อย, การผูกตัวแปรกับคลาส ... มีหลายวิธีในการเข้าถึง บางส่วนมีประโยชน์สำหรับการสร้างอัตโนมัติ API, เครื่องมือตรวจสอบ, API บันทึกอัตโนมัติ เราไม่สามารถทำนายกรณีการใช้ทั้งหมด การยิงตัวเองที่เท้าก็เป็นผลมาจากการใช้กลอุบายดังกล่าว
DavidO

4

พร้อมกับคำตอบที่ดีจำนวนมากสิ่งที่แยกความแตกต่างของการblessอ้างอิง -ed ก็คือการที่SV มันหยิบขึ้นมาเพิ่มเติมFLAGS( OBJECT) และSTASH

perl -MDevel::Peek -wE'
    package Pack  { sub func { return { a=>1 } } }; 
    package Class { sub new  { return bless { A=>10 } } }; 
    $vp  = Pack::func(); print Dump $vp;   say"---"; 
    $obj = Class->new;   print Dump $obj'

พิมพ์ด้วยชิ้นส่วน (และไม่เกี่ยวข้องกับสิ่งนี้) ที่ถูกระงับ

SV = IV (0x12d5530) ที่ 0x12d5540
  REFCNT = 1
  ธง = (ROK)
  RV = 0x12a5a68
  SV = PVHV (0x12ab980) ที่ 0x12a5a68
    REFCNT = 1
    ธง = (SHAREKEYS)
    ...
      SV = IV (0x12a5ce0) ที่ 0x12a5cf0
      REFCNT = 1
      FLAGS = (IOK, pIOK)
      IV = 1
---
SV = IV (0x12cb8b8) ที่ 0x12cb8c8
  REFCNT = 1
  ธง = (PADMY, ROK)
  RV = 0x12c26b0
  SV = PVHV (0x12aba00) ที่ 0x12c26b0
    REFCNT = 1
    FLAGS = (OBJECT, SHAREKEYS)
    STASH = 0x12d5300 "คลาส"
    ...
      SV = IV (0x12c26b8) ที่ 0x12c26c8
      REFCNT = 1
      FLAGS = (IOK, pIOK)
      IV = 10

เมื่อทราบว่า 1) เป็นวัตถุ 2) เป็นแพคเกจใดและแจ้งให้ทราบถึงการใช้งาน

ตัวอย่างเช่นเมื่อพบการเปลี่ยนแปลงในตัวแปรนั้น ( $obj->name) จะค้นหา sub ที่มีชื่อนั้นในแพ็คเกจ (หรือลำดับชั้น) วัตถุจะถูกส่งเป็นอาร์กิวเมนต์แรกเป็นต้น


1

ฉันทำตามความคิดนี้เพื่อเป็นแนวทางในการพัฒนา Perl เชิงวัตถุ

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

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


นี่คือประกาศเดียวกันกับวิธีการทำงานของ Perl กับ namespaces ของแพคเกจและวิธีการทำงานกับรัฐที่ลงทะเบียนใน namespace ของคุณ เพราะสิ่งนี้มีอยู่ pragmas เช่นใช้ namespace :: clean แต่พยายามทำให้สิ่งต่าง ๆ เป็นไปได้ง่ายขึ้น
Steven Koch

-9

ตัวอย่างเช่นหากคุณมั่นใจได้ว่าวัตถุ Bug ใด ๆ จะเป็นแฮชที่มีความสุขคุณสามารถ (ในที่สุด!) กรอกรหัสที่หายไปในวิธี Bug :: print_me:

 package Bug;
 sub print_me
 {
     my ($self) = @_;
     print "ID: $self->{id}\n";
     print "$self->{descr}\n";
     print "(Note: problem is fatal)\n" if $self->{type} eq "fatal";
 }

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


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