【问题标题】:How do I make a well designed validation for a complex collection model?如何为复杂的集合模型进行精心设计的验证?
【发布时间】:2013-10-09 16:57:58
【问题描述】:

作为输入,我有一个书籍列表。作为输出,我期望一个 SimilarBookCollection。

SimilarBookCollection 有作者、publishYear 和图书列表。如果书籍的作者不同或 publishYear 不同,则无法创建 SimilarBookCollection。

目前在 PHP 中的解决方案:

client.php
----
$arrBook = array(...); // array of books
$objValidator = new SimilarBookCollectionValidator($arrBook);
if ($objValidator->IsValid()) {
   $objSimilarBookCollection = new SimilarBookCollection($arrBook);
   echo $objSimilarBookCollection->GetAuthor();
}
else {
   echo 'Invalid input';
}


SimilarBookCollection.php
---
class SimilarBookCollection() {
 public function SimilarBookCollection(array $arrBook) { 
       $objValidator = new SimilarBookCollectionValidator($arrBook); 
       if ($objValidator->IsValid()) {
         throw new Exception('Invalid books to create collection');
       }
       $this->author = $arrBook[0]->GetAuthor();
       $this->publishYear = $arrBook[0]->GetPublishYear();
       $this->books = $arrBook;
 }
 public function GetAuthor() {
      return $this->author;
 }

 public function GetPublishYear() {
      return $this->publishYear;
 }

 public function GetBooks() {
      return $this->books;
 }
}

SimilarBookCollectionValidator.php
---
class SimilarBookCollectionValidator() {
 public function IsValid() {
   $this->ValidateAtLeastOneBook();
   $this->ValidateSameAuthor();
   $this->ValidateSameYear();

   return $this->blnValid;
 }

 ... //actual validation routines
}

我们的目标是拥有一个“特殊”收藏,其中仅包含具有相同作者和发布年份的书籍。这样做的目的是轻松访问对象中的重复信息,例如作者或年份。

  • 您会如何命名 SimilarBookCollection?现在的名字是 通用的。使用像 SameYearAuthorBookCollection 这样的名称看起来有点 又长又奇怪(如果要添加更多条件,则名称会增加)

  • 您会在 SimilarBookCollection 构造函数中使用验证器吗? 防御性编程风格?

  • 你会改变代码的设计吗?如果是怎么办?

【问题讨论】:

    标签: oop design-patterns


    【解决方案1】:

    这一切都取决于;)

    因此,如果我的目标是通用的适应性解决方案,我会执行以下操作:

    构造函数中的验证器 一方面,您要验证两次;如果先决条件/合同被破坏(没有给出有效的列表),这会提供信息,但是要运行的代码是双倍的——究竟是为了什么目的? 如果您想在系统中使用它,取决于它的大小、它的重要性、产品阶段以及可能的更多标准。 但它也是适合模型的控制器逻辑,这意味着您正在传播您的代码。

    我不会把它放在构造函数中。

    名称/设计 我想说的是保持 BookCollection 的通用性,并严格在控制器空间中进行任何验证,而不是使集合变得臃肿,这本质上似乎是一个带有额外作者字段的数组。

    如果您想区分不同的集合类型,请使用(多重)继承或某种附加字段“collectionType”;前者,如果您希望出现许多衍生品或不同的功能(同时保持不同的逻辑很好地分开)。

    您还可以将您的集合视为执行查询的集合,并且为方便起见,您可以维护某种元数据,例如 $AuthorCount = N, $publicationDates = array(...) 从中您可以快速派生集合的性质。这种方法还可以使您的验证器代码最小化(或不存在),因为它隐含在集合中,您只需在控制器中进行验证,使其背后的有效逻辑清晰可见。

    这也将使您在未来更加舒适。但问题实际上是您想要什么和需要它,以及您期望什么变化,因为您应该使您的设计符合您的要求和可能的变化。

    对于您的非常特殊的问题,据我了解,限制如下:

    1. 在任何给定的系统中只有一个集合类型类 时间点。
    2. 该类的项目具有多个属性,对于这些特定的、可能更改的子集(称为相同属性),该集合仅接受所有项目的所选属性相同的项目列表。
    3. 该类为所有相同的属性提供 getter
    4. 该类不得以预期方式以外的任何其他方式使用。

    如果不是第 1 点,我将使用参数化的通用基类(即您在实例化时告诉它这是一组相同的属性)或使用多重继承(或在 php 特征中)与需要的接口。子类可能依赖于基类,但使用相同属性的预定义子集。

    参数化变体可能如下所示:

    class BookCollection {
        public function __construct($book_list, $identical_fields=array())
        {
             if (empty($book_list))
             {
                 throw new EmptyCollectionException("Empty book list");
             }
    
             $default = $book_list[0];
             $this->ia = array();
             foreach($identical_fields as $f)
             {
                  $this->ia[$f] = $default->$f;
             }
    
             foreach($book_list as $book)
             {
                  foreach($identical_fields as $f)
                  {
                       if ($this->ia[$f] !== $book->$f)
                       {
                            throw new NotIdenticalFieldException("Field $f is not identical for all");
                       }
                   }
              }
    
              $this->book_list = $book_list;
        }
        public function getIdentical($key)
        {
            $this->ia[$key];
        }
    }
    
    final class BC_by_Author extends BookCollection{
        public function __construct($book_list)
        {
            parent::__construct($book_list,array('author'));
        }
    
        public function getAuthor(){ $this->ia['author']; }
    }
    

    或者在抽象和最终类型上胡闹(不确定这样是否有效)

    abstract class BookCollection{
        public final function __construct($book_list){...}
        abstract public function getIdenticalAttributes();
    }
    final class BC_by_Author {
        public function getIdenticalAttributes(){ return array('author'); }
        public function getAuthor(){ return $this->ia['author']; }
    }
    

    如果您依赖的 getter 不一定与字段名称匹配,我会选择多重继承/特征。 然后命名将类似于 BC_Field1Field2Field3。

    另外或另外,您也可以使用完全相同的类名,但在不同的命名空间中开发您的解决方案,这意味着您在更改命名空间时不必更改代码,而且您可以在控制器中保持简短.

    但是因为永远只有一个类,所以我将其命名为 BookCollection,不再不必要地讨论它。

    由于约束4,白盒约束,给定的书单必须由类本身验证,即在构造函数中。

    【讨论】:

    • 这是重要的一行:$objBookCollection->GetAuthor(),如果书籍没有相同的属性,则无法从集合中提取作者。
    • 如果我只是让 BookCollection 类没有验证,任何开发人员都可以看到它并用一组不具有相同特征的书籍实例化它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-02
    • 2013-02-28
    • 2010-09-11
    • 1970-01-01
    • 2021-04-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多