Вопрос по абстракциям (филосовско-паттернский)

gelik-vani

Новичок
Всем привет,
вопрос немного нестандартыный даже скажем с заходом в оффтоп.

Входные данные: Нужно написать 2 Генератора и 2 Фильтра.
Условия генераторов (а)
1) возвращает стринг [A-z09]{10}, (10смволов буквы+цифры)
2) возвращает массив из 10 элементов [A-z09]{10},

Условия фильтров (б)
1) Оставляет только цифры
1) Оставляет все кроме цифр

Как видим пример специально подобран максимально простой, чтобы на него не отвлекаться, а перейти к сути:
Вот как это все лучше скомпоновать, чтобы потом безболезненно наращивать и генераторы и фильтры, и максимально абстрагироваться от передаваемых типов

Чтобы выразить свои мысли обозначу классы заранее (ничего конкретно не значат, просто первое что пришло в голову)
а1 - StringGenerator
a2 - ArrayGenerator
б1 - DigitFilter
б2 - CharFilter

Первый уровень абстракции, надо отделить string от array чтобы не дублировать для каждого типа фильтры? Что на ум приходит? Что оба Generator могли бы возвращать array, просто первый возвращает массив с 1 элементом, и тогда уже любой фильтр принимает массив и не парится что там под низом. Вроде как абстракция? Абстракция! Генераторы клепать можем дальше. Фильтра тоже клепаем сколько хотим.

Второй уровень абстракции, а что если мы захотим сделать какой нибудь ArrayCollectionGenerator (или любой другой класс как носитель), который возвращает ArrayCollection (да я знаю что его можно быстро конвернтуть в array, но давайте для чистоты эксперимента забудем что там есть toArray()) чтобы потом в коде где-то использовать $collection->filter();
Тогда вроде как логично добавить прослойку для "array" и ArrayCollection, что-то вроде HeapCollection (тоже особо ничего не значит и с Heap общего не имеет ), где будут методы типа next(), set(), echo(), getRaw(), или даже сделать HeapCollectionInterface, чтобы имеенно им перекидываться между DigitFilter\CharFilter, чтобы они не знали какой конкретный класс перед ними, и пользовались только методами для итерации данных.

Собственно вопрос в чем, тупиковый ли у меня ход или так оно и должно быть?
 

Valick

Новичок
gelik-vani, ты сейчас сказал примерно вот это: "iouseloi oijeji erouer pokegrlij op oijeg kljsvu hhsejfjhfghjjsg"
Для того, что бы тебя понимали, ты должен общаться на языке который понимают твои собеседники. Не надо ничего выдумывать и выражать своими словами. Есть общепринятые названия паттернов. На мой взгляд книга Мэтт Зандстра - "PHP. Объекты, шаблоны и методики программирования" идеальна для начинающих осваивать ООП. Из того, что ты написал, если я правильно дешифровал, тебе нужны паттерны "композиция" и "стратегия" в основе которых делегирование.
 

WMix

герр M:)ller
Партнер клуба
@gelik-vani
я так понял, ты хочешь написать "Filter" для разношерстных данных (в одном случае это string в другом array)
тебе просто нужен третий тип который стандартизирует доступ к элементам этих значений к примеру https://www.php.net/manual/en/class.iterator.php
 

gelik-vani

Новичок
@Valick в том то и беда, прочитал я паттерны, а вот ими думать не научился
ок глянув что там с композицией + interface Iterator я так понимаю с ваших слов что

Логичнее всего заимметь Контейнер компонентов который был бы совместим с interface Iterator , в Листы запихивать пока строки (String generator - вернет один лист, ArrayGenerator вернет столько сколько надо)

А для фильтров использовать Strategy, у которых будет общий метод filter() ? и потом билдить new ConcreteStrategyDigitFilter() , new ConcreteStrategyCharFilter() ?
так я понял?
 

WMix

герр M:)ller
Партнер клуба
я просто сказал, что работать надо с данными которые одинаково обрабатываются (имеют единый interface), например iterator.
PHP:
// приводим к одному типу
interface MyIterator extends Iterator{
    public function value();
}
class MyStringIterator implements MyIterator{
    private string $value = 'hello';
}
class MyArrayIterator implements MyIterator{
    private array $value = [1,2,3];
}

// расширяем генераторы на возврат одного типа

interface IteratorGenerator{
    public function getIterator():MyIterator;
}

class StringGenerator implements IteratorGenerator{
    public function getIterator():MyIterator{
        return new MyStringIterator('hello');
    }
}
class ArrayGenerator implements IteratorGenerator{
    public function getIterator():MyIterator{
        return new MyArrayIterator([1,2,3]);
    }
}

// Все фильтры работают с этим типом

interface Filter{
    public function filter(MyIterator $iterator):MyIterator
}

class DigitFilter implements Filter{}
class CharFilter implements Filter{}

// пользуем

$sg = new StringGenerator();
$df = new DigitFilter();
echo $df->filter($sg->getIterator())->value()
какой ты выберешь, мне все равно )

и кстати iterator'а тебе не хватит, тебе понадобится изменять фильтром содержимое твоих капсул без понимания типа внутренних данных
 
Последнее редактирование:

Valick

Новичок
gelik-vani, начни с самого простого
PHP:
<?php

interface Filter
{
    public function execute(string $string): string;
}

class DigitFilter implements Filter
{
    public function execute(string $string): string
    {
        return preg_replace('/\d/', '', $string);
    }
}

class CharLowerFilter implements Filter
{
    public function execute(string $string): string
    {
        return preg_replace('/[a-z]/', '', $string);
    }
}

interface MyGenerator
{
    public function filter(Filter $objFilter): self;

    public function getValue();
}

class StringGenerator implements MyGenerator
{
    private string $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    private string $string = '';

    public function __construct(int $length)
    {
        $max = mb_strlen($this->chars) - 1;
        while (mb_strlen($this->string) <= $length) {
            $this->string .= $this->chars[mt_rand(0, $max)];
        }
        $this->string = str_shuffle($this->string);
    }

    public function filter(Filter $objFilter): MyGenerator
    {
        $this->string = $objFilter->execute($this->string);
        return $this;
    }

    public function getValue(): string
    {
        return $this->string;
    }
}

class ArrayGenerator implements MyGenerator
{
    private array $arrString = [];

    public function __construct(int $count, int $length)
    {
        for ($i = 0; $i <= $count; $i++) {
            $this->arrString[] = (new StringGenerator($length))->getValue();
        }
    }

    public function filter(Filter $objFilter): MyGenerator
    {
        foreach ($this->arrString as $key => $string) {
            $this->arrString[$key] = $objFilter->execute($string);
        }
        return $this;
    }

    public function getValue(): array
    {
        return $this->arrString;
    }
}

$objDigitFilter = new DigitFilter();
$objCharLowerFilter = new CharLowerFilter();

echo '<pre>';
echo (new StringGenerator(100))
    ->filter($objDigitFilter)
    ->filter($objCharLowerFilter)
    ->getValue();
echo '<hr />';
print_r((new ArrayGenerator(10, 30))
    //->filter($objDigitFilter)
    ->filter($objCharLowerFilter)
    ->getValue());
echo '</pre>';
 

Redjik

Джедай-мастер
Тут надо не паттерны смотреть, а усиленно вникать в полиморфизм.
 
  • Like
Реакции: WMix

Valick

Новичок
Усиленный полиморфизм приведёт к "изобретению велосипеда", который после спринта по граблям обернётся паттерном.
Трудно ТС ответить, что-то конкретное, так как сама постановка задачи достаточно абстрактная.
Я предложил начать с самого простого паттерна - делегирование.
 

Valick

Новичок
он про то что, реализацию не надо было писать :) достаточно было пустых методов
мне не жалко несколько строчек учебного кода написанных за час "на коленке"
я всего лишь считаю, что сводить всё к массиву это костыль
вот дошли руки сделал небольшую реинкарнацию кода, вам будет над чем подумать и без пустых методов
PHP:
<?php

interface Filter
{
    public function execute(string $string): string;
}

class DigitFilter implements Filter
{
    public function execute(string $string): string
    {
        return preg_replace('/\d/', '', $string);
    }
}

class CharLowerFilter implements Filter
{
    public function execute(string $string): string
    {
        return preg_replace('/[a-z]/', '', $string);
    }
}

class ReplaceFilter implements Filter
{
    private string $from;
    private string $to;

    public function __construct(string $from, string $to)
    {
        $this->from = $from;
        $this->to = $to;
    }
    public function execute(string $string): string
    {
        return preg_replace("/$this->from/", $this->to, $string);
    }
}

interface MyGenerator
{
    public function addFilter(Filter $objFilter): self;

    public function getValue();
}

abstract class GeneratorLeaf implements MyGenerator
{
    protected array $arrFilterObject = [];

    public function addFilter(Filter $objFilter): MyGenerator
    {
        $this->arrFilterObject[] = $objFilter;
        return $this;
    }
}

class StringGenerator extends GeneratorLeaf
{
    private string $length = '';
    private string $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

    public function __construct(int $length)
    {
        $this->length = $length;
    }

    public function getValue(): string
    {
        $string = '';
        $max = mb_strlen($this->chars) - 1;
        while (mb_strlen($string) <= $this->length) {
            $string .= $this->chars[mt_rand(0, $max)];
        }
        foreach ($this->arrFilterObject as $objFilter) {
            $string = $objFilter->execute($string);
        }
        return str_shuffle($string);
    }
}

abstract class GeneratorComposite extends GeneratorLeaf
{
    protected array $arrComposite = [];

    public function addElement(MyGenerator $objElement): GeneratorComposite
    {
        $this->arrComposite[] = $objElement;
        return $this;
    }
}

class ArrayGenerator extends GeneratorComposite
{
    public function getValue(): array
    {
        $arrValue = [];
        foreach ($this->arrComposite as $objElement) {
            foreach ($this->arrFilterObject as $objFilter) {
                $objElement->addFilter($objFilter);
            }
            $arrValue[] = $objElement->getValue();
        }
        return $arrValue;
    }
}

$objDigitFilter = new DigitFilter();
$objCharLowerFilter = new CharLowerFilter();

echo '<pre>';
echo (new StringGenerator(100))
    ->addFilter($objDigitFilter)
    ->addFilter($objCharLowerFilter)
    ->addFilter(new ReplaceFilter('A', 'a'))
    ->getValue();
echo '<hr />';
print_r(
    (new ArrayGenerator())
        ->addElement(
            (new ArrayGenerator())
                ->addElement(new StringGenerator(200))
                ->addFilter($objCharLowerFilter)
        )
        ->addElement(
            (new StringGenerator(100))
                ->addFilter($objDigitFilter)
        )
        ->addElement(
            (new StringGenerator(100))
                ->addFilter($objCharLowerFilter)
        )
        ->addFilter(new ReplaceFilter('A', '*'))
        ->getValue()
);
echo '</pre>';
 
  • Wow
Реакции: WMix

WMix

герр M:)ller
Партнер клуба
только вот ты упростил себе задачу и фильтруешь только string'и,
я так понял, что было желание фильтровать любые данные
если это массив то к примеру так
PHP:
self::assertEquals(
    numberFilter(['he110', 1234, 'world']),
    [1234]
);
если string
PHP:
self::AssertEquals(
    numberFilter("he110"),
    "110"
);
 

Valick

Новичок
только вот ты упростил себе задачу и фильтруешь только string'и,
это не так, просто у нас разное понимание того, что должен делать фильтр, но это не имеет особого значения
в моей реализации фильтру абсолютно неизвестно, что он фильтрует, процессом "что и как фильтровать" руководит только сам объект
вы же понимаете, что для того что бы изменить поведение фильтра для ArrayGenerator (оставлять или исключать строки массива в соответствии с фильтром как у вас, а не применять фильтр к каждому символу элемента массива как у меня) мне ничего не надо исправлять в коде кроме самого ArrayGenerator
 
Последнее редактирование:

WMix

герр M:)ller
Партнер клуба
мне ничего не надо исправлять в коде кроме самого ArrayGenerator
композиция это как раз частный случай. применить фильтр к каждому элементу массива легко делается с помощью foreach.
ваша реализация фильтров изначально подразумевает что приходит string и возвращается string.

я же предложил не учитывать тип данных при фильтрации. те функция фильтр должна возвращать bool соответствие или не соответствие фильтру а решение что делать с этим значением (удалить в случае не соответствия) возлагается на interface доступа

боюсь в вашем случае поменять придется практически все включая типы данных - ArrayGenerator является GeneratorComposite
 

Valick

Новичок
ваша реализация фильтров изначально подразумевает что приходит string и возвращается string.
просто у нас разное понимание того, что должен делать фильтр,
Трудно ТС ответить, что-то конкретное, так как сама постановка задачи достаточно абстрактная.
боюсь в вашем случае поменять придется практически все включая типы данных
бояться не надо :)
 
Сверху