如何为复杂的集合模型进行精心设计的验证?

时间:2013-10-09 16:57:58

标签: oop design-patterns

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

SimilarBookCollection有一个author,publishYear和Books列表。如果书籍的作者不同或者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
}

目标是拥有一个“特殊”集合,其中只有具有相同作者和publishYear的书籍。我们的想法是从对象轻松访问作者或年份等重复信息。

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

  • 你会在SimilarBookCollection构造函数中使用一个Validator吗? 防御性编程风格?

  • 您是否会更改代码的设计?如果是,如何?

1 个答案:

答案 0 :(得分:0)

一切都取决于;)

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

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

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

名称/设计 我会说BookCollection一直保持通用,并且在控制器空间中严格进行任何验证,而不是膨胀集合,这实际上似乎是一个带有额外字段的作者的数组。

如果要区分不同的集合类型,请使用(多个)继承或某种附加字段“collectionType”;前者,如果你期望许多衍生物或不同的功能来(也保持逻辑在不同的地方很好地分开)。

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

这也将使你今后感觉更舒服。但问题确实是你想要的,需要它,以及你期望的变化,因为你应该根据你的要求和可能的变化来适应你的设计。

对于非常特殊的问题,我理解的约束如下:

  1. 在任何给定的系统中只有一个集合类型类 时间点。
  2. 该类的项目具有多个属性,对于特定的,可能更改这些属性的子集(称为相同属性),该集合仅接受所有项目的所选属性相同的项目列表。
  3. 该类提供所有相同属性的getter
  4. 除了预期的方式之外,该课程不得以任何其他方式使用。
  5. 如果不是第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,白盒约束,给定的书列表必须由类本身验证,即在构造函数中验证。