Как правильно писать на ООП?

Всем привет.

Я уже знаком с ООП и все мне в нем нравится. Когда еще читал книгу по Java, смотрел на примеры использования ООП и делал домашнее задание (по книге), то мой внутренний перфекционист был счастлив. После познакомился с JavaSript, а в последствии с ES6. Недавно придумал проект (плагин), и при написании, с толкнулся с тем, что все сводится к процедурному стилю.

То есть есть плагин, и следующие методы:
  1. Парсинг входных данных
  2. Создание объекта XMLHttpRequest
  3. Определенная обработка запроса
  4. Вывод полученной информации пользователю


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

Как правильно структурировать подобные проекты (простые). Или никак с ООП это не сделать? А где тогда вообще используют ООП?
  • Вопрос задан
  • 1293 просмотра
Решения вопроса 3
Adamos
@Adamos
ООП - это компьютерная мечта о дамской сумочке. Которая большая внутри и крошечная снаружи.
Главное в хорошем классе - это интерфейс, позволяющий вообще не думать о том, что находится внутри класса.
Представьте себе черный ящик, решающий вашу задачу. Сформулируйте, каких внешних данных ему должно быть достаточно и какими внешними же признаками и логикой должен обладать этот ящик. Старательно абстрагируясь от того, что там будет происходить внутри (процедурное программирование приучает думать об этом, придется напрячься).

Простейший пример - jQuery.cookies. Кукисы в браузере хранятся неудобно для редактирования, но это проблемы внутри черного ящика, снаружи их быть не должно. Снаружи вам надо поставить куку и прочитать куку. С коротким списком возможных свойств. Вот это класс и реализует, вполне успешно. Буквально одним методом.
Мог бы этот метод быть простой процедурой? Да, конечно. Но как раз это - неважно.
Ответ написан
Xuxicheta
@Xuxicheta
инженер
У вас линейная обработка данных, это одна процедура, а вы сделали класс ради класса.

Вообще в js я начал стараться предпочитать функциональный стиль там, где это возможно.
Даже методы пишу максимально похожими на чистые функции. Так проще переиспользовать их в других местах и, вообще, понятнее что происходит.
А то уже замучался разбираться с этим хардкорным ООП, которое встречается сплошь и рядом.

Качественный класс написать сложно, плюс наследование в js не особо рекомендуется. А для композиции лучше подходят простые функции.
Ответ написан
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 для пользователя и кучу возможностей для расширения и поддержки вами. Вы можете протестировать каждый кусочек, каждую отправку, мокнуть всё, что угодно

В качестве вывода повторю самое главное - просто тренируйтесь, пишите и набивайте шишки, читайте о паттернах и приемах, но помните, что это рекомендации, а не законы и старайтесь думать и анализировать, что пишете и читаете.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
dmitry_pavlov
@dmitry_pavlov
World-class .NET freelance contractor (remotely)
Подавляющее большинство современных языков программирования поддерживают возможность использования нескольких парадигм программирования. В какой парадигме писать - дело программиста. Я лично вижу только плюсы в том, что язык программирования гибок в плане выбора парадигмы. JavaScript на мой взгляд - один из наиболее гибких языков и, когда шаблонность мышления, присущая программисту на ранних стадиях вхождения в профессию, перестает мешать - это становится как говорится - мощным оружием в умелых руках. Дерзайте! :)
Ответ написан
Комментировать
В данном случае - никак. Не стоит использовать ООП там, где он вовсе не нужен.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы