【问题标题】:Design pattern for repetitive switch in getters?吸气剂中重复开关的设计模式?
【发布时间】:2012-04-26 22:53:57
【问题描述】:

我有一个 ORM 模型(PHP Active Record),例如,用于博客系统。我有一个 post 模型,用于存储喜欢的数量。 post 可以是 picturequote(比如说),它们是不同的表(因此是模型)。

架构是postpicturequote 一起保存分享数量、喜欢、描述等数据。

所以在为 post 模型编写 getter 时,我不得不编写

public function getX() {
    if ($this->isPicture()) {
       return $this->picture->getX();
    }
    else if ($this->isQuote()) {
       return $this->quote->getX()
    }
    else {
       return self::DEFAULT_X
    }
}

我目前不得不为许多 getter 编写这个结构。有什么办法可以避免这种情况吗?

PS:标记为 PHP,因为那是我的代码。

编辑

  • 将 cmets 更改为代码。
  • 这是一个模型(以及数据库中的相应表),它的数据不仅仅是picturequote。例如,descriptionpost 的一部分,不位于 picturequote 上。
  • pictures 和quotes 的表格。
  • 使用 PHP Active Record,三个类中的每一个都扩展了 PHP Active Record 提供的通用模型类。
  • picture 模型有自己的数据。 quote 也一样。

【问题讨论】:

  • 我认为我们需要更多的代码来决定如何最好地处理这个问题,但策略模式听起来很合理。
  • 绝对需要更清晰。我对按原样解释的模型设置有点困惑。
  • @KristianAntonsen 我添加了一些代码。如果有任何特定区域需要详细信息,我可以这样做。
  • @leemachin 我做了一些编辑。这有帮助吗?与此同时,我正在阅读策略模式。

标签: php design-patterns language-agnostic if-statement


【解决方案1】:

扩展 cmets 中提到的策略模式的概念:

class Post {
    // get the correct 'strategy'
    public function getModel() {
        if ($this->isPicture()) {
            return $this->picture;
        }

        if ($this->isQuote()) {
            return $this->quote;
        }

        return null;
    }

    // using the strategy
    public function getX() {
        $model = $this->getModel();

        if (null === $model) {
            return self::DEFAULT_X;
        }

        return $model->getX();
    }
}

每个策略都可能实现与 Post 类相同的接口,以公开这些 getter。更好的是提供一个默认策略(而不是返回 null)并让它返回默认值。这样一来,每个 getter 中的 null 检查就变得多余了。

【讨论】:

  • +1 用于提及默认策略。仅此一项就可以为减少条件语句创造奇迹。
  • 我的老板最终推荐了同样的东西。我们在两个模型上没有相同的 getter 名称,但添加别名很简单。谢谢!
【解决方案2】:

另一种方法是元编程的一种非常基本的形式。这个想法是你比手动调用你的方法更高级,让代码为你做这件事。

(假设方法定义都是Post的一部分)

public function getX($model = null) {
   if ($model) return $model->getX();
   else return self::DEFAULT_X;
}

// usage
$postModel->getX($pictureModel);

这里发生的情况是,在 getX 模型中的 getX 的单个实例中,您传入另一个类的名称,并在该实例上执行 `getX' 方法(如果它存在并且是可调用的)。

您可以通过其他方式扩展它。例如,也许你不想传入一个实例,但无论如何方法都可以做到:

public function getX($model_name = null) {
  if ($model_name && $class_exists($model_name) && is_callable(array($model_name, 'getX')) {
    $model = new $model_name;
    return $model->getX();

  } else {
    return self::DEFAULT_X;
  }
} 

// usage
$postModel->getX('Picture');

在这种情况下,您将模型作为字符串传入,然后该方法将完成其余的工作。虽然这可以更快地获得您想要的东西,但您可能会发现您不想一直使用新实例(或者您不能),因此需要对这种“方便”进行权衡方式。

但是,这仍然不能完全解决您的问题,因为您仍然必须一遍又一遍地为每个 getter 重复此操作。相反,您可以尝试以下方法:

public function __call($method, $args) {
  $class = $args[0];

  if (class_exists($class) && is_callable(array($class, $method))) {
    $model = new $class;  
    return $model->$method();
  }
}

// usage
$postModel->getX('Picture');
$postModel->getY('Quote');
$postModel->getZ('Picture');

如果您调用 Post 模型上不存在的函数,该魔术方法将被调用,它会启动您作为参数提供的模型名称的新实例,并调用 @ 987654328@ 方法就可以了,如果它存在的话。

请务必注意,您不得在 Post 中定义这些 getter,除非您想覆盖其他类中的方法。

尽管如此,仍然存在创建新实例的问题,为了解决这个问题,您可以使用一些依赖注入。这意味着您让Post 类包含一个它想要在将来使用的其他类实例的列表,因此您可以随意添加和删除它们。

这是我认为的实际解决方案,其他示例有望展示我是如何到达这里的(当然,会进行编辑以澄清事情)。

public $models = array();

public function addModel($instance) {
  $this->models[get_class($instance)] = $instance;
}

public function __call($method, $args) {
  $class = $args[0];

  if (array_key_exists($class, $this->models)) {
    $model = $this->models[$class];
    if (is_callable(array($model, $method)) {
      return $model->$method();
    }
  }     
}

// usage
$this->addModel($pictureModel);
$this->addModel($quoteModel);

$this->getX('Picture');
$this->getY('Quote');

在这里,您将现有模型实例传递给Post 类,然后该类将它们存储在一个数组中,并以类的名称为键。然后,当您使用上一个示例中描述的类时,它不会创建新实例,而是使用它已经存储的实例。这样做的好处是您可以对您的实例做一些您希望在 Post 模型中反映出来的事情。

这意味着您可以添加任意数量的新模型,需要插入Post,您唯一需要做的就是将addModel 注入它们,并在这些模型上实现getter。

它们都要求你告诉班级在某个时候要调用什么模型。既然你有一系列依赖模型,为什么不添加一种获取所有内容的方法呢?

public function __call($method, $args) {
  $class = $args[0];

  if (array_key_exists($class, $this->models)) {
    $model = $this->models[$class];
    if (is_callable(array($model, $method)) {
      return $model->$method();
    }
  } elseif ($class === 'all') {
    // return an array containing the results of each method call on each model
    return array_map(function($model) use ($method) {
      if (is_callable(array($model, $method) return $model->$method();
    }, $this->models);

  }    
}


// usage
$postModel->getX('all');

使用它,您将获得一个数组,其中包含您使用addModel 添加的每个模型上每个getX 方法的返回值。您可以创建非常强大的函数和类来完成所有这些工作,而无需重复繁琐的逻辑。

我必须提到这些示例未经测试,但至少我希望你可以做什么的概念已经明确。

注意: 同样的事情也可以应用于__GET__SET 方法,它们用于访问属性。还值得一提的是,库已经在使用这些魔术方法可能存在轻微风险,在这种情况下,您需要使代码更加智能。

【讨论】:

  • 反射和元编程很少是答案,因为它很难理解,也很难做到正确。我建议不要这样做,尤其是当有更简单的解决方案可用时。
猜你喜欢
  • 2018-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-16
  • 2017-10-11
  • 1970-01-01
  • 2018-04-05
相关资源
最近更新 更多