2011-05-11 26 views
29

我正在設計一個類,它定義了一個非常複雜的對象,其中大多數可選參數爲噸(50+),其中許多參數都具有默認值(例如:$type = 'foo'; $width = '300'; $interactive = false;)。我試圖確定設置構造函數和實例/類變量,以最好的方式能夠:PHP - 使用大量參數和默認值初始化對象的最佳方法

  • 可以很容易地使用類
  • 可以很容易地自動文檔類(即:使用的phpDocumentor)
  • 代碼本典雅

在上述的光,我不想是通過構造一噸的參數。我會被傳入一個哈希包含初始化值,例如:$foo = new Foo(array('type'=>'bar', 'width'=>300, 'interactive'=>false));

在編碼之類的條款,我還是覺得我寧願......

class Foo { 
    private $_type = 'default_type'; 
    private $_width = 100; 
    private $_interactive = true; 

    ... 
} 

...因爲我相信這將有助於文檔生成(您可以獲得該類的屬性列表,讓API用戶知道他們必須使用哪些「選項」),並且「感覺」是正確的方式。然後你遇到了將構造函數中的傳入參數映射到類變量的問題,並且沒有利用符號表,你會陷入一種「蠻力」的方式,這對我來說就是擊敗了目的(雖然我'對其他意見開放)。例如:

function __construct($args){ 
    if(isset($args['type'])) $_type = $args['type']; // yuck! 
} 

我曾考慮過創建一個本身是關聯數組的類變量。初始化這將是非常容易的,然後,例如:

private $_instance_params = array(
    'type' => 'default_type', 
    'width' => 100, 
    'interactive' => true 
); 

function __construct($args){ 
    foreach($args as $key=>$value){ 
     $_instance_params[$key] = $value; 
    } 
} 

但這好像我沒有采取像私有類變量,本地特性優勢,而且感覺像文檔生成就不會使用這種方法的工作。

感謝您閱讀這本書;我可能在這裏問了很多,但我是PHP的新手,我真的只是尋找這樣做的慣用/優雅的方式。你最好的做法是什麼?


附錄(關於這個特殊類的細節)

這很可能是這個類是試圖做太多,但它是一個古老的Perl庫的一個端口,用於創建和處理形式。可能有一種方法可以將配置選項分開以利用繼承和多態性,但實際上可能會適得其反。

通過請求,這裏是一些參數(Perl代碼)的部分列表。你應該看到它們不能很好地映射到子類。

這個類當然有許多這些屬性的getter和setter,所以用戶可以重載它們;本文的目標(以及原始代碼很好地實現的目的)是提供一種緊湊的方式來實例化這些Form對象,並且已經設置了所需的參數。它實際上使非常可讀的代碼。

# Form Behaviour Parameters 
     # -------------------------- 
     $self->{id}; # the id and the name of the <form> tag 
     $self->{name} = "webform"; # legacy - replaced by {id} 
     $self->{user_id} = $global->{user_id}; # used to make sure that all links have the user id encoded in them. Usually this gets returned as the {'i'} user input parameter 
     $self->{no_form}; # if set, the <form> tag will be omitted 
     $self->{readonly}; # if set, the entire form will be read-only 
     $self->{autosave} = ''; # when set to true, un-focusing a field causes the field data to be saved immediately 
     $self->{scrubbed}; # if set to "true" or non-null, places a "changed" radio button on far right of row-per-record forms that indicates that a record has been edited. Used to allow users to edit multiple records at the same time and save the results all at once. Very cool. 
     $self->{add_rowid}; # if set, each row in a form will have a hidden "rowid" input field with the row_id of that record (used primarily for scrubbable records). If the 'scrubbed' parameter is set, this parameter is also automatically set. Note that for this to work, the SELECT statement must pull out a unique row id. 
     $self->{row_id_prefix} = "row_"; # each row gets a unique id of the form id="row_##" where ## corresponds to the record's rowid. In the case of multiple forms, if we need to identify a specific row, we can change the "row_" prefix to something unique. By default it's "row_" 

     $self->{validate_form}; # parses user_input and validates required fields and the like on a form 
     $self->{target}; # adds a target window to the form tag if specified 
     $self->{focus_on_field}; # if supplied, this will add a <script> tag at the end of the form that will set the focus on the named field once the form loads. 
     $self->{on_submit}; # adds the onSubmit event handler to the form tag if supplied 
     $self->{ctrl_s_button_name}; # if supplied with the name of the savebutton, this will add an onKeypress handler to process CTRL-S as a way of saving the form 

     # Form Paging Parameters 
     # ---------------------- 
     $self->{max_rows_per_page}; # when displaying a complete form using printForm() method, determines the number of rows shown on screen at a time. If this is blank or undef, then all rows in the query are shown and no header/footer is produced. 
     $self->{max_pages_in_nav} = 7; # when displaying the navbar above and below list forms, determines how many page links are shown. Should be an odd number 
     $self->{current_offset}; # the current page that we're displaying 
     $self->{total_records}; # the number of records returned by the query 
     $self->{hide_max_rows_selector} = ""; # hide the <select> tag allowing users to choose the max_rows_per_page 
     $self->{force_selected_row} = ""; # if this is set, calls to showPage() will also clear the rowid hidden field on the form, forcing the first record to be displayed if none were selected 
     $self->{paging_style} = "normal"; # Options: "compact" 

我們當然可以讓自己陷入圍繞編程風格的更長時間的辯論。但我希望避免它,因爲所有參與者的理智!這裏(Perl代碼,再次)是一個用相當多的一組參數實例化這個對象的例子。

my $form = new Valz::Webform (
      id      => "dbForm", 
      form_name    => "user_mailbox_recip_list_students", 
      user_input    => \%params, 
      user_id     => $params{i}, 
      no_form     => "no_form", 
      selectable    => "checkbox", 
      selectable_row_prefix => "student", 
      selected_row   => join (",", getRecipientIDsByType('student')), 
      this_page    => $params{c}, 
      paging_style   => "compact", 
      hide_max_rows_selector => 'true', 
      max_pages_in_nav  => 5 
     ); 
+3

這聽起來像類waaaaaaaaay做很多。你能否詳細說明這個班級應該做什麼,並且可能列出50個屬性中的更多或全部。 – Gordon 2011-05-11 16:14:38

+0

讓這些公衆成員有什麼缺點?您是否需要在施工後對其進行修復,並且不提供任何其他方法來更改這些值? – Mel 2011-05-11 17:00:09

+0

@梅爾 - 除非我有誤,否則公開會員並不能改善情況。它只會在前端而不是後端鼓勵一些難看的代碼。 – 2011-05-11 17:17:05

回答

7

我可以想到兩種方法。如果你想保持你的實例變量,你可以通過傳遞給構造函數的數組只是迭代,並動態地設置實例變量:

<?php 

    class Foo { 
     private $_type = 'default_type'; 
     private $_width = 100; 
     private $_interactive = true; 

     function __construct($args){ 
      foreach($args as $key => $val) { 
       $name = '_' . $key; 
       if(isset($this->{$name})) { 
        $this->{$name} = $val; 
       } 
      } 
     } 
    } 

    ?> 

使用時,你並不真的不得不放棄文檔陣列的方法。只需使用@財產註釋在類主體:

<?php 

/** 
* @property string $type 
* @property integer $width 
* @property boolean $interactive 
*/ 
class Foo { 
    private $_instance_params = array(
     'type' => 'default_type', 
     'width' => 100, 
     'interactive' => true 
    ); 

    function __construct($args){ 
     $this->_instance_params = array_merge_recursive($this->_instance_params, $args); 
    } 

    public function __get($name) 
    { 
     return $this->_instance_params[$name]; 
    } 

    public function __set($name, $value) 
    { 
     $this->_instance_params[$name] = $value; 
    } 
} 

?> 

這就是說,與50個成員變量的類要麼只用於配置(可拆分),或者只是做太多,你可能想要考慮重構它。

+0

我喜歡你的兩種方法,並沒有真正想過我會使用$ this訪問成員變量,因此可以通過編程方式訪問它們。 isset()返回true,如果變量被聲明但沒有賦值(我甚至不確定這在PHP中是否有意義)。我只是在想 - 如果我沒有一個默認值(例如:'private $ _foo;'),該怎麼辦? – 2011-05-11 17:25:13

+0

我會爲這些分配null。這樣你可以檢查是否(isset($ this - > {$ name})|| $ this - > {$ name} === null)... – Daff 2011-05-11 17:41:43

+0

只是要清楚,如果我要實現你的上面的方法1,I _must_在聲明類屬性時分配某種類型的值?即:'private $ foo = null;'並且必須避免'private $ foo;'?我認爲isset()返回false如果該值爲空,我想過聲明一個成員變量,而不分配給它一個值爲空的值? – 2011-05-11 19:45:10

0

你也可以做一個父類。

在那個類中,你只定義了變量。

protected function _SetVarName($arg){ 

    $this->varName=$arg; 
} 

然後將該類擴展爲新文件,並在該文件中創建所有進程。

所以,你得到

classname.vars.php 
classname.php 

classname extends classnameVars { 

} 

因爲大多數會在默認情況下,你只需要設置/復位你需要的人。

$cn=new classname(); 
$cn->setVar($arg);  
//do your functions.. 
+0

如果我要強制API用戶這樣做,爲什麼不直接在類中使用公共變量並讓用戶直接設置它們呢? – 2011-05-11 17:30:54

6

另一種方法是將類實例與FooOptions對象,只是作爲一個選項容器:

<?php 
class Foo 
{ 
    /* 
    * @var FooOptions 
    */ 
    private $_options; 

    public function __construct(FooOptions $options) 
    { 
     $this->_options = $options; 
    } 
} 


class FooOptions 
{ 
    private $_type = 'default_type'; 
    private $_width = 100; 
    private $_interactive = true; 

    public function setType($type); 
    public function getType(); 

    public function setWidth($width); 
    public function getWidth(); 

    // ... 
} 

你的選擇是有據可查的,你有一個簡單的方法來設置/檢索。這甚至可以幫助您進行測試,因爲您可以創建和設置不同的選項對象。

我不記得這個模式的確切名稱,但我認爲這是生成器選項模式。

+0

這實際上聽起來更像一個模型模式。我不確定你的建議實際上運行良好,除非你定義了一個接口(PHP可以這樣做嗎?)IFooOptions,然後讓API用戶在MyOptions類中實現該接口,或者擴展該類(例如:MyFooOptions擴展FooOptions)這是Foo構造函數的一個實例。如果用戶僅僅實例化Foo,這可以工作。在用戶創建這個類的許多實例的情況下,參數可能需要動態設置,這變得非常尷尬。 – 2011-05-11 17:48:23

+0

是的,PHP可以像類一樣定義接口:'interface IFooInterface'。這種方法當然有其弱點,會讓用戶的生活變得更加困難,但是您可以通過選擇類來獲得定義良好的API。也許你的情況這不是最好的選擇...... :) – 2011-05-11 19:26:22

2

只是爲了跟進我是如何實現這一點,基於Daff's的一個解決方案:

function __construct($args = array()){ 
     // build all args into their corresponding class properties 
     foreach($args as $key => $val) {     
      // only accept keys that have explicitly been defined as class member variables 
      if(property_exists($this, $key)) { 
       $this->{$key} = $val; 
      } 
     } 
    } 

改進建議歡迎!

0

我在我的一些課上使用這個。使複製和粘貼易於快速開發。

private $CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType; 
function __construct($CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType){ 
    $varsValues = array($CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType); 
    $varNames = array('CCNumber', 'ExpMonth', 'ExpYear', 'CV3', 'CardType'); 
    $varCombined = array_combine($varNames, $varsValues); 
    foreach ($varCombined as $varName => $varValue) {$this->$varName = $varValue;} 
} 

使用步驟如下:

  1. 粘貼並從當前的__construct函數得到的變量列表,刪除任何可選參數值
  2. 如果您還沒有準備好,貼在以使用您選擇的範圍聲明您的類的變量
  3. 將同一行粘貼到$ varValues和$ varNames行中。
  4. 對「','」在「,$」上做文本替換。這將得到所有,但首先和最後,你將不得不手動改變
  5. 享受!
0

剛上DAFF的第一個解決方案稍加改進,以支持可能有一個空的默認值,將返回FALSE到isset()函數對象屬性條件:

<?php 

class Foo { 
    private $_type = 'default_type'; 
    private $_width = 100; 
    private $_interactive = true; 
    private $_nullable_par = null; 

    function __construct($args){ 
     foreach($args as $key => $val) { 
      $name = '_' . $key; 
      if(property_exists(get_called_class(),$name)) 
       $this->{$name} = $val; 
      } 
     } 
    } 
} 

?> 
相關問題