Исходник Гайд "Чистый код" в читах. SOLID + GRASP + GoF patterns

эксперт в майнкрафт апи
Read Only
Статус
Оффлайн
Регистрация
25 Янв 2023
Сообщения
684
Реакции[?]
286
Поинты[?]
21K
Всем привет!
На протяжении длительного времени я изучал парадигму "чистого кода/архитектуры приложения" и готов показать некий пример, возможно, заинтересовав кого либо из тех, кто будет это читать
Язык на котором будет писаться код - Java, но никакие трудные фьючеры джавы не будут использованы.по моим меркам джаву в данной статье можно считать псевдоязыком понятным любому.в не совсем очевидных местах оставил комментарии.повторить данные трюки можно в любом ОО языке.
Глава 0: кому это нужно?
это пригодиться тем, кто пишет код с друзьями/просто хочет улучшить качество своего кода.
на практике в проектах которые не будут жить больше нескольких месяцев нет смысла все это использовать.оверхед.
Глава 1: вступление.
Начну статью с того, что же мы разберем.
Мы пробежимся по всем 5 принципам SOLID, применим несколько паттернов программирования из GoF
В статье я сначала напишу "плохой код", а потом мы вместе разберем что же там не так с точки зрения SOLID + перепишем его по правилам SOLID и применим паттерны программирования.
Все будет основано на примерах.
Lets go
Глава 2: обзор случая.
Нам понадобилось сохранение конфигов в нашем чите.(IO компонент)
(Случай взят из воздуха.Он субъективно нравится мне для описания проблем, их решения.)
Глава 3: написание "плохого кода".​
Java:
package ru.metafaze.io;

import java.io.InputStream;
import java.io.OutputStream;

public class InputOutputStream {
    private final InputStream input;
    private final OutputStream output;

    public InputOutputStream(InputStream input, OutputStream output) {
        this.input = input;
        this.output = output;
    }

    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }

    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int readShort() {
        return readByte() | readByte() << 8;
    }

    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Вдруг, вас осинило...Значения все таки лучше зашифровать от дядей Эдиков...
Ну, что ж поделать, добавим в этот же класс xor предварительно переименовав InputOutputStream в XorInputOutputStream.​
Java:
package ru.metafaze.io;

import java.io.InputStream;
import java.io.OutputStream;

public class XorInputOutputStream {
    private final InputStream input;
    private final OutputStream output;
    private final int xorKey;

    public XorInputOutputStream(InputStream input, OutputStream output, int xorKey) {
        this.input = input;
        this.output = output;
        this.xorKey = xorKey;
    }

    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value ^ xorKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }

    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read() ^ xorKey;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int readShort() {
        return readByte() | readByte() << 8;
    }

    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Потом, вдруг, ты понял, что было бы неплохо добавить компрессию.
Но что мы получим в итоге?CompressedProtectedInputOutputStream?
Глава 4. в чем проблема данного подхода?
Проблем тут уже ОЧЕНЬ много.Дело в том, что ваш класс уже на данном этапе трансформирует числа в байты, шифрует их, компрессит(!).Это
1) усложняет в миллион раз анализ кода написанного внутри
2) делает абсолютно невозможным переиспользование.ваш проект будет разрастаться в размерах со скоростью света.вы будете городить один и тот же код под одинаковые задачи.
можно продолжать ещё очень долго, но давайте перейдем к SOLID.
Глава 5. SOLID - что это за хуйня?
SOLID - акроним который скрывает в себе 5 принципов которые расшифровываются как:
SRP - принцип единственной ответственности.(single responsibility principle)
OCP - принцип открытости для расширения, закрытости для изменения.(open-closed principle)
LSP - принцип подставления барбары лисков.(не бойтесь названия, это ирл никнейм бабушки)(Liskov substitution principle)
ISP - принцип разделения интерфейса.(interface segregation principle)
DIP - принцип инверсии зависимостей.(dependency inversion principle)
Глава 6. В чем тут нарушение принципов?Что они вообще из себя представляют?

SRP говорит о том, что ваш компонент должен иметь лишь одну ответственность.Как это понять?Роберт Мартин описывает это как одну причину для изменения.И ведь действительно
1) вдруг появится потребность в изменении шифра?
2) вдруг появится потребность в изменении трансформации числа в байты?
3) вдруг появится потребность в изменении компрессии?
Это абсолютно 3 не связанных между собой задачи которые и противоречат принципу единственной ответственности.
Тут важная ремарка, не стоит проводить декомпозицию до максимально возможного предела.Дробить пока дробиться - плохой подход.В адекватном случае в следствии "правильной ответственности" вы получите код который сможете переиспользовать.

Это самый легкий принцип(на первый взгляд, на самом деле он самый трудный), но он убивает миллион зайцев сразу :
ISP
(он затрагивает даже принципы GRASP)
High Cohesion(высокое сцепление) <- говорят что лучше не переводить этот принцип на русский язык.

OCP говорит о закрытости для изменения(старого кода) и открытости для расширения(старого кода).Т.е новую функциональность старым компонентам вы должны вводить наследованием, композицией и тд.
Не стоит брать этот принцип близко к сердцу, разумеется баги нужно фиксить изменением старого кода)

LSP - самый ебанутый принцип который к нам к тому же и не относится.
Его ебанутость заключается в том, что его название не отображает его смысл, отнюдь, он отображает фамилию бабушки которая его придумала.
Заключается он в том, что нельзя менять сигнатуру виртуальных методов при наследовании, добавлять исключения не описанные в начале иерархии.Можете не беспокоиться, хуй вам компилятор даст это сделать в ОО языках на которых пишут читы.
Так же этот принцип описывает то, что компоненты при наследовании должны уметь "встрять" за своего родственника по иерархии.Т.е если метод принимает Button, то MegaSuperPuperButton(extends Button) в любом случае должен корректно выполняться в том же методе.

ISP - разбивай свои интерфейсы по максимуму(также как с SRP.знайте грани декомпозиции).

DIP - с первого взгляда самый трудный и непонятный принцип.Вот его описание с wiki(в книге Роберта Мартина вроде как звучит как то похуже) :
  • A. Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  • B. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

На самом деле он самый легкий относительно его пользы.

Глава 6: Мы нарушили только SRP?
Нет. Мы нарушили все кроме принципа подставления Барбары Лисков.
OCP - мы ввели функциональность изменяя старый код.
ISP - наш класс имеет интерфейс записи, чтения.(относительно спорный момент в данном контексте, но в огромном проекте лучше не выебываться и все таки разбивать IO на две иерархии для переиспользования.Мало ли вашему второму другу кодеру понадобится только Reader?)
DIP - подход изначально был вне инверсии зависимостей.
Глава 7: Перепишем код.
Для начала поподробнее расскажу про DIP, он очень важный в построении любого компонента.Формулировка очень трудная, на самом деле все в разы легче, а импакта миллион.
Dependency qqqqqq111111 -(повторюсь)
  • A. Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  • B. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Уровень абстракции - уровень того, насколько близко лежит решение задачи.
Если бы это можно было б визуализировать, я б визуализировал так :
Уровень абстракции уменьшается с увеличением размера стека, где вершина стека всегда является самым низким уровнем абстракции по отношению к элементам которые распологаются ниже по стеку.
Если уж разъяснять на пальцах, то метод где вы вызываете writeInt - высокий уровень абстракции.Ведь вам совсем похуй что там происходит внутри, вы просто пишите несколько символов которые запускают огромную задачу.Для вас это все та же абстракция "writeInt".
То есть место где мы будем юзать наш IO(высокий уровень абстракции) не должен зависить от реализации интерфейса(относительно низкого уровня абстракции), отнюдь, они должны оба зависеть от интерфейса(абстракции).
Компонент A имея потребность в компоненте B описывает интерфейс который ему нужен.компонент B в свою очередь реализует его.Таким образом компонент A зависит от интерфейса, компонент B зависит от интерфейса, а не компонент A зависит от B.
Поэтому это и называют инверсией зависимостей.
Это избавляет нас от конкретной реализации и дает по полной программе насладиться полиморфизмом.
+ Low Coupling из GRASP в подарок.
Также реализуем ISP, разбивая Input и Output на отдельные иерархии.Это даст нам возможность переиспользования в других случаях.
Приступим.​
Java:
package ru.metafaze.io;

public interface BaseInputStream {
    int readByte();

    int readShort();

    int readInt();
}
Java:
package ru.metafaze.io;

public interface BaseOutputStream {
    void writeByte(int value);

    void writeShort(int value);

    void writeInt(int value);
}
Я предпочту делегирование для реализации компрессии, крипта.
Почему не наследование?Дело в том, что наследование в некоторых случаях(90%) - антипаттерн.Не буду много городить, в двух словах, нарушение инкапсуляции и ещё несколько проблем которые появляются в следствии наследования.Делегирование в данном контексте сыпит нам лишь плюсы которые мы сейчас и увидим.
Во первых имплементируем обычный Input/Output​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;

import java.io.InputStream;

public class InputStreamImpl implements BaseInputStream {
    private final InputStream input;

    public InputStreamImpl(InputStream input) {
        this.input = input;
    }

    @Override
    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int readShort() {
        return readByte() | readByte() << 8;
    }

    @Override
    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

import java.io.OutputStream;

public class OutputStreamImpl implements BaseOutputStream {
    private final OutputStream output;

    public OutputStreamImpl(OutputStream output) {
        this.output = output;
    }

    @Override
    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    @Override
    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }
}
Ну а теперь давайте к шифрованию.​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;

public class XorInputStream implements BaseInputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private final BaseInputStream input;
    private final int xorKey;

    public XorInputStream(BaseInputStream input, int xorKey) {
        this.input = input;
        this.xorKey = xorKey;
    }

    @Override
    public int readByte() {
        return xorAndApplyMask(input.readByte(), xorKey, BYTE_MASK);
    }

    @Override
    public int readShort() {
        return xorAndApplyMask(input.readShort(), xorKey, SHORT_MASK);
    }

    @Override
    public int readInt() {
        return xorAndApplyMask(input.readInt(), xorKey, INT_MASK);
    }

    private int xorAndApplyMask(int value, int xorKey, int mask) {
        return (value ^ xorKey) & mask;
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

public class XorOutputStream implements BaseOutputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private final BaseOutputStream output;
    private final int xorKey;

    public XorOutputStream(BaseOutputStream output, int xorKey) {
        this.output = output;
        this.xorKey = xorKey;
    }

    @Override
    public void writeByte(int value) {
        output.writeByte(value ^ xorKey);
    }

    @Override
    public void writeShort(int value) {
        output.writeShort(value ^ xorKey);
    }

    @Override
    public void writeInt(int value) {
        output.writeInt(value ^ xorKey);
    }
}
Но что на счет компрессии?
Компрессия в данном контексте - частный случай делегации, либо же паттерн под названием Decorator который расширяет функционал готового класса.
Нет смысла реализовывать интерфейс по очевидным причинам.​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;
import ru.metafaze.io.BaseOutputStream;

public class CompressedInputStream {
    private static final int BYTE_FINGERPRINT = 0b00000000;
    private static final int SHORT_FINGERPRINT = 0b10000000;
    private static final int INT_FINGERPRINT = 0b11000000;
    private final BaseInputStream input;

    public CompressedInputStream(BaseInputStream input) {
        this.input = input;
    }

    public int readCompressedInt() {
        int fingerPrint = input.readByte();
        return resolveReadMethodFromFingerPrintAndRead(fingerPrint);
    }

    private int resolveReadMethodFromFingerPrintAndRead(int fingerPrint) {
        switch (fingerPrint) {
            case BYTE_FINGERPRINT -> {
                return input.readByte();
            }
            case SHORT_FINGERPRINT -> {
                return input.readShort();
            }
            case INT_FINGERPRINT -> {
                return input.readInt();
            }
            default -> throw new IllegalArgumentException("unknown fingerprint " + Integer.toBinaryString(fingerPrint));
        }
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

public class CompressedOutputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private static final int BYTE_FINGERPRINT = 0b00000000;
    private static final int SHORT_FINGERPRINT = 0b10000000;
    private static final int INT_FINGERPRINT = 0b11000000;
    private final BaseOutputStream output;

    public CompressedOutputStream(BaseOutputStream output) {
        this.output = output;
    }

    public void writeCompressedInt(int value) {
        int fingerPrint = getFingerPrintFromValue(value);
        output.writeByte(fingerPrint);
        resolveWriteMethodByFingerPrintAndWriteValue(fingerPrint, value);
    }

    private void resolveWriteMethodByFingerPrintAndWriteValue(int fingerPrint, int value) {
        switch (fingerPrint) {
            case BYTE_FINGERPRINT -> output.writeByte(value);
            case SHORT_FINGERPRINT -> output.writeShort(value);
            case INT_FINGERPRINT -> output.writeInt(value);
        }
    }

    private int getFingerPrintFromValue(int value) {
        if ((value & BYTE_MASK) == value) {
            return BYTE_FINGERPRINT;
        }
        if ((value & SHORT_MASK) == value) {
            return SHORT_FINGERPRINT;
        }
        return INT_FINGERPRINT;
    }
}
В целом все.Теперь посмотрим на применения нашего кода.​
Java:
    @Test
    void globalTest() throws Exception {
        int xorKey = 0xAABBCCDD;
        Path filePath = Paths.get("file");
        BaseOutputStream fileOutput = new OutputStreamImpl(Files.newOutputStream(filePath));
        BaseOutputStream xorOutput = new XorOutputStream(fileOutput, xorKey);
        CompressedOutputStream compressedOutput = new CompressedOutputStream(xorOutput);
        compressedOutput.writeCompressedInt(3000);
    }
Тут видны плюсы делегации(объект может становится кем угодно в этапе исполнения программы.наследование разумеется так не может)
а так же видны и наши старания - теперь каждый компонент можно применять отдельно.
Но теперь встает опять вопрос...Видов реализаций так много, будет довольно трудно поддерживать пары input/output.
Давайте это исправим добавив в нашу систему паттерн AbstractFactory.
AbstractFactory позволяет нам описать интерфейс создания объектов связанных по смыслу, но имеющих разные "наборы" реализаций.​
Java:
package ru.metafaze.io;

public interface IOFactory {
    BaseInputStream createInputStream();

    BaseOutputStream createOutputStream();
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;
import ru.metafaze.io.BaseOutputStream;
import ru.metafaze.io.IOFactory;

public class XorIOFactory implements IOFactory {
    private final BaseInputStream input;
    private final BaseOutputStream output;
    private final int xorKey;

    public XorIOFactory(BaseInputStream input, BaseOutputStream output, int xorKey) {
        this.input = input;
        this.output = output;
        this.xorKey = xorKey;
    }

    @Override
    public BaseInputStream createInputStream() {
        return new XorInputStream(input, xorKey);
    }

    @Override
    public BaseOutputStream createOutputStream() {
        return new XorOutputStream(output, xorKey);
    }
}
Java:
        IOFactory ioFactory = new XorIOFactory(fileInput, fileOutput, xorKey);
        BaseInputStream xorInput = ioFactory.createInputStream();
        BaseOutputStream xorOutput = ioFactory.createOutputStream();
Спасибо что прочитали эту огромную статью, на сегодня все.Однозначно смотивирую вас разобраться в этом самостоятельно : то что описано в статье - 1% из того всего что я изучал на счет клинкода.
Помимо этого, добавлю, что вполне где то мог ошибиться/привести неуместный пример.Принимается любая критика.
P.S ко всем классам написаны юнит тесты.Если нужно, прикреплю как метаинформацию.
Bye
 
Последнее редактирование:
read only ambassador
Пользователь
Статус
Оффлайн
Регистрация
28 Июн 2022
Сообщения
627
Реакции[?]
105
Поинты[?]
9K
Всем привет!
На протяжении длительного времени я изучал парадигму "чистого кода/архитектуры приложения" и готов показать некий пример, возможно, заинтересовав кого либо из тех, кто будет это читать
Язык на котором будет писаться код - Java, но никакие трудные фьючеры джавы не будут использованы.по моим меркам джаву в данной статье можно считать псевдоязыком понятным любому.в не совсем очевидных местах оставил комментарии.повторить данные трюки можно в любом ОО языке.
Глава 0: кому это нужно?
это пригодиться тем, кто пишет код с друзьями/просто хочет улучшить качество своего кода.
на практике в проектах которые не будут жить больше нескольких месяцев нет смысла все это использовать.оверхед.
Глава 1: вступление.
Начну статью с того, что же мы разберем.
Мы пробежимся по всем 5 принципам SOLID, применим несколько паттернов программирования из GoF
В статье я сначала напишу "плохой код", а потом мы вместе разберем что же там не так с точки зрения SOLID + перепишем его по правилам SOLID и применим паттерны программирования.
Все будет основано на примерах.
Lets go
Глава 2: обзор случая.
Нам понадобилось сохранение конфигов в нашем чите.(IO компонент)
(Случай взят из воздуха.Он субъективно нравится мне для описания проблем, их решения.)
Глава 3: написание "плохого кода".​
Java:
package ru.metafaze.io;

import java.io.InputStream;
import java.io.OutputStream;

public class InputOutputStream {
    private final InputStream input;
    private final OutputStream output;

    public InputOutputStream(InputStream input, OutputStream output) {
        this.input = input;
        this.output = output;
    }

    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }

    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int readShort() {
        return readByte() | readByte() << 8;
    }

    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Вдруг, вас осинило...Значения все таки лучше зашифровать от дядей Эдиков...
Ну, что ж поделать, добавим в этот же класс xor предварительно переименовав InputOutputStream в XorInputOutputStream.​
Java:
package ru.metafaze.io;

import java.io.InputStream;
import java.io.OutputStream;

public class XorInputOutputStream {
    private final InputStream input;
    private final OutputStream output;
    private final int xorKey;

    public XorInputOutputStream(InputStream input, OutputStream output, int xorKey) {
        this.input = input;
        this.output = output;
        this.xorKey = xorKey;
    }

    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value ^ xorKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }

    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read() ^ xorKey;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int readShort() {
        return readByte() | readByte() << 8;
    }

    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Потом, вдруг, ты понял, что было бы неплохо добавить компрессию.
Но что мы получим в итоге?CompressedProtectedInputOutputStream?
Глава 4. в чем проблема данного подхода?
Проблем тут уже ОЧЕНЬ много.Дело в том, что ваш класс уже на данном этапе трансформирует числа в байты, шифрует их, компрессит(!).Это
1) усложняет в миллион раз анализ кода написанного внутри
2) делает абсолютно невозможным переиспользование.ваш проект будет разрастаться в размерах со скоростью света.вы будете городить один и тот же код под одинаковые задачи.
можно продолжать ещё очень долго, но давайте перейдем к SOLID.
Глава 5. SOLID - что это за хуйня?
SOLID - акроним который скрывает в себе 5 принципов которые расшифровываются как:
SRP - принцип единственной ответственности.(single responsibility principle)
OCP - принцип открытости для расширения, закрытости для изменения.(open-closed principle)
LSP - принцип подставления барбары лисков.(не бойтесь названия, это ирл никнейм бабушки)(Liskov substitution principle)
ISP - принцип разделения интерфейса.(interface segregation principle)
DIP - принцип инверсии зависимостей.(dependency inversion principle)
Глава 6. В чем тут нарушение принципов?Что они вообще из себя представляют?

SRP говорит о том, что ваш компонент должен иметь лишь одну ответственность.Как это понять?Роберт Мартин описывает это как одну причину для изменения.И ведь действительно
1) вдруг появится потребность в изменении шифра?
2) вдруг появится потребность в изменении трансформации числа в байты?
3) вдруг появится потребность в изменении компрессии?
Это абсолютно 3 не связанных между собой задачи которые и противоречат принципу единственной ответственности.
Тут важная ремарка, не стоит проводить декомпозицию до максимально возможного предела.Дробить пока дробиться - плохой подход.В адекватном случае в следствии "правильной ответственности" вы получите код который сможете переиспользовать.

Это самый легкий принцип(на первый взгляд, на самом деле он самый трудный), но он убивает миллион зайцев сразу :
ISP
(он затрагивает даже принципы GRASP)
High Cohesion(высокое сцепление) <- говорят что лучше не переводить этот принцип на русский язык.

OCP говорит о закрытости для изменения(старого кода) и открытости для расширения(старого кода).Т.е новую функциональность старым компонентам вы должны вводить наследованием, композицией и тд.
Не стоит брать этот принцип близко к сердцу, разумеется баги нужно фиксить изменением старого кода)

LSP - самый ебанутый принцип который к нам к тому же и не относится.
Его ебанутость заключается в том, что его название не отображает его смысл, отнюдь, он отображает фамилию бабушки которая его придумала.
Заключается он в том, что нельзя менять сигнатуру виртуальных методов при наследовании, добавлять исключения не описанные в начале иерархии.Можете не беспокоиться, хуй вам компилятор даст это сделать в ОО языках на которых пишут читы.
Так же этот принцип описывает то, что компоненты при наследовании должны уметь "встрять" за своего родственника по иерархии.Т.е если метод принимает Button, то MegaSuperPuperButton(extends Button) в любом случае должен корректно выполняться в том же методе.

ISP - разбивай свои интерфейсы по максимуму(также как с SRP.знайте грани декомпозиции).

DIP - с первого взгляда самый трудный и непонятный принцип.Вот его описание с wiki(в книге Роберта Мартина вроде как звучит как то похуже) :



На самом деле он самый легкий относительно его пользы.

Глава 6: Мы нарушили только SRP?
Нет. Мы нарушили все кроме принципа подставления Барбары Лисков.
OCP - мы ввели функциональность изменяя старый код.
ISP - наш класс имеет интерфейс записи, чтения.(относительно спорный момент в данном контексте, но в огромном проекте лучше не выебываться и все таки разбивать IO на две иерархии для переиспользования.Мало ли вашему второму другу кодеру понадобится только Reader?)
DIP - подход изначально был вне инверсии зависимостей.
Глава 7: Перепишем код.
Для начала поподробнее расскажу про DIP, он очень важный в построении любого компонента.Формулировка очень трудная, на самом деле все в разы легче, а импакта миллион.
Dependency qqqqqq111111 -(повторюсь)


Уровень абстракции - уровень того, насколько близко лежит решение задачи.
Если бы это можно было б визуализировать, я б визуализировал так :
Уровень абстракции нарастает с увеличением размера стека, где вершина стека всегда является самым низким уровнем абстракции по отношению к элементам которые распологаются ниже по стеку.
Если уж разъяснять на пальцах, то метод где вы вызываете writeInt - высокий уровень абстракции.Ведь вам совсем похуй что там происходит внутри, вы просто пишите несколько символов которые запускают огромную задачу.Для вас это все та же абстракция "writeInt".
То есть место где мы будем юзать наш IO(высокий уровень абстракции) не должен зависить от реализации интерфейса(относительно низкого уровня абстракции), отнюдь, они должны оба зависеть от интерфейса(абстракции).
Компонент A имея потребность в компоненте B описывает интерфейс который ему нужен.компонент B в свою очередь реализует его.Таким образом компонент A зависит от интерфейса, компонент B зависит от интерфейса, а не компонент A зависит от B.
Поэтому это и называют инверсией зависимостей.
Это избавляет нас от конкретной реализации и дает по полной программе насладиться полиморфизмом.
+ Low Coupling из GRASP в подарок.
Также реализуем ISP, разбивая Input и Output на отдельные иерархии.Это даст нам возможность переиспользования в других случаях.
Приступим.​
Java:
package ru.metafaze.io;

public interface BaseInputStream {
    int readByte();

    int readShort();

    int readInt();
}
Java:
package ru.metafaze.io;

public interface BaseOutputStream {
    void writeByte(int value);

    void writeShort(int value);

    void writeInt(int value);
}
Я предпочту делегирование для реализации компрессии, крипта.
Почему не наследование?Дело в том, что наследование в некоторых случаях(90%) - антипаттерн.Не буду много городить, в двух словах, нарушение инкапсуляции и ещё несколько проблем которые появляются в следствии наследования.Делегирование в данном контексте сыпит нам лишь плюсы которые мы сейчас и увидим.
Во первых имплементируем обычный Input/Output​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;

import java.io.InputStream;

public class InputStreamImpl implements BaseInputStream {
    private final InputStream input;

    public InputStreamImpl(InputStream input) {
        this.input = input;
    }

    @Override
    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int readShort() {
        return readByte() | readByte() << 8;
    }

    @Override
    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

import java.io.OutputStream;

public class OutputStreamImpl implements BaseOutputStream {
    private final OutputStream output;

    public OutputStreamImpl(OutputStream output) {
        this.output = output;
    }

    @Override
    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    @Override
    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }
}
Ну а теперь давайте к шифрованию.​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;

public class XorInputStream implements BaseInputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private final BaseInputStream input;
    private final int xorKey;

    public XorInputStream(BaseInputStream input, int xorKey) {
        this.input = input;
        this.xorKey = xorKey;
    }

    @Override
    public int readByte() {
        return xorAndApplyMask(input.readByte(), xorKey, BYTE_MASK);
    }

    @Override
    public int readShort() {
        return xorAndApplyMask(input.readShort(), xorKey, SHORT_MASK);
    }

    @Override
    public int readInt() {
        return xorAndApplyMask(input.readInt(), xorKey, INT_MASK);
    }

    private int xorAndApplyMask(int value, int xorKey, int mask) {
        return (value ^ xorKey) & mask;
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

public class XorOutputStream implements BaseOutputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private final BaseOutputStream output;
    private final int xorKey;

    public XorOutputStream(BaseOutputStream output, int xorKey) {
        this.output = output;
        this.xorKey = xorKey;
    }

    @Override
    public void writeByte(int value) {
        output.writeByte(value ^ xorKey);
    }

    @Override
    public void writeShort(int value) {
        output.writeShort(value ^ xorKey);
    }

    @Override
    public void writeInt(int value) {
        output.writeInt(value ^ xorKey);
    }
}
Но что на счет компрессии?
Компрессия в данном контексте - частный случай делегации, либо же паттерн под названием Decorator который расширяет функционал готового класса.
Нет смысла реализовывать интерфейс по очевидным причинам.​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;
import ru.metafaze.io.BaseOutputStream;

public class CompressedInputStream {
    private static final int BYTE_FINGERPRINT = 0b00000000;
    private static final int SHORT_FINGERPRINT = 0b10000000;
    private static final int INT_FINGERPRINT = 0b11000000;
    private final BaseInputStream input;

    public CompressedInputStream(BaseInputStream input) {
        this.input = input;
    }

    public int readCompressedInt() {
        int fingerPrint = input.readByte();
        return resolveReadMethodFromFingerPrintAndRead(fingerPrint);
    }

    private int resolveReadMethodFromFingerPrintAndRead(int fingerPrint) {
        switch (fingerPrint) {
            case BYTE_FINGERPRINT -> {
                return input.readByte();
            }
            case SHORT_FINGERPRINT -> {
                return input.readShort();
            }
            case INT_FINGERPRINT -> {
                return input.readInt();
            }
            default -> throw new IllegalArgumentException("unknown fingerprint " + Integer.toBinaryString(fingerPrint));
        }
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

public class CompressedOutputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private static final int BYTE_FINGERPRINT = 0b00000000;
    private static final int SHORT_FINGERPRINT = 0b10000000;
    private static final int INT_FINGERPRINT = 0b11000000;
    private final BaseOutputStream output;

    public CompressedOutputStream(BaseOutputStream output) {
        this.output = output;
    }

    public void writeCompressedInt(int value) {
        int fingerPrint = getFingerPrintFromValue(value);
        output.writeByte(fingerPrint);
        resolveWriteMethodByFingerPrintAndWriteValue(fingerPrint, value);
    }

    private void resolveWriteMethodByFingerPrintAndWriteValue(int fingerPrint, int value) {
        switch (fingerPrint) {
            case BYTE_FINGERPRINT -> output.writeByte(value);
            case SHORT_FINGERPRINT -> output.writeShort(value);
            case INT_FINGERPRINT -> output.writeInt(value);
        }
    }

    private int getFingerPrintFromValue(int value) {
        if ((value & BYTE_MASK) == value) {
            return BYTE_FINGERPRINT;
        }
        if ((value & SHORT_MASK) == value) {
            return SHORT_FINGERPRINT;
        }
        return INT_FINGERPRINT;
    }
}
В целом все.Теперь посмотрим на применения нашего кода.​
Java:
    @Test
    void globalTest() throws Exception {
        int xorKey = 0xAABBCCDD;
        Path filePath = Paths.get("file");
        BaseOutputStream fileOutput = new OutputStreamImpl(Files.newOutputStream(filePath));
        BaseOutputStream xorOutput = new XorOutputStream(fileOutput, xorKey);
        CompressedOutputStream compressedOutput = new CompressedOutputStream(xorOutput);
        compressedOutput.writeCompressedInt(3000);
    }
Тут видны плюсы делегации(объект может становится кем угодно в этапе исполнения программы.наследование разумеется так не может)
а так же видны и наши старания - теперь каждый компонент можно применять отдельно.
Но теперь встает опять вопрос...Видов реализаций так много, будет довольно трудно поддерживать пары input/output.
Давайте это исправим добавив в нашу систему паттерн AbstractFactory.
AbstractFactory позволяет нам описать интерфейс создания объектов связанных по смыслу, но имеющих разные "наборы" реализаций.​
Java:
package ru.metafaze.io;

public interface IOFactory {
    BaseInputStream createInputStream();

    BaseOutputStream createOutputStream();
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;
import ru.metafaze.io.BaseOutputStream;
import ru.metafaze.io.IOFactory;

public class XorIOFactory implements IOFactory {
    private final BaseInputStream input;
    private final BaseOutputStream output;
    private final int xorKey;

    public XorIOFactory(BaseInputStream input, BaseOutputStream output, int xorKey) {
        this.input = input;
        this.output = output;
        this.xorKey = xorKey;
    }

    @Override
    public BaseInputStream createInputStream() {
        return new XorInputStream(input, xorKey);
    }

    @Override
    public BaseOutputStream createOutputStream() {
        return new XorOutputStream(output, xorKey);
    }
}
Java:
        IOFactory ioFactory = new XorIOFactory(fileInput, fileOutput, xorKey);
        BaseInputStream xorInput = ioFactory.createInputStream();
        BaseOutputStream xorOutput = ioFactory.createOutputStream();
Спасибо что прочитали эту огромную статью, на сегодня все.Однозначно смотивирую вас разобраться в этом самостоятельно : то что описано в статье - 1% из того всего что я изучал на счет клинкода.
Помимо этого, добавлю, что вполне где то мог ошибиться/привести неуместный пример.Принимается любая критика.
P.S ко всем классам написаны юнит тесты.Если нужно, прикреплю как метаинформацию.
Bye
гофман опять шизофазит ёмаё
 
артём диджитал дизайнер
Участник
Статус
Оффлайн
Регистрация
10 Окт 2020
Сообщения
490
Реакции[?]
477
Поинты[?]
66K
если бы это было запощенно в разделе кубоголовых, а не в общем, можно было бы смело сказать что это самый полезный тред во всём разделе)
красава, особо не углублялся в каждый аспект, но читать было интересно
 
read only ambassador
Пользователь
Статус
Оффлайн
Регистрация
28 Июн 2022
Сообщения
627
Реакции[?]
105
Поинты[?]
9K
если бы это было запощенно в разделе кубоголовых
они и слова не разберут, в чём смысл постить что то полезное этим клопам, которые после стольки гайдов не умнеют?
 
Участник
Статус
Оффлайн
Регистрация
23 Апр 2022
Сообщения
695
Реакции[?]
327
Поинты[?]
11K
Всем привет!
На протяжении длительного времени я изучал парадигму "чистого кода/архитектуры приложения" и готов показать некий пример, возможно, заинтересовав кого либо из тех, кто будет это читать
Язык на котором будет писаться код - Java, но никакие трудные фьючеры джавы не будут использованы.по моим меркам джаву в данной статье можно считать псевдоязыком понятным любому.в не совсем очевидных местах оставил комментарии.повторить данные трюки можно в любом ОО языке.
Глава 0: кому это нужно?
это пригодиться тем, кто пишет код с друзьями/просто хочет улучшить качество своего кода.
на практике в проектах которые не будут жить больше нескольких месяцев нет смысла все это использовать.оверхед.
Глава 1: вступление.
Начну статью с того, что же мы разберем.
Мы пробежимся по всем 5 принципам SOLID, применим несколько паттернов программирования из GoF
В статье я сначала напишу "плохой код", а потом мы вместе разберем что же там не так с точки зрения SOLID + перепишем его по правилам SOLID и применим паттерны программирования.
Все будет основано на примерах.
Lets go
Глава 2: обзор случая.
Нам понадобилось сохранение конфигов в нашем чите.(IO компонент)
(Случай взят из воздуха.Он субъективно нравится мне для описания проблем, их решения.)
Глава 3: написание "плохого кода".​
Java:
package ru.metafaze.io;

import java.io.InputStream;
import java.io.OutputStream;

public class InputOutputStream {
    private final InputStream input;
    private final OutputStream output;

    public InputOutputStream(InputStream input, OutputStream output) {
        this.input = input;
        this.output = output;
    }

    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }

    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int readShort() {
        return readByte() | readByte() << 8;
    }

    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Вдруг, вас осинило...Значения все таки лучше зашифровать от дядей Эдиков...
Ну, что ж поделать, добавим в этот же класс xor предварительно переименовав InputOutputStream в XorInputOutputStream.​
Java:
package ru.metafaze.io;

import java.io.InputStream;
import java.io.OutputStream;

public class XorInputOutputStream {
    private final InputStream input;
    private final OutputStream output;
    private final int xorKey;

    public XorInputOutputStream(InputStream input, OutputStream output, int xorKey) {
        this.input = input;
        this.output = output;
        this.xorKey = xorKey;
    }

    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value ^ xorKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }

    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read() ^ xorKey;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int readShort() {
        return readByte() | readByte() << 8;
    }

    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Потом, вдруг, ты понял, что было бы неплохо добавить компрессию.
Но что мы получим в итоге?CompressedProtectedInputOutputStream?
Глава 4. в чем проблема данного подхода?
Проблем тут уже ОЧЕНЬ много.Дело в том, что ваш класс уже на данном этапе трансформирует числа в байты, шифрует их, компрессит(!).Это
1) усложняет в миллион раз анализ кода написанного внутри
2) делает абсолютно невозможным переиспользование.ваш проект будет разрастаться в размерах со скоростью света.вы будете городить один и тот же код под одинаковые задачи.
можно продолжать ещё очень долго, но давайте перейдем к SOLID.
Глава 5. SOLID - что это за хуйня?
SOLID - акроним который скрывает в себе 5 принципов которые расшифровываются как:
SRP - принцип единственной ответственности.(single responsibility principle)
OCP - принцип открытости для расширения, закрытости для изменения.(open-closed principle)
LSP - принцип подставления барбары лисков.(не бойтесь названия, это ирл никнейм бабушки)(Liskov substitution principle)
ISP - принцип разделения интерфейса.(interface segregation principle)
DIP - принцип инверсии зависимостей.(dependency inversion principle)
Глава 6. В чем тут нарушение принципов?Что они вообще из себя представляют?

SRP говорит о том, что ваш компонент должен иметь лишь одну ответственность.Как это понять?Роберт Мартин описывает это как одну причину для изменения.И ведь действительно
1) вдруг появится потребность в изменении шифра?
2) вдруг появится потребность в изменении трансформации числа в байты?
3) вдруг появится потребность в изменении компрессии?
Это абсолютно 3 не связанных между собой задачи которые и противоречат принципу единственной ответственности.
Тут важная ремарка, не стоит проводить декомпозицию до максимально возможного предела.Дробить пока дробиться - плохой подход.В адекватном случае в следствии "правильной ответственности" вы получите код который сможете переиспользовать.

Это самый легкий принцип(на первый взгляд, на самом деле он самый трудный), но он убивает миллион зайцев сразу :
ISP
(он затрагивает даже принципы GRASP)
High Cohesion(высокое сцепление) <- говорят что лучше не переводить этот принцип на русский язык.

OCP говорит о закрытости для изменения(старого кода) и открытости для расширения(старого кода).Т.е новую функциональность старым компонентам вы должны вводить наследованием, композицией и тд.
Не стоит брать этот принцип близко к сердцу, разумеется баги нужно фиксить изменением старого кода)

LSP - самый ебанутый принцип который к нам к тому же и не относится.
Его ебанутость заключается в том, что его название не отображает его смысл, отнюдь, он отображает фамилию бабушки которая его придумала.
Заключается он в том, что нельзя менять сигнатуру виртуальных методов при наследовании, добавлять исключения не описанные в начале иерархии.Можете не беспокоиться, хуй вам компилятор даст это сделать в ОО языках на которых пишут читы.
Так же этот принцип описывает то, что компоненты при наследовании должны уметь "встрять" за своего родственника по иерархии.Т.е если метод принимает Button, то MegaSuperPuperButton(extends Button) в любом случае должен корректно выполняться в том же методе.

ISP - разбивай свои интерфейсы по максимуму(также как с SRP.знайте грани декомпозиции).

DIP - с первого взгляда самый трудный и непонятный принцип.Вот его описание с wiki(в книге Роберта Мартина вроде как звучит как то похуже) :



На самом деле он самый легкий относительно его пользы.

Глава 6: Мы нарушили только SRP?
Нет. Мы нарушили все кроме принципа подставления Барбары Лисков.
OCP - мы ввели функциональность изменяя старый код.
ISP - наш класс имеет интерфейс записи, чтения.(относительно спорный момент в данном контексте, но в огромном проекте лучше не выебываться и все таки разбивать IO на две иерархии для переиспользования.Мало ли вашему второму другу кодеру понадобится только Reader?)
DIP - подход изначально был вне инверсии зависимостей.
Глава 7: Перепишем код.
Для начала поподробнее расскажу про DIP, он очень важный в построении любого компонента.Формулировка очень трудная, на самом деле все в разы легче, а импакта миллион.
Dependency qqqqqq111111 -(повторюсь)


Уровень абстракции - уровень того, насколько близко лежит решение задачи.
Если бы это можно было б визуализировать, я б визуализировал так :
Уровень абстракции уменьшается с увеличением размера стека, где вершина стека всегда является самым низким уровнем абстракции по отношению к элементам которые распологаются ниже по стеку.
Если уж разъяснять на пальцах, то метод где вы вызываете writeInt - высокий уровень абстракции.Ведь вам совсем похуй что там происходит внутри, вы просто пишите несколько символов которые запускают огромную задачу.Для вас это все та же абстракция "writeInt".
То есть место где мы будем юзать наш IO(высокий уровень абстракции) не должен зависить от реализации интерфейса(относительно низкого уровня абстракции), отнюдь, они должны оба зависеть от интерфейса(абстракции).
Компонент A имея потребность в компоненте B описывает интерфейс который ему нужен.компонент B в свою очередь реализует его.Таким образом компонент A зависит от интерфейса, компонент B зависит от интерфейса, а не компонент A зависит от B.
Поэтому это и называют инверсией зависимостей.
Это избавляет нас от конкретной реализации и дает по полной программе насладиться полиморфизмом.
+ Low Coupling из GRASP в подарок.
Также реализуем ISP, разбивая Input и Output на отдельные иерархии.Это даст нам возможность переиспользования в других случаях.
Приступим.​
Java:
package ru.metafaze.io;

public interface BaseInputStream {
    int readByte();

    int readShort();

    int readInt();
}
Java:
package ru.metafaze.io;

public interface BaseOutputStream {
    void writeByte(int value);

    void writeShort(int value);

    void writeInt(int value);
}
Я предпочту делегирование для реализации компрессии, крипта.
Почему не наследование?Дело в том, что наследование в некоторых случаях(90%) - антипаттерн.Не буду много городить, в двух словах, нарушение инкапсуляции и ещё несколько проблем которые появляются в следствии наследования.Делегирование в данном контексте сыпит нам лишь плюсы которые мы сейчас и увидим.
Во первых имплементируем обычный Input/Output​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;

import java.io.InputStream;

public class InputStreamImpl implements BaseInputStream {
    private final InputStream input;

    public InputStreamImpl(InputStream input) {
        this.input = input;
    }

    @Override
    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int readShort() {
        return readByte() | readByte() << 8;
    }

    @Override
    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

import java.io.OutputStream;

public class OutputStreamImpl implements BaseOutputStream {
    private final OutputStream output;

    public OutputStreamImpl(OutputStream output) {
        this.output = output;
    }

    @Override
    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    @Override
    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }
}
Ну а теперь давайте к шифрованию.​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;

public class XorInputStream implements BaseInputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private final BaseInputStream input;
    private final int xorKey;

    public XorInputStream(BaseInputStream input, int xorKey) {
        this.input = input;
        this.xorKey = xorKey;
    }

    @Override
    public int readByte() {
        return xorAndApplyMask(input.readByte(), xorKey, BYTE_MASK);
    }

    @Override
    public int readShort() {
        return xorAndApplyMask(input.readShort(), xorKey, SHORT_MASK);
    }

    @Override
    public int readInt() {
        return xorAndApplyMask(input.readInt(), xorKey, INT_MASK);
    }

    private int xorAndApplyMask(int value, int xorKey, int mask) {
        return (value ^ xorKey) & mask;
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

public class XorOutputStream implements BaseOutputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private final BaseOutputStream output;
    private final int xorKey;

    public XorOutputStream(BaseOutputStream output, int xorKey) {
        this.output = output;
        this.xorKey = xorKey;
    }

    @Override
    public void writeByte(int value) {
        output.writeByte(value ^ xorKey);
    }

    @Override
    public void writeShort(int value) {
        output.writeShort(value ^ xorKey);
    }

    @Override
    public void writeInt(int value) {
        output.writeInt(value ^ xorKey);
    }
}
Но что на счет компрессии?
Компрессия в данном контексте - частный случай делегации, либо же паттерн под названием Decorator который расширяет функционал готового класса.
Нет смысла реализовывать интерфейс по очевидным причинам.​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;
import ru.metafaze.io.BaseOutputStream;

public class CompressedInputStream {
    private static final int BYTE_FINGERPRINT = 0b00000000;
    private static final int SHORT_FINGERPRINT = 0b10000000;
    private static final int INT_FINGERPRINT = 0b11000000;
    private final BaseInputStream input;

    public CompressedInputStream(BaseInputStream input) {
        this.input = input;
    }

    public int readCompressedInt() {
        int fingerPrint = input.readByte();
        return resolveReadMethodFromFingerPrintAndRead(fingerPrint);
    }

    private int resolveReadMethodFromFingerPrintAndRead(int fingerPrint) {
        switch (fingerPrint) {
            case BYTE_FINGERPRINT -> {
                return input.readByte();
            }
            case SHORT_FINGERPRINT -> {
                return input.readShort();
            }
            case INT_FINGERPRINT -> {
                return input.readInt();
            }
            default -> throw new IllegalArgumentException("unknown fingerprint " + Integer.toBinaryString(fingerPrint));
        }
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

public class CompressedOutputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private static final int BYTE_FINGERPRINT = 0b00000000;
    private static final int SHORT_FINGERPRINT = 0b10000000;
    private static final int INT_FINGERPRINT = 0b11000000;
    private final BaseOutputStream output;

    public CompressedOutputStream(BaseOutputStream output) {
        this.output = output;
    }

    public void writeCompressedInt(int value) {
        int fingerPrint = getFingerPrintFromValue(value);
        output.writeByte(fingerPrint);
        resolveWriteMethodByFingerPrintAndWriteValue(fingerPrint, value);
    }

    private void resolveWriteMethodByFingerPrintAndWriteValue(int fingerPrint, int value) {
        switch (fingerPrint) {
            case BYTE_FINGERPRINT -> output.writeByte(value);
            case SHORT_FINGERPRINT -> output.writeShort(value);
            case INT_FINGERPRINT -> output.writeInt(value);
        }
    }

    private int getFingerPrintFromValue(int value) {
        if ((value & BYTE_MASK) == value) {
            return BYTE_FINGERPRINT;
        }
        if ((value & SHORT_MASK) == value) {
            return SHORT_FINGERPRINT;
        }
        return INT_FINGERPRINT;
    }
}
В целом все.Теперь посмотрим на применения нашего кода.​
Java:
    @Test
    void globalTest() throws Exception {
        int xorKey = 0xAABBCCDD;
        Path filePath = Paths.get("file");
        BaseOutputStream fileOutput = new OutputStreamImpl(Files.newOutputStream(filePath));
        BaseOutputStream xorOutput = new XorOutputStream(fileOutput, xorKey);
        CompressedOutputStream compressedOutput = new CompressedOutputStream(xorOutput);
        compressedOutput.writeCompressedInt(3000);
    }
Тут видны плюсы делегации(объект может становится кем угодно в этапе исполнения программы.наследование разумеется так не может)
а так же видны и наши старания - теперь каждый компонент можно применять отдельно.
Но теперь встает опять вопрос...Видов реализаций так много, будет довольно трудно поддерживать пары input/output.
Давайте это исправим добавив в нашу систему паттерн AbstractFactory.
AbstractFactory позволяет нам описать интерфейс создания объектов связанных по смыслу, но имеющих разные "наборы" реализаций.​
Java:
package ru.metafaze.io;

public interface IOFactory {
    BaseInputStream createInputStream();

    BaseOutputStream createOutputStream();
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;
import ru.metafaze.io.BaseOutputStream;
import ru.metafaze.io.IOFactory;

public class XorIOFactory implements IOFactory {
    private final BaseInputStream input;
    private final BaseOutputStream output;
    private final int xorKey;

    public XorIOFactory(BaseInputStream input, BaseOutputStream output, int xorKey) {
        this.input = input;
        this.output = output;
        this.xorKey = xorKey;
    }

    @Override
    public BaseInputStream createInputStream() {
        return new XorInputStream(input, xorKey);
    }

    @Override
    public BaseOutputStream createOutputStream() {
        return new XorOutputStream(output, xorKey);
    }
}
Java:
        IOFactory ioFactory = new XorIOFactory(fileInput, fileOutput, xorKey);
        BaseInputStream xorInput = ioFactory.createInputStream();
        BaseOutputStream xorOutput = ioFactory.createOutputStream();
Спасибо что прочитали эту огромную статью, на сегодня все.Однозначно смотивирую вас разобраться в этом самостоятельно : то что описано в статье - 1% из того всего что я изучал на счет клинкода.
Помимо этого, добавлю, что вполне где то мог ошибиться/привести неуместный пример.Принимается любая критика.
P.S ко всем классам написаны юнит тесты.Если нужно, прикреплю как метаинформацию.
Bye
Расписал солид для школьников. Только причем тут он 1688590417620.png непонятно.
 
эксперт в майнкрафт апи
Read Only
Статус
Оффлайн
Регистрация
25 Янв 2023
Сообщения
684
Реакции[?]
286
Поинты[?]
21K
если бы это было запощенно в разделе кубоголовых, а не в общем, можно было бы смело сказать что это самый полезный тред во всём разделе)
thanks.
но читать было интересно
значит моя задача выполнена, SOLID представлен популярным языком.надеюсь нигде не ошибся.
Расписал солид для школьников.
По сути да, но я также постарался пустить корни в GRASP, GoF(Delegate, Decorator, AbstractFactory на довольно небольшом кол-ве классов).
Также я упомянул довольно важную деталь на счет наследования предоставив как альтернативу делегирование(лично для меня раньше это было не совсем очевидным в некоторых моментах)
Только причем тут он Посмотреть вложение 252560 непонятно.
Я изначально очень много времени постарался уделить dependency inversion, ибо как по мне(я не знаю что может меня переубедить) это принцип который является самым важным на уровне с SRP.Его проблема в том, что он описан как адронный коллайдер который будет расположен у вас в коде.Переписывая фрагмент в n раз тыкнул @ и решил оставить пасхалку, хуле.
 
Nike.lua
Олдфаг
Статус
Оффлайн
Регистрация
13 Окт 2020
Сообщения
2,748
Реакции[?]
1,466
Поинты[?]
2K
Я хоть пишу код на ООП языках уже около 2 лет, понимаю каждый из SOLID принципов, но даже для меня здесь очень много терминов, который приходится гуглить. Навряд ли кубозавры поймут здесь хотя бы половину материала, а так очень даже годно.
 
Ревёрсер среднего звена
Пользователь
Статус
Оффлайн
Регистрация
24 Ноя 2022
Сообщения
303
Реакции[?]
107
Поинты[?]
56K
Не читал, но осуждаю Java-код
Уважаю за объяснение столь болезненных для новичков тем
 
pasting corporation
Read Only
Статус
Онлайн
Регистрация
4 Дек 2022
Сообщения
716
Реакции[?]
210
Поинты[?]
4K
Всем привет!
На протяжении длительного времени я изучал парадигму "чистого кода/архитектуры приложения" и готов показать некий пример, возможно, заинтересовав кого либо из тех, кто будет это читать
Язык на котором будет писаться код - Java, но никакие трудные фьючеры джавы не будут использованы.по моим меркам джаву в данной статье можно считать псевдоязыком понятным любому.в не совсем очевидных местах оставил комментарии.повторить данные трюки можно в любом ОО языке.
Глава 0: кому это нужно?
это пригодиться тем, кто пишет код с друзьями/просто хочет улучшить качество своего кода.
на практике в проектах которые не будут жить больше нескольких месяцев нет смысла все это использовать.оверхед.
Глава 1: вступление.
Начну статью с того, что же мы разберем.
Мы пробежимся по всем 5 принципам SOLID, применим несколько паттернов программирования из GoF
В статье я сначала напишу "плохой код", а потом мы вместе разберем что же там не так с точки зрения SOLID + перепишем его по правилам SOLID и применим паттерны программирования.
Все будет основано на примерах.
Lets go
Глава 2: обзор случая.
Нам понадобилось сохранение конфигов в нашем чите.(IO компонент)
(Случай взят из воздуха.Он субъективно нравится мне для описания проблем, их решения.)
Глава 3: написание "плохого кода".​
Java:
package ru.metafaze.io;

import java.io.InputStream;
import java.io.OutputStream;

public class InputOutputStream {
    private final InputStream input;
    private final OutputStream output;

    public InputOutputStream(InputStream input, OutputStream output) {
        this.input = input;
        this.output = output;
    }

    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }

    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int readShort() {
        return readByte() | readByte() << 8;
    }

    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Вдруг, вас осинило...Значения все таки лучше зашифровать от дядей Эдиков...
Ну, что ж поделать, добавим в этот же класс xor предварительно переименовав InputOutputStream в XorInputOutputStream.​
Java:
package ru.metafaze.io;

import java.io.InputStream;
import java.io.OutputStream;

public class XorInputOutputStream {
    private final InputStream input;
    private final OutputStream output;
    private final int xorKey;

    public XorInputOutputStream(InputStream input, OutputStream output, int xorKey) {
        this.input = input;
        this.output = output;
        this.xorKey = xorKey;
    }

    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value ^ xorKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }

    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read() ^ xorKey;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int readShort() {
        return readByte() | readByte() << 8;
    }

    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Потом, вдруг, ты понял, что было бы неплохо добавить компрессию.
Но что мы получим в итоге?CompressedProtectedInputOutputStream?
Глава 4. в чем проблема данного подхода?
Проблем тут уже ОЧЕНЬ много.Дело в том, что ваш класс уже на данном этапе трансформирует числа в байты, шифрует их, компрессит(!).Это
1) усложняет в миллион раз анализ кода написанного внутри
2) делает абсолютно невозможным переиспользование.ваш проект будет разрастаться в размерах со скоростью света.вы будете городить один и тот же код под одинаковые задачи.
можно продолжать ещё очень долго, но давайте перейдем к SOLID.
Глава 5. SOLID - что это за хуйня?
SOLID - акроним который скрывает в себе 5 принципов которые расшифровываются как:
SRP - принцип единственной ответственности.(single responsibility principle)
OCP - принцип открытости для расширения, закрытости для изменения.(open-closed principle)
LSP - принцип подставления барбары лисков.(не бойтесь названия, это ирл никнейм бабушки)(Liskov substitution principle)
ISP - принцип разделения интерфейса.(interface segregation principle)
DIP - принцип инверсии зависимостей.(dependency inversion principle)
Глава 6. В чем тут нарушение принципов?Что они вообще из себя представляют?

SRP говорит о том, что ваш компонент должен иметь лишь одну ответственность.Как это понять?Роберт Мартин описывает это как одну причину для изменения.И ведь действительно
1) вдруг появится потребность в изменении шифра?
2) вдруг появится потребность в изменении трансформации числа в байты?
3) вдруг появится потребность в изменении компрессии?
Это абсолютно 3 не связанных между собой задачи которые и противоречат принципу единственной ответственности.
Тут важная ремарка, не стоит проводить декомпозицию до максимально возможного предела.Дробить пока дробиться - плохой подход.В адекватном случае в следствии "правильной ответственности" вы получите код который сможете переиспользовать.

Это самый легкий принцип(на первый взгляд, на самом деле он самый трудный), но он убивает миллион зайцев сразу :
ISP
(он затрагивает даже принципы GRASP)
High Cohesion(высокое сцепление) <- говорят что лучше не переводить этот принцип на русский язык.

OCP говорит о закрытости для изменения(старого кода) и открытости для расширения(старого кода).Т.е новую функциональность старым компонентам вы должны вводить наследованием, композицией и тд.
Не стоит брать этот принцип близко к сердцу, разумеется баги нужно фиксить изменением старого кода)

LSP - самый ебанутый принцип который к нам к тому же и не относится.
Его ебанутость заключается в том, что его название не отображает его смысл, отнюдь, он отображает фамилию бабушки которая его придумала.
Заключается он в том, что нельзя менять сигнатуру виртуальных методов при наследовании, добавлять исключения не описанные в начале иерархии.Можете не беспокоиться, хуй вам компилятор даст это сделать в ОО языках на которых пишут читы.
Так же этот принцип описывает то, что компоненты при наследовании должны уметь "встрять" за своего родственника по иерархии.Т.е если метод принимает Button, то MegaSuperPuperButton(extends Button) в любом случае должен корректно выполняться в том же методе.

ISP - разбивай свои интерфейсы по максимуму(также как с SRP.знайте грани декомпозиции).

DIP - с первого взгляда самый трудный и непонятный принцип.Вот его описание с wiki(в книге Роберта Мартина вроде как звучит как то похуже) :



На самом деле он самый легкий относительно его пользы.

Глава 6: Мы нарушили только SRP?
Нет. Мы нарушили все кроме принципа подставления Барбары Лисков.
OCP - мы ввели функциональность изменяя старый код.
ISP - наш класс имеет интерфейс записи, чтения.(относительно спорный момент в данном контексте, но в огромном проекте лучше не выебываться и все таки разбивать IO на две иерархии для переиспользования.Мало ли вашему второму другу кодеру понадобится только Reader?)
DIP - подход изначально был вне инверсии зависимостей.
Глава 7: Перепишем код.
Для начала поподробнее расскажу про DIP, он очень важный в построении любого компонента.Формулировка очень трудная, на самом деле все в разы легче, а импакта миллион.
Dependency qqqqqq111111 -(повторюсь)


Уровень абстракции - уровень того, насколько близко лежит решение задачи.
Если бы это можно было б визуализировать, я б визуализировал так :
Уровень абстракции уменьшается с увеличением размера стека, где вершина стека всегда является самым низким уровнем абстракции по отношению к элементам которые распологаются ниже по стеку.
Если уж разъяснять на пальцах, то метод где вы вызываете writeInt - высокий уровень абстракции.Ведь вам совсем похуй что там происходит внутри, вы просто пишите несколько символов которые запускают огромную задачу.Для вас это все та же абстракция "writeInt".
То есть место где мы будем юзать наш IO(высокий уровень абстракции) не должен зависить от реализации интерфейса(относительно низкого уровня абстракции), отнюдь, они должны оба зависеть от интерфейса(абстракции).
Компонент A имея потребность в компоненте B описывает интерфейс который ему нужен.компонент B в свою очередь реализует его.Таким образом компонент A зависит от интерфейса, компонент B зависит от интерфейса, а не компонент A зависит от B.
Поэтому это и называют инверсией зависимостей.
Это избавляет нас от конкретной реализации и дает по полной программе насладиться полиморфизмом.
+ Low Coupling из GRASP в подарок.
Также реализуем ISP, разбивая Input и Output на отдельные иерархии.Это даст нам возможность переиспользования в других случаях.
Приступим.​
Java:
package ru.metafaze.io;

public interface BaseInputStream {
    int readByte();

    int readShort();

    int readInt();
}
Java:
package ru.metafaze.io;

public interface BaseOutputStream {
    void writeByte(int value);

    void writeShort(int value);

    void writeInt(int value);
}
Я предпочту делегирование для реализации компрессии, крипта.
Почему не наследование?Дело в том, что наследование в некоторых случаях(90%) - антипаттерн.Не буду много городить, в двух словах, нарушение инкапсуляции и ещё несколько проблем которые появляются в следствии наследования.Делегирование в данном контексте сыпит нам лишь плюсы которые мы сейчас и увидим.
Во первых имплементируем обычный Input/Output​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;

import java.io.InputStream;

public class InputStreamImpl implements BaseInputStream {
    private final InputStream input;

    public InputStreamImpl(InputStream input) {
        this.input = input;
    }

    @Override
    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int readShort() {
        return readByte() | readByte() << 8;
    }

    @Override
    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

import java.io.OutputStream;

public class OutputStreamImpl implements BaseOutputStream {
    private final OutputStream output;

    public OutputStreamImpl(OutputStream output) {
        this.output = output;
    }

    @Override
    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    @Override
    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }
}
Ну а теперь давайте к шифрованию.​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;

public class XorInputStream implements BaseInputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private final BaseInputStream input;
    private final int xorKey;

    public XorInputStream(BaseInputStream input, int xorKey) {
        this.input = input;
        this.xorKey = xorKey;
    }

    @Override
    public int readByte() {
        return xorAndApplyMask(input.readByte(), xorKey, BYTE_MASK);
    }

    @Override
    public int readShort() {
        return xorAndApplyMask(input.readShort(), xorKey, SHORT_MASK);
    }

    @Override
    public int readInt() {
        return xorAndApplyMask(input.readInt(), xorKey, INT_MASK);
    }

    private int xorAndApplyMask(int value, int xorKey, int mask) {
        return (value ^ xorKey) & mask;
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

public class XorOutputStream implements BaseOutputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private final BaseOutputStream output;
    private final int xorKey;

    public XorOutputStream(BaseOutputStream output, int xorKey) {
        this.output = output;
        this.xorKey = xorKey;
    }

    @Override
    public void writeByte(int value) {
        output.writeByte(value ^ xorKey);
    }

    @Override
    public void writeShort(int value) {
        output.writeShort(value ^ xorKey);
    }

    @Override
    public void writeInt(int value) {
        output.writeInt(value ^ xorKey);
    }
}
Но что на счет компрессии?
Компрессия в данном контексте - частный случай делегации, либо же паттерн под названием Decorator который расширяет функционал готового класса.
Нет смысла реализовывать интерфейс по очевидным причинам.​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;
import ru.metafaze.io.BaseOutputStream;

public class CompressedInputStream {
    private static final int BYTE_FINGERPRINT = 0b00000000;
    private static final int SHORT_FINGERPRINT = 0b10000000;
    private static final int INT_FINGERPRINT = 0b11000000;
    private final BaseInputStream input;

    public CompressedInputStream(BaseInputStream input) {
        this.input = input;
    }

    public int readCompressedInt() {
        int fingerPrint = input.readByte();
        return resolveReadMethodFromFingerPrintAndRead(fingerPrint);
    }

    private int resolveReadMethodFromFingerPrintAndRead(int fingerPrint) {
        switch (fingerPrint) {
            case BYTE_FINGERPRINT -> {
                return input.readByte();
            }
            case SHORT_FINGERPRINT -> {
                return input.readShort();
            }
            case INT_FINGERPRINT -> {
                return input.readInt();
            }
            default -> throw new IllegalArgumentException("unknown fingerprint " + Integer.toBinaryString(fingerPrint));
        }
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

public class CompressedOutputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private static final int BYTE_FINGERPRINT = 0b00000000;
    private static final int SHORT_FINGERPRINT = 0b10000000;
    private static final int INT_FINGERPRINT = 0b11000000;
    private final BaseOutputStream output;

    public CompressedOutputStream(BaseOutputStream output) {
        this.output = output;
    }

    public void writeCompressedInt(int value) {
        int fingerPrint = getFingerPrintFromValue(value);
        output.writeByte(fingerPrint);
        resolveWriteMethodByFingerPrintAndWriteValue(fingerPrint, value);
    }

    private void resolveWriteMethodByFingerPrintAndWriteValue(int fingerPrint, int value) {
        switch (fingerPrint) {
            case BYTE_FINGERPRINT -> output.writeByte(value);
            case SHORT_FINGERPRINT -> output.writeShort(value);
            case INT_FINGERPRINT -> output.writeInt(value);
        }
    }

    private int getFingerPrintFromValue(int value) {
        if ((value & BYTE_MASK) == value) {
            return BYTE_FINGERPRINT;
        }
        if ((value & SHORT_MASK) == value) {
            return SHORT_FINGERPRINT;
        }
        return INT_FINGERPRINT;
    }
}
В целом все.Теперь посмотрим на применения нашего кода.​
Java:
    @Test
    void globalTest() throws Exception {
        int xorKey = 0xAABBCCDD;
        Path filePath = Paths.get("file");
        BaseOutputStream fileOutput = new OutputStreamImpl(Files.newOutputStream(filePath));
        BaseOutputStream xorOutput = new XorOutputStream(fileOutput, xorKey);
        CompressedOutputStream compressedOutput = new CompressedOutputStream(xorOutput);
        compressedOutput.writeCompressedInt(3000);
    }
Тут видны плюсы делегации(объект может становится кем угодно в этапе исполнения программы.наследование разумеется так не может)
а так же видны и наши старания - теперь каждый компонент можно применять отдельно.
Но теперь встает опять вопрос...Видов реализаций так много, будет довольно трудно поддерживать пары input/output.
Давайте это исправим добавив в нашу систему паттерн AbstractFactory.
AbstractFactory позволяет нам описать интерфейс создания объектов связанных по смыслу, но имеющих разные "наборы" реализаций.​
Java:
package ru.metafaze.io;

public interface IOFactory {
    BaseInputStream createInputStream();

    BaseOutputStream createOutputStream();
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;
import ru.metafaze.io.BaseOutputStream;
import ru.metafaze.io.IOFactory;

public class XorIOFactory implements IOFactory {
    private final BaseInputStream input;
    private final BaseOutputStream output;
    private final int xorKey;

    public XorIOFactory(BaseInputStream input, BaseOutputStream output, int xorKey) {
        this.input = input;
        this.output = output;
        this.xorKey = xorKey;
    }

    @Override
    public BaseInputStream createInputStream() {
        return new XorInputStream(input, xorKey);
    }

    @Override
    public BaseOutputStream createOutputStream() {
        return new XorOutputStream(output, xorKey);
    }
}
Java:
        IOFactory ioFactory = new XorIOFactory(fileInput, fileOutput, xorKey);
        BaseInputStream xorInput = ioFactory.createInputStream();
        BaseOutputStream xorOutput = ioFactory.createOutputStream();
Спасибо что прочитали эту огромную статью, на сегодня все.Однозначно смотивирую вас разобраться в этом самостоятельно : то что описано в статье - 1% из того всего что я изучал на счет клинкода.
Помимо этого, добавлю, что вполне где то мог ошибиться/привести неуместный пример.Принимается любая критика.
P.S ко всем классам написаны юнит тесты.Если нужно, прикреплю как метаинформацию.
Bye
ага пиздеш ето все чат гпт тебе сделал так я тебе и поверил пери уткагофман
 
эксперт в майнкрафт апи
Read Only
Статус
Оффлайн
Регистрация
25 Янв 2023
Сообщения
684
Реакции[?]
286
Поинты[?]
21K
Не читал, но осуждаю Java-код
на самом деле java - ахуенный, простой на базовом уровне, кроссплатформенный, проверенный временем, оптимизированный ооп язык
на нем обычно пишут все бекенды веб приложух огромных компаний
в снг сегменте (как я понял)джава - самый востребованный язык
на основе jvm лежит кучу языков по типу котлина со скалой

но относительно югейма ты прав.тут на нем пишут лишь уебищные ватермарки позоря прекрасный яп.
 
Ревёрсер среднего звена
Пользователь
Статус
Оффлайн
Регистрация
24 Ноя 2022
Сообщения
303
Реакции[?]
107
Поинты[?]
56K
эксперт в майнкрафт апи
Read Only
Статус
Оффлайн
Регистрация
25 Янв 2023
Сообщения
684
Реакции[?]
286
Поинты[?]
21K
В сравнение с C# не идёт, небо и земля.
как по мне субъективное мнение и дело вкуса.я писал код на шарпе и из различий заметил лишь огромное колво синтаксического сахара.
Особенно те её версии, на которых часто пишут код(восьмая), это вообще адский пиздец
в 8 джаву из синтаксиса добавили лямбда выражения, метод референсы.сомневаюсь что именно они ввели тебя в ступор.скорее всего ты просто смотрел код какого то сумасшедшего разработчика ватермарок.
синтаксис джавы сам по себе довольно устаревший и не имеет каких либо ебнутых конструкций.
 
Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,606
Реакции[?]
872
Поинты[?]
113K
Всем привет!
На протяжении длительного времени я изучал парадигму "чистого кода/архитектуры приложения" и готов показать некий пример, возможно, заинтересовав кого либо из тех, кто будет это читать
Язык на котором будет писаться код - Java, но никакие трудные фьючеры джавы не будут использованы.по моим меркам джаву в данной статье можно считать псевдоязыком понятным любому.в не совсем очевидных местах оставил комментарии.повторить данные трюки можно в любом ОО языке.
Глава 0: кому это нужно?
это пригодиться тем, кто пишет код с друзьями/просто хочет улучшить качество своего кода.
на практике в проектах которые не будут жить больше нескольких месяцев нет смысла все это использовать.оверхед.
Глава 1: вступление.
Начну статью с того, что же мы разберем.
Мы пробежимся по всем 5 принципам SOLID, применим несколько паттернов программирования из GoF
В статье я сначала напишу "плохой код", а потом мы вместе разберем что же там не так с точки зрения SOLID + перепишем его по правилам SOLID и применим паттерны программирования.
Все будет основано на примерах.
Lets go
Глава 2: обзор случая.
Нам понадобилось сохранение конфигов в нашем чите.(IO компонент)
(Случай взят из воздуха.Он субъективно нравится мне для описания проблем, их решения.)
Глава 3: написание "плохого кода".​
Java:
package ru.metafaze.io;

import java.io.InputStream;
import java.io.OutputStream;

public class InputOutputStream {
    private final InputStream input;
    private final OutputStream output;

    public InputOutputStream(InputStream input, OutputStream output) {
        this.input = input;
        this.output = output;
    }

    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }

    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int readShort() {
        return readByte() | readByte() << 8;
    }

    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Вдруг, вас осинило...Значения все таки лучше зашифровать от дядей Эдиков...
Ну, что ж поделать, добавим в этот же класс xor предварительно переименовав InputOutputStream в XorInputOutputStream.​
Java:
package ru.metafaze.io;

import java.io.InputStream;
import java.io.OutputStream;

public class XorInputOutputStream {
    private final InputStream input;
    private final OutputStream output;
    private final int xorKey;

    public XorInputOutputStream(InputStream input, OutputStream output, int xorKey) {
        this.input = input;
        this.output = output;
        this.xorKey = xorKey;
    }

    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value ^ xorKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }

    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read() ^ xorKey;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public int readShort() {
        return readByte() | readByte() << 8;
    }

    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Потом, вдруг, ты понял, что было бы неплохо добавить компрессию.
Но что мы получим в итоге?CompressedProtectedInputOutputStream?
Глава 4. в чем проблема данного подхода?
Проблем тут уже ОЧЕНЬ много.Дело в том, что ваш класс уже на данном этапе трансформирует числа в байты, шифрует их, компрессит(!).Это
1) усложняет в миллион раз анализ кода написанного внутри
2) делает абсолютно невозможным переиспользование.ваш проект будет разрастаться в размерах со скоростью света.вы будете городить один и тот же код под одинаковые задачи.
можно продолжать ещё очень долго, но давайте перейдем к SOLID.
Глава 5. SOLID - что это за хуйня?
SOLID - акроним который скрывает в себе 5 принципов которые расшифровываются как:
SRP - принцип единственной ответственности.(single responsibility principle)
OCP - принцип открытости для расширения, закрытости для изменения.(open-closed principle)
LSP - принцип подставления барбары лисков.(не бойтесь названия, это ирл никнейм бабушки)(Liskov substitution principle)
ISP - принцип разделения интерфейса.(interface segregation principle)
DIP - принцип инверсии зависимостей.(dependency inversion principle)
Глава 6. В чем тут нарушение принципов?Что они вообще из себя представляют?

SRP говорит о том, что ваш компонент должен иметь лишь одну ответственность.Как это понять?Роберт Мартин описывает это как одну причину для изменения.И ведь действительно
1) вдруг появится потребность в изменении шифра?
2) вдруг появится потребность в изменении трансформации числа в байты?
3) вдруг появится потребность в изменении компрессии?
Это абсолютно 3 не связанных между собой задачи которые и противоречат принципу единственной ответственности.
Тут важная ремарка, не стоит проводить декомпозицию до максимально возможного предела.Дробить пока дробиться - плохой подход.В адекватном случае в следствии "правильной ответственности" вы получите код который сможете переиспользовать.

Это самый легкий принцип(на первый взгляд, на самом деле он самый трудный), но он убивает миллион зайцев сразу :
ISP
(он затрагивает даже принципы GRASP)
High Cohesion(высокое сцепление) <- говорят что лучше не переводить этот принцип на русский язык.

OCP говорит о закрытости для изменения(старого кода) и открытости для расширения(старого кода).Т.е новую функциональность старым компонентам вы должны вводить наследованием, композицией и тд.
Не стоит брать этот принцип близко к сердцу, разумеется баги нужно фиксить изменением старого кода)

LSP - самый ебанутый принцип который к нам к тому же и не относится.
Его ебанутость заключается в том, что его название не отображает его смысл, отнюдь, он отображает фамилию бабушки которая его придумала.
Заключается он в том, что нельзя менять сигнатуру виртуальных методов при наследовании, добавлять исключения не описанные в начале иерархии.Можете не беспокоиться, хуй вам компилятор даст это сделать в ОО языках на которых пишут читы.
Так же этот принцип описывает то, что компоненты при наследовании должны уметь "встрять" за своего родственника по иерархии.Т.е если метод принимает Button, то MegaSuperPuperButton(extends Button) в любом случае должен корректно выполняться в том же методе.

ISP - разбивай свои интерфейсы по максимуму(также как с SRP.знайте грани декомпозиции).

DIP - с первого взгляда самый трудный и непонятный принцип.Вот его описание с wiki(в книге Роберта Мартина вроде как звучит как то похуже) :



На самом деле он самый легкий относительно его пользы.

Глава 6: Мы нарушили только SRP?
Нет. Мы нарушили все кроме принципа подставления Барбары Лисков.
OCP - мы ввели функциональность изменяя старый код.
ISP - наш класс имеет интерфейс записи, чтения.(относительно спорный момент в данном контексте, но в огромном проекте лучше не выебываться и все таки разбивать IO на две иерархии для переиспользования.Мало ли вашему второму другу кодеру понадобится только Reader?)
DIP - подход изначально был вне инверсии зависимостей.
Глава 7: Перепишем код.
Для начала поподробнее расскажу про DIP, он очень важный в построении любого компонента.Формулировка очень трудная, на самом деле все в разы легче, а импакта миллион.
Dependency qqqqqq111111 -(повторюсь)


Уровень абстракции - уровень того, насколько близко лежит решение задачи.
Если бы это можно было б визуализировать, я б визуализировал так :
Уровень абстракции уменьшается с увеличением размера стека, где вершина стека всегда является самым низким уровнем абстракции по отношению к элементам которые распологаются ниже по стеку.
Если уж разъяснять на пальцах, то метод где вы вызываете writeInt - высокий уровень абстракции.Ведь вам совсем похуй что там происходит внутри, вы просто пишите несколько символов которые запускают огромную задачу.Для вас это все та же абстракция "writeInt".
То есть место где мы будем юзать наш IO(высокий уровень абстракции) не должен зависить от реализации интерфейса(относительно низкого уровня абстракции), отнюдь, они должны оба зависеть от интерфейса(абстракции).
Компонент A имея потребность в компоненте B описывает интерфейс который ему нужен.компонент B в свою очередь реализует его.Таким образом компонент A зависит от интерфейса, компонент B зависит от интерфейса, а не компонент A зависит от B.
Поэтому это и называют инверсией зависимостей.
Это избавляет нас от конкретной реализации и дает по полной программе насладиться полиморфизмом.
+ Low Coupling из GRASP в подарок.
Также реализуем ISP, разбивая Input и Output на отдельные иерархии.Это даст нам возможность переиспользования в других случаях.
Приступим.​
Java:
package ru.metafaze.io;

public interface BaseInputStream {
    int readByte();

    int readShort();

    int readInt();
}
Java:
package ru.metafaze.io;

public interface BaseOutputStream {
    void writeByte(int value);

    void writeShort(int value);

    void writeInt(int value);
}
Я предпочту делегирование для реализации компрессии, крипта.
Почему не наследование?Дело в том, что наследование в некоторых случаях(90%) - антипаттерн.Не буду много городить, в двух словах, нарушение инкапсуляции и ещё несколько проблем которые появляются в следствии наследования.Делегирование в данном контексте сыпит нам лишь плюсы которые мы сейчас и увидим.
Во первых имплементируем обычный Input/Output​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;

import java.io.InputStream;

public class InputStreamImpl implements BaseInputStream {
    private final InputStream input;

    public InputStreamImpl(InputStream input) {
        this.input = input;
    }

    @Override
    public int readByte() {
        try {
            /**
             * метод InputStream.read имеет сигнатуру int, но по факту читает только 1 байт
             */
            return input.read();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int readShort() {
        return readByte() | readByte() << 8;
    }

    @Override
    public int readInt() {
        return readShort() | readShort() << 16;
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

import java.io.OutputStream;

public class OutputStreamImpl implements BaseOutputStream {
    private final OutputStream output;

    public OutputStreamImpl(OutputStream output) {
        this.output = output;
    }

    @Override
    public void writeByte(int value) {
        try {
            /**
             * метод InputStream.write имеет сигнатуру int, но по факту записывает только 1 байт
             */
            output.write(value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void writeShort(int value) {
        writeByte(value & 0xFF);
        writeByte(value >> 8);
    }

    @Override
    public void writeInt(int value) {
        writeShort(value & 0xFFFF);
        writeShort(value >> 16);
    }
}
Ну а теперь давайте к шифрованию.​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;

public class XorInputStream implements BaseInputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private final BaseInputStream input;
    private final int xorKey;

    public XorInputStream(BaseInputStream input, int xorKey) {
        this.input = input;
        this.xorKey = xorKey;
    }

    @Override
    public int readByte() {
        return xorAndApplyMask(input.readByte(), xorKey, BYTE_MASK);
    }

    @Override
    public int readShort() {
        return xorAndApplyMask(input.readShort(), xorKey, SHORT_MASK);
    }

    @Override
    public int readInt() {
        return xorAndApplyMask(input.readInt(), xorKey, INT_MASK);
    }

    private int xorAndApplyMask(int value, int xorKey, int mask) {
        return (value ^ xorKey) & mask;
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

public class XorOutputStream implements BaseOutputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private final BaseOutputStream output;
    private final int xorKey;

    public XorOutputStream(BaseOutputStream output, int xorKey) {
        this.output = output;
        this.xorKey = xorKey;
    }

    @Override
    public void writeByte(int value) {
        output.writeByte(value ^ xorKey);
    }

    @Override
    public void writeShort(int value) {
        output.writeShort(value ^ xorKey);
    }

    @Override
    public void writeInt(int value) {
        output.writeInt(value ^ xorKey);
    }
}
Но что на счет компрессии?
Компрессия в данном контексте - частный случай делегации, либо же паттерн под названием Decorator который расширяет функционал готового класса.
Нет смысла реализовывать интерфейс по очевидным причинам.​
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;
import ru.metafaze.io.BaseOutputStream;

public class CompressedInputStream {
    private static final int BYTE_FINGERPRINT = 0b00000000;
    private static final int SHORT_FINGERPRINT = 0b10000000;
    private static final int INT_FINGERPRINT = 0b11000000;
    private final BaseInputStream input;

    public CompressedInputStream(BaseInputStream input) {
        this.input = input;
    }

    public int readCompressedInt() {
        int fingerPrint = input.readByte();
        return resolveReadMethodFromFingerPrintAndRead(fingerPrint);
    }

    private int resolveReadMethodFromFingerPrintAndRead(int fingerPrint) {
        switch (fingerPrint) {
            case BYTE_FINGERPRINT -> {
                return input.readByte();
            }
            case SHORT_FINGERPRINT -> {
                return input.readShort();
            }
            case INT_FINGERPRINT -> {
                return input.readInt();
            }
            default -> throw new IllegalArgumentException("unknown fingerprint " + Integer.toBinaryString(fingerPrint));
        }
    }
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseOutputStream;

public class CompressedOutputStream {
    private static final int BYTE_MASK = 0xFF;
    private static final int SHORT_MASK = 0xFFFF;
    private static final int INT_MASK = 0xFFFFFFFF;
    private static final int BYTE_FINGERPRINT = 0b00000000;
    private static final int SHORT_FINGERPRINT = 0b10000000;
    private static final int INT_FINGERPRINT = 0b11000000;
    private final BaseOutputStream output;

    public CompressedOutputStream(BaseOutputStream output) {
        this.output = output;
    }

    public void writeCompressedInt(int value) {
        int fingerPrint = getFingerPrintFromValue(value);
        output.writeByte(fingerPrint);
        resolveWriteMethodByFingerPrintAndWriteValue(fingerPrint, value);
    }

    private void resolveWriteMethodByFingerPrintAndWriteValue(int fingerPrint, int value) {
        switch (fingerPrint) {
            case BYTE_FINGERPRINT -> output.writeByte(value);
            case SHORT_FINGERPRINT -> output.writeShort(value);
            case INT_FINGERPRINT -> output.writeInt(value);
        }
    }

    private int getFingerPrintFromValue(int value) {
        if ((value & BYTE_MASK) == value) {
            return BYTE_FINGERPRINT;
        }
        if ((value & SHORT_MASK) == value) {
            return SHORT_FINGERPRINT;
        }
        return INT_FINGERPRINT;
    }
}
В целом все.Теперь посмотрим на применения нашего кода.​
Java:
    @Test
    void globalTest() throws Exception {
        int xorKey = 0xAABBCCDD;
        Path filePath = Paths.get("file");
        BaseOutputStream fileOutput = new OutputStreamImpl(Files.newOutputStream(filePath));
        BaseOutputStream xorOutput = new XorOutputStream(fileOutput, xorKey);
        CompressedOutputStream compressedOutput = new CompressedOutputStream(xorOutput);
        compressedOutput.writeCompressedInt(3000);
    }
Тут видны плюсы делегации(объект может становится кем угодно в этапе исполнения программы.наследование разумеется так не может)
а так же видны и наши старания - теперь каждый компонент можно применять отдельно.
Но теперь встает опять вопрос...Видов реализаций так много, будет довольно трудно поддерживать пары input/output.
Давайте это исправим добавив в нашу систему паттерн AbstractFactory.
AbstractFactory позволяет нам описать интерфейс создания объектов связанных по смыслу, но имеющих разные "наборы" реализаций.​
Java:
package ru.metafaze.io;

public interface IOFactory {
    BaseInputStream createInputStream();

    BaseOutputStream createOutputStream();
}
Java:
package ru.metafaze.io.impl;

import ru.metafaze.io.BaseInputStream;
import ru.metafaze.io.BaseOutputStream;
import ru.metafaze.io.IOFactory;

public class XorIOFactory implements IOFactory {
    private final BaseInputStream input;
    private final BaseOutputStream output;
    private final int xorKey;

    public XorIOFactory(BaseInputStream input, BaseOutputStream output, int xorKey) {
        this.input = input;
        this.output = output;
        this.xorKey = xorKey;
    }

    @Override
    public BaseInputStream createInputStream() {
        return new XorInputStream(input, xorKey);
    }

    @Override
    public BaseOutputStream createOutputStream() {
        return new XorOutputStream(output, xorKey);
    }
}
Java:
        IOFactory ioFactory = new XorIOFactory(fileInput, fileOutput, xorKey);
        BaseInputStream xorInput = ioFactory.createInputStream();
        BaseOutputStream xorOutput = ioFactory.createOutputStream();
Спасибо что прочитали эту огромную статью, на сегодня все.Однозначно смотивирую вас разобраться в этом самостоятельно : то что описано в статье - 1% из того всего что я изучал на счет клинкода.
Помимо этого, добавлю, что вполне где то мог ошибиться/привести неуместный пример.Принимается любая критика.
P.S ко всем классам написаны юнит тесты.Если нужно, прикреплю как метаинформацию.
Bye
Не вижу смысла в куче друг другу противостоящих архитектурных решениях. Ведь в итоге вы будете писать свой код одни, а не в большой команде. Тем более, вы не будете его расширять ( наверное )
P.S: Пока вы будете думать о том как правильно написать метод, чтобы не нарушить ни одно говно - другой разработчик уже напишет пол чита)0)
 
Последнее редактирование:
Пользователь
Статус
Оффлайн
Регистрация
18 Фев 2022
Сообщения
579
Реакции[?]
99
Поинты[?]
38K
на нем обычно пишут все бекенды веб приложух огромных компаний
c# вытеснил из бекенда это старье, и не только из бекенда
другой разработчик уже напишет пол чита)0)
быстрый старт и медленное развитие
 
Эксперт
Статус
Онлайн
Регистрация
29 Мар 2021
Сообщения
1,523
Реакции[?]
572
Поинты[?]
8K
P.S: Пока вы будете думать о том как правильно написать метод, чтобы не нарушить ни одно говно - другой разработчик уже напишет пол чита)0)
ты как Тейт - скажешь много правильных вещей и насрешь ебаной шизофренией под конец?
 
Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,606
Реакции[?]
872
Поинты[?]
113K
ты как Тейт - скажешь много правильных вещей и насрешь ебаной шизофренией под конец?
В чём шизофрения то? Моё последнее предложение описывает архитектурную тиранию
 
Эксперт
Статус
Онлайн
Регистрация
29 Мар 2021
Сообщения
1,523
Реакции[?]
572
Поинты[?]
8K
В чём шизофрения то? Моё последнее предложение описывает архитектурную тиранию
Никто концепт не пишет архитектурно. В мейн/мастер/транк соизвольте коммитить архитектурный код.
 
Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,606
Реакции[?]
872
Поинты[?]
113K
Никто концепт не пишет архитектурно. В мейн/мастер/транк соизвольте коммитить архитектурный код.
Ты неправильно понял мою мысль. Я не говорю, что не нужно соблюдать паттерны проектирования. Я говорю, что не стоит использовать их слишком много, потому что в итоге они могут создавать проблемы при разработке, что приведет к потере времени на продумывание базовых методов.
 
Эксперт
Статус
Онлайн
Регистрация
29 Мар 2021
Сообщения
1,523
Реакции[?]
572
Поинты[?]
8K
Сверху Снизу