2011-05-11 25 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; 
      } 
     } 
    } 
} 

?> 
相关问题