Jusper Jusper

По требованию Яндекса, разбили на 2 отдельные странички.
Вторая часть переехала сюда: https://devtribe.ru/p/indie-kitchen/podcast_devgamm_volume_2

Jusper Jusper

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

Jusper Jusper

Мелочь, но - новая поза персонажа в главном меню. Да, он даже чешет себе задницу.

...
Dreaman Dreaman

IDALGAME,

Действительно оригинально и интересно игра выглядит ;)

Rummy_Games Rummy_Games

Saturated Outer Space #saturdayscreenshot

...
Razz Razz

Мясной шторм в исполнении Юрия Маркова

Stabilitron Stabilitron

Метроидвания в мире славянских сказок. Разбойникам тоже нужно отдыхать.
https://twitter.com/SlavaniaGame/status/1292048996115120128

TheDarkestRed TheDarkestRed

Заброшенный город - концепт арт The Darkest Red 🎮 🕸 🏚
https://vk.com/the_darkestred

IDALGAME IDALGAME

Геймплей беты и создания кофейных пятен.

Следите за разработкой игры: https://vk.com/pt_game

...
Jusper Jusper

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

Jusper Jusper

GenElCon,

HypeTrain издают в основном инди-разрабочиков, а мне там особо делать нечего, потому что я на данном этапе не инди (точнее инди-мобильник).

GenElCon GenElCon

Ну вот, например, если бы ты в какую-нибудь игру вписаться типо тех, что выходят из под издательского крыла TinyBuild или HypeTrain Digital (такие типичные небольшие крепкие и не очень ПК проекты), как думаешь...

GenElCon GenElCon

Jusper,

А ты пробовал в такие подобные места идти: это были прям гиганты или не только? Просто если это что-то акромя этих гигантов, например, какая-нибудь студия делающая крпепкие не ААА, например Endless Space...

alexprey alexprey

GenElCon, ну это почти тоже самое, что сказать, что я сверх спец по юнити, т.к. C# знаю уже 9 лет, но опыт веб разработки на нем не тоже самое, что скрипты под юньку писать. Тоже самое и с мобилками, там немного другие задачи решаются...

...
Jusper Jusper

GenElCon, в большинстве мест крупных типа Blizzard прямо написано: опыт разработки игры AAA PC проекта, поэтому мобилка там нерелевантна, меня несколько раз развернули.

По личным ощущениям приходится делать конкретное усилие...

GenElCon GenElCon

Jusper,

Хм, а были конкретные кейсы? Ты просто не первый знакомый, кто про это говорит. Любопытно..

Jusper Jusper

Xakkar, lol

Логотип проекта Программирование

Принципы SOLID

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PricingLibrary
{
    //класс обычного продукта
    public class Product
    {
        //обозначение или название
        public string code { private set; get; }

        //цена за единицу продукта
        protected double pricePerUnit { private set; get; }

        //конструктор
        public Product (string code, double pricePerUnit)
        {
            this.code = code;
            this.pricePerUnit = pricePerUnit;
        }

        //метод для вычисления стоимости count единиц
        public virtual double CalculateCost(int count)
        {
            return pricePerUnit * count;
        }
    }

    //продукт по акции
    public class VolumeProduct : Product
    {
        //цена набора
        protected double pricePerVolume { private set; get; }

        //сколько единиц входит в набор
        protected int volumeSize { private set; get; }

        //конструктор
        public VolumeProduct(string code, double pricePerUnit, double pricePerVolume, int volumeSize):base(code, pricePerUnit)
        {
            this.pricePerVolume = pricePerVolume;
            this.volumeSize = volumeSize;
        }
      
        //метод для расчета стоимости с учетом акции
        public override double CalculateCost(int count)
        {
            if(count < volumeSize)
                   return base.CalculateCost(count);
            else
                return (count - volumeSize) * pricePerUnit + pricePerVolume;

        }
    }

    //информация о покупке определенного продукта
    class ProductCounter
    {
        //сколько единиц продукта было куплено
        private int count = 0;

        //непосредственно сам продукт
        private Product product;

        //конструктор
        public ProductCounter(Product product)
        {
            this.product = product;
        }

        //покупка нового продукта
        public void AddProductUnit()
        {
            count++;
        }

        //расчет текущей стоимости всех единиц продукта
        public double CalculatePrice()
        {
            return product.CalculateCost(count);
        }
     
        //сброс счетчика
        public void Reset()
        {
            count = 0;
        }
    }

    //класс непосредственно устройства
    public class PointOfSaleTerminal
    {
        //словарь, хранящий инфу об обозначениях и соответствующих им продуктах
        private Dictionary<string, ProductCounter> allProducts = new Dictionary<string, ProductCounter>();

        //метод для установки цены за продукты
        public void SetPricing(params Product[] productList)
        {
            foreach(Product product in productList)
            {
                allProducts.Add(product.code, new ProductCounter(product));
            }
        }

        //сканирование товара
        public void Scan(string productName)
        {
            allProducts[productName].AddProductUnit();
        }

        //расчет общей стоимости покупки
        public double CalculateTotal()
        {
            double value = 0;

            ProductCounter[] productList = allProducts.Select(item => item.Value).ToArray();
            foreach(ProductCounter product in productList)
            {
                value += product.CalculatePrice();
            }

            return value;
        }

        //сброс, для возможности обработки новой покупки
        public void Reset()
        {
            ProductCounter[] productList = allProducts.Select(item => item.Value).ToArray();
            foreach (ProductCounter product in productList)
            {
                product.Reset();
            }
        }
    }
}

Вопрос следующий - что надо сделать с этим кодом, чтобы он соответствовал стандартам SOLID? OCP и LSP, вроде как, соблюдены, да и то не факт. Мне нужны хотя бы наводки.

Ответ

lentinant, мое понимание SOLID:
S - один класс на одну задачу, сложные задачи разбиваются до подзадач и, соответственно, получаем один класс на большую задачу и по классу на подзадачу. Условно можно выразить так "если класс занимается выпеканием хлеба, то он не должен заниматься его доставкой". Важно не увлекаться дроблением сверх меры на этапе проектирования - если видишь что класс начинает разбухать и обростать группами не связанных методов, то самое время использовать этот принцип.
O - избегать изменения контрактов уже стабилизировавшегося кода. Применяется когда код уже может где-то использоваться как зависимость. По сути это требование обратной совместимости - расширять функционал можно и нужно, но старый код, зависящий от твоего, должен работать даже после превращения калькулятора в подводную лодку с вертикальным взлетом.
L - требование, согласно которому экземпляры родительских объектов должно быть можно заменить экземплярами дочерних объектов, не нарушая целостности программы. По сути это критерий, по которому можно определить и устранить избыточное или неправильное наследование, заменив его наследованием обоих объектов от общего родителя или агрегацией или хоть чертом лысым - сам принцип ничего не говорит о том, как именно его надо выпонять. Есть небольшой нюанс - применяется этот принцип только к экземплярам классов, а не к ссылкам или самому дереву наследования - если, например, где-то есть ссылка на A, который родительский класс для B, но по факту там используются только экземпляры классов B, C и D, то "nothing to do here".
I - аналог S для интерфейсов. Его главная идея в том, что не стоит перегружать класс, реализующий интерфейс, лишними методами.
D - суть в том, чтобы разорвать связи между объектами, находящимися на разных уровнях абстракции. Применяется в обоих направлениях - и для менее абстрактных объектов, включенных в более абстрактный и наоборот, более того - применяется не только при прямом включении, но и к любым другим ссылкам. Предположим нам нужно составить програмную модель кирпичной стены, класс стены не должен ничего знать о конкретных реализациях кирпичей и работать с любыми кирпичами, какие ему дадут, а кирпичи не должны напрямую зависеть от конкретных реализаций стены и быть пригодны к использованию в любой стене (или другой конструкции, если модель подразумевает не только стены).

Еще хочу сказать, что слепое следование всем принципам ни к чему хорошему не приводит - важно понимать грань, за которой начинается ад и содомия и вовремя остановиться.

Смотрите также:


Комментарии



Чтобы упростить задачу, перегружу немного дополню вопрос своими соображениями (как я понял аспекты SOLID)
Насколько я понял, L - просто концепция, при которой в программе экземпляр класса можно заменить экземпляром его дочернего класса, без ущерба программе; O - по сути, не должно быть классов, выполняющих "избыточную" работу, то есть, если мне надо выполнять некоторые базовые действия, и в некоторых случаях, кроме базовых выполнять еще дополнительные, то надо создать базовый класс с базовыми действиями, а дополнительные прописывать уже в классе, наследующем базовый; I - если методы класса можно разделить на тематические группы, то лучше всего каждую группу делать отдельным интерфейсом.

Немного непонятно, что требуется в D. Если у нас класс A включает в себя экземпляр класса B, то соответственно этому принципу, лучше взять какой-то интерфейс I, наследовать класс B от этого интерфейса, и переменную в классе A тоже стоит сделать в виде интерфейса I?

Принцип S говорит "не надо нагружать класс методами, выноси функционал в отдельные классы", однако, с этим можно дойти до того, что на каждый метод надо будет делать отдельный класс. Или тут надо выделять определенные категории, как в принципе I?

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

lentinant, мое понимание SOLID:
S - один класс на одну задачу, сложные задачи разбиваются до подзадач и, соответственно, получаем один класс на большую задачу и по классу на подзадачу. Условно можно выразить так "если класс занимается выпеканием хлеба, то он не должен заниматься его доставкой". Важно не увлекаться дроблением сверх меры на этапе проектирования - если видишь что класс начинает разбухать и обростать группами не связанных методов, то самое время использовать этот принцип.
O - избегать изменения контрактов уже стабилизировавшегося кода. Применяется когда код уже может где-то использоваться как зависимость. По сути это требование обратной совместимости - расширять функционал можно и нужно, но старый код, зависящий от твоего, должен работать даже после превращения калькулятора в подводную лодку с вертикальным взлетом.
L - требование, согласно которому экземпляры родительских объектов должно быть можно заменить экземплярами дочерних объектов, не нарушая целостности программы. По сути это критерий, по которому можно определить и устранить избыточное или неправильное наследование, заменив его наследованием обоих объектов от общего родителя или агрегацией или хоть чертом лысым - сам принцип ничего не говорит о том, как именно его надо выпонять. Есть небольшой нюанс - применяется этот принцип только к экземплярам классов, а не к ссылкам или самому дереву наследования - если, например, где-то есть ссылка на A, который родительский класс для B, но по факту там используются только экземпляры классов B, C и D, то "nothing to do here".
I - аналог S для интерфейсов. Его главная идея в том, что не стоит перегружать класс, реализующий интерфейс, лишними методами.
D - суть в том, чтобы разорвать связи между объектами, находящимися на разных уровнях абстракции. Применяется в обоих направлениях - и для менее абстрактных объектов, включенных в более абстрактный и наоборот, более того - применяется не только при прямом включении, но и к любым другим ссылкам. Предположим нам нужно составить програмную модель кирпичной стены, класс стены не должен ничего знать о конкретных реализациях кирпичей и работать с любыми кирпичами, какие ему дадут, а кирпичи не должны напрямую зависеть от конкретных реализаций стены и быть пригодны к использованию в любой стене (или другой конструкции, если модель подразумевает не только стены).

Еще хочу сказать, что слепое следование всем принципам ни к чему хорошему не приводит - важно понимать грань, за которой начинается ад и содомия и вовремя остановиться.

prog:

Еще хочу сказать, что слепое следование всем принципам ни к чему хорошему не приводит - важно понимать грань, за которой начинается ад и содомия и вовремя остановиться.

Вот самое важное замечание.

Clamp:

Вот самое важное замечание.

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

prog:

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

Так суть принципа - уменьшить количество методов в классе, или инкапсулировать различные обязанности? Если в пекарне сделали отдельно отдел доставки и отдел выпечки, всё равно придется обращаться и к тому, и к тому (если я в примере в классе PointOfSaleTerminal вынесу в отдельный класс методы Scan и Reset, так как они отвечают за менеджмент списка покупок, всё равно надо будет оставить методы, которые будут вызывать Scan и Reset с нашего подкласса).
prog:

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

Грубо говоря, использовать в классах не прямые типы в ссылочных переменных, а интерфейсы? Чтобы туда можно было всунуть экземпляр любого класса, реализующего интерфейс?


Ладно, куратор потом всё равно сказал, что не страшно, если не всё будет подчиняться этим принципам. Правда, теперь непонятно, зачем я два дня сидел практически без дела.

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