Looking for a strategy to ensure name of child node stays unique in collection
https://softwareengineering.stackexchange.com/questions/363597
-
25-01-2021 - |
题
Even though I'm programming in PHP, I'm open to reviewing language-agnostic suggestions, as they might point me to valuable directions.
To remove any possible confusion I feel some comments appear to allude to: the Page
s in this question do not refer to web pages, but to in-memory pages for an API akin to word processing and/or a spreadsheet.
I have a Page
class whose instances can be part of a Pages
collection. These Page
objects must be uniquely named if they're part of a Pages
collection, as they must be uniquely identifiable by their name.
A Page
object can never belong to multiple Pages
collections at the same time: they will either be moved, or be copied (cloned).
Consumers will also be able to alter the name of individual Page
objects, for instance with1:
$page = new Page( 'Optional name' );
// or ($pages is a Pages collection instance)
$page = $pages->getByName( 'Page 1' );
// and then
$page->setName( 'My Page' );
If a Page
is added to a Pages
collection, the collection will ensure the Page
is renamed, if its name conflicts with another Page
in the collection, like so:
class Pages
{
private $objectIndex;
private $nameIndex;
public function __construct() {
$this->objectIndex = new SplObjectStorage;
$this->nameIndex = [];
}
public function add( Page $page ) {
if( !$this->objectIndex->contains( $page ) ) {
$name = $page->getName();
if( isset( $this->nameIndex[ $name ] ) ) {
// make name unique to this collection
$name = $this->someLogicToMakeNameUnique( $name );
// alter name
$page->setName( $name );
}
$this->nameIndex[ $name ] = $page;
$this->objectIndex->attach( $page, $name );
}
}
public function getByName( $name ) {
if( isset( $this->nameIndex[ $name ] ) ) {
$this->nameIndex[ $name ];
}
return null;
}
}
I'm trying to come up with a strategy to make sure that, if a Page
is part of a collection and its name is altered, the name automatically gets adjusted to a unique name (i.e. if "Page 1"
already exists, rename it to "Page 2"
) and that the Pages::$nameIndex
keys get properly updated as well (as this allows for faster retrieval of a Page
by name than looping through it's contained Page
s until a name matches).
Strategies I've come up with so far are:
Pass
Pages
collection toPage::setParent( Pages $container )
and call some verification/sanitation mechanism on$this->container
inPage::setName( $name )
, before altering.Emit a
rename
event fromPage::setName( $name )
and havePages
do something like$event->preventDefault()
if the name conflicts and then havePages
alter the name to something unique instead.
Option 1 seems the easiest/laziest/least process intensive, but puts the responsibility of verifying uniqueness in an object where it does not belong.
Option 2 appeals the most to me so far, but it has a downside as well: It will emit multiple rename
events, if the Pages
collection needs to alter the name itself again.
NB: Even though both the Page::setParent( Pages $container )
(as a Page
can only ever be owned by one collection) and event dispatching capabilities (primarily meant for consumer purposes) are already implemented, neither are utilized for my unique naming conundrum yet.
Do you have any other suggestions that satisfy the constraints that Pages
should be responsible for verifying uniqueness and that there's not to much back-and-forth communication necessary?
1) As a reference: my objective is akin to Excel VBA's Workbook.Sheets("Sheet 1").Name = "Sheet"
type of behavior, which I believe behaves similar to what I am after.
解决方案
Uniqueness of those names is a property which is clearly defined only in context of a Pages
collection. That is most easiest understood when you think of a Page
object changing its "parent" container, or a Page
object with no parent container at all.
Thus verifying uniqueness is clearly a responsibility of the Pages
class, not of a Page
. As a consequence, when the verification/sanitation mechanism is implemented in Pages
, it is exactly where it belongs to. Calling this mechanism from Page::setName
does not change the responsibility, so your "Option 1" is perfectly ok.
As a side note: the most simple "verification/sanitation mechanism" I can think of is to simply forbid any name change of a Page
once it has been assigned to a Pages
collection. This simple strategy might be totally sufficient for several use cases. If a page has to be renamed, it needs to be taken out of the collection first, then renamed, and then added again by using the add
method in your example.