我有一组这样的类:
abstract class CollectionAbs implements Iterator {
public function GetListAsXml() {...}
public function GetItemsByFilter(criteria: array) {...}
public function Sort(comparisonFunction) {...}
public function AddItem(newItem: CollectionItemAbs);
public function RemoveItem(newItem: CollectionItemAbs);
public function UpdateItem(newItem: CollectionItemAbs);
protected itemList: array of CollectionItemAbs;
}
abstract class CollectionItemAbs {
abstract public function Save();
abstract public function Load();
abstract public function Delete();
public function GetAsXml(): string {...}
public function ItemMatchesFilter(criteria: array): boolean {}
protected property1;
protected property2;
}
这个想法是CollectionItemAbs
实现的具体实例表示一个项,其数据来自数据库表中的一行,CollectionAbs
的相应具体实例提供对这些项的集合的操作实例,例如提供interator实现。抽象类提供了大部分功能,但具体实例将提供特定于数据类型的添加,例如声明与其各自数据库表中的字段对应的额外属性。然后这两个类一起工作以执行所需的任何操作。
因此,例如,如果调用GetListAsXml(),它将循环遍历列表中的项,在每个项上调用GetAsXml(),连接结果,并将其全部返回到适当的XML容器中。类似地,调用AddItem()接受未保存的新项对象并调用其Save()方法将其提交到数据库。要对集合进行排序,只需调用Sort(),传入比较两个项的比较函数(抽象集合类本身提供了几个默认值,而实现类可以定义使用其唯一集合项类型的其他类)。
现在,所有这些都假设整个集合已加载,并且由CollectionAbs
实现实例中的构造函数处理。
所以暂停,这个设计是否合适?那里有一种可能更好的模式吗?我喜欢用于管理单个项目的功能封装在项目类中,而用于处理项目集合的功能封装在集合类中。并且,我喜欢CollectionAbs
类可以为其子项提供如此多的功能,因为它需要最少的关于项目的“内部知识”。
然而,在整个集合无法一次加载的情况下,我不太确定这种设计,因为这种情况需要更多,更紧密,集合和项目类之间的通信,以及类似于一次加载单个记录的大量额外查询。修改此设计以处理部分集合的最佳方法是什么?我应该关注某种模式吗?
如果重要的话,我在PHP 5.3中这样做。
[编辑:在下面添加示例;还澄清了有关比较函数的错误观点。]
由于有人要求提供一个例子,说明如何在评论中使用它:
这些类将构成各种类型的大量数据集合的基础。一个示例是跟踪系统其他部分使用的状态代码集。系统的不同部分使用略微不同的状态代码,这些代码映射到不同的数据库表。所以我设置了这样的东西:
abstract class StatusCodeCollectionAbs extends CollectionAbs {
protected positionCompareFunction(item1, item2: StatusCodeAbs): integer {...};
protected descriptionCompareFunction(item1, item2: StatusCodeAbs): integer {...};
}
abstract class StatusCodeAbs extends CollectionItemAbs {
protected position: integer;
protected description: integer;
}
这两个类将作为所有状态代码集合的基础。要添加对特定集合的支持,我只需创建具体的子集:
class CustomerStatusCodeCollection extends StatusCodeCollectionAbs {
public function constructor() {
//load all items to list
}
//sort comparison closure unique to this status collection type
protected legacyCodeCompareFunction(item1, item2: CustomerStatusCode): integer {...};
}
class CustomerStatusCode extends StatusCodeAbs {
public function Load() {
//load this item from database
}
public function Save() {
//save this item to the database
}
//data unique to this status type
protected legacyCode: integer
}
系统的要求是通过更高应用程序层中提供的统一CRUD方法集来管理所有集合数据。数据层中的此层次结构通过为集合提供统一接口来实现,但仍允许正确跟踪特定于集合类型的数据。还有额外的状态代码层来跟踪系统将跟踪的数十种状态类型共享的公共数据;其他类型的集合可能在CollectionAbs / CollectionItemAbs下有自己的抽象层,或者它们可以根据需要直接对CollectionAbs和CollectionItemAbs进行子类化。
答案 0 :(得分:3)
一些设计要点:
Item
和Items
共享相同的界面,请不要使用类型检查强制执行此操作。 Item
只需一个ID,而Items
则需要一个数组。 PHP重载很糟糕,所以我不会强制array
让你更轻松。sort
之外,我还会添加一种比较项目的默认方式。
compareTo()
课程中定义了Item
。equals()
方法。count()
。这是获取某些内容大小的PHP方法。轻松完成,也可以扔进去。总的来说,你做得很好,但我有一些建议。
下面的代码并不完美也不完整,因为它没有解决我上面提出的一些问题。但是,它确实提供了比您给出的功能更多的功能。另请注意,我已将事物重命名为ActiveNode
而非CollectionItem
等。这对我来说更有意义。
<?php
/**
* Resource exception would represent a problem with a resource such as a
* database connection or a service like an API. Not everything uses a database
* these days.
*/
class ResourceException extends RuntimeException {}
/**
* A database resource exception.
*/
class DatabaseException extends ResourceException {}
/**
* Allows you to convert an item to and from XML.
*/
interface XmlSerializeable {
/**
* @return string A string in XML format representing the object.
*/
public function xmlSerialize();
/**
* @param string $xml A string in XML format representing the object.
* @throws InvalidArgumentException if the $xml is not well-formed.
* @throws InvalidArgumentException if the $xml does not represent the correct object.
*/
public function xmlUnserialize($xml);
}
/**
* Allows you to sort an object.
*/
interface Sortable {
/**
* Sorts the collection with the function provided. If none is provided, it
* will simply use compareTo on each item.
* @param function $fn The sorting function.
*/
function sort($fn=null);
}
/**
* An active node. An active node contains methods to save, load, delete,
* convert to XML, etc. It is 'active' because it is tied to the resource it
* represents.
*/
interface IActiveNode extends XMLSerializeable {
/**
* Saves the item to the database.
* @throws DatabaseException if an error occurs during the save.
*/
public function save();
/**
* Loads the item from the database.
* @throws DatabaseException if an error occurs during the load.
*/
public function load();
/**
* Deletes the item from the database.
* @throws DatabaseException if an error occurs during the deletion.
*/
public function delete();
/**
* Compares an item to another.
* @param IActiveNode $node The node to compare to.
* @return int A negative number for less than, 0 for equality, and a positive number for greater than.
* @throws InvalidArgumentException if the item provided cannot be compared to.
*/
public function compareTo(IActiveNode $node);
/**
* Tests for equality against the provided item.
* @param IActiveNode $node The node to compare to.
* @return boolean if the nodes are equal.
*/
public function equals(IActiveNode $node);
}
/**
* A collection of active nodes. Note that you should override behavior of many
* off the methods this inherits to ensure that
*/
interface IActiveNodes extends IActiveNode, Sortable, Countable, Iterator {
/**
* Adds a node to the collection.
* @param IActiveNode $node The IActiveNode to add.
* @return int The index the node was added into.
* @throws InvalidArgumentException if the IActiveNode is the wrong type for this collection.
*/
function addNode(IActiveNode $node);
/**
* Removes a node from the collection. Uses the equals method. Nodes will be
* reordered after a remove.
* @param IActiveNode $node The IActiveNode to remove.
* @return IActiveNode The removed node.
* @throws InvalidArgumentException if the IActiveNode is the wrong type for this collection.
*/
function removeNode(IActiveNode $node);
/**
* Gets an item from the list.
* @param IActiveNode $node The node to retrieve.
* @return IActiveNode The IActive node that matches the one provided.
*/
function getNode(IActiveNode $node);
/**
* Checks to see if a node exists in the collection. Uses the equals method.
* @param IActiveNode $node The IActiveNode to check for containment.
* @return boolean Returns true if the IActiveNode is in the collection.
* @throws InvalidArgumentException if the IActiveNode is the wrong type for this collection.
*/
function contains(IActiveNode $node);
/**
* Gets an item from the list.
* @param int $index The index to retrieve.
* @return IActiveNode The IActive node at the index provided.
* @throws InvalidArgumentException if the index is not an integer.
* @throws OutOfBoundsException if the index is out of bounds.
*/
function getIndex($index);
/**
* Removes an item from the list by index.
* @param int $index The index to remove.
* @return IActiveNode The IActive node at the index provided.
* @throws InvalidArgumentException if the index is not an integer.
* @throws OutOfBoundsException if the index is out of bounds.
*/
function removeIndex($index);
/**
* Filters the collection with a function. It calls the filter function on
* each item in the collection, and if the filter function returns true, then
* it will add that to a new IActiveNodes collection, and return it.
* @param function $fn A filter function.
* @return IActiveNodes The filtered nodes.
*/
function filter($fn);
}
?>
备注:强>
IActiveNode
作为参数的任何内容抛出InvalidArgumentExceptions。这允许您扩展IActiveNodes
,扩展名只适用于某些类型。非常有用。ArrayAccess
,如果您希望您的列表能够在语法上像数组一样对待。 答案 1 :(得分:1)
tl; dr 但
public function AddItem(newItem: CollectionItemAbs);
而不是那样,如果您将集合基于ArrayObject
,则您已经定义了许多功能:
class CollectionAbs extends ArrayObject {
public function offsetSet($index, $value) {
if(!$value instanceof CollectionItemAbs) {
throw new InvalidArgumentException(__CLASS__." only contains instances of CollectionItemAbs");
}
return parent::offsetSet($index, $value);
}
}
SPL中有更多非常好的例子供您使用。