ลักษณะของ PHP - ตัวอย่าง / แนวทางปฏิบัติที่ดีที่สุดในโลกแห่งความเป็นจริง? [ปิด]


148

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

อย่างไรก็ตามฉันยังไม่รู้ว่าฉันจะใช้ประโยชน์จากคุณลักษณะต่าง ๆ ในโครงการของฉันอย่างไร

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


8
นี่คือความเห็นของฉัน: โพสต์บล็อกในเรื่องที่ฉันเขียนในหัวข้อ TL; DR: โดยพื้นฐานแล้วฉันกลัวว่าในขณะที่มันมีพลังและสามารถใช้เพื่อประโยชน์ได้ดีการใช้งานส่วนใหญ่ที่เราเห็นจะเป็นรูปแบบการต่อต้านที่สมบูรณ์และทำให้เกิดความเจ็บปวดมากกว่าที่พวกเขาแก้ ...
ircmaxell

1
ลองดูที่ห้องสมุดมาตรฐานของสกาล่าแล้วคุณจะพบตัวอย่างที่มีประโยชน์มากมาย
dmitry

คำตอบ:


89

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

แทนที่จะใช้ลักษณะการแฮ็กโค้ดในคลาสมันเป็นการดีกว่าที่จะส่งผ่านการพึ่งพาผ่านตัวสร้างหรือผ่านตัวตั้งค่า:

class ClassName {
    protected $logger;

    public function __construct(LoggerInterface $logger) {
        $this->logger = $logger;
    }
    // or
    public function setLogger(LoggerInterface $logger) {
        $this->logger = $logger;
    }
}

เหตุผลหลักที่ฉันพบว่าดีกว่าการใช้คุณสมบัติคือรหัสของคุณมีความยืดหยุ่นมากขึ้นโดยการลบการมีเพศสัมพันธ์อย่างหนักเพื่อลักษณะ ตัวอย่างเช่นคุณสามารถผ่านชั้นเรียนคนตัดไม้ที่แตกต่างกันในขณะนี้ ทำให้รหัสของคุณสามารถนำมาใช้ใหม่และทดสอบได้


4
คุณสามารถใช้คลาสตัวบันทึกอื่นได้ใช่ไหม เพียงแก้ไขคุณสมบัติและคลาสทั้งหมดที่ใช้คุณสมบัติจะได้รับการอัปเดต แก้ไขฉันถ้าฉันผิด
rickchristie

14
@rickchristie แน่นอนคุณสามารถทำได้ แต่คุณจะต้องแก้ไขซอร์สโค้ดของคุณลักษณะ ดังนั้นคุณจะเปลี่ยนมันสำหรับทุก ๆ คลาสที่ใช้มันไม่ใช่เฉพาะอันที่คุณต้องการให้คนตัดไม้คนอื่นใช้ และถ้าคุณต้องการใช้คลาสเดียวกัน แต่มีตัวบันทึกสองตัวที่ต่างกัน หรือถ้าคุณต้องการที่จะส่งคนลอกเลียนแบบในขณะที่การทดสอบ? คุณไม่สามารถถ้าคุณใช้คุณลักษณะคุณสามารถถ้าคุณใช้การฉีดพึ่งพา
NikiC

2
ฉันสามารถเห็นประเด็นของคุณฉันยังไตร่ตรองว่าคุณลักษณะนั้นมีค่าหรือไม่ ฉันหมายถึงในกรอบสมัยใหม่เช่น Symfony 2 คุณมีการฉีดยาเสพติดทั่วสถานที่ซึ่งดูเหมือนว่ายอดเยี่ยมเกินกว่าคุณสมบัติในกรณีส่วนใหญ่ ในขณะนี้ฉันเห็นลักษณะที่ไม่มากแล้ว "คอมไพเลอร์ช่วยคัดลอกและวาง" ;)
สูงสุด

11
ในขณะนี้ฉันเห็นลักษณะที่ไม่มากแล้ว "คอมไพเลอร์ช่วยคัดลอกและวาง" ;) : @Max: นั่นคือสิ่งที่คุณสมบัติที่ได้รับการออกแบบมาเพื่อให้ถูกต้องอย่างสมบูรณ์ มันทำให้มันมากขึ้น "การบำรุงรักษา" เนื่องจากมีเพียงหนึ่งความหมาย แต่ก็เพียงพื้น C & P ...
ircmaxell

29
NikiC หายไปจากจุด: การใช้คุณลักษณะไม่ได้ป้องกันการใช้ Dependency Injection ในกรณีนี้คุณลักษณะจะให้ทุกคลาสที่ใช้การบันทึกโดยไม่ต้องทำซ้ำเมธอด setLogger () และการสร้างคุณสมบัติ $ logger ลักษณะที่จะให้พวกเขา setLogger () จะพิมพ์คำใบ้บน LoggerInterface ตามที่เป็นตัวอย่างเพื่อให้สามารถบันทึกประเภทใดก็ได้แนวคิดนี้คล้ายกับคำตอบของกอร์ดอนด้านล่าง )
อีธาน

205

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

ตัวอย่างสำหรับลักษณะตัวบันทึก:

interface Logger
{
    public function log($message, $level);    
}

class DemoLogger implements Logger
{
    public function log($message, $level)
    {
        echo "Logged message: $message with level $level", PHP_EOL; 
    }
}

trait Loggable // implements Logger
{
    protected $logger;
    public function setLogger(Logger $logger)
    {
        $this->logger = $logger;
    }
    public function log($message, $level)
    {
        $this->logger->log($message, $level);
    }
}

class Foo implements Logger
{
    use Loggable;
}

แล้วคุณก็ทำ ( ตัวอย่าง )

$foo = new Foo;
$foo->setLogger(new DemoLogger);
$foo->log('It works', 1);

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

trait T {
    protected function foo() {}
}
class A { 
    public function foo() {}
}
class B extends A
{
    use T;
}

ด้านบนจะส่งผลให้เกิดข้อผิดพลาด ( สาธิต ) ในทำนองเดียวกันวิธีการใด ๆ ที่ประกาศในลักษณะที่ประกาศไว้แล้วในชั้นเรียนการใช้จะไม่ได้รับการคัดลอกลงในชั้นเรียนเช่น

trait T {
    public function foo() {
    return 1;
}
}
class A { 
    use T;
    public function foo() {
    return 2;
}
}

$a = new A;
echo $a->foo();

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

class A
{
    use T;
    protected $prop = 1;
    protected function getProp() {
        return $this->prop;
    }
}

trait T
{
    public function foo()
    {
        return $this->getProp();
    }
}

$a = new A;
echo $a->foo();

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

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

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

หมายเหตุ: เนื่องจากลักษณะเป็นแนวคิดใหม่ใน PHP ความคิดเห็นทั้งหมดที่แสดงด้านบนอาจมีการเปลี่ยนแปลง ฉันยังไม่ได้มีเวลามากในการประเมินแนวคิดด้วยตนเอง แต่ฉันหวังว่ามันจะดีพอที่จะให้สิ่งที่คุณคิด


41
กรณีการใช้งานที่น่าสนใจ: ใช้อินเทอร์เฟซที่กำหนดสัญญาใช้ลักษณะเพื่อตอบสนองสัญญานั้น สิ่งที่ดี.
สูงสุด

13
ฉันชอบโปรแกรมเมอร์ที่แท้จริงประเภทนี้ผู้เสนอตัวอย่างการทำงานจริงโดยมีการสืบทอดสั้น ๆ สำหรับแต่ละคน ขอบคุณ
Arthur Kushman

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

12
@sumanchalki ระดับนามธรรมปฏิบัติตามกฎของการสืบทอด ถ้าคุณต้องการคลาสที่ใช้ Loggable และ Cacheable คุณต้องการคลาสเพื่อขยาย AbstractLogger ซึ่งต้องการขยาย AbstractCache แต่นั่นหมายถึง Loggables ทั้งหมดเป็นแคช นั่นคือการแต่งงานกันที่คุณไม่ต้องการ มัน จำกัด การนำมาใช้ใหม่และทำให้กราฟมรดกของคุณยุ่ง
Gordon

1
ฉันคิดว่าลิงก์สาธิตนั้นตายแล้ว
2559

19

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

ลักษณะ - พวกเขาเป็นที่ดีในการใช้กลยุทธ์ รูปแบบการออกแบบกลยุทธ์โดยย่อมีประโยชน์เมื่อคุณต้องการจัดการข้อมูลเดียวกัน (กรองเรียงลำดับ ฯลฯ ) แตกต่างกัน

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

ลองมัน:

<?php
trait SortStrategy {
    private $sort_field = null;
    private function string_asc($item1, $item2) {
        return strnatcmp($item1[$this->sort_field], $item2[$this->sort_field]);
    }
    private function string_desc($item1, $item2) {
        return strnatcmp($item2[$this->sort_field], $item1[$this->sort_field]);
    }
    private function num_asc($item1, $item2) {
        if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
        return ($item1[$this->sort_field] < $item2[$this->sort_field] ? -1 : 1 );
    }
    private function num_desc($item1, $item2) {
        if ($item1[$this->sort_field] == $item2[$this->sort_field]) return 0;
        return ($item1[$this->sort_field] > $item2[$this->sort_field] ? -1 : 1 );
    }
    private function date_asc($item1, $item2) {
        $date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
        $date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
        if ($date1 == $date2) return 0;
        return ($date1 < $date2 ? -1 : 1 );
    }
    private function date_desc($item1, $item2) {
        $date1 = intval(str_replace('-', '', $item1[$this->sort_field]));
        $date2 = intval(str_replace('-', '', $item2[$this->sort_field]));
        if ($date1 == $date2) return 0;
        return ($date1 > $date2 ? -1 : 1 );
    }
}

class Product {
    public $data = array();

    use SortStrategy;

    public function get() {
        // do something to get the data, for this ex. I just included an array
        $this->data = array(
            101222 => array('label' => 'Awesome product', 'price' => 10.50, 'date_added' => '2012-02-01'),
            101232 => array('label' => 'Not so awesome product', 'price' => 5.20, 'date_added' => '2012-03-20'),
            101241 => array('label' => 'Pretty neat product', 'price' => 9.65, 'date_added' => '2012-04-15'),
            101256 => array('label' => 'Freakishly cool product', 'price' => 12.55, 'date_added' => '2012-01-11'),
            101219 => array('label' => 'Meh product', 'price' => 3.69, 'date_added' => '2012-06-11'),
        );
    }

    public function sort_by($by = 'price', $type = 'asc') {
        if (!preg_match('/^(asc|desc)$/', $type)) $type = 'asc';
        switch ($by) {
            case 'name':
                $this->sort_field = 'label';
                uasort($this->data, array('Product', 'string_'.$type));
            break;
            case 'date':
                $this->sort_field = 'date_added';
                uasort($this->data, array('Product', 'date_'.$type));
            break;
            default:
                $this->sort_field = 'price';
                uasort($this->data, array('Product', 'num_'.$type));
        }
    }
}

$product = new Product();
$product->get();
$product->sort_by('name');
echo '<pre>'.print_r($product->data, true).'</pre>';
?>

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


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

ฉันชอบคำstrategiesนี้
Rannie Ollit

4

ฉันตื่นเต้นสำหรับลักษณะเพราะพวกเขาแก้ปัญหาทั่วไปเมื่อพัฒนาส่วนขยายสำหรับแพลตฟอร์มอีคอมเมิร์ซของวีโอไอพี ปัญหาเกิดขึ้นเมื่อส่วนขยายเพิ่มฟังก์ชันการทำงานให้กับคลาสหลัก (เช่นพูดว่ารุ่นผู้ใช้) โดยขยายออก สิ่งนี้ทำได้โดยการชี้ Zolo autoloader (ผ่านไฟล์กำหนดค่า XML) เพื่อใช้โมเดลผู้ใช้จากส่วนขยายและให้โมเดลใหม่นั้นขยายโมเดลหลัก ( ตัวอย่าง ) แต่จะเกิดอะไรขึ้นถ้าส่วนขยายสองรายการแทนที่รุ่นเดียวกัน คุณได้รับ "สภาพการแข่งขัน" และโหลดเพียงหนึ่งเดียว

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

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

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

TL; DR ฉันคิดว่าคุณลักษณะอาจมีประโยชน์สำหรับการสร้างส่วนขยาย / โมดูล / ปลั๊กอินสำหรับแพ็คเกจซอฟต์แวร์ PHP ขนาดใหญ่เช่น Magento


0

คุณสามารถมีลักษณะของวัตถุแบบอ่านอย่างเดียว:

  trait ReadOnly{  
      protected $readonly = false;

      public function setReadonly($value){ $this->readonly = (bool)$value; }
      public function getReadonly($value){ return $this->readonly; }
  }

คุณสามารถตรวจสอบได้ว่ามีการใช้คุณลักษณะดังกล่าวหรือไม่และควรตรวจสอบว่าคุณควรเขียนวัตถุนั้นในฐานข้อมูลไฟล์ ฯลฯ หรือไม่


ดังนั้นคลาสที่มีคุณสมบัติuseนี้ก็จะเรียกว่าif($this -> getReadonly($value)); แต่สิ่งนี้จะสร้างข้อผิดพลาดหากคุณไม่ได้มีuseลักษณะนี้ ดังนั้นตัวอย่างนี้มีข้อบกพร่อง
Luceos

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

ฉันทำหลักฐานทั่วไปเกี่ยวกับแนวคิดสำหรับลักษณะเช่นนี้ในgist.github.com/gooh/4960073
Gordon

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