This is a mobile optimized page that loads fast, if you want to load the real page, click this text.

Урок Гайд на проектирование

MoonFusion

Гуру
Автор темы
high coder
14 Июн 2021
308
250
143


Мотивацией написать это руководство служит безграничное количество оупен-сорс/слитых проектов с процедурным, нечитаемым, ужасным кодом, по этому и начнём мы с этого.

Ниже я привёл несколько популярных вопросов и заблуждений связанных с темой.

Я думаю примерно такие мысли посещают умы людей которые пишут ресурсы/проекты процедурно, используя "модульный подход".
К сожалению модульный подход не решает все проблемы, он лишь обеспечивает читабельность кода и структурирует файлы проекта.

В чем конкретно поможет мне проектирование?
В первую очередь проектирование поможет найти дыры в логике, потенциальные проблемы с реализацией ещё до того как вы откроете вашу любимую среду разработки.
Вы сможете до мелочей продумать определённую систему, разбить все на конкретные классы и исключить повторение кода, что уже добавить вас +100 к эффективности разработки.
Так же это даст вам полный контроль над системой, упростится её поддержка и расширение, при условии что вы все сделали правильно.

Проектирование занимает слишком много времени
Я слышал это утверждение сразу от нескольких моих знакомых, "Проектировать на моём пет проекте/маленьком проекте не имеет смысла, я потрачу время зря".
Забавно но все работает в точности да наоборот, как раз таки проектирование поможет сохранить ваше время в будущем, потратив час-второй на то что бы расписать классы и их взаимодействие на draw.io сэкономит вам уйму времени в будущем.


Теперь же, после того как мы выяснили зачем нам это нужно, мы можем приступить к более подробному алгоритму действий, а так-же к наглядным примерам.

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


Что бы понять как все это работает мы сделаем все как обычно, без проектирования, просто сядем и напишем какую-то процедурную систему, которая будет выполнять одну простенькую задачу, а затем сядем и спроектируем эту же задачу, после чего снова реализуем её в коде, писать мы будем систему админ-авто, её описание ( ТЗ ) будет ниже.

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


Техническое задание:

Для начала сделаем базовую настройку проекта, я буду использовать NodeJS + TypeScript ( Server ) / JavaScript + TypeScript ( Client ) / Esbuild в качестве сборщика.
Разделим проект на Server/Client, сделаем npm i в каждой папочке, настроим Esbuild так, что бы он собирал все в один файлик в папку дист как для сервера так и для клиента.
Я не буду показывать как, все это вы сможете найти в github репозитории.

Далее я сделаю один костыль, я немного расширю базовый тип PlayerMp, добавив туда пару своих полей.
( не рекомендую это делать, рано или поздно вы запутаетесь в этом, но для примера - сойдёт. )



Посмотрим на структуру файлов:


Основной файл нашего модуля adminCar.ts, с него и начнём.

JavaScript:
// Время после выхода из авто, для его удаления
const TIMEOUT_TIME = 10000;

// Функция создающая админ-машину
export function createCar(player: PlayerMp, model: number): VehicleMp {
    const { x, y, z } = player.position;

    // Создаём машину рядом с игроком
    const vehicle = mp.vehicles.new(model, new mp.Vector3(x + 2, y, z), {
        dimension: player.dimension,
        heading: player.heading,
        numberPlate: 'JhonnyGay(ts)'
    });

    // Таймер - это костыль, что бы подождать пока машина появится
    setTimeout(() => {
        // Засовываем игрока на место водителя
        player.putIntoVehicle(vehicle, 0);
    }, 100);

    // Возвращаем авто
    return vehicle;
}

// Функция для уничтожения админ-машины
export function destroyCar(vehicle: VehicleMp, reason?: string) {
    // Проверяем есть ли такая машина на сервере
    if(!mp.vehicles.exists(vehicle)) {
        return
    }

    // Выкидываем всех игроков с машины
    removeOccupants(vehicle);

    // Удаляем Entity машины
    vehicle.destroy();

    // Дополнительно, выводим по какой причине была удалена машина
    if(reason) {
        console.log(`Машина удалена по причине: ${reason}`);
    }
}

// Функция для обработки выхода админа из машины
export function resetTimeout(player: PlayerMp, vehicle: VehicleMp) {
    // Проверяем что машина и игрок на сервере
    if(!mp.players.exists(player) || !mp.vehicles.exists(vehicle)) {
        return
    }

    // Запускаем таймер, и в хендлере удаляем авто
    const timeout = setTimeout(() => {
        destroyCar(vehicle, 'timeout');
        player.adminCar = null;
    }, TIMEOUT_TIME);

    // Возвращаем таймер
    return timeout;
}

// Функция для замены авто на другое
export function swapCar(player: PlayerMp, currentVehicle: VehicleMp, newVehicleModel: number): VehicleMp {
    if(mp.vehicles.exists(currentVehicle)) {
        removeOccupants(currentVehicle);
        destroyCar(currentVehicle, 'swapCar')
    }

    return createCar(player, newVehicleModel);
}

// Функция для починки авто
export function repairCar(vehicle: VehicleMp) {
    if(!mp.vehicles.exists(vehicle)) {
        return;
    }

    vehicle.repair();
}

// Функция для смены цвета авто
export function changeColor(vehicle: VehicleMp, color: RGB) {
    if(!mp.vehicles.exists(vehicle)) {
        return
    }

    vehicle.setColorRGB(color[0], color[1], color[2], color[0], color[1], color[2]);
}

// Утилити функция для высадки всех из машины
function removeOccupants(vehicle: VehicleMp) {
    if(vehicle.getOccupants().length > 0) {
        vehicle.getOccupants().forEach((player) => {
            if(mp.players.exists(player)) {
                player.removeFromVehicle();
            }
        });
    }
}

Далее нам нужно обрабатывать ивенты такие как игрок вышел с машину, что бы удалить машину, игрок вышел из игры, что бы опять же удалить машину, игрок зашел в игру, что бы подготовить его окружение к системе и игрок вошел обратно в транспорт, что бы сбросить таймер, все это мы обрабатываем в events.ts

JavaScript:
import { destroyCar, resetTimeout } from "./adminCar";

// Игрок зашел в игру
mp.events.add('playerJoin', (player: PlayerMp) => {
    // Подготовим игрока к системе
    player.isAdmin = true;
    player.adminCar = null;
    player.adminCarTimer = null;
});

// Игрок покинул транспорт
mp.events.add('playerExitVehicle', (player: PlayerMp, vehicle: VehicleMp) => {
    // Куча проверок, что бы не получить ошибки при исполнении
    if(!player.adminCar || player.adminCar.id !== vehicle.id) {
        return;
    }

    if(player.adminCarTimer !== null) {
        clearTimeout(player.adminCarTimer);
        player.adminCarTimer = null;
    }

    // Ресетим таймер модулем
    player.adminCarTimer = resetTimeout(player, vehicle);
});

// Игрок зашел в машину
mp.events.add('playerEnterVehicle', (player: PlayerMp, vehicle: VehicleMp) => {
    // Опять же, миллион проверок.
    if(!player.adminCar || player.adminCar.id !== vehicle.id) {
        return;
    }

    if(player.adminCarTimer === null) {
        return;
    }

    // Чистим таймер
    clearTimeout(player.adminCarTimer);
    player.adminCarTimer = null;
});

// Игрок вышел
mp.events.add('playerQuit', (player: PlayerMp) => {
    // Убираем машину
    if(player.adminCar !== null) {
        destroyCar(player.adminCar);
    }

    // Чистим таймер
    if(player.adminCarTimer !== null) {
        clearTimeout(player.adminCarTimer);
    }
});

Последний файл, commands.ts, задача файла принимать команды игрока, обрабатывать доступ и параметры, после чего делегировать логику на основной модуль ( adminCar.ts )

JavaScript:
import { changeColor, createCar, destroyCar, repairCar, swapCar } from "./adminCar";

mp.events.addCommand('admincar', (player: PlayerMp, fullText: string, model: string) => {
    // Куча проверок
    if(!player.isAdmin || player.adminCar !== null) {
        return;
    }

    // Получаем модель
    const modelHash = model ? mp.joaat(model) : null;

    if(modelHash === null) {
        return;
    }

    // Создаём машину с помощью основного модуля
    player.adminCar = createCar(player, modelHash);
});

// Удаляем машину, все аналогично
mp.events.addCommand('delcar', (player: PlayerMp, fullText: string) => {
    if(!player.isAdmin || player.adminCar === null) {
        return;
    }

    destroyCar(player.adminCar, 'delcar');

    if(player.adminCarTimer !== null) {
        clearTimeout(player.adminCarTimer);
        player.adminCarTimer = null;
    }

    player.adminCar = null;
});

// Меняем цвет машины
mp.events.addCommand('colorcar', (player: PlayerMp, fullText: string, red: string, green: string, blue: string) => {
    if(!player.isAdmin || player.adminCar === null) {
        return;
    }


    if(!red || !green || !blue) {
        return;
    }

    const rgb: RGB = [parseInt(red), parseInt(green), parseInt(blue)];
    changeColor(player.adminCar, rgb);
});

// Меняем машину
mp.events.addCommand('swapcar', (player: PlayerMp, fullText: string, newModel: string) => {
    if(!player.isAdmin || player.adminCar === null) {
        return;
    }

    const modelHash = newModel ? mp.joaat(newModel) : null;

    if(modelHash === null) {
        return;
    }

    player.adminCar = swapCar(player, player.adminCar, modelHash);
});

// Чиним машину
mp.events.addCommand('repair', (player: PlayerMp, fullText: string) => {
    if(!player.isAdmin || player.adminCar === null) {
        return;
    }

    repairCar(player.adminCar);
});

Подведём итоги нашей реализации.

Минусы:

  • Низкая читабельность кода, причём чем больше сюда будет добавляться методов, тем менее читабельнее будет код.
  • Трудно расширять, что бы что то добавить нужно писать ещё одну функцию, причем чем больше мы добавляем, тем сложнее добавлять, а что бы к примеру добавить уровни к админке и и сделать все эти команды с определённого уровня нужно будет залезть в каждую команду и поменять условия доступа, а что если нужно будет ввести авторизацию для администратора? Теперь нам нужно опять идти по всем командам связанным с системой и добавлять ещё одну проверку.
  • Низкая эффективность, исходя из пунктов выше вытекает этот, что бы расширять систему, её нужно читать, а у нас проблемы и с тем и с другим.
  • Контроль над кодом, у нас есть плавающие значения по типу player.adminCar и player.adminCarTimer, которые нужно постоянно перепроверять.

Плюсы:

  • Модульная структура файлов
  • Быстрая реализация
  • TypeScript

А теперь попробуем спроектировать эту систему, используя объектно ориентированный подход

Для начала немного объясню что мы будем использовать внутри сервиса, для удобства я создаю белый прямоугольник на фоне и блокирую его с помощью контекстного меню, почему то меня раздражает базовая сетка. Далее обратим внимание на блок слева, там нам в основном понадобится раздел UML.



Начать стоит с разделение нашей системы на сущности в нашем примере будет логично создать три сущности: Admin, AdminHandler и AdminVehicle, причем в нашем варианте реализации мы будем использовать агрегацию.

Создадим две сущности, в Admin добавим поля: id: number, player: PlayerMp, vehicle: AdminVehicle, как видим вот и агрегация, наш Admin в определённый момент времени будет содержать ссылку на объект AdminVehicle, но при это AdminVehicle остаётся отдельной сущностью, далее опишем некоторые методы, так-как наш класс Admin в данном случае выступает классом-ацессором, который будет выполнять всего две функции, проверять доступ к командам и предоставлять доступ к AdminVehicle, тут будет всего четыре метода:
JavaScript:
hasAccess(), createVehicle(mode: Hash), onPlayerLeave() и onVehicleDestroy()

В AdminVehicle добавим следующие поля:
JavaScript:
vehicleEntity: VehicleMp, destroyTimer: ReturnType<typeof setTimeout> model: Hash, color: RGB
Как видим наша сущность машины даже не знает о существовании игрока, собственно нам это и не нужно.

Так же добавим методы для обработки некоторых действий, а именно:
JavaScript:
init(player: PlayerMp), destroy(), changeColor(color: RGB), swapModel(newModel: Hash), onDriverExit(), onDriverEnter(), removeOccupants(), checkVehicle(), resetTimer(), repair()
Все эти методы нужны для обработки определённых событий, но каких событий?



А теперь подумаем о том, как же нам получить доступ к классу Admin? Откуда мы будем у какого игрока есть сущность Admin, а у какого нет?


По этому мы пойдём по второму пути, мы создадим статический класс AdminHandler, который будет создавать, хранить, удалять и отдавать экземпляры.
Давайте посмотрим какие у него будут поля и методы, начнём с поля, тут есть одна очень распространённая ошибка делать storage статическим полем класса, таким образом мы сможем изменять этот сторедж с любого места программы, что нам не подходит, почему? Думаю объяснять не нужно. Тут есть много всяких вариантов и рекомендаций, но проще всего - создать переменную чуть выше класса, и не экспортировать её, что позволит нам иметь доступ к ней только внутри файла, чего нам будет достаточно.

JavaScript:
const storage = new Map<number, Admin>();
let idGenerator = 0;


Методы нашего статического класса:
JavaScript:
create(player: PlayerMp), getByPlayer(player: PlayerMp), remove(id: number).

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



Выглядит неплохо, я потратил на это от силы минут 10, теперь же давайте напишем код


Реализация в отличии от предыдущей будет опираясь на парадигму ООП, с использованием принципов SOLID, DRY и так далее.

Рассмотрим файлы, структура получилась следующая:


Admin.ts

JavaScript:
import { AdminVehicle } from "./AdminVehicle";

export class Admin {
    public readonly id: number;
    private readonly _player: PlayerMp;
    private _adminVehicle: AdminVehicle;


    public get player() : PlayerMp {
        return this._player;
    }

    public get adminVehicle() : AdminVehicle {
        return this._adminVehicle;
    }

    constructor(id: number, player: PlayerMp) {
        this.id = id;
        this._player = player;
        this._adminVehicle = null;
    }

    hasAccess(): boolean {
        if(this._adminVehicle === null) {
            return false;
        }

        // Сколько угодно различных проверок
        return true;
    }

    createVehicle(model: number) {
        if(this._adminVehicle !== null) {
            return;
        }

        this._adminVehicle = new AdminVehicle(model, [0, 0, 0]);
        this._adminVehicle.init(this.player);
    }

    onPlayerLeave() {
        this._adminVehicle.destroy();
    }

    onVehicleDestroy() {
        this._adminVehicle = null;
    }
}

AdminHandler.ts

JavaScript:
import { Admin } from "./Admin";
import { AdminVehicle } from "./AdminVehicle";

const storage = new Map<number, Admin>();
let idGenerator = 0;

export class AdminHandler {
    static create(player: PlayerMp) {
        if(this.getByPlayer(player)) {
            return;
        }

        const id = idGenerator++;
        const admin = new Admin(id, player);

        storage.set(id, admin);
    }

    static getByPlayer(player: PlayerMp) {
        return [...storage.values()].find((item) => item.player.id === player.id);
    }

    static getByAdminVehicle(adminVehicle: AdminVehicle) {
        return [...storage.values()].find((item) => item.adminVehicle.vehicleEntity.id === adminVehicle.vehicleEntity.id);
    }

    static remove(id: number) {
        if(storage.has(id)) {
            storage.delete(id);
        }
    }
}

AdminVehicle.ts
JavaScript:
import { AdminHandler } from "./AdminHandler";

const DESTROY_TIMER = 10000;

export class AdminVehicle {
    private _vehicleEntity: VehicleMp | null;
    private destroyTimer: ReturnType<typeof setTimeout> | null;
    private model: number;
    private color: RGB;

    public get vehicleEntity(): VehicleMp {
        return this._vehicleEntity;
    }

    constructor(model: number, color: RGB) {
        this.model = model;
        this.color = color;

        this.destroyTimer = null;
        this._vehicleEntity = null;
    }

    init(player: PlayerMp) {
        const { x, y, z } = player.position;

        this._vehicleEntity = mp.vehicles.new(this.model, new mp.Vector3(x + 2, y, z), {
            dimension: player.dimension,
            color: [this.color, this.color]
        });

        setTimeout(() => {
            player.putIntoVehicle(this._vehicleEntity, 0);
        }, 100);
    }

    changeColor(newColor: RGB) {
        if(!this.checkVehicle()) {
            return;
        }

        this.color = newColor;
        this._vehicleEntity.setColorRGB(this.color[0], this.color[1], this.color[2], this.color[0], this.color[1], this.color[2]);
    }

    swapModel(player: PlayerMp, newModel: number) {
        if(!this.checkVehicle()) {
            return
        }

        this.model = newModel;
        this.destroy();
        this.init(player);
    }

    onDriverExit() {
        if(!this.checkVehicle()) {
            return;
        }

        this.resetTimer();
        this.destroyTimer = setTimeout(() => {
            this.destroy();
        }, DESTROY_TIMER);
    }

    onDriverEnter() {
        if(!this.checkVehicle) {
            return;
        }

        this.resetTimer();
    }

    repair() {
        if(!this.checkVehicle()) {
            return;
        }

        this._vehicleEntity.repair();
    }

    private removeOccupants() {
        if(!this.checkVehicle()) {
            return;
        }

        const occupants = this._vehicleEntity.getOccupants();

        if(occupants.length > 0) {
            occupants.forEach((player) => {
                player.removeFromVehicle();
            });
        }
    }

    private checkVehicle() {
        if(this._vehicleEntity === null) {
            return false;
        }
      
        if(!mp.vehicles.exists(this._vehicleEntity)) {
            return false
        }
      
        return true;
    }

    private resetTimer() {
        if(this.destroyTimer !== null) {
            clearTimeout(this.destroyTimer);
            this.destroyTimer = null;
        }
    }

    destroy() {
        this.resetTimer();

        if(mp.vehicles.exists(this._vehicleEntity)) {
            this.removeOccupants();
            this._vehicleEntity.destroy();
        }

        const admin = AdminHandler.getByAdminVehicle(this);
        admin.onVehicleDestroy();
    }
}

commands.ts
JavaScript:
import { AdminHandler } from "./AdminHandler";

mp.events.addCommand('admincar', (player: PlayerMp, fullText: string, model: string) => {
    const admin = AdminHandler.getByPlayer(player);

    if(!admin || !admin.hasAccess()) {
        return;
    }

    const modelHash = model ? mp.joaat(model) : null;

    if(modelHash === null) {
        return;
    }

    admin.createVehicle(modelHash);
});

// Удаляем машину, все аналогично
mp.events.addCommand('delcar', (player: PlayerMp, fullText: string) => {
    const admin = AdminHandler.getByPlayer(player);

    if(!admin || !admin.hasAccess()) {
        return;
    }

    admin.adminVehicle.destroy();
});

// Меняем цвет машины
mp.events.addCommand('colorcar', (player: PlayerMp, fullText: string, red: string, green: string, blue: string) => {
    const admin = AdminHandler.getByPlayer(player);

    if(!admin || !admin.hasAccess()) {
        return;
    }

    const rgb: RGB = [parseInt(red), parseInt(green), parseInt(blue)];
    admin.adminVehicle.changeColor(rgb);
});

// Меняем машину
mp.events.addCommand('swapcar', (player: PlayerMp, fullText: string, newModel: string) => {
    const admin = AdminHandler.getByPlayer(player);

    if(!admin || !admin.hasAccess()) {
        return;
    }

    const modelHash = newModel ? mp.joaat(newModel) : null;

    if(modelHash === null) {
        return;
    }

    admin.adminVehicle.swapModel(player, modelHash);
});

// Чиним машину
mp.events.addCommand('repair', (player: PlayerMp, fullText: string) => {
    const admin = AdminHandler.getByPlayer(player);

    if(!admin || !admin.hasAccess()) {
        return;
    }

    admin.adminVehicle.repair();
});

controller.ts
JavaScript:
import { AdminHandler } from "./AdminHandler";

mp.events.add('playerJoin', (player: PlayerMp) => {
    AdminHandler.create(player);
});

mp.events.add('playerQuit', (player: PlayerMp) => {
    const admin = AdminHandler.getByPlayer(player);

    if(admin) {
        admin.onPlayerLeave();
        AdminHandler.remove(admin.id);
    }
});

mp.events.add('onPlayerExitVehicle', (player: PlayerMp, vehicle: VehicleMp) => {
    const admin = AdminHandler.getByPlayer(player);

    if(admin || admin.hasAccess()) {
        if(vehicle.id === admin.adminVehicle.vehicleEntity.id) {
            admin.adminVehicle.onDriverExit();
        }
    }
});

mp.events.add('onPlayerEnterVehicle', (player: PlayerMp, vehicle: VehicleMp) => {
    const admin = AdminHandler.getByPlayer(player);

    if(admin || admin.hasAccess()) {
        if(vehicle.id === admin.adminVehicle.vehicleEntity.id) {
            admin.adminVehicle.onDriverEnter();
        }
    }
});


Подведём итоги этой реализации.

Минусы:

  • Нужно потратить время на проектирование
  • Сложное взаимодействие классов в месте, где происходит удаление/создание авто

Плюсы:

  • Высокая читабельность кода, все разбито на сущности
  • Объектно ориентированный подход
  • Легко расширять систему
  • Относительно просто дебажить
Спасибо что дочитал до конца, напомню, прежде чем писать гневный ответ, перечитай самое начало темы.
Делюсь всеми ресурсами:

GitHub репозиторий: ( будет добавлен если попросите )
Draw.io диаграмма:
Тык
 
Последнее редактирование:

Harland David Sanders

Куратор портала
Команда форума
Куратор портала
VIP
high coder
media
10 Сен 2020
3,082
2,477
219

wholinc

Специалист
24 Янв 2023
784
460
96
24
+rep за работу
 

kirillzver

Гуру
2 Ноя 2020
156
117
104
Как для нынешнего комьюнити разработчиков на нашей любимой платформе, урок более чем исчерпывающий.

Немного бросается в глаза история с множеством mp.events.addCommand, их можно было впихнуть объектом в один вызов addCommand, плюс организовав функции в класс и/или добавив декоратор для того, чтобы абстрагироваться от одинаковой проверки на админа и его доступ.
Также, не стоит забывать про обработку ошибок с помощью try catch и что не менее важно — проверять наличие сущности (mp.pool.exists) перед первым взаимодействием с ней.
По большей части это касается игроков, так как рейдж любитель выбросить сегу. Хоть статья не об этом, но это достаточно важно.

В общем и целом, может хоть кто-то подчеркнёт для себя что-то новое и в сливах мы будет видеть приемлемый код, хотя бы на уровне слитого драйва/стрита.
 

MoonFusion

Гуру
Автор темы
high coder
14 Июн 2021
308
250
143
Отличное дополнение.
 
Реакции: kirillzver

MiLT

Начинающий специалист
2 Янв 2023
59
45
47

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

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

MoonFusion

Гуру
Автор темы
high coder
14 Июн 2021
308
250
143
Второй части не будет, ты меня оскорбил..
 
Реакции: MaxFanti

Amazingevich

Гуру
27 Апр 2021
671
490
124
Я не буду показывать как, все это вы сможете найти в github репозитории.
GitHub репозиторий: ( будет добавлен если попросите )
Ультрамегахарош

В целом полезно и хорошо расписано, но применять в здравом смысле это мало кто будет (если конечно у разработчика проектирование не привито изначально). Почему? Потому что каждый дрочит как он хочет и при разработке хороших (RAGE) проектов на это смотрят в последний момент. Именно поэтому мы все удивляемся говнокоду, когда сливается очередной большой проект в публичный доступ (а это происходит, потому что задача №1 была не подготовить понятный для другого разработчика код, а побыстрее написать свою задумку и открыть проект).

Если смотреть за пределы рейджа - всё верно @MoonFusion красавчик
 

MoonFusion

Гуру
Автор темы
high coder
14 Июн 2021
308
250
143
На счёт гита, кринжово вышло, я на одном дыхании написал статью, а потом заливал это на гит, да так залил, что случайно шифт делитнул весь проект, но файлы описаны в теме, по этому собрать заново не проблема, вопрос лишь в том на сколько это востребовано
 
Реакции: Amazingevich

JJIGolem

Гуру
high coder
19 Окт 2020
218
277
142
Противоречие, так как в методы init и swapModel всё же передается игрок, хотя можно было бы обойтись и без этого. Плюс не совсем нравится то, что машина порождает себя и сажает игрока в себя же.

ошибка делать storage статическим полем класса

таким образом мы сможем изменять этот сторедж с любого места программы
Почему это ошибка? Разве нельзя сделать поле приватным, только для чтения и пользоваться им внутри самого класса? Если поле приватное, им никто другой, кроме твоего класса и не воспользуется.

AdminHandler, который будет создавать, хранить, удалять и отдавать экземпляры
Больше похоже на Repository, а не Handler, но это дело наименования.

опираясь на парадигму ООП, с использованием принципов SOLID, DRY
Не могли бы Вы, уточнить в каких местах мы использовали какой-то из принципов SOLID?

Согласен с этим. Поэтому и стало интересно, где же это мы принципы SOLID использовали
 

MoonFusion

Гуру
Автор темы
high coder
14 Июн 2021
308
250
143

Так, ну разберём тобою сказанное.

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

Приватное поле в статическом классе? Он на то и статический, что бы не создавать его экземпляр.

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

А так, спасибо за внимание к теме и её изучение.
 

MoonFusion

Гуру
Автор темы
high coder
14 Июн 2021
308
250
143
Повнимательнее изучил вопрос с приватным статическим методом, оказывается TypeScript поддерживает такое, о чем я к сожалению не знал, да, в таком случае можно использовать статический приватный метод.
 

JJIGolem

Гуру
high coder
19 Окт 2020
218
277
142
Имелось ввиду, раз для новичков готовится гайд, в котором мы упоминаем такое (SOLID), то лучше проводить эту параллель, как бы было не используя мы этот принцип, и как используя. Если мы это не подчеркиваем никак хотя бы, то лучше такое не упоминать.

S - удаление авто стучится в Handler, чтобы взять админа, к которому привязан, чтобы сообщить ему о том, что мы себя удалили. Все равно не избавились от проблемы, что используем админа и к нему привязаны. Также когда сажаем того же игрока при создании сущности авто в себя.
Не лучше ли, чтобы какой-то отдельный класс полностью взял на себя ответственность за удаление, создание авто и посадку в авто. То машина у нас стучится в админа, то админ в машину.

O - если мы захотим создать несколько тачек для админа, то натыкаемся на проблему, что придется переписать практически все 3 класса.
 
Реакции: Inoi и MoonFusion

MaxFanti

Активный участник
5 Апр 2022
3
1
43
Автор , ты молодец, что решил затронуть такую важную тему! Естественно, небольшие косяки в коде имеются, о которых говорили выше, но он все равно данный код является прекрасным примером для наглядного изучения.