LINUX.ORG.RU

MVC. M

 ,


0

2

Приветствую. Эта статья - http://habrahabr.ru/post/175465/ навела на мысль о том, что нужно задуматься: «все ли по фэн-шую у меня»? И в то же время не привела в пример ни одной реализации «правильной модели» (или я ее слепой не заметил).

Если убрать воду, то можно получить что-то вроде: модель отвечает за работу с данными (предоставляет интерфейсы для работы с ними), их валидацию и косвенную работа с ресурсом для хранения данных (об этом ниже).

Так вот, размышляя на эту тему мне в голову приходит следующее (возможно велосипед, поделитесь ссылкой если уже реализовано): существует некий PHP-интерфейс Resource, который содержит методы получения, удаления, изменения данных; существует класс MySQLResource, который реализует интерфейс Resource; и существует класс модели User, расширяющий класс MySQLResource и использующий интерфейс Resource для сохранения данных и их получения.

В описании я опустил валидацию поскольку еще не решил как она может быть использована в моделях. Есть вариант вынести стандартный набор методов валидации в некий класс, допустим Validation, и чтобы его расширяла модель и использовала его методы. Т.е. фактически мне необходимо наследование двух классов для реализации этой идеи. Я что-то слышал о trait, надо почитать. (=

Идем дальше. Каким образом передавать нужные данные для использования ресурса? Я предлагаю через конструктор. Т.е. конструктора у модели не будет и он наследуется у ресурса. Возможно не самое лучшее решение, но зато явное.

Почему я не представляю использование такого решения в нынешних фремворках? Потому-что архитектура не та и вынуждает пользователя в контроллере прописывать настройки для ресурса.

P.S. В моем приложении, например, контроллеры принимают зависимости через конструктор и создаются вручную без использования автозагрузчика в точке входа — файле index.php. Контроллер может потребовать модель в качестве зависимости и я могу передать ее. Т.к. модель будет создавать в корневом файле, где есть прямой доступ к настройкам, то проблемы их передачи в ресурс я не вижу.

Спасибо за внимание.

P.S.S. все выше изложенное мое субъективное мнение и взгляд на этот вопрос основанный на личном опыте работы с фреймворками и т.д.

★★

Не люблю я все эти фреймворки, которые навязывают свои подходы. Вот, как делаю я.

Если в двух словах, то есть например Post\Storage, который общается с MySQL базой через PDO, есть Post\Service, в котором находится вся необходимая логика. В текущем случае используется JSON-RPC. Всё, что мне нужно — подключить эти сервисы к библиотеке. Когда захочу превратить сервисы в контроллёры, мне будет достаточно поправить конфиг для маршрутизатора. Решение возможно далеко не идеальное, но достаточно гибкое и в тоже время простое.

Смена фреймворка, как мне кажется, будет не такой болезненной. А вот если захочу сменить MySQL на что-либо другое, то возможно придётся немного повозиться (хотя тут нет ничего сложного), но до такого лучше вообще не доводить, а сразу выбирать то, что лучше всего подходит для задачи.

Kilte ★★★★★ ()

не очень ясно, в чём вопрос

всё, что не относится к бизнес-логике и отвечающее за уровень представления (в идеале «плоский вывод данных») - , относится в большей степени к уровню модели

anonymous ()

Встречные рассуждения по вопросу.

MVC с моей точки зрения нужно для двух целей — уменьшения хаоса в коде и возможности гибко сменить любую из этих трех частей , в особенности модель и представление не переписывая весь код.

Далее, для уменьшения хаоса и одновременно для повышения гибкости разработки придумали такую штуку как слабая связанность( так же смотри Dependency Injection).

Валидация может тоже поменяться в любой момент и может быть не связанна жестко с данными, поэтому возможно валидацию стоит сделать отдельным объектом, который передавать через коструктор или сеттер.

Model.addVallidator(MyValidatorInterface Validator){.....}

Jaberwock ★★★ ()
Ответ на: комментарий от Jaberwock

В таком случае можно сделать два интерфейса - для ресурса и для валидации. Принимать их реализации как параметры в конструктор модели. Со стороны полиформизма наследование несвязанной сущности неверный ход, поэтому в этом плане это решение будет лучше (я о наследовании моделью класса ресурса).

Razip ★★ ()
Ответ на: комментарий от Razip

Еще ты жестко связываешься с базой данных. Возможно стоит обернуть запросы в функции, Это даст возможность протестировать поведения приложения на тестовых данных вообще без использования базы данных

Jaberwock ★★★ ()
Ответ на: комментарий от Jaberwock

Разве? Модель обращается к ресурсу через единый интерфейс Resource. Можно легко переключаться между его реализациями, будь то файлы, будь то базы данных и т.д.

Razip ★★ ()

Почему я не представляю использование такого решения в нынешних фремворках? Потому-что архитектура не та и вынуждает пользователя в контроллере прописывать настройки для ресурса.

Я у себя практикую такой подход. Реализован не всюду до конца, всё же, 15 лет развития тянут тонну legacy и я сторонник принципа «преждевременная оптимизация — зло». Но в целом — так:

— Есть базовый класс, описывающий унифицированную работу с данными/интерфейсами/сеттерами/геттерами. Исторически сложилось так, что инициацией и конфигурированием занимается не конструктор, а отдельные методы, которые вызываются класс-лоадером. Класс может быть написан на PHP (и с недавних пор метод совместим с Composer), может быть написан на YAML (при вызове компилируется в PHP). В принципе, достаточно примитивный класс, но исторически тянет в себе много мусора. Понемногу перевожу в аналоги DI с подключением нужных зависимостей, но это процесс долгий и не всюду однозначный. А пока там «внутре» даже такие монстрообразные решения, как формирование человеко-читаемого вида классов с автоматизированным склонением на русском (достаточно задать class_title='Пользователь', чтобы в админках появились «Управление пользователями» или «Добавить пользователя»). Но это, повторюсь, вопросы больше legacy.

— Есть модели. Собственно, это описание формата данных и основных методов для работы с ними. Работа с бэкендом сюда не включена. В простейшем случае это просто перечисление имён полей в таблице БД или даже вообще одно только указание на таблицу (поля извлекутся из описания БД). Но может быть и весьма сложным, с указанием типов полей в базе, типов полей при обработке/редактировании, условий валидации... С одной стороны это немного нарушает чистоту MVC, с другой — я гонюсь не за шашечками, а за удобством разработки. И один из главных принципов, которые усвоил тут — избегать многократного повторения имён полей/свойств. А то один список в модели, другой — в форме редактирования, третий — в валидаторе. Переименовываешь в одном, надо переименовывать в других... Я стараюсь всё задать сразу в модели. Что-то в духе:

class lor_user extends lor_model
{
    function table_fields()
    {
        return [
            'id',
            'title' => 'username', // это когда свойство класса не равно полю таблицы
            'create_ts' => 'UNIX_TIMESTAMP(registered)',
            'group_id' => [ // расширенное описание
                'class' => 'lor_group',
            ],
        ];
    }
}

— Есть классы бэкендов (у меня исторически это «storage»). В модели указывается, с каким бэкендом она работает и бэкенд можно в идеале менять одной строчкой, не затрагивая модель целиком. Класс бэкенда занимается извлечением/парсингом данных и, опционально, их записью. Бэкенды бывают разнообразные, от mysql и oracle до markdown (объекты-«страницы» могут целиком описываться в текстовых плоских файлах)

— Есть классы представлений. В простейшем случае — вообще, одна заглушка для указания шаблона, откуда берутся данные. В сложном — довольно жирные классы, тянущие в себе уйму кода для подготовки данных для шаблонов. При чём исторически есть смешанные случаи, когда один класс описывает как модель, так и представление. Краткость записи провоцировала в своё время экономить на спичках :) Последующий опыт показал ущербность такого подхода, но такого legacy, написанного в 2006..2008гг ещё полно.

А вот чего нет в явном виде — это контроллеров. Я считаю, что это функция, которой и должен заниматься сам фреймворк. Поэтому контроллеры размазаны по всей структуре в целом. Моё дело, как разработчика прикладного проекта, в точке входа вызвать инициацию фреймворка. Всё. Дальше он всё делает сам. Найдёт карты роутинга или автоматически роутящийся класс. По найденному представлению загрузит необходимые для представления объекты. Проведёт все шаблонные обработки, если нужно и, если нужно, закеширует результаты.

То есть при разработке какого-то компонента мне обычно достаточно указать модель и описать представление. Всё. Их взаимодействие и прочее — «Machines should work; people should think» © IBM

Например, представление может состоять из:
— user.php — собственно, класса, который может быть пустышкой только для указания наличия сущности
— user.tpl — шаблона тела представления (тип может быть любой, реально я поддерживаю/развиваю Smarty, чистый PHP и HAML)
— user.inc.css — дополнительные CSS-элементы для конечной страницы встраивающиеся в результат
— user.inc.js — аналогично для JS.

Вместо того, чтобы связывать это всё вручную, я поручил заниматься этим фреймворку. Он загрузит user.php и выполнит методы, генерирующие данные. Скормит их шаблонизатору вместе с user.tpl и получит html основного тела страницы. Это тело потом скормит общему генератору темы, типовому шаблону страницы (хедеры/футеры/меню и т.п.), добавив user.inc.css и user.inc.js. И уже готовую страницу отдаст браузеру пользователя.

В последний год ковыряю 2-ю версию фреймворка, которая изначально задумывалась полностью несовместимой, но сейчас понемногу свожу в один. На чём имею много внутренних идеологических проблем, над которыми понемногу и ломаю голову. Основным отличием второй версии должны были стать «без-NPE-шная логика», т.е. никакой метод не должен возвращать NULL. И цепочки вызовов методов можно писать без лишних проверок. В этом основная проблема несовместимости со старым кодом. Плюс к этому — работа с Composer, но это в итоге появилось и в первой версии. Плюс переделка логики загрузки объектов из бэкендов и переход от множества процедурных вызовов к объектным. Также планировался полный рефакторинг с целью выноса из базовых классов всего legacy, в итоге это привело к тому, что в composer-зависимостях сейчас вторая версия тащит целиком фреймворк первой :) Ну да буду чистить ещё...

KRoN73 ★★★★★ ()
Последнее исправление: KRoN73 (всего исправлений: 1)
Ответ на: комментарий от Kilte

Буээ. Так красивее гораздо

В первоначальном варианте автолоадера так и было :) Но исторически мне snake_style больше нравится, чем CamelCase. Кроме того, лет 10 назад преобразование str_replace('_', '/', $class_name) для автозагрузки работало заметно быстрее, чем preg_replace('/[a-z][A-Z]/'...), а тогда такие мелочи нужно было учитывать. Но, главное, всё же, именно личные предпочтения. Меня в бытность Java-разработки страшно достали неоднозначности CamelCase в отношении аббревиатур. Вот LorUser или LORUser? DoRest или DoREST?

и соответствует PSR ^_^

Увы, моя система именования куда старше :) Хотя сейчас подумываю на тему совместимости с PSR, но в смысле namespaces.

KRoN73 ★★★★★ ()
Ответ на: комментарий от KRoN73

неоднозначности CamelCase в отношении аббревиатур.

Не знаю, как в java, а в питоне рекомендуется писать аббревиатуры в верхнем регистре. В пыхе же наоборот, если следовать PSR.

Kilte ★★★★★ ()
Ответ на: комментарий от Kilte

в питоне рекомендуется писать аббревиатуры в верхнем регистре. В пыхе же наоборот

Вот эти все неоднозначности и задалбывают :) В Java, например, то так пишут, то эдак, в зависимости от популярности и читаемости аббревиатуры. Т.е. в моём примере будут LorUser и DoREST.

Это и довело меня до того, что перешёл на snake_style и стал писать всё в нижнем регистре :)

KRoN73 ★★★★★ ()
Последнее исправление: KRoN73 (всего исправлений: 1)
Ответ на: комментарий от Kilte

Буээ. Так красивее гораздо: LorUser, LorModel, tableFields() и соответствует PSR ^_^

лучше тогда-уж так:

<?php
namespace My\Controller;

use LOR\User;
use LOR\Model as LORModel;

DB::table()->fields();

anonymous ()
Ответ на: комментарий от Kilte

Тысячи их

Да, на лурке. И паттернов проектирования тоже много. Пойду еще один сочиню.

deep-purple ★★★★★ ()
Ответ на: комментарий от Kilte

Жесть )) Теперь придется еще итти и «жопа-уши-кодинг-стандарт» придумывать. Ато мало же их..

deep-purple ★★★★★ ()

Решил реализовать свою задумку и выяснить как на деле оно.

Сначала пытался абстрагироваться от ресурса в модели, но выходил DataMapper-велосипед. А DataMapper это просто очередной диалект для *SQL баз, записываемый либо PHP-методами, либо строкой (привет DQL). А мне хотелось чтобы модель была ресурсо-независимой.

Пока есть вот что:

<?php


class UserValidation {
	public function name($name) {
		// any validation algorithm
		return $name !== 'Razip';
	}
}

// contains shared interfaces
class Model {
	protected $validation;

	function __construct($validation) {
		// validation object is only used for
		// providing necessary validation methods
		$this->validation = $validation;
	}
}

class User extends Model {
	protected $id;

	protected $name;

	public function getName() {
		return $this->name;
	}

	public function setName($name) {
		$this->name = $name;

		return $this;
	}

	// you can optionally select what
	// you want to check
	public function validate() {
		return $this->validation->name($this->name);
	}
}

$user = new User(new UserValidation());

// razip@elementaryOS:~$ php poHaPe.php
// bool(false)
// bool(true)

var_dump($user->setName('Razip')->validate());
var_dump($user->setName('jopa-ushi')->validate());
Осталось написать набор стандартной валидации (по длине, для Email-адреса и т.д.), придумать способ сериализации для выгрузки в базу (допустим возвращать массив полей) и способ загрузки (предположительно выделенный метод).

Razip ★★ ()
Ответ на: комментарий от Razip

На уютном ЛОРе однако уютная подсветка, хочу такую в редактор. :3

Razip ★★ ()
Ответ на: комментарий от Razip

Валидация ведь это уже бизнес-логика и соответственно такая модель превращается как-бы в «тыкву-контроллер»

anonymous ()
Ответ на: комментарий от anonymous

Бизнес логику можно размещать как в контроллере, так и в модели. Каждый решает для себя сам. Бесконечная почва для спора.

Razip ★★ ()
<?php

class Model {
	public function import(array $fields) {
		foreach ($fields as $key => $value) {
			property_exists($this, $key) && $this->$key = $value;
		}
	}

	public function export() {
		return get_object_vars($this);
	}
}

class User extends Model {
	protected $id;

	protected $name;

	public function getId() {
		return $this->id;
	}

	public function setId($id) {
		$this->id = $id;

		return $this;
	}

	public function getName() {
		return $this->name;
	}

	public function setName($name) {
		$this->name = $name;

		return $this;
	}

	public function validate() {
		if (!is_int($this->id)) {
			return false;
		}

		if (!is_string($this->name)) {
			return false;
		}

		return true;
	}
}

$user = new User();

$user->import(['id' => 1, 'name' => 'Razip']);

var_dump($user);

Вот такой подход мне нравиться. Когда из модели не делают интерфейса для работы с ресурсом.

Razip ★★ ()
Последнее исправление: Razip (всего исправлений: 1)
Ответ на: комментарий от Razip

Можно еще добавить какой-нибудь интерфейс для гарантии существования методов validate, import и export.

Razip ★★ ()
Ответ на: комментарий от Razip

А не было мысли прикрутить какую-нибудь библиотеку для валидации, чтобы избавить себя от рутины и просто описывать правила?

Например:

<?php

class Validator
{
    private $rules;

    public function __construct(array $rules)
    {
        $this->rules = $rules;
    }
    public function validate(array $data)
    {
        // TODO: validate...
    }
}

interface HasRules
{
    /**
     * @return array
     */
    public static function getRules();
}

class ModelFactory
{
    /**
     * @var Validator[]
     */
    private $validators = [];

    public function forge($model, array $data)
    {
        if (!class_exists($model)) {
            throw new \InvalidArgumentException();
        }
        if (in_array(HasRules::class, class_implements($model))) {
            if (!array_key_exists($model, $this->validators)) {
                $this->validators[$model] = new Validator(call_user_func([$model, 'getRules']));
            }
            $this->validators[$model]->validate($data);
        }

        return new $model($data);
    }
}

class Model implements HasRules
{
    public static function getRules()
    {
        return [
            'name' => 'required',
            'email' => ['required', 'email'],
        ];
    }
}

(new ModelFactory())->forge(Model::class, ['name' => 'Foo', 'email' => 'foo@bar.baz']);

Kilte ★★★★★ ()
Последнее исправление: Kilte (всего исправлений: 2)
Ответ на: комментарий от Kilte

Я подумал над этим, и решил не использовать данное решение, поскольку: PHP содержит различные способы валидации (отпадает потребность в кастомных); мне не нравиться неявный интерфейс установки правил валидации.

Это просто мое имхо.

Razip ★★ ()
// декларация класса и другие методы опущены

    /**
     * @return string
     */
    public function login() {
        $JSONResponse = ['status' => 'success'];

        try {
            // имя может быть '0' например
            if (!isset($_POST['name'])) {
                throw new Exception('имя не должно быть пустым');
            }

            if (empty($_POST['password'])) {
                throw new Exception('пароль не должен быть пустым');
            }

            $loginFlag = $this->storage->get('loginFlag');

            $user = (new User())->setLoginFlag(is_null($loginFlag) ? false : $loginFlag);

            if ($user->isLoggedIn()) {
                throw new Exception('пользователь уже авторизован');
            }

            $userQuery = $this->PDO->prepare('SELECT * FROM `user` WHERE `name` = ? AND `password` = ? LIMIT 1');

            $userQuery->execute([$_POST['name'], $_POST['password']]);

            if ($userQuery->rowCount() === 0) {
                throw new Exception('имя и/или пароль указаны не верно');
            }

            $user->import($userQuery->fetch());

            if (!$user->isConfirmed()) {
                throw new Exception('пользователь не был подтвержден');
            }

            $user->setLoginFlag(true);

            // в сессию
            $this->storage->import($user->export());

            $JSONResponse['message'] = 'Вы успешно вошли.';
        } catch (Exception $e) {
            $JSONResponse['status'] = 'error';
            $JSONResponse['message'] = $e->getMessage();
        }

        header('Content-type: application/json');

        return json_encode($JSONResponse);
    }

Боевой код.

Razip ★★ ()
Ответ на: комментарий от Razip

Не думал, что скажу это, может я и эстет дофига, но этот код с запашком. Готовится к выходу PSR-7 и его реализация, уже имеется зарекомендовавший себя Symfony/HttpFoundation. Почему суперглобальные переменные?

И заголовки ты отправляешь сразу, но тело ответа возвращаешь. Всё-таки отправлять ответ лучше в одном месте, чем искать по всему коду, где же там ещё заголовки отдаются, а потом долго выяснять, почему тело ответа отправилось раньше заголовков.

Вот эти две строки стоит вынести куда-нибудь:

$userQuery = $this->PDO->prepare('SELECT * FROM `user` WHERE `name` = ? AND `password` = ? LIMIT 1');
$userQuery->execute([$_POST['name'], $_POST['password']]);

Kilte ★★★★★ ()
Ответ на: комментарий от Kilte

Ну да, конечно, мой код лютое г. :)

Жду поддержки языком объектного интерфейса для работы с HTTP. Обертки над функциями не люблю.

То что возвращается в методе контроллера - выводиться. Следовательно тело ответа не может отправиться раньше.

Куда вынести эти строчки? Чем они мешают? Почему тебе именно это не понравилось? Ведь в коде есть еще несколько строк работы с другим ресурсом - storage.

Razip ★★ ()
Ответ на: комментарий от Razip

Жду поддержки языком объектного интерфейса для работы с HTTP. Обертки над функциями не люблю.

Быстрее состаришься, чем они это запилят. Для начала PHP 7 будет уже не плохо дождаться.

То что возвращается в методе контроллера - выводиться. Следовательно тело ответа не может отправиться раньше.

Но заголовки могут быть отправлены позже и за этим надо следить. Вот если бы заголовки и тело отправлялись в одном месте, то тогда ещё более менее ок.

Ведь в коде есть еще несколько строк работы с другим ресурсом - storage.

Так почему не сделать аналогично storage, чтобы отвязаться от PDO и мешанины SQL запросов?

class UserStorage 
{
    public function find($name, $password) 
    {
         $statement = $this->pdo->prepare('SELECT * FROM `user` WHERE `name` = ? AND `password` = ? LIMIT 1');
         $statement->execute([$name, $password]);
         if ($statement->rowCount() === 0) {
             throw new NotFoundException();
         }
         return $statement->fetch();
    }
}
Kilte ★★★★★ ()
Ответ на: комментарий от Razip

Тут уже сам решай, как лучше будет. Главное, чтобы потом, если ты вдруг захочешь постгрес, не нужно было перелопачивать весь проект. Хотя это и с PDO вроде осуществимо, но всё же я бы не стал смешивать всё с SQL в одну кучу.

Kilte ★★★★★ ()

В общем идея разделения модели и ресурсов хорошо прижилась.

Класс-ресурс теперь может загружать в модель данные, дабы не делать этого в контроллере. Для этого достаточно передать модель через сеттер (по ссылке) в объект ресурса.

Теперь SessionStorage включен в ресурс UserResource (тот, который принадлежит модели UserModel), т.к. это такой же равноправный интерфейс для работы с данными как и PDO.

$user = (new UserModel())->setName($_POST['name'])->setPassword($_POST['password']);

$userResource = (new UserResource())->useModel($user);

if (!$userResource->findByNameAndPassword()) {
    throw new Exception('имя и/или пароль указаны не верно');
}

if (!$user->isConfirmed()) {
    throw new Exception('пользователь не был подтвержден');
}

$user->setLoginFlag(true);

$userResource->saveinSession();
Если раньше ресурс передавался через конструктор контроллера (из-за зависимостей), то теперь можно просто создавать новый экземпляр класса-ресурса прямо в нем! Как это удается? Вот так:
// где-то в точке входа программы
UserResource::setPDO($PDO);
UserResource::setSessionStorage(new FileSession());
Я сделал статические методы-сеттеры для зависимостей, хранящихся в статических свойствах. В конструкторе проверяется удовлетворение зависимостей. Дабы не делать такого:
/**
 * @param UserModel $currentUser
 * @param UserResource $userResource
 * @param CategoryResource $categoryResource
 */
function __construct(UserModel &$currentUser, UserResource &$userResource, CategoryResource &$categoryResource) {
    $this->currentUser = $currentUser;
    $this->userResource = $userResource;
    $this->categoryResource = $categoryResource;
}
И ведь это еще мало ресурсов используется! По логике контроллера там нужно штук 5 таких ресурсов.

Razip ★★ ()
Последнее исправление: Razip (всего исправлений: 2)
Ответ на: комментарий от Razip

Как-то всё это странно выглядит.

Конкретно, смутило вот что:

$user = (new UserModel())->setName($_POST['name'])->setPassword($_POST['password']);

$userResource = (new UserResource())->useModel($user);

if (!$userResource->findByNameAndPassword()) {
    throw new Exception('имя и/или пароль указаны не верно');
}

if (!$user->isConfirmed()) {
    throw new Exception('пользователь не был подтвержден');
}

В каком месте в модель загружаются данные из базы? При вызове useModel() или findByNameAndPassword()? Зачем каждый раз создавать экземпляр ресурса, если ему можно просто подсунуть новую модель? И что будет, если захочется использовать один экземпляр ресурса на всё приложение. А конкретно, я подсунул ему одну модель, поработал с ней и ресурсом, потом подставил дуругую модель уже в другом месте, но продолжаю думать, что работаю со старой моделью. Это будет очень сложно поддерживать, ИМХО. Словом, какая-то жуть.

Вот так получше будет http://code.re/7HV (Не отрицаю, писал с оглядкой на Symfony/Security) :3

Я сделал статические методы-сеттеры для зависимостей, хранящихся в статических свойствах. В конструкторе проверяется удовлетворение зависимостей.

Жуткий костыль. ИХМО. Если класс знает слишком много, то ты что-то делаешь не так.

Kilte ★★★★★ ()
Ответ на: комментарий от Kilte

По порядку.

В каком месте в модель загружаются данные из базы?

В findByNameAndPassword().

Зачем каждый раз создавать экземпляр ресурса, если ему можно просто подсунуть новую модель?

Честно говоря уже и не помню, ибо ночью кодил.

И что будет, если захочется использовать один экземпляр ресурса на всё приложение.

Абсолютно ничего плохого.

А конкретно, я подсунул ему одну модель, поработал с ней и ресурсом, потом подставил дуругую модель уже в другом месте, но продолжаю думать, что работаю со старой моделью.

Глупо так думать. :)

Жуткий костыль. ИХМО. Если класс знает слишком много, то ты что-то делаешь не так.

Эти свойства присущи этому классу, единственное отличие - я сделал их статическими.

Razip ★★ ()

Реализовал реестр (Registry шаблон) и храню в нем Resource'ы с Dependcy Injection через конструктор. Сам реестр имеет интерфейс реализованный на статических методах, поэтому я его не реализую у Resource'ов.

Razip ★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.