Ответы пользователя по тегу ООП
  • Можно ли вот так разделять класс?

    Впринципе, можно. Но то, что класс А знает о своем наследнике, классе В - пахнет оооочень стрёмно.
    Ответ написан
    Комментировать
  • Можно ли в классе-потомке переопределить метод с новыми параметрами?

    Это противоречит LSP.

    Вот представьте, вам дали такую возможность и вы переопределили метод с новыми параметрами. Теперь у нас есть метод, который принимает Layer или даже ICreatable. Допустим так:

    void DoSomething (ICreatable layer) {
      layer.Create();
    }


    Но мы ведь можем передать потомка в этот метод! Делаем следующее и ломаем наш код:
    DoSomething(new PerlinNoiseLayer())

    Потому именно в таком виде - нет, нельзя.

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

    Это слой? - Да. - Ок, тогда посмотрим его высоты.
    Если есть такая необходимость - почему бы не ввести отдельный интерфейс для высот?

    void DoSomething (IHasHeights layer) {
      layer.Heights; // <== тут есть высоты
    }


    Почему бы не воспользоваться фабрикой или билдером? Или даже заставить передавать все эти параметры в конструктор?

    public abstract class Layer: ICreatable
    {
        float[,] Heights { get; set; }
        public abstract void Create();
    }
    
    public class PerlinNoiseLayer : Layer
    {
        private float[,] _heights;
        readonly int _resolution ;
    
        public PerlinNoiseLayer (int resolution) {
            _resolution = resolution;
        }
    
        public override void Create()
        {
            // тут расширение уже есть
        }
    }
    Ответ написан
    6 комментариев
  • Какой профит в архитектуре приложения React + MobX + TS + OOP?

    Какой профит в архитектуре приложения React + MobX + TS + OOP?

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

    Если углубляться в ваш пример, то выглядит всё странно. Например, мне совершенно непонятен этот пункт:
    В том числе у нас теперь по правилам OOP есть view, model, controller - что собственно из маленького компонента делает огроменный компонет и еще в трёх файлах.

    Компоненты Реакт - это исключительно View. Нету никакой логики делать model-компоненты, ибо вся модель лежит где-то в области MobX в файлах с расширением .ts. Реакт - это вьюшка и то, что они пишут модель и контроллеры во вьюшке - это странно.

    this.addCssClassIf - пример того, где злоупотребляют наследованием там, где, очевидно, нужна композиция.

    functional component

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

    pure

    Чистый pure (простите за тафтологию) в Реакте встречается редко. Обычно только в каких-то мелких вьюшках. И pure как самоцель, на самом деле, не нужна, если только вы не гонитесь за временным хайпом.
    Ответ написан
    2 комментария
  • Как обратиться к protected методу другого класса?

    На самом деле "изменить на паблик" - это единственно-правильное решение. А ещё лучше ответсвенность этого метода вынести в отдельный класс, раз уже столько классов от него зависит
    Ответ написан
    Комментировать
  • Как правильно писать на ООП?

    TLDR: Все зависит от ситуации. Набивайте свои шишки и наберетесь опыта.

    Самый главный вопрос - зачем вы вообще хотите использовать ООП, насколько сложный и "правильный", какие цели хотите достичь.

    Когда разрабатываете библиотеку, то есть несколько важных целей (независимо от парадигмы):
    1. Апи должнен быть простым, понятным и самодокументируемым
    2. Вы должны иметь возможность развивать библиотеку и не нарушать при этом уже используемый пользователями API

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

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

    Не слушайте евангелистов ФП - сейчас в JS функциональностью модно прикрывать говнокод.

    Так же не слушайте тех, которые на каждом углу кричат "Composition over inheritance", "Banana monkey problem" другие страшные термины. В своем религиозном угаре они забывают, что все эти принципы рекомендации для определенных ситуаций. Да, иногда (в большинстве случаев на самом деле) композиция значительно лучше наследования, очень часто стыкаешься с ситуацией, что наследование применено не в тему и из-за этого усложнено развитие. Но иногда наследование - правильный путь и пока сами не набъете шишек - будет крайне сложно понять разницу. Помогает принцип is-a/has-a. И я видел код отличных программистов, где наследование было применено так, что значительно разгрузило API и упростило код. Не зацикливайтесь из-за религиозных фанатиков.

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

    Получился класс, в котором есть эти 4 метода. Все эти методы я вызываю в конструкторе. То есть получается, я процедурный стиль просто запихнул в класс.
    Да, вы правы, но это не обязательно плохо, идеальный код никто и никогда не пишет. Но посмотрите какая проблема - у вас и парсятся входные данные и делается запрос и делается обработатка. Можно ли изменить парсинг входных данных, оставив другие шаги? Можно ли не отправлять запрос? Как вы будете тестировать эту функцию, если она обязательно выполняет запрос и нету никакого способа не дать ей отправить этот запрос?

    С другой стороны, будут ли довольны пользователи вашей библиотеки, если им придется инджектить все зависимости? Даже если вы сделаете вызов по-умолчанию, то чтобы изменить одну зависимость - придется изменять все. Я приведу пример, где использование наследования даст необходимую вам гибкость и API для пользователя не разбухнет, зато он сможет изменить каждый шаг. Вот:

    interface IPipeline {
    	ResultView Render (RawData rawData);
    }
    
    class Pipeline : IPipeline {
    	protected virtual ParsedRawData ParseRawData(RawData rawData) {
    		return new DataParser(rawData).GetParsedData();
    	}
    	
    	protected virtual ActualData GetActualData (ParsedRawData rawData) {
    		return new HttpRequest(rawData.src);
    	}
    	
    	protected virtual ResultState GetResultState (RawData rawData, ActualData actualData) {
    		return new StateCounter().Count(rawData, actualData);
    	}
    	
    	public virtual ResultView Render (RawData rawData) {
    		var parsed = ParseRawData(rawData);
    		
    		if (parsed.IsValid) {
    			var actual = GetActualData(rawData);
    			var state = GetResultState(rawData, response);
    			return new Renderer().Render(state);
    		} else {
    			throw new Exception("Raw data is invalid");
    		}
    	}
    }


    Для пользователя это выглядит довольно симпатично:
    new Pipeline().Render(rawData);

    Если же ему необходимо брать из другого источника данные - он легко это исправит:
    class MyPipeline : Pipeline {
    	protected virtual ActualData GetActualData (ParsedRawData rawData) {
    		return new LocalStorageRequest(rawData.src);
    	}
    }
    
    new MyPipeline().Render(rawData);


    Конечно, это же можно сделать при помощи композиции. Ну давайте поиграем на основе паттерна Билдер:

    interface IPipeline {
    	ResultView Render (RawData rawData);
    }
    
    class Pipeline : IPipeline {
    	private IRawDataParser rawDataParser;
    	private IActualDataReciever actualDataReciever
    	private IResultStateCounter resultStateCounter;
    	private IRenderer renderer;
    	
    	public GetRawDataParser () {
    		return rawDataParser ?? new RawDataParser();
    	}
    	public GetActualDataReciever () {
    		return rawDataParser ?? new ActualDataReciever();
    	}
    	public Pipeline GetActualDataReciever (actualDataReciever) {
    		actualDataReciever = actualDataReciever;
    		return this;
    	}
    	
    	// ...
    	private GetRenderer () {
    		return renderer ?? new Renderer();
    	}
    	
    	
    	public virtual ResultView Render (RawData rawData) {
    		
    		var parsed = GetRawDataParser().Parse(rawData);
    		
    		if (parsed.IsValid) {
    			var actual = GetActualDataReciever().Get(rawData);
    			// ...
    			return GetRenderer().Render(state);
    		} else {
    			throw new Exception("Raw data is invalid");
    		}
    	}
    }


    Использование по-умолчанию всё то же:
    new Pipeline().Render(rawData);

    А изменить пользователь их может так:
    new Pipeline()
    	.SetActualDataReciever(new MyDataReciever())
    	.Render(rawData);


    Тут впору уже и о DI Container'ах почитать.

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

    В качестве вывода повторю самое главное - просто тренируйтесь, пишите и набивайте шишки, читайте о паттернах и приемах, но помните, что это рекомендации, а не законы и старайтесь думать и анализировать, что пишете и читаете.
    Ответ написан
    Комментировать
  • Какой JavaScript framework облегчающий написание объектно-ориентированного кода посоветуете?

    Я запросто юзал MooTools.Class паралельно с jQuery. Вполне себе решение)
    Ответ написан
    Комментировать