Открыть все вопросы по Spring
Открыть все вопросы по Android
Содержание:
➤ Какие существуют уровни сложности у коллекций
➤ Какие наиболее часто используемые паттерны
➤ В чем отличия наследования от композиции
➤ Что такое SOLID
➤ Что такое функции высшего порядка
➤ В чем отличие структурной, функциональный и объектно-ориентированной парадигмы в программировании
➤ Какие форматы обмена данными с сервером существуют
➤ Какие существуют уровни сложности у коллекций
Constant time (O(1)):
операции выполняются за постоянное время, не зависящее от размера коллекции. Примеры операций с такой оценкой сложности: добавление и удаление элементов из HashSet, получение элемента по индексу из Array.
Logarithmic time (O(log n)):
операции выполняются за время, логарифмически зависящее от размера коллекции. Примеры операций с такой оценкой сложности: добавление и удаление элементов из TreeSet, поиск элемента в TreeMap.
Linear time (O(n)):
операции выполняются за время, линейно зависящее от размера коллекции. Примеры операций с такой оценкой сложности: поиск элемента в ArrayList, удаление элемента из LinkedList.
Linearithmic time (O(n log n)):
операции выполняются за время, линейно умноженное на логарифм от размера коллекции. Примеры операций с такой оценкой сложности: сортировка элементов в List с использованием алгоритма Merge Sort.
Quadratic time (O(n²)):
операции выполняются за время, квадратично зависящее от размера коллекции. Примеры операций с такой оценкой сложности: сортировка элементов в List с использованием алгоритма Bubble Sort.
Exponential time (O(2^n)):
операции выполняются за время, экспоненциально зависящее от размера коллекции. Примеры операций с такой оценкой сложности: вычисление всех подмножеств множества.
➤ Какие наиболее часто используемые паттерны
Singleton:
гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.
public class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
object Singleton { init { println("Singleton instance has been created.") } fun doSomething() { println("Singleton is doing something.") } }
Observer:
устанавливает зависимость между объектами таким образом, что если один объект изменяется, то все его зависимые объекты уведомляются об этом и автоматически обновляются.
Builder:
позволяет создавать объекты с помощью пошагового процесса, в котором можно задавать различные параметры, и в конечном итоге получить объект с определенными свойствами.
class Person private constructor( val firstName: String?, val lastName: String?, ) { class Builder { private var firstName: String? = null private var lastName: String? = null fun firstName(firstName: String) = apply { this.firstName = firstName } fun lastName(lastName: String) = apply { this.lastName = lastName } fun build() = Person(firstName, lastName, age, city, country) } } val person = Person.Builder() .firstName("John") .lastName("Doe") .build()
Factory:
предоставляет общий интерфейс для создания объектов, но делегирует фактическое создание объектов на подклассы.
interface Transport { fun deliver(): String } class Car : Transport { override fun deliver() = "Delivering by car" } class Truck : Transport { override fun deliver() = "Delivering by truck" } enum class TransportType { CAR, TRUCK, } object TransportFactory { fun createTransport(transportType: TransportType): Transport { return when (transportType) { TransportType.CAR -> Car() TransportType.TRUCK -> Truck() } } } val car = TransportFactory.createTransport(TransportType.CAR) println(car.deliver()) // "Delivering by car" val truck = TransportFactory.createTransport(TransportType.TRUCK) println(truck.deliver()) // "Delivering by truck"
Adapter:
преобразует интерфейс одного класса в интерфейс, ожидаемый другим классом, чтобы они могли взаимодействовать друг с другом.
interface MediaPlayer { fun play(audioType: String, fileName: String) } interface AdvancedMediaPlayer { fun playVlc(fileName: String) fun playMp4(fileName: String) } class VlcPlayer : AdvancedMediaPlayer { override fun playVlc(fileName: String) { println("Playing vlc file. Name: $fileName") } override fun playMp4(fileName: String) { // do nothing } } class Mp4Player : AdvancedMediaPlayer { override fun playVlc(fileName: String) { // do nothing } override fun playMp4(fileName: String) { println("Playing mp4 file. Name: $fileName") } } class MediaAdapter(audioType: String) : MediaPlayer { private val advancedMediaPlayer: AdvancedMediaPlayer? init { when (audioType) { "vlc" -> advancedMediaPlayer = VlcPlayer() "mp4" -> advancedMediaPlayer = Mp4Player() else -> advancedMediaPlayer = null } } override fun play(audioType: String, fileName: String) { when (audioType) { "vlc" -> advancedMediaPlayer?.playVlc(fileName) "mp4" -> advancedMediaPlayer?.playMp4(fileName) else -> println("Invalid media. $audioType format not supported") } } } class AudioPlayer : MediaPlayer { private val mediaAdapter: MediaAdapter? override fun play(audioType: String, fileName: String) { when (audioType) { "mp3" -> println("Playing mp3 file. Name: $fileName") "vlc", "mp4" -> { mediaAdapter = MediaAdapter(audioType) mediaAdapter.play(audioType, fileName) } else -> println("Invalid media. $audioType format not supported") } } }
Decorator:
динамически добавляет объектам новую функциональность, обертывая их в другие объекты, которые имеют эту функциональность.
abstract class Beverage { abstract val description: String abstract fun cost(): Double } abstract class CondimentDecorator : Beverage() class Milk(private val beverage: Beverage) : CondimentDecorator() { override val description = "${beverage.description}, Milk" override fun cost() = beverage.cost() + 0.1 } val espresso = Espresso() val latte = Milk(Whip(Mocha(espresso)))
Facade:
предоставляет унифицированный интерфейс для группы интерфейсов в подсистеме, упрощая таким образом взаимодействие с ней.
class OrderProcessor { fun processOrder(order: Order): String { val warehouse = Warehouse() val paymentSystem = PaymentSystem() val warehouseResult = warehouse.checkInventory(order) if (warehouseResult == "available") { val paymentResult = paymentSystem.processPayment(order) if (paymentResult == "success") { warehouse.updateInventory(order) return "Order processed successfully" } } return "Order processing failed" } } class Warehouse { fun checkInventory(order: Order): String { // check inventory return "available" } fun updateInventory(order: Order) { // update inventory } } class PaymentSystem { fun processPayment(order: Order): String { // process payment return "success" } } class Order { // order details } class OrderFacade { fun processOrder(order: Order): String { val warehouseResult = checkInventory(order) if (warehouseResult == "available") { val paymentResult = processPayment(order) if (paymentResult == "success") { updateInventory(order) return "Order processed successfully" } } return "Order processing failed" } private fun checkInventory(order: Order): String { val warehouse = Warehouse() return warehouse.checkInventory(order) } private fun updateInventory(order: Order) { val warehouse = Warehouse() warehouse.updateInventory(order) } private fun processPayment(order: Order): String { val paymentSystem = PaymentSystem() return paymentSystem.processPayment(order) } }
Template Method:
определяет основу алгоритма, но позволяет подклассам переопределить некоторые шаги этого алгоритма без изменения его общей структуры.
abstract class Pizza { fun make() { prepareDough() addIngredients() bakePizza() cutPizza() } protected fun prepareDough() { println("Preparing pizza dough") } protected abstract fun addIngredients() protected fun bakePizza() { println("Baking pizza") } protected fun cutPizza() { println("Cutting pizza") } } class PepperoniPizza : Pizza() { override fun addIngredients() { println("Adding pepperoni to pizza") } } class MargheritaPizza : Pizza() { override fun addIngredients() { println("Adding mozzarella and basil to pizza") } } fun main() { val pepperoniPizza = PepperoniPizza() pepperoniPizza.make() val margheritaPizza = MargheritaPizza() margheritaPizza.make() } // Preparing pizza dough // Adding pepperoni to pizza // Baking pizza // Cutting pizza // Preparing pizza dough // Adding mozzarella and basil to pizza // Baking pizza // Cutting pizza
Strategy:
При использовании паттерна стратегия мы выносим некоторую логику из основного кода в отдельные классы, которые реализуют общий интерфейс. В основном коде мы потом можем выбирать нужную реализацию в зависимости от определенных условий, без необходимости использовать множество условных операторов.
interface PaymentStrategy { fun pay(amount: Double) } class CreditCardStrategy(private val cardNumber: String, private val cvv: String) : PaymentStrategy { override fun pay(amount: Double) { // логика оплаты с помощью кредитной карты } } class PayPalStrategy(private val email: String, private val password: String) : PaymentStrategy { override fun pay(amount: Double) { // логика оплаты с помощью PayPal } } class PaymentProcessor(private val paymentStrategy: PaymentStrategy) { fun processPayment(amount: Double) { paymentStrategy.pay(amount) } } // использование val paymentStrategy = if (useCreditCard) { CreditCardStrategy(cardNumber, cvv) } else { PayPalStrategy(email, password) } val paymentProcessor = PaymentProcessor(paymentStrategy) paymentProcessor.processPayment(amount)
➤ В чем отличия наследования от композиции
Наследование и композиция — это два разных подхода к проектированию классов в объектно-ориентированном программировании.
Наследование:
это механизм, позволяющий создать новый класс на основе уже существующего класса (базового класса или суперкласса). При этом новый класс (подкласс или производный класс) наследует все свойства и методы базового класса, а также может добавлять свои собственные свойства и методы.
// Базовый класс open class Person(val name: String, val age: Int) { open fun introduce() { println("Hi, my name is $name and I am $age years old.") } } // Класс-наследник с дополнительными параметрами в конструкторе class Employee(name: String, age: Int, val jobTitle: String) : Person(name, age) { override fun introduce() { println("Hi, my name is $name, I am $age years old and I work as a $jobTitle.") } } fun main() { val employee = Employee("John", 30, "Software Developer") employee.introduce() // Output: Hi, my name is John, I am 30 years old and I work as a Software Developer. }
Композиция:
это механизм, при котором объекты одного класса используют объекты других классов для выполнения своих функций. Класс, использующий другой класс, называется составным (или компонентом), а класс, чьи объекты используются, называется композитом. Составной класс содержит объекты других классов как свои свойства и использует их методы для реализации своих функций.
// Класс, представляющий двигатель class Engine(val type: String, val horsepower: Int) { fun start() { println("Engine of type $type with $horsepower HP started.") } } // Класс, использующий композицию class Car(val model: String, val engine: Engine) { fun drive() { engine.start() println("Driving a $model.") } } fun main() { val engine = Engine("V8", 450) val car = Car("Mustang", engine) car.drive() // Output: Engine of type V8 with 450 HP started. // Driving a Mustang. }
Различия между наследованием и композицией:
Связь:
Наследование — это отношение «является», где подкласс является расширением суперкласса. Композиция — это отношение «имеет», где объект одного класса имеет ссылку на объект другого класса.
Гибкость:
Композиция более гибкая, чем наследование. В наследовании изменение суперкласса может повлиять на все подклассы, что не всегда является желаемым. В композиции объекты могут быть легко заменены другими объектами, если они реализуют необходимый интерфейс или абстрактный класс.
Повторное использование:
Композиция обеспечивает более высокую степень повторного использования кода, поскольку объекты могут быть использованы в разных контекстах. В наследовании повторное использование кода возможно только в контексте иерархии наследования.
Принцип подстановки Барбары Лисков:
Если наследование используется неправильно, оно может нарушать принцип подстановки Барбары Лисков (Liskov substitution principle), который гласит, что любой экземпляр класса должен быть заменяем любым экземпляром его подкласса без изменения корректности программы. В композиции этот принцип не нарушается, так как объекты разных классов могут быть заменены объектами, реализующими один и тот
➤ Что такое SOLID
Принципы, собранные Робертом Мартином, автором Чистой архитектуры, из нее и процитирую ответы
Single Responsibility Principle:
принцип единственной ответственности. У класса должна быть только одна причина для изменения.
Плохой вариант реализации, так как в нем много причин для изменения:
class Man { void work() {} void eat() {} void sleep() {} }
Правильный вариант реализации, в котором в каждом классе только одна причина для изменения:
class Man { } class Work extends Man { void work() {} } class Eat extends Man { void eat() {} } class Sleep extends Man { void sleep() {} } // паттерн Фасад class ManFacade { Work work = new Work(); Eat eat = new Eat(); Sleep sleep = new Sleep(); void work() { work.work(); } void eat() { eat.eat(); } void sleep() { sleep.sleep(); } }
Open-Closed Principle:
принцип открытости/закрытости, программные сущности должны быть открыты для расширения и закрыты для изменения
Плохой вариант реализации, так как направление зависимостей Main -> Toyota:
public class Main { public static void main(String[] args) { SportToyota sportToyota = new SportToyota(); workInTaxi(sportToyota); } static void workInTaxi(Toyota toyota){ if(toyota instanceof SportToyota){ ((SportToyota) toyota).getOnePassenger(); } else { toyota.getFourPassengers(); } } } class Toyota { void getFourPassengers(){ } } class SportToyota extends Toyota { void getOnePassenger(){ } }
Правильный вариант реализации, так как направление зависимостей Main -> Car <- Toyota:
public class Main { public static void main(String[] args) { SportToyota sportToyota = new SportToyota(); workInTaxi(sportToyota); } static void workInTaxi(Car car){ car.workInTaxi(); } } interface Car { void workInTaxi(); } class Toyota implements Car { void getFourPassengers(){ } @Override public void workInTaxi() { getFourPassengers(); } } class SportToyota extends Toyota { void getOnePassenger(){ } @Override public void workInTaxi() { getOnePassenger(); } }
Liskov Substitution Principle:
принцип подстановки Барбары Лисков. Для создания программных систем из взаимозаменяемых частей эти части должны соответствовать контракту, который позволяет заменять эти части друг другом. При наследовании мы не должны затрагивать функционал родительских классов
Плохой вариант реализации, в нем изменяется работа методов родительского класса:
public class Main { public static void main(String[] args) { Rectangle rectangle = new Rectangle(); rectangle.setHeight(10); rectangle.setWeight(12); rectangle.getSquare(); rectangle = new Square(); rectangle.setHeight(10); rectangle.setWeight(12); rectangle.getSquare(); // здесь будет ошибка } } class Rectangle { protected int weight; protected int height; void setWeight(int weight) { this.weight = weight; } void setHeight(int height) { this.height = height; } int getSquare() { return weight * height; } } class Square extends Rectangle { @Override void setWeight(int weight) { this.weight = weight; this.height = weight; } @Override void setHeight(int height) { this.height = height; this.weight = height; } @Override int getSquare() { return weight * height; } }
Правильный вариант реализации:
public class Main { public static void main(String[] args) { Rectangle rectangle = new Rectangle(); rectangle.setHeight(10); rectangle.setWeight(12); rectangle.getSquare(); Square square = new Square(); square.setSide(10); square.getSquare(); } } interface Shape{ int getSquare(); } class Rectangle implements Shape { protected int weight; protected int height; void setWeight(int weight) { this.weight = weight; } void setHeight(int height) { this.height = height; } @Override public int getSquare() { return weight * height; } } class Square implements Shape { int side; public void setSide(int side) { this.side = side; } @Override public int getSquare() { return side * side; } }
Interface Segregation Principle:
принцип разделения интерфейсов. Избегать зависимости от всего, что не используется. Не должно быть ситуаций, когда методы интерфейсов не используются при их реализации.
Плохой вариант реализации, в нем метод work не используется для Intern:
interface Worker { void work(); void eat(); } class Man implements Worker { @Override public void work() { System.out.println("work"); } @Override public void eat() { System.out.println("eat"); } } class Intern implements Worker { @Override public void work() { // intern is study, not work } @Override public void eat() { System.out.println("eat"); } }
Правильный вариант реализации, разбиваем интерфейс на несколько:
interface Worker { void work(); } interface Eater { void eat(); } interface Person extends Worker, Eater { } class Man implements Person { @Override public void work() { System.out.println("work"); } @Override public void eat() { System.out.println("eat"); } } class Intern implements Eater { @Override public void eat() { System.out.println("eat"); } }
Dependency Inversion Principle:
принцип инверсии зависимости. Код, реализующий высокоуровневую политику, не должен зависеть от кода, реализующего низкоуровневые детали. Напротив, детали должны зависеть от политики. Все зависимости в исходном коде пересекают эту границу в одном направлении — в сторону абстракции.
Абстрактный компонент содержит все высокоуровневые бизнес-правила приложения. Конкретный компонент содержит детали реализации этих правил.
➤ Что такое функции высшего порядка
Функции высшего порядка этофункции, которые могут принимать другие функции в качестве аргументов или возвращать функции в качестве результата. Это значит, что функции высшего порядка могут рассматриваться как объекты первого класса в языке программирования.
В языках, которые поддерживают функции высшего порядка, функции могут использоваться как значения и передаваться другим функциям. Например, функция высшего порядка может принимать другую функцию в качестве аргумента и применять ее к другим данным.
Функции высшего порядка могут использоваться для создания более абстрактных и гибких алгоритмов и API. Они могут упростить код, уменьшить его дублирование и улучшить его читабельность.
// пример функции высшего порядка, которая принимает функцию в качестве аргумента fun applyOperation( value: Int, operation: (Int) -> Int ): Int { return operation(value) } // пример использования функции высшего порядка val result = applyOperation(5) { value -> value * 2 } // результат будет 10
В этом примере, функция applyOperation принимает аргумент operation, который является функцией, принимающей один аргумент типа Int и возвращающей результат типа Int. Внутри функции applyOperation, operation вызывается с аргументом value, и результат возвращается из функции applyOperation.
➤ В чем отличие структурной, функциональный и объектно-ориентированной парадигмы в программировании
Существуют 3 парадигмы в программировании, они были открыты между 1958 и 1968 годами. Каждая из парадигм накладывает на код ограничения- парадигмы говорят нам, что нельзя делать.
Структурное программирование накладывает ограничение на прямую передачу управления (например при помощи оператора goto / jump)
Объектно-ориентированное программирование накладывает ограничение на косвенную передачу управления
Функциональное программирование накладывает ограничение на присваивание
см. Роберт Мартин, Чистая архитектура
➤ Какие форматы обмена данными с сервером существуют
JSON(JavaScript Object Notation):
был создан как альтернатива более сложным форматам обмена данными, таким как XML, и предназначен для простоты чтения и записи как человеком, так и компьютером. Формат JSON представляет собой набор пар “ключ-значение”, где ключ — это строка, а значение может быть любым допустимым типом данных, включая объекты, массивы, числа, строки, булевы значения и null.
SOAP (Simple Object Access Protocol):
основывается на XML (Extensible Markup Language) и определяет формат сообщения, а также правила его обработки.
Protobuf(Protocol Buffers):
бинарный формат, который не является человекочитаемым, в отличие от форматов, таких как XML и JSON. Вместо этого он представляет собой последовательность байтов, которые можно разобрать только с помощью специального инструмента. Сам протокол protobuf определяет язык описания данных, который используется для описания структуры данных, которые будут передаваться через сеть. Этот язык имеет свой синтаксис, который используется для описания полей и их типов данных. Каждое поле в структуре данных имеет уникальный идентификатор и тип данных. Позволяет компактно и эффективно сериализовать данные для передачи через сеть, при этом обеспечивая высокую производительность и совместимость между различными версиями приложений.